docs: modifica messaggi e firma in chat — STATUS, STATE, design system
La cosa da rileggere fra sei mesi: propagare una modifica richiede insieme il filtro allargato a edited_at e il watermark del client sul massimo dei due timestamp. Una sola delle due e o la modifica non arriva, o arriva a ogni giro per sempre. Più il perché di due scelte che sembrano sviste: il non-letto resta su created_at, e la foto è un URL esterno perché l'upload su volume non esiste (la deroga LOCKED #5 è scritta ma mai costruita). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,7 +151,9 @@ across all future admin pages.
|
||||
| `ClientProfitability` | `src/components/admin/dashboard/ClientProfitability.tsx` | Dashboard widget: valore orario reale per cliente (`getClientProfitability` → contrattualizzato ÷ ore tracciate) con badge margine (Ottimo `bg-primary/10 text-primary` / In Linea `bg-muted`). |
|
||||
| `YearSelector` | `src/components/admin/YearSelector.tsx` | Selettore anno restilizzato a pill (`rounded-full border-border bg-card shadow-card`), frecce ←/→, valore `font-mono`. Guida i dati year-scoped della dashboard via `?year=`. |
|
||||
| `ConversationsView` | `src/components/admin/conversazioni/ConversationsView.tsx` | Inbox messaggi clienti (`/admin/conversazioni`) a due pannelli: lista conversazioni filtrabile (search su nome/brand/ultimo messaggio, pallino unread) + thread attivo con **tab per canale** (Generale + una per fase, pallino accent sul tab non letto) e bolle messaggio (admin a destra `bg-primary`, cliente a sinistra). Badge entità solo sui messaggi storici di task/deliverable. La risposta va sull'entità del canale attivo. Auto-mark-read all'apertura, su snapshot di `adminLastReadAt`. |
|
||||
| `ChatPanel` | `src/components/client/ChatPanel.tsx` | Chat del portale cliente: FAB `h-14 w-14` fisso in basso a destra con pallino unread, drawer laterale `fixed inset-y-0 right-0 bg-card shadow-2xl` che in `expanded` diventa full-screen (`Maximize2`/`Minimize2`, `Esc` chiude prima l'espansione poi il pannello). Tab canale scrollabili orizzontalmente (`no-scrollbar`), invio ottimistico, polling `GET /api/client/chat` ogni 20s. |
|
||||
| `ChatPanel` | `src/components/client/ChatPanel.tsx` | Chat del portale cliente: FAB `h-14 w-14` fisso in basso a destra con pallino unread, drawer laterale `fixed inset-y-0 right-0 bg-card shadow-2xl` che in `expanded` diventa full-screen (`Maximize2`/`Minimize2`, `Esc` chiude prima l'espansione poi il pannello). Tab canale scrollabili orizzontalmente (`no-scrollbar`), invio **e modifica** ottimistici, polling `GET /api/client/chat` ogni 20s. La firma di chi risponde arriva da `settings` via `ChatData`, non è più cablata. |
|
||||
| `MessageRow` | `src/components/client/ChatPanel.tsx` | Una riga del feed, componente a sé perché la modifica ha bisogno di stato per messaggio (bozza, salvataggio, errore). Divider di giornata, raggruppamento dei messaggi consecutivi dello stesso autore entro `GROUP_WINDOW_MS`, badge entità per lo storico di task/deliverable, «(modificato)» inline dopo il corpo. |
|
||||
| `Avatar` (chat) | `src/components/client/ChatPanel.tsx` | Cerchio `h-9 w-9` con immagine da URL esterno e **fallback al monogramma** se `src` manca o il caricamento fallisce (`onError`). `<img>` nudo e non `next/image`: l'URL lo incolla l'utente in Impostazioni e non è noto in fase di build, quindi non si può dichiarare l'host in `next.config`. |
|
||||
| `ChatProvider` | `src/components/client/ChatProvider.tsx` | Context del solo stato UI (aperto/espanso/canale attivo) — i messaggi restano dentro `ChatPanel`, unico a fare polling e a tenere la copia optimistic. Il canale sta nel context perché lo decide anche chi è fuori dal pannello: `openChat(phaseId)` dalla bolla di una `PhaseCard` apre la chat già sulla fase giusta. |
|
||||
| `MessagesWidget` | `src/components/admin/dashboard/MessagesWidget.tsx` | Widget dashboard "Messaggi Clienti": chat clienti in attesa di risposta (`getConversations()` filtrato su `unread`), pill emerald "N Nuovi" + pallino pulsante, righe con anteprima e link `Rispondi →` deep-link a `/admin/conversazioni?c=<clientId>`. Empty state "Nessun messaggio in attesa ✓". |
|
||||
| `CopyLinkButton` | `src/components/admin/CopyLinkButton.tsx` | Icon button che copia un URL assoluto (`window.location.origin + path`) negli appunti; swap icona → check emerald per 1.5s con tooltip "Copiato!". Usato nella lista Clienti per il link profilo pubblico. |
|
||||
@@ -237,6 +239,19 @@ resta a due pannelli, con i canali come tab dentro il thread attivo.
|
||||
giorno 0. La 0021 aggiunge `(entity_id, created_at)`, che è esattamente il modo
|
||||
in cui il feed si legge.
|
||||
|
||||
**Pattern «Modifica» sui messaggi** (portale e inbox admin, stessa forma): pulsante
|
||||
testuale piccolo, non un'icona, coerente con i già presenti `Riprova`/`Elimina`. Si
|
||||
rivela al passaggio del mouse ma **deve restare raggiungibile senza puntatore**:
|
||||
|
||||
opacity-0 transition-opacity group-hover:opacity-100
|
||||
focus-visible:opacity-100 [@media(hover:none)]:opacity-100
|
||||
|
||||
L'ultima clausola non è un dettaglio — senza, il pulsante è irraggiungibile da telefono,
|
||||
ed è da telefono che il cliente apre il portale. In modifica la bolla diventa una
|
||||
textarea: `Invio` salva, `Esc` annulla, più i due pulsanti espliciti. Nell'inbox admin i
|
||||
controlli stanno **fuori** dalla bolla: sopra `bg-primary` un testo tenue non si legge.
|
||||
Il pulsante nascosto non è una difesa — chi può modificare lo ridecide il server.
|
||||
|
||||
### Clienti (lista) notes
|
||||
|
||||
`/admin/clients` migrata a "Quiet Luxury" (replica `design-reference/pagina-clienti.html`).
|
||||
|
||||
Reference in New Issue
Block a user