"use client"; import React, { useEffect, useState, useCallback } from "react"; import Sidebar from "../components/Sidebar"; import TopBar from "../components/TopBar"; import { fetchMeetings, fetchMeetingDetail, fetchMeetingTranscript, fetchMeetingSignals, Meeting, MeetingDetail, TranscriptChunk, Signal, formatRelativeTime, getSeverityColor, getSignalIcon, parseMetaList, } from "../lib/api"; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDate(iso: string): string { if (!iso) return "—"; try { return new Date(iso).toLocaleString("en-GB", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return iso; } } function SignalTypeBadge({ type }: { type: string }) { const colors: Record = { meet_decision: "#34D399", meet_action_item: "#60A5FA", meet_blocker: "#F87171", meet_risk: "#FBBF24", meet_open_q: "#A78BFA", meet_summary: "#A78BFA", meet_chunk_raw: "#71717A", }; const color = colors[type] || "#A78BFA"; return ( {type.replace("meet_", "").replace(/_/g, " ")} ); } // ─── Meeting List Item ───────────────────────────────────────────────────────── function MeetingListItem({ meeting, isSelected, onClick, dateFrom, dateTo, }: { meeting: Meeting; isSelected: boolean; onClick: () => void; dateFrom: string; dateTo: string; }) { const ts = meeting.started_at || ""; if (dateFrom && ts && ts < dateFrom) return null; if (dateTo && ts && ts > dateTo) return null; return ( ); } // ─── Transcript Panel ────────────────────────────────────────────────────────── function TranscriptPanel({ chunks }: { chunks: TranscriptChunk[] }) { if (chunks.length === 0) { return (
mic_off

No transcript chunks stored

); } return (
{chunks.map((chunk, i) => (
record_voice_over
{chunk.speaker || "Unknown Speaker"} CHUNK {String(i + 1).padStart(2, "0")}
{formatDate(chunk.timestamp)}

{chunk.text}

))}
); } // ─── Signals Panel ───────────────────────────────────────────────────────────── function SignalsPanel({ signals }: { signals: Signal[] }) { const filtered = signals.filter( (s) => !["meet_chunk_raw", "meet_started"].includes(s.metadata?.type || "") ); if (filtered.length === 0) { return (
sensors_off

No signals extracted yet

); } return (
{filtered.map((sig, i) => { const meta = sig.metadata; const color = getSeverityColor(meta.severity); const icon = getSignalIcon(meta.type); return (
{icon}
{formatRelativeTime(meta.timestamp)}

{meta.summary || sig.document}

{meta.raw_quote && meta.raw_quote !== meta.summary && (

"{meta.raw_quote.slice(0, 200)}"

)} {meta.entities && (
{parseMetaList(meta.entities).map((e, j) => ( {e} ))}
)}
); })}
); } // ─── Main Page ───────────────────────────────────────────────────────────────── export default function MeetingsPage() { const [meetings, setMeetings] = useState([]); const [loading, setLoading] = useState(true); const [selectedId, setSelectedId] = useState(null); const [detail, setDetail] = useState(null); const [transcript, setTranscript] = useState([]); const [signals, setSignals] = useState([]); const [activeTab, setActiveTab] = useState<"transcript" | "signals">("signals"); const [detailLoading, setDetailLoading] = useState(false); // Filters const [dateFrom, setDateFrom] = useState(""); const [dateTo, setDateTo] = useState(""); const [search, setSearch] = useState(""); useEffect(() => { fetchMeetings() .then((data) => setMeetings(data)) .catch(console.error) .finally(() => setLoading(false)); }, []); const selectMeeting = useCallback(async (id: string) => { setSelectedId(id); setDetail(null); setTranscript([]); setSignals([]); setDetailLoading(true); try { const [det, trans, sigs] = await Promise.all([ fetchMeetingDetail(id), fetchMeetingTranscript(id), fetchMeetingSignals(id), ]); setDetail(det); setTranscript(trans.transcript); setSignals(sigs); } catch (e) { console.error(e); } finally { setDetailLoading(false); } }, []); const filteredMeetings = meetings.filter((m) => { if (search && !m.meeting_id.toLowerCase().includes(search.toLowerCase())) return false; if (dateFrom && m.started_at && m.started_at < dateFrom) return false; if (dateTo && m.started_at && m.started_at > dateTo) return false; return true; }); const totalSignals = meetings.reduce((sum, m) => sum + m.signal_count, 0); return (
{/* Header */}
video_camera_front

Meeting History

Google Meet · Signal Intelligence

{/* Stats Row */}
{[ { label: "Total Meetings", value: meetings.length, icon: "video_camera_front" }, { label: "Total Signals", value: totalSignals, icon: "sensors" }, { label: "Avg Signals / Meeting", value: meetings.length ? Math.round(totalSignals / meetings.length) : 0, icon: "analytics", }, ].map((stat) => (
{stat.icon}

{stat.value}

{stat.label}

))}
{/* Filter bar */}
search setSearch(e.target.value)} className="w-full bg-[#141419] border border-white/10 rounded-lg pl-9 pr-4 py-2.5 text-[12px] text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-[#A78BFA]/50" />
From setDateFrom(e.target.value ? new Date(e.target.value).toISOString() : "")} className="bg-[#141419] border border-white/10 rounded-lg px-3 py-2 text-[12px] text-zinc-300 focus:outline-none focus:border-[#A78BFA]/50" /> To setDateTo(e.target.value ? new Date(e.target.value).toISOString() : "")} className="bg-[#141419] border border-white/10 rounded-lg px-3 py-2 text-[12px] text-zinc-300 focus:outline-none focus:border-[#A78BFA]/50" /> {(dateFrom || dateTo || search) && ( )}
{/* Two-panel layout */}
{/* Left: Meeting List */}
{loading ? (

Loading meetings...

) : filteredMeetings.length === 0 ? (
video_camera_front

No meetings found

Meetings appear when Google Meet extension sends data

) : (
{filteredMeetings.map((m) => ( selectMeeting(m.meeting_id)} dateFrom={dateFrom} dateTo={dateTo} /> ))}
)}
{/* Right: Detail Panel */}
{!selectedId ? (
video_camera_front

Select a meeting

to view transcript and signals

) : detailLoading ? (

Loading meeting data...

) : detail ? (
{/* Meeting header */}
MEETING {detail.group_id}

{detail.meeting_id}

schedule {formatDate(detail.started_at)}
{detail.speaker && detail.speaker !== "Unknown" && (
person {detail.speaker}
)}

{detail.total_signals}

signals

{/* Signal type counts */}
{Object.entries(detail.signal_counts) .filter(([t]) => t !== "meet_started" && t !== "meet_chunk_raw") .map(([type, count]) => (
{count}
))}
{/* Summary */} {detail.summary && (
summarize AI Summary

{detail.summary}

)}
{/* Tabs */}
{(["signals", "transcript"] as const).map((tab) => ( ))}
{/* Tab content */}
{activeTab === "signals" ? ( ) : ( )}
) : null}
); }