"use client"; import { useState, useTransition, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; import type { Comment } from "@/db/schema"; import { useChatContext } from "./ChatProvider"; // TODO: Email-on-tag (admin→client notification when admin posts on a phase/task) is OUT OF SCOPE // Hook point: after db.insert(comments) in /src/app/api/client/comment/route.ts and // postAdminComment in /src/app/admin/clients/[id]/actions.ts — send Resend email here. function formatTime(ts: Date | string): string { const d = typeof ts === "string" ? new Date(ts) : ts; return d.toLocaleString("it-IT", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit", }); } interface ChatPanelProps { token: string; comments: Comment[]; } export function ChatPanel({ token, comments }: ChatPanelProps) { const { isOpen, selectedPhaseId, phases, clientId, openChat, closeChat } = useChatContext(); const [body, setBody] = useState(""); const [error, setError] = useState(null); const [, startTransition] = useTransition(); const router = useRouter(); const bottomRef = useRef(null); // Build entity list: Generale + phases type EntityOption = { id: string; type: "general" | "phase"; label: string }; const entityOptions: EntityOption[] = [ { id: clientId, type: "general", label: "Generale" }, ...phases.map((p) => ({ id: p.id, type: "phase" as const, label: p.title })), ]; // Build label map for displaying tags on existing messages const labelMap = new Map(); labelMap.set(clientId, "Generale"); for (const p of phases) { labelMap.set(p.id, p.title); for (const t of p.tasks) { labelMap.set(t.id, `${p.title} — ${t.title}`); for (const d of t.deliverables) { labelMap.set(d.id, `${t.title} — ${d.title}`); } } } // Current selection: sync with selectedPhaseId from context const [currentEntityId, setCurrentEntityId] = useState(clientId); useEffect(() => { setCurrentEntityId(selectedPhaseId ?? clientId); }, [selectedPhaseId, clientId]); // Sort all comments chronologically const sorted = [...comments].sort( (a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime() ); useEffect(() => { if (isOpen) { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); } }, [isOpen, comments.length]); function handleSend(e: React.FormEvent) { e.preventDefault(); const trimmed = body.trim(); if (!trimmed) return; const selectedEntity = entityOptions.find((en) => en.id === currentEntityId); if (!selectedEntity) return; setError(null); startTransition(async () => { try { const res = await fetch("/api/client/comment", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token, entity_type: selectedEntity.type, entity_id: selectedEntity.id, body: trimmed, }), }); if (!res.ok) { const data = await res.json(); setError(data.error ?? "Errore nell'invio"); return; } setBody(""); router.refresh(); } catch { setError("Errore di rete"); } }); } return ( <> {/* FAB — floating action button bottom-right */} {/* Overlay — subtle backdrop on mobile */} {isOpen && (