import React, { useState, useRef, useEffect } from 'react'; import { Ticket, KBArticle, ChatMessage, TicketPriority, TicketStatus, SurveyResult, Attachment, ClientUser, TicketQueue, AppSettings } from '../types'; import { getSupportResponse } from '../services/geminiService'; import { ToastType } from './Toast'; import { Search, Send, FileText, AlertCircle, MessageSquare, Star, X, Paperclip, LogOut, Home, PlusCircle, Clock, CheckCircle, ChevronRight, ArrowLeft, MoreVertical, Minus, ExternalLink, BookOpen } from 'lucide-react'; interface ClientPortalProps { currentUser: ClientUser; articles: KBArticle[]; queues: TicketQueue[]; settings: AppSettings; // Added settings prop onCreateTicket: (ticket: Omit) => void; onReplyTicket: (ticketId: string, message: string) => void; onSubmitSurvey: (survey: Omit) => void; tickets?: Ticket[]; onLogout: () => void; showToast: (message: string, type: ToastType) => void; } export const ClientPortal: React.FC = ({ currentUser, articles, queues, settings, onCreateTicket, onReplyTicket, onSubmitSurvey, tickets = [], onLogout, showToast }) => { const [activeView, setActiveView] = useState<'dashboard' | 'create_ticket' | 'ticket_detail' | 'kb'>('dashboard'); const [selectedTicket, setSelectedTicket] = useState(null); const [searchQuery, setSearchQuery] = useState(''); // KB Modal State const [viewingArticle, setViewingArticle] = useState(null); // Chat Widget State const [isChatOpen, setIsChatOpen] = useState(false); const [chatMessages, setChatMessages] = useState([{ id: '0', role: 'assistant', content: `Ciao ${currentUser.name}! 👋\nSono l'assistente AI. Come posso aiutarti oggi?`, timestamp: new Date().toISOString() }]); const [inputMessage, setInputMessage] = useState(''); const [isTyping, setIsTyping] = useState(false); const chatEndRef = useRef(null); // Survey State const [showSurvey, setShowSurvey] = useState(false); const [surveyData, setSurveyData] = useState({ rating: 0, comment: '', context: '' as 'chat' | 'ticket', refId: '' }); // Ticket Creation Form const [ticketForm, setTicketForm] = useState({ subject: '', description: '', queue: queues.length > 0 ? queues[0].name : 'General' }); const [ticketFiles, setTicketFiles] = useState(null); // Ticket Reply State const [replyText, setReplyText] = useState(''); // --- Logic --- useEffect(() => { if (isChatOpen) chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [chatMessages, isChatOpen]); const handleSendMessage = async () => { if (!inputMessage.trim()) return; const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', content: inputMessage, timestamp: new Date().toISOString() }; setChatMessages(prev => [...prev, userMsg]); setInputMessage(''); setIsTyping(true); const apiKey = settings.aiConfig.apiKey; // Check if API Key exists before calling service let aiResponseText = ''; if (!apiKey) { aiResponseText = "L'assistente AI non è disponibile al momento (Configurazione incompleta)."; } else { aiResponseText = await getSupportResponse( apiKey, userMsg.content, chatMessages.map(m => m.content).slice(-5), articles ); } const aiMsg: ChatMessage = { id: (Date.now() + 1).toString(), role: 'assistant', content: aiResponseText, timestamp: new Date().toISOString() }; setChatMessages(prev => [...prev, aiMsg]); setIsTyping(false); }; const submitTicket = (e: React.FormEvent) => { e.preventDefault(); const attachments: Attachment[] = []; if (ticketFiles) { for (let i = 0; i < ticketFiles.length; i++) { const file = ticketFiles[i]; attachments.push({ id: `att-${Date.now()}-${i}`, name: file.name, type: file.type, url: URL.createObjectURL(file) }); } } onCreateTicket({ subject: ticketForm.subject, description: ticketForm.description, customerName: currentUser.name, priority: TicketPriority.MEDIUM, queue: ticketForm.queue, attachments: attachments }); setTicketForm({ subject: '', description: '', queue: queues.length > 0 ? queues[0].name : 'General' }); setTicketFiles(null); setActiveView('dashboard'); }; const submitReply = () => { if (!replyText.trim() || !selectedTicket) return; onReplyTicket(selectedTicket.id, replyText); setReplyText(''); // 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() }; setSelectedTicket({ ...selectedTicket, messages: [...selectedTicket.messages, newMsg], status: selectedTicket.status === TicketStatus.RESOLVED ? TicketStatus.OPEN : selectedTicket.status }); showToast("Risposta inviata", 'success'); }; const submitSurvey = () => { if (surveyData.rating === 0) { showToast("Per favore seleziona una valutazione.", 'error'); return; } onSubmitSurvey({ rating: surveyData.rating, comment: surveyData.comment, source: surveyData.context, referenceId: surveyData.refId || undefined }); setShowSurvey(false); if (surveyData.context === 'chat') { setChatMessages([{ id: '0', role: 'assistant', content: `Ciao ${currentUser.name}! Come posso aiutarti oggi?`, timestamp: new Date().toISOString() }]); setIsChatOpen(false); } }; const handleTicketClick = (ticket: Ticket) => { setSelectedTicket(ticket); setActiveView('ticket_detail'); }; const activeTickets = tickets.filter(t => t.status !== TicketStatus.RESOLVED && t.status !== TicketStatus.CLOSED); const resolvedTickets = tickets.filter(t => t.status === TicketStatus.RESOLVED || t.status === TicketStatus.CLOSED); const filteredArticles = articles.filter(a => a.title.toLowerCase().includes(searchQuery.toLowerCase()) || a.content.toLowerCase().includes(searchQuery.toLowerCase()) ); return (
{/* Top Navigation */}
{/* DASHBOARD VIEW */} {activeView === 'dashboard' && (
{/* Stats Cards */}

Ticket Aperti

{activeTickets.filter(t => t.status === TicketStatus.OPEN).length}

In Lavorazione

{activeTickets.filter(t => t.status === TicketStatus.IN_PROGRESS).length}

Risolti Totali

{resolvedTickets.length}

{/* Active Tickets List */}

I tuoi Ticket Attivi

{activeTickets.length} in corso
{activeTickets.length === 0 ? (

Tutto tranquillo!

Non hai ticket aperti al momento.

) : (
{activeTickets.map(ticket => (
handleTicketClick(ticket)} className="p-6 hover:bg-gray-50 transition cursor-pointer flex items-center justify-between group" >
#{ticket.id} {ticket.status} {ticket.queue}

{ticket.subject}

{ticket.description}

))}
)}
{/* Resolved History */}

Storico Recente

{resolvedTickets.length === 0 ? (
Nessun ticket nello storico.
) : (
{resolvedTickets.slice(0, 5).map(ticket => (
handleTicketClick(ticket)} className="p-4 hover:bg-gray-50 cursor-pointer transition">

{ticket.subject}

{ticket.createdAt.split('T')[0]}

{ticket.status}
))}
)} {resolvedTickets.length > 5 && (
)}
)} {/* TICKET DETAIL VIEW */} {activeView === 'ticket_detail' && selectedTicket && (
{/* Header */}
#{selectedTicket.id} {selectedTicket.status} {selectedTicket.createdAt.split('T')[0]}

{selectedTicket.subject}

Coda: {selectedTicket.queue}

{selectedTicket.status === TicketStatus.RESOLVED && ( )}
{/* Description */}

Descrizione Problema

{selectedTicket.description}
{selectedTicket.attachments && selectedTicket.attachments.length > 0 && (
{selectedTicket.attachments.map(att => ( {att.name} ))}
)}
{/* Conversation */}

Cronologia Conversazione

{selectedTicket.messages.length === 0 && (

Nessuna risposta ancora. Un agente ti risponderà presto.

)} {selectedTicket.messages.map(msg => (
{msg.role === 'user' ? 'Tu' : 'Supporto'} {msg.timestamp.split('T')[1].substring(0,5)}

{msg.content}

))}
{/* Reply Box */}

Rispondi