Files
voiceon-character-admin/src/features/comments/components/CommentItem.tsx

48 lines
3.3 KiB
TypeScript

import { useState } from "react";
import type { CommentRecord } from "@/features/comments/model/types";
import { formatSeoulDateTime } from "@/shared/lib/formatters";
export function CommentItem({ canDelete = true, canEdit, comment, isSaving, onDelete, onEdit, onShowReplies, replyActionLabel }: { readonly canDelete?: boolean; readonly canEdit: boolean; readonly comment: CommentRecord; readonly isSaving: boolean; readonly onDelete: () => void; readonly onEdit: (comment: string) => void; readonly onShowReplies?: () => void; readonly replyActionLabel?: "답글 보기" | "답글 작성" }) {
const [draft, setDraft] = useState(comment.comment);
const [isEditing, setIsEditing] = useState(false);
const label = `${comment.comment}`;
function saveEdit() {
const nextComment = draft.trim();
if (nextComment.length === 0) {
return;
}
onEdit(nextComment);
setIsEditing(false);
}
return (
<article className="rounded-lg border border-border bg-card p-3" aria-label={`${comment.nickname} 댓글`}>
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<p className="text-sm font-semibold text-foreground">{comment.nickname}</p>
<p className="text-xs text-muted-foreground">{formatSeoulDateTime(comment.date)}{comment.isSecret ? " · 비밀" : ""}</p>
</div>
<div className="flex flex-wrap gap-2">
{replyActionLabel !== undefined && onShowReplies !== undefined ? <button className="rounded-md border border-input bg-card px-3 py-2 text-sm font-semibold hover:bg-accent disabled:opacity-60" disabled={isSaving} onClick={onShowReplies} type="button">{label} {replyActionLabel}</button> : null}
{canEdit ? <button className="rounded-md border border-input bg-card px-3 py-2 text-sm font-semibold hover:bg-accent disabled:opacity-60" disabled={isSaving} onClick={() => setIsEditing(true)} type="button">{label} </button> : null}
{canDelete ? <button className="rounded-md border border-destructive bg-card px-3 py-2 text-sm font-semibold text-destructive hover:bg-accent disabled:opacity-60" disabled={isSaving} onClick={onDelete} type="button">{label} </button> : null}
</div>
</div>
{isEditing ? (
<div className="mt-3 flex flex-col gap-2">
<label className="flex flex-col gap-2 text-sm font-semibold">
<textarea className="min-h-24 rounded-md border border-input bg-card px-3 py-2 text-base font-normal" onChange={(event) => setDraft(event.currentTarget.value)} value={draft} />
</label>
<div className="flex flex-wrap gap-2">
<button className="rounded-md border border-input bg-primary px-4 py-2 font-semibold text-primary-foreground hover:bg-[var(--button-bg-hover)] active:bg-[var(--button-bg-active)] disabled:opacity-60" disabled={isSaving || draft.trim().length === 0} onClick={saveEdit} type="button"> </button>
<button className="rounded-md border border-input bg-card px-4 py-2 font-semibold hover:bg-accent" onClick={() => { setDraft(comment.comment); setIsEditing(false); }} type="button"></button>
</div>
</div>
) : <p className="mt-3 break-words text-sm">{comment.comment}</p>}
</article>
);
}