← back to Dw Boardroom Governance

frontend/src/App.tsx

95 lines

import { useState, useEffect } from 'react';
import { api } from './api';
import { useWebSocket } from './hooks/useWebSocket';
import TabNav from './components/TabNav';
import Boardroom from './components/Boardroom';
import DecisionPanel from './components/DecisionPanel';
import Ledger from './components/Ledger';
import DelegationForm from './components/DelegationForm';
import Initiatives from './components/Initiatives';
import EscalationQueue from './components/EscalationQueue';
import AutonomyControl from './components/AutonomyControl';
import type { GovernanceStatus } from './types';

const TABS = ['Meeting Room', 'Decisions', 'Delegation', 'Initiatives', 'Escalations'];

export default function App() {
  const [activeTab, setActiveTab] = useState(0);
  const [status, setStatus] = useState<GovernanceStatus | null>(null);
  const { events, connected } = useWebSocket();

  useEffect(() => {
    api.getStatus().then(setStatus).catch(() => {});
    const interval = setInterval(() => {
      api.getStatus().then(setStatus).catch(() => {});
    }, 15000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div style={styles.app}>
      {/* Header */}
      <header style={styles.header}>
        <div style={styles.headerLeft}>
          <div style={styles.logo}>G</div>
          <div>
            <h1 style={styles.title}>DW Boardroom Governance</h1>
            <div style={styles.subtitle}>
              Decision Engine &bull; Delegation &bull; Escalation
              <span style={{ ...styles.dot, background: connected ? '#22c55e' : '#ef4444' }} />
              {connected ? 'Live' : 'Disconnected'}
              {status && ` | ${status.uptime ? Math.round(status.uptime / 60) + 'm uptime' : ''}`}
            </div>
          </div>
        </div>
        <div style={styles.headerRight}>
          {status && (
            <>
              <span style={styles.badge}>{status.decisions?.pending || 0} pending</span>
              <span style={{ ...styles.badge, background: '#7c3aed33', borderColor: '#7c3aed' }}>
                {status.meeting?.active ? 'Meeting Active' : 'No Meeting'}
              </span>
            </>
          )}
        </div>
      </header>

      {/* Tabs */}
      <TabNav tabs={TABS} active={activeTab} onChange={setActiveTab} />

      {/* Content */}
      <main style={styles.main}>
        {activeTab === 0 && <Boardroom events={events} />}
        {activeTab === 1 && (
          <div style={styles.split}>
            <DecisionPanel />
            <Ledger />
          </div>
        )}
        {activeTab === 2 && <DelegationForm />}
        {activeTab === 3 && <Initiatives />}
        {activeTab === 4 && (
          <div style={styles.split}>
            <EscalationQueue />
            <AutonomyControl />
          </div>
        )}
      </main>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  app: { minHeight: '100vh', background: '#0a0a12', color: '#e8eaf0', fontFamily: "'Inter', -apple-system, sans-serif" },
  header: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #252540', background: '#12121f' },
  headerLeft: { display: 'flex', alignItems: 'center', gap: 12 },
  logo: { width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg, #8b5cf6, #6366f1)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: 20 },
  title: { margin: 0, fontSize: 20, fontWeight: 700, letterSpacing: '-0.02em' },
  subtitle: { fontSize: 12, color: '#7a7f96', display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 },
  dot: { width: 6, height: 6, borderRadius: '50%', display: 'inline-block' },
  headerRight: { display: 'flex', gap: 8 },
  badge: { padding: '4px 10px', borderRadius: 6, fontSize: 11, fontWeight: 600, background: '#22c55e22', border: '1px solid #22c55e44', color: '#e8eaf0' },
  main: { padding: 24, maxWidth: 1400, margin: '0 auto' },
  split: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 },
};