You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
5.9 KiB
TypeScript

// frontend/components/mail/AriPanel.tsx — 아리 분석 패널 (요약 + 할일/일정/답장/정리 + 모두 적용)
"use client";
import { Icon } from "@/components/Icon";
import type { AiReply, EmailDetail, EmailRow, ExtractKind } from "@/lib/types";
import { Sug } from "./Sug";
type Mail = EmailRow | EmailDetail;
export function AriPanel({
m,
added,
onAdd,
onReply,
onAddAll,
}: {
m: Mail;
added: Record<string, boolean>;
onAdd: (key: string, kind: ExtractKind, index: number) => void;
onReply: (m: Mail, replyIndex: number, r: AiReply) => void;
onAddAll: (m: Mail) => void;
}) {
const ai = m.ai;
if (!ai) return null;
const keys: string[] = [];
ai.tasks.forEach((_, i) => keys.push("t:" + m.id + ":" + i));
ai.events.forEach((_, i) => keys.push("e:" + m.id + ":" + i));
if (ai.file) keys.push("f:" + m.id);
const allDone = keys.length > 0 && keys.every((k) => added[k]);
return (
<div className="ari-panel">
<div className="ap-head">
<div className="ap-avatar">
<Icon name="spark" />
</div>
<div className="ap-h">
<b> </b>
<span>{ai.category} · </span>
</div>
<span className={"ap-prio " + ai.priority}>{ai.priority}</span>
</div>
<div
className="ap-summary"
dangerouslySetInnerHTML={{ __html: "<b>요약 — </b>" + ai.summary }}
/>
{ai.tasks.length > 0 && (
<div className="ap-section">
<div className="ap-sec-title">
<Icon name="check" /> <span className="n">{ai.tasks.length}</span>
</div>
{ai.tasks.map((t, i) => {
const k = "t:" + m.id + ":" + i;
return (
<Sug
key={k}
kind="task"
text={t.text}
added={!!added[k]}
onAdd={() => onAdd(k, "task", i)}
meta={
<>
<span className="chip2">
<Icon name="folder" />
{t.project}
</span>
<span className="dotsep" />
<span className="chip2">
<Icon name="cal" />
{t.due}
</span>
<span className="dotsep" />
<span className="chip2">
<Icon name="flag" />
{t.prio}
</span>
</>
}
/>
);
})}
</div>
)}
{ai.events.length > 0 && (
<div className="ap-section">
<div className="ap-sec-title">
<Icon name="cal" />
<span className="n">{ai.events.length}</span>
</div>
{ai.events.map((ev, i) => {
const k = "e:" + m.id + ":" + i;
return (
<Sug
key={k}
kind="event"
text={ev.title}
added={!!added[k]}
onAdd={() => onAdd(k, "event", i)}
meta={
<>
<span className="chip2">
<Icon name="cal" />
{ev.date} ({ev.day})
</span>
<span className="dotsep" />
<span className="chip2">
<Icon name="clock" />
{ev.time}
{ev.dur ? " · " + ev.dur : ""}
</span>
{ev.place && (
<>
<span className="dotsep" />
<span className="chip2">{ev.place}</span>
</>
)}
</>
}
/>
);
})}
</div>
)}
{ai.replies.length > 0 && (
<div className="ap-section">
<div className="ap-sec-title">
<Icon name="reply" />
<span className="n">{ai.replies.length} </span>
</div>
<div className="replies">
{ai.replies.map((r, i) => (
<button className="rep" key={i} onClick={() => onReply(m, i, r)}>
<div className="rep-top">
<span className="rep-tone">{r.tone}</span>
<span className="rep-go">
<Icon name="arrow" />
</span>
</div>
<div className="rep-prev">{r.preview}</div>
</button>
))}
</div>
</div>
)}
{ai.file &&
(() => {
const k = "f:" + m.id;
return (
<div className={"ap-file" + (added[k] ? " added" : "")}>
<div className="fi-ico">
<Icon name="layers" />
</div>
<div className="fi-main">
<b>{ai.file.project} </b>
<span>{ai.file.reason}</span>
</div>
<button
className={"sug-add" + (added[k] ? " done" : "")}
onClick={() => onAdd(k, "file", 0)}
>
<Icon name={added[k] ? "tick" : "plus"} w={added[k] ? 3 : 1.9} />
{added[k] ? "정리됨" : "정리"}
</button>
</div>
);
})()}
{keys.length > 1 && (
<div className="ap-allbar">
<span className="txt"> {keys.length} </span>
<button className={"ap-all-btn" + (allDone ? " done" : "")} onClick={() => onAddAll(m)}>
<Icon name={allDone ? "tick" : "zap"} w={allDone ? 3 : 1.9} />
{allDone ? "모두 적용됨" : "모두 적용"}
</button>
</div>
)}
</div>
);
}