From df70ce6f2b9bf3d1d07cc5e3af51b4d8620700a7 Mon Sep 17 00:00:00 2001 From: fcarraUniSa Date: Tue, 17 Feb 2026 14:25:25 +0100 Subject: [PATCH] Update ClientPortal.tsx --- components/ClientPortal.tsx | 106 +++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 8 deletions(-) diff --git a/components/ClientPortal.tsx b/components/ClientPortal.tsx index bc3044b..3b92207 100644 --- a/components/ClientPortal.tsx +++ b/components/ClientPortal.tsx @@ -21,7 +21,9 @@ import { MoreVertical, Minus, ExternalLink, - BookOpen + BookOpen, + Download, + Loader2 } from 'lucide-react'; interface ClientPortalProps { @@ -30,7 +32,7 @@ interface ClientPortalProps { queues: TicketQueue[]; settings: AppSettings; onCreateTicket: (ticket: Omit) => void; - onReplyTicket: (ticketId: string, message: string) => void; + onReplyTicket: (ticketId: string, message: string, attachments?: Attachment[]) => void; onSubmitSurvey: (survey: Omit) => void; tickets?: Ticket[]; onLogout: () => void; @@ -77,6 +79,8 @@ export const ClientPortal: React.FC = ({ // Ticket Reply State const [replyText, setReplyText] = useState(''); + const [replyAttachments, setReplyAttachments] = useState([]); + const [isUploading, setIsUploading] = useState(false); // Initial Chat Message with Custom Agent Name useEffect(() => { @@ -147,7 +151,7 @@ export const ClientPortal: React.FC = ({ id: `att-${Date.now()}-${i}`, name: file.name, type: file.type, - url: URL.createObjectURL(file) + url: URL.createObjectURL(file) // Note: This is client-side only URL for preview, real implementation would upload here too or later }); } } @@ -167,11 +171,23 @@ export const ClientPortal: React.FC = ({ }; const submitReply = () => { - if (!replyText.trim() || !selectedTicket) return; - onReplyTicket(selectedTicket.id, replyText); + if ((!replyText.trim() && replyAttachments.length === 0) || !selectedTicket) return; + + // Pass attachments to parent handler + onReplyTicket(selectedTicket.id, replyText, replyAttachments); + setReplyText(''); + setReplyAttachments([]); + // Optimistic update for UI smoothness (actual update comes from props change) - const newMsg: ChatMessage = { id: `temp-${Date.now()}`, role: 'user', content: replyText, timestamp: new Date().toISOString() }; + const newMsg: ChatMessage = { + id: `temp-${Date.now()}`, + role: 'user', + content: replyText, + attachments: replyAttachments, + timestamp: new Date().toISOString() + }; + setSelectedTicket({ ...selectedTicket, messages: [...selectedTicket.messages, newMsg], @@ -180,6 +196,51 @@ export const ClientPortal: React.FC = ({ showToast("Risposta inviata", 'success'); }; + const handleFileUpload = async (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const file = e.target.files[0]; + + // Validation + const maxSize = (settings.features.maxFileSizeMb || 5) * 1024 * 1024; + if (file.size > maxSize) { + showToast(`File troppo grande. Max ${settings.features.maxFileSizeMb}MB`, 'error'); + return; + } + + const allowed = (settings.features.allowedFileTypes || 'jpg,png,pdf').split(',').map(t => t.trim().toLowerCase()); + const ext = file.name.split('.').pop()?.toLowerCase() || ''; + if (!allowed.includes(ext)) { + showToast(`Estensione non permessa. Ammessi: ${settings.features.allowedFileTypes}`, 'error'); + return; + } + + setIsUploading(true); + const formData = new FormData(); + formData.append('file', file); + + try { + const res = await fetch('/api/upload', { + method: 'POST', + body: formData + }); + const data = await res.json(); + if (data.url) { + setReplyAttachments(prev => [...prev, { id: data.id, name: data.name, url: data.url, type: data.type }]); + } + } catch (err) { + showToast('Errore caricamento file', 'error'); + } finally { + setIsUploading(false); + // Reset input + e.target.value = ''; + } + } + }; + + const removeAttachment = (id: string) => { + setReplyAttachments(prev => prev.filter(a => a.id !== id)); + }; + const submitSurvey = () => { if (surveyData.rating === 0) { showToast("Per favore seleziona una valutazione.", 'error'); @@ -334,12 +395,39 @@ export const ClientPortal: React.FC = ({ {selectedTicket.messages.map(m => (

{m.content}

+ {m.attachments && m.attachments.length > 0 && ( +
+ {m.attachments.map(att => ( + + {att.name} + + ))} +
+ )}
))} -
+
+
+ {replyAttachments.map(att => ( +
+ {att.name} + +
+ ))} +