← back to Dw Boardroom V2

frontend/src/components/MeetingRoom.tsx

87 lines

import { useState, useEffect, useRef } from 'react';
import { api } from '../api';
import type { Meeting, MeetingMessage, WSEvent } from '../types';
import PhaseBar from './PhaseBar';
import MeetingControls from './MeetingControls';
import LiveFeed from './LiveFeed';

interface Props {
  events: WSEvent[];
}

export default function MeetingRoom({ events }: Props) {
  const [meeting, setMeeting] = useState<Meeting | null>(null);
  const [messages, setMessages] = useState<MeetingMessage[]>([]);
  const [loading, setLoading] = useState(true);

  function fetchActive() {
    api.getActiveMeeting().then(data => {
      setMeeting(data.meeting);
      setMessages(data.messages || []);
      setLoading(false);
    }).catch(() => setLoading(false));
  }

  useEffect(() => { fetchActive(); }, []);

  // Listen for WS events
  useEffect(() => {
    const latest = events[events.length - 1];
    if (!latest) return;

    if (latest.type === 'meeting_started' || latest.type === 'meeting_completed' || latest.type === 'meeting_halted') {
      fetchActive();
    } else if (latest.type === 'meeting_phase' && meeting) {
      setMeeting(prev => prev ? { ...prev, phase: latest.payload.phase } : null);
    } else if (latest.type === 'meeting_message') {
      setMessages(prev => [...prev, latest.payload]);
    }
  }, [events]);

  async function handleStart(type: string) {
    try {
      await api.startMeeting(type);
      fetchActive();
    } catch (err: any) {
      alert(err.message);
    }
  }

  async function handleHalt() {
    if (!meeting) return;
    await api.haltMeeting(meeting.id);
    fetchActive();
  }

  async function handleAdvance() {
    if (!meeting) return;
    await api.advanceMeeting(meeting.id);
    fetchActive();
  }

  if (loading) return <div style={styles.card}><div style={styles.empty}>Loading...</div></div>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      {/* Phase Bar */}
      <PhaseBar currentPhase={meeting?.phase || null} status={meeting?.status || null} />

      {/* Controls */}
      <MeetingControls
        activeMeeting={meeting}
        onStart={handleStart}
        onHalt={handleHalt}
        onAdvance={handleAdvance}
      />

      {/* Live Feed */}
      <LiveFeed messages={messages} meetingActive={!!meeting && meeting.status === 'in_progress'} />
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  card: { background: '#1a1a2e', borderRadius: 12, padding: 20, border: '1px solid #252540' },
  empty: { color: '#7a7f96', fontSize: 13, textAlign: 'center' as const, padding: 40 },
};