// 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) => (
- {t}
))}
>
);
}
/* ---- 예정 회의 — 안건 · 지난 회의 · 지난 액션 · 인사이트 ---- */
function UpcomingBody({ meet }: { meet: Meeting }) {
const doneCount = meet.last_actions.filter((a) => a.done).length;
return (
<>
{meet.agenda.map((a, i) => (
- {a}
))}
{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 (
<>
>
);
}