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.

142 lines
4.4 KiB
TypeScript

// frontend/components/tasks/Comments.tsx
"use client";
import { useState } from "react";
import { Icon } from "@/components/Icon";
import type { Person, TaskComment } from "@/lib/types";
function relTime(iso: string): string {
if (!iso) return "방금";
const then = new Date(iso).getTime();
if (Number.isNaN(then)) return "방금";
const diff = Date.now() - then;
const m = Math.floor(diff / 60000);
if (m < 1) return "방금";
if (m < 60) return `${m}분 전`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}시간 전`;
const d = Math.floor(h / 24);
if (d === 1) return "어제";
if (d < 7) return `${d}일 전`;
const dt = new Date(iso);
return `${dt.getMonth() + 1}/${dt.getDate()}`;
}
export function Comments({
list,
people,
me,
onAdd,
onEdit,
onDelete,
}: {
list: TaskComment[];
people: Record<string, Person>;
me: Person;
onAdd: (text: string) => void;
onEdit?: (cid: string, text: string) => void;
onDelete?: (cid: string) => void;
}) {
const [txt, setTxt] = useState("");
const [editId, setEditId] = useState<string | null>(null);
const [editTxt, setEditTxt] = useState("");
const submit = () => {
if (txt.trim()) {
onAdd(txt.trim());
setTxt("");
}
};
const startEdit = (c: TaskComment) => {
setEditId(c.id);
setEditTxt(c.text);
};
const saveEdit = () => {
if (editId && editTxt.trim()) onEdit?.(editId, editTxt.trim());
setEditId(null);
setEditTxt("");
};
return (
<div className="cmt">
<div className="cmt-list">
{list.length === 0 && (
<div className="cmt-empty"> . .</div>
)}
{list.map((c) => {
const p = people[c.person_id] || me;
const mine = c.person_id === me.id;
const editing = editId === c.id;
return (
<div className="cmt-row" key={c.id}>
<div className="av" style={{ background: p.color }}>
{p.initial}
</div>
<div className="cmt-bub">
<div className="cmt-top">
<b>
{p.name}
{p.is_me ? " (나)" : ""}
</b>
<span className="t">
{relTime(c.created_at)}
{c.edited_at ? " · 수정됨" : ""}
</span>
{mine && onEdit && onDelete && !editing && (
<span className="cmt-acts">
<button onClick={() => startEdit(c)} aria-label="코멘트 수정">
<Icon name="pen" />
</button>
<button
onClick={() => onDelete(c.id)}
aria-label="코멘트 삭제"
className="danger"
>
<Icon name="trash" />
</button>
</span>
)}
</div>
{editing ? (
<div className="cmt-edit">
<input
value={editTxt}
autoFocus
onChange={(e) => setEditTxt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") saveEdit();
if (e.key === "Escape") setEditId(null);
}}
/>
<button onClick={saveEdit} disabled={!editTxt.trim()}>
</button>
<button className="ghost" onClick={() => setEditId(null)}>
</button>
</div>
) : (
<div className="cmt-text">{c.text}</div>
)}
</div>
</div>
);
})}
</div>
<div className="cmt-compose">
<div className="av" style={{ background: me.color }}>
{me.initial}
</div>
<input
value={txt}
placeholder="코멘트 추가…"
onChange={(e) => setTxt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") submit();
}}
/>
<button className="cmt-send" onClick={submit} disabled={!txt.trim()} aria-label="코멘트 등록">
<Icon name="send" />
</button>
</div>
</div>
);
}