import React, { useState, useEffect } from 'react'; import { AgentDashboard } from './components/AgentDashboard'; import { ClientPortal } from './components/ClientPortal'; import { AuthScreen } from './components/AuthScreen'; import { ToastContainer, ToastMessage, ToastType } from './components/Toast'; import { INITIAL_SETTINGS } from './constants'; import { Agent, AppSettings, AppState, ClientUser, KBArticle, Ticket, TicketStatus, SurveyResult, TicketQueue, ChatMessage, AgentRole } from './types'; const App: React.FC = () => { const [state, setState] = useState({ tickets: [], articles: [], agents: [], queues: [], surveys: [], clientUsers: [], settings: INITIAL_SETTINGS, currentUser: null, userRole: 'guest' }); const [toasts, setToasts] = useState([]); const [loading, setLoading] = useState(false); // --- API HELPER --- const apiFetch = async (endpoint: string, options: RequestInit = {}) => { try { const res = await fetch(`/api${endpoint}`, { headers: { 'Content-Type': 'application/json' }, ...options }); if (!res.ok) throw new Error(`API Error: ${res.statusText}`); return await res.json(); } catch (error) { console.error(error); throw error; } }; const loadData = async () => { try { setLoading(true); // Load initial config data const data = await apiFetch('/initial-data'); // Load tickets const tickets = await apiFetch('/tickets'); setState(prev => ({ ...prev, agents: data.agents, clientUsers: data.clientUsers, queues: data.queues, articles: data.articles, surveys: data.surveys, tickets: tickets, settings: data.settings || prev.settings })); } catch (e) { showToast("Errore caricamento dati dal server", 'error'); } finally { setLoading(false); } }; useEffect(() => { // Carica dati al caricamento se l'utente รจ loggato if (state.currentUser) { loadData(); } }, [state.currentUser]); // Effect to refresh tickets periodically if logged in useEffect(() => { if (state.currentUser) { const interval = setInterval(loadData, 10000); // Poll every 10s return () => clearInterval(interval); } }, [state.currentUser]); const showToast = (message: string, type: ToastType = 'info') => { const id = Date.now().toString(); setToasts(prev => [...prev, { id, message, type }]); }; const removeToast = (id: string) => { setToasts(prev => prev.filter(t => t.id !== id)); }; // --- Auth Management --- const handleLogin = async (email: string, pass: string, isAgent: boolean) => { try { const res = await apiFetch('/auth/login', { method: 'POST', body: JSON.stringify({ email, password: pass }) }); if (res.user) { setState(prev => ({ ...prev, currentUser: res.user, userRole: res.role })); showToast(`Benvenuto ${res.user.name}`, 'success'); return true; } return false; } catch (e) { return false; } }; const handleClientRegister = async (name: string, email: string, pass: string, company: string) => { try { const res = await apiFetch('/auth/register', { method: 'POST', body: JSON.stringify({ name, email, password: pass, company }) }); if (res.success) { setState(prev => ({ ...prev, clientUsers: [...prev.clientUsers, res.user], currentUser: res.user, userRole: 'client' })); showToast("Registrazione completata!", 'success'); } } catch (e) { showToast("Errore registrazione", 'error'); } }; const handleLogout = () => { setState(prev => ({ ...prev, currentUser: null, userRole: 'guest', tickets: [] })); showToast("Logout effettuato", 'info'); }; // --- Ticket Management --- const createTicket = async (ticketData: Omit) => { try { const newTicket = await apiFetch('/tickets', { method: 'POST', body: JSON.stringify(ticketData) }); setState(prev => ({ ...prev, tickets: [newTicket, ...prev.tickets] })); showToast("Ticket creato correttamente", 'success'); } catch (e) { showToast("Errore creazione ticket", 'error'); } }; const replyToTicket = async (ticketId: string, message: string) => { const role = state.userRole === 'client' ? 'user' : 'assistant'; try { const newMsg = await apiFetch(`/tickets/${ticketId}/messages`, { method: 'POST', body: JSON.stringify({ role, content: message }) }); setState(prev => ({ ...prev, tickets: prev.tickets.map(t => { if (t.id !== ticketId) return t; const shouldReopen = role === 'user' && t.status === TicketStatus.RESOLVED; return { ...t, messages: [...t.messages, newMsg], status: shouldReopen ? TicketStatus.OPEN : t.status }; }) })); } catch (e) { showToast("Errore invio messaggio", 'error'); } }; const updateTicketStatus = async (id: string, status: TicketStatus) => { try { await apiFetch(`/tickets/${id}`, { method: 'PATCH', body: JSON.stringify({ status }) }); setState(prev => ({ ...prev, tickets: prev.tickets.map(t => t.id === id ? { ...t, status } : t) })); showToast(`Stato aggiornato a ${status}`, 'info'); } catch (e) { showToast("Errore aggiornamento stato", 'error'); } }; const updateTicketAgent = async (id: string, agentId: string) => { try { await apiFetch(`/tickets/${id}`, { method: 'PATCH', body: JSON.stringify({ assignedAgentId: agentId }) }); setState(prev => ({ ...prev, tickets: prev.tickets.map(t => t.id === id ? { ...t, assignedAgentId: agentId } : t) })); showToast("Agente assegnato", 'success'); } catch (e) { showToast("Errore assegnazione agente", 'error'); } }; // --- KB Management --- const addArticle = async (article: KBArticle) => { try { const res = await apiFetch('/articles', { method: 'POST', body: JSON.stringify(article) }); setState(prev => ({ ...prev, articles: [{...article, id: res.id, lastUpdated: res.lastUpdated}, ...prev.articles] })); showToast("Articolo salvato", 'success'); } catch (e) { showToast("Errore salvataggio articolo", 'error'); } }; const updateArticle = async (updatedArticle: KBArticle) => { try { await apiFetch(`/articles/${updatedArticle.id}`, { method: 'PATCH', body: JSON.stringify(updatedArticle) }); setState(prev => ({ ...prev, articles: prev.articles.map(a => a.id === updatedArticle.id ? updatedArticle : a) })); showToast("Articolo aggiornato", 'success'); } catch (e) { showToast("Errore aggiornamento articolo", 'error'); } }; // --- Survey Management --- const submitSurvey = async (surveyData: Omit) => { try { const res = await apiFetch('/surveys', { method: 'POST', body: JSON.stringify(surveyData) }); const newSurvey = { ...surveyData, id: res.id, timestamp: new Date().toISOString() }; setState(prev => ({ ...prev, surveys: [...prev.surveys, newSurvey] })); showToast("Feedback inviato", 'success'); } catch (e) { showToast("Errore invio feedback", 'error'); } }; // --- Settings & Management (Now with Persistence) --- const addAgent = async (agent: Agent) => { try { await apiFetch('/agents', { method: 'POST', body: JSON.stringify(agent) }); setState(prev => ({ ...prev, agents: [...prev.agents, agent] })); showToast("Agente creato", 'success'); } catch(e) { showToast("Errore creazione agente", 'error'); } }; const updateAgent = async (agent: Agent) => { try { await apiFetch(`/agents/${agent.id}`, { method: 'PUT', body: JSON.stringify(agent) }); setState(prev => ({ ...prev, agents: prev.agents.map(a => a.id === agent.id ? agent : a) })); showToast("Agente aggiornato", 'success'); } catch(e) { showToast("Errore aggiornamento agente", 'error'); } }; const removeAgent = async (id: string) => { try { await apiFetch(`/agents/${id}`, { method: 'DELETE' }); setState(prev => ({ ...prev, agents: prev.agents.filter(a => a.id !== id) })); showToast("Agente rimosso", 'info'); } catch(e) { showToast("Errore rimozione agente", 'error'); } }; const addClientUser = async (user: ClientUser) => { // Reuses auth/register usually, but for admin adding user: try { await apiFetch('/auth/register', { method: 'POST', body: JSON.stringify(user) }); setState(prev => ({ ...prev, clientUsers: [...prev.clientUsers, user] })); showToast("Utente aggiunto", 'success'); } catch(e) { showToast("Errore aggiunta utente", 'error'); } }; const updateClientUser = async (user: ClientUser) => { try { await apiFetch(`/client_users/${user.id}`, { method: 'PUT', body: JSON.stringify(user) }); setState(prev => ({ ...prev, clientUsers: prev.clientUsers.map(u => u.id === user.id ? user : u) })); showToast("Utente aggiornato", 'success'); } catch(e) { showToast("Errore aggiornamento utente", 'error'); } }; const removeClientUser = async (id: string) => { try { await apiFetch(`/client_users/${id}`, { method: 'DELETE' }); setState(prev => ({ ...prev, clientUsers: prev.clientUsers.filter(u => u.id !== id) })); showToast("Utente rimosso", 'info'); } catch(e) { showToast("Errore rimozione utente", 'error'); } }; const updateSettings = async (newSettings: AppSettings) => { try { await apiFetch('/settings', { method: 'PUT', body: JSON.stringify(newSettings) }); setState(prev => ({ ...prev, settings: newSettings })); // Note: Real-time update of theme might require reload or context update, but setState handles it for current session } catch(e) { showToast("Errore salvataggio impostazioni", 'error'); } }; const addQueue = async (queue: TicketQueue) => { try { await apiFetch('/queues', { method: 'POST', body: JSON.stringify(queue) }); setState(prev => ({ ...prev, queues: [...prev.queues, queue] })); showToast("Coda aggiunta", 'success'); } catch(e) { showToast("Errore aggiunta coda", 'error'); } }; const removeQueue = async (id: string) => { try { await apiFetch(`/queues/${id}`, { method: 'DELETE' }); setState(prev => ({ ...prev, queues: prev.queues.filter(q => q.id !== id) })); showToast("Coda rimossa", 'info'); } catch(e) { showToast("Errore rimozione coda", 'error'); } }; // Render Logic if (!state.currentUser) { return ( <> handleLogin(e, p, false)} onAgentLogin={(e, p) => handleLogin(e, p, true)} onClientRegister={handleClientRegister} /> ); } // Filter Tickets const isAgentOrSupervisor = state.userRole === 'agent' || state.userRole === 'supervisor'; const agentTickets = isAgentOrSupervisor ? state.tickets.filter(t => (state.currentUser as Agent).queues?.includes(t.queue)) : state.tickets; return (
{state.userRole === 'client' ? ( t.customerName === state.currentUser?.name)} queues={state.queues} settings={state.settings} onCreateTicket={createTicket} onReplyTicket={replyToTicket} onSubmitSurvey={submitSurvey} onLogout={handleLogout} showToast={showToast} /> ) : ( )}
); }; export default App;