// frontend/components/calendar/MeetDrawer.tsx — 회의 도우미 드로어 (done/live/upcoming/1:1) "use client"; import { useEffect, useState, type ReactNode } from "react"; import { Icon } from "@/components/Icon"; import type { IconName } from "@/components/icons/paths"; import { calendarApi } from "@/lib/calendar/api"; import type { CalEvent, Calendar, Meeting, MeetingAction } from "@/lib/calendar/types"; function Sec({ icon, title, hint, children, }: { icon: IconName; title: string; hint?: string; children: ReactNode; }) { return (
{title} {hint && {hint}}
{children}
); } /* 액션 아이템 한 줄 — '작업으로' 보내기 */ function ActionRow({ a, onAdd }: { a: MeetingAction; onAdd: () => void }) { return (
{a.text} {a.who} · {a.when} {a.added ? ( 작업에 있음 ) : ( )}
); } /* ---- 1:1 — 지난 대화 · 약속 · 시그널 · 대화 주제 ---- */ function OneOnOneBody({ meet }: { meet: Meeting }) { return ( <>

{meet.last_meeting.note}

{meet.promises.map((p, i) => (
{p.text} {!p.done && 아직}
))}
{meet.signals.map((s, i) => (
{s.k} {s.v}
))}
    {meet.talking_points.map((t, i) => (
  1. {t}
  2. ))}
); } /* ---- 예정 회의 — 안건 · 지난 회의 · 지난 액션 · 인사이트 ---- */ function UpcomingBody({ meet }: { meet: Meeting }) { const doneCount = meet.last_actions.filter((a) => a.done).length; return ( <>
    {meet.agenda.map((a, i) => (
  1. {a}
  2. ))}

{meet.last_meeting.note}

{meet.last_actions.map((a, i) => (
{a.text} {a.who} {!a.done && 미완}
))}
{meet.insights.map((t, i) => ( // 신뢰된 시드 데이터 ( 강조). overview §15 화이트리스트 준수.

))} {meet.docs.length > 0 && (

{meet.docs.map((d, i) => ( {d} ))}
)} ); } /* ---- 끝난/진행 중 회의 — 요약 · 결정 · 액션 추출 ---- */ function DoneLiveBody({ meet, remaining, onAddOne, onAddAll, }: { meet: Meeting; remaining: number; onAddOne: (idx: number) => void; onAddAll: () => void; }) { const live = meet.phase === "live"; return ( <>
    {meet.summary.map((s, i) => (
  • {s}
  • ))}
{meet.decisions.length > 0 && ( {meet.decisions.map((d, i) => (
{d}
))}
)} {meet.actions.map((a) => ( onAddOne(a.idx)} /> ))} {remaining > 0 ? ( ) : (
모든 액션이 담당자 작업으로 등록됐어요
)}
); } export function MeetDrawer({ ev, cal, onClose, }: { ev: CalEvent; cal: Calendar; onClose: () => void; }) { const [meet, setMeet] = useState(null); const [err, setErr] = useState(false); useEffect(() => { let active = true; setMeet(null); setErr(false); calendarApi .meeting(ev.id) .then((m) => active && setMeet(m)) .catch(() => active && setErr(true)); return () => { active = false; }; }, [ev.id]); useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [onClose]); const addOne = async (idx: number) => { // 낙관적 업데이트 후 federation → 작업 페이지에 등장 setMeet((m) => m ? { ...m, actions: m.actions.map((a) => (a.idx === idx ? { ...a, added: true } : a)) } : m, ); await calendarApi.materialize(ev.id, idx); }; const addAll = async () => { setMeet((m) => (m ? { ...m, actions: m.actions.map((a) => ({ ...a, added: true })) } : m)); await calendarApi.materializeAll(ev.id); }; if (err) { return ( <>
); } if (!meet) { return ( <>
); } const tone = meet.one_on_one && meet.person.tone ? meet.person.tone : cal.tone; const remaining = meet.actions.filter((a) => !a.added).length; const phaseBadge = meet.phase === "done" ? ( 회의 노트 정리됨 ) : meet.phase === "live" ? ( 기록 중 ) : ( {meet.starts_in} ); const kicker = meet.one_on_one ? "1:1 어시스턴트" : meet.phase === "upcoming" ? "사전 브리핑" : "회의 도우미"; return ( <>
); }