"use client"; import { createContext, useContext, useState, useCallback, useMemo } from "react"; import type { ClientView } from "@/lib/client-view"; /** * Stato di sola UI della chat: quale canale è aperto, se il pannello è visibile, * se è a tutto schermo. I messaggi NON stanno qui — vivono in ChatPanel, che è * l'unico a leggerli, a fare polling e a tenerne la copia optimistic. * * Il canale sta invece nel context perché lo decide anche chi è fuori dal * pannello: la bolla su una PhaseCard apre la chat già sulla fase giusta. */ interface ChatContextValue { isOpen: boolean; expanded: boolean; /** Chiave del canale attivo: clientId per "Generale", phases.id per una fase. */ activeChannel: string; phases: ClientView["phases"]; clientId: string; openChat: (phaseId?: string) => void; closeChat: () => void; toggleExpanded: () => void; setActiveChannel: (key: string) => void; } const ChatContext = createContext(null); export function useChatContext() { const ctx = useContext(ChatContext); if (!ctx) throw new Error("useChatContext must be used inside ChatProvider"); return ctx; } export function ChatProvider({ children, phases, clientId, initialChannel, }: { children: React.ReactNode; phases: ClientView["phases"]; clientId: string; /** * Canale richiesto da fuori con `?chat=` — è così che il pulsante della * mail di tag porta il cliente dentro la conversazione giusta invece che sulla * dashboard, dove la chat resterebbe chiusa e il messaggio non letto. * * Arriva dal server, già validato: leggerlo qui da `window.location` vorrebbe * dire aprire il pannello in un effetto, cioè renderizzarlo chiuso e riaprirlo * subito dopo l'idratazione. */ initialChannel?: string | null; }) { // Un canale richiesto vale anche come "apri il pannello": chi arriva da quel // link sta venendo a leggere un messaggio, non a guardare la timeline. const [isOpen, setIsOpen] = useState(!!initialChannel); const [expanded, setExpanded] = useState(false); // Il canale "Generale" è identificato dall'id del cliente: stessa convenzione // di comments.entity_id, così non serve tradurre nulla in scrittura. const [activeChannel, setActiveChannel] = useState( initialChannel ?? clientId ); const openChat = useCallback( (phaseId?: string) => { setActiveChannel(phaseId ?? clientId); setIsOpen(true); }, [clientId] ); const closeChat = useCallback(() => setIsOpen(false), []); const toggleExpanded = useCallback(() => setExpanded((v) => !v), []); const value = useMemo( () => ({ isOpen, expanded, activeChannel, phases, clientId, openChat, closeChat, toggleExpanded, setActiveChannel, }), [isOpen, expanded, activeChannel, phases, clientId, openChat, closeChat, toggleExpanded] ); return {children}; }