// frontend/components/calendar/EvPopover.tsx — 이벤트 상세 팝오버 "use client"; import { useState } from "react"; import { Icon } from "@/components/Icon"; import { fmt } from "@/lib/calendar/time"; import type { Attendee, CalEvent, Calendar } from "@/lib/calendar/types"; function rruleLabel(r: string): string { if (!r) return ""; if (r.includes("DAILY")) return "매일 반복"; if (r.includes("BYDAY=MO,TU,WE,TH,FR")) return "평일 반복"; if (r.includes("WEEKLY")) return "매주 반복"; if (r.includes("MONTHLY")) return "매월 반복"; if (r.includes("YEARLY")) return "매년 반복"; return "반복 일정"; } function reminderLabel(mins: number[]): string { if (!mins.length) return ""; return mins.map((m) => (m === 0 ? "시작 시각" : `${m}분 전`)).join(", ") + " 알림"; } const STATUS_KO: Record = { accepted: "수락", declined: "거절", tentative: "미정", needsAction: "대기", notResponded: "대기", none: "", }; export function EvPopover({ ev, x, y, cal, onClose, onMeet, onEdit, onDelete, onAddToTasks, onRsvp, }: { ev: CalEvent; x: number; y: number; cal: Calendar; onClose: () => void; onMeet: (ev: CalEvent) => void; onEdit: (ev: CalEvent) => void; onDelete: (ev: CalEvent) => void; onAddToTasks: (ev: CalEvent) => Promise; onRsvp: (ev: CalEvent, status: "accepted" | "declined" | "tentative") => Promise; }) { // 작업 추가 상태: idle → busy → done | error const [addState, setAddState] = useState<"idle" | "busy" | "done" | "error">("idle"); const addToTasks = async () => { if (addState === "busy" || addState === "done") return; setAddState("busy"); try { await onAddToTasks(ev); setAddState("done"); } catch { setAddState("error"); } }; let left = x; let top = y; if (typeof window !== "undefined") { if (left + 300 > window.innerWidth - 12) left = window.innerWidth - 312; if (top + 280 > window.innerHeight - 12) top = window.innerHeight - 292; } return ( <>
{ev.title}
{fmt(ev.start)} – {fmt(ev.end)}
{" "} {cal.name}
{ev.loc && (
{ev.loc}
)} {ev.rrule && (
{rruleLabel(ev.rrule)}
)} {ev.reminders.length > 0 && (
{reminderLabel(ev.reminders)}
)} {ev.attendees.length > 0 ? (
참석자 {ev.attendees.length}명
{ev.attendees.map((a: Attendee, i) => (
{a.name || a.email} {STATUS_KO[a.status] && ( {STATUS_KO[a.status]} )}
))}
) : ( ev.people.length > 0 && (
{ev.people.join(", ")}
) )} {ev.attendees.length > 0 && (
내 응답 {(["accepted", "tentative", "declined"] as const).map((st) => ( ))}
)} {ev.note &&
{ev.note}
} {ev.actions.length > 0 && (
내가 맡기로 한 일{ev.actions.length}
{ev.actions.map((a, i) => (
{a.text} {a.when}
))}
)} {ev.has_meeting && ( )}
); }