import React, { useState, useRef, useEffect } from 'react'; import { Ticket, KBArticle, Agent, TicketStatus, TicketPriority, SurveyResult, AppSettings, ClientUser, TicketQueue, EmailTemplate, EmailTrigger, EmailAudience, AgentAvatarConfig, AgentRole, AiProvider } from '../types'; import { generateNewKBArticle } from '../services/geminiService'; import { ToastType } from './Toast'; import { LayoutDashboard, BookOpen, Users, Sparkles, CheckCircle, Clock, Edit3, Plus, ExternalLink, FileText, BarChart3, TrendingUp, MessageCircle, Star, Settings, Trash2, Mail, Palette, Shield, Save, LogOut, Paperclip, Layers, X, Camera, Move, Check, Zap, Copy, Activity, UserPlus, Loader2, Lock, Cpu, AlertTriangle, RotateCcw, Bot, Key } from 'lucide-react'; interface AgentDashboardProps { currentUser: Agent; tickets: Ticket[]; articles: KBArticle[]; agents: Agent[]; queues: TicketQueue[]; surveys?: SurveyResult[]; clientUsers: ClientUser[]; settings: AppSettings; updateTicketStatus: (id: string, status: TicketStatus) => void; updateTicketAgent: (id: string, agentId: string) => void; addArticle: (article: KBArticle) => void; updateArticle: (article: KBArticle) => void; addAgent: (agent: Agent) => void; updateAgent: (agent: Agent) => void; removeAgent: (id: string) => void; addClientUser: (user: ClientUser) => void; updateClientUser: (user: ClientUser) => void; removeClientUser: (id: string) => void; updateSettings: (settings: AppSettings) => void; addQueue: (queue: TicketQueue) => void; removeQueue: (id: string) => void; onLogout: () => void; showToast: (message: string, type: ToastType) => void; } // --- SUB-COMPONENT: Avatar Editor --- interface AvatarEditorProps { initialImage: string; initialConfig?: AgentAvatarConfig; onSave: (image: string, config: AgentAvatarConfig) => void; } const AvatarEditor: React.FC = ({ initialImage, initialConfig, onSave }) => { const [image, setImage] = useState(initialImage); const [config, setConfig] = useState(initialConfig || { x: 50, y: 50, scale: 1 }); const [isDragging, setIsDragging] = useState(false); const dragStart = useRef<{x: number, y: number}>({ x: 0, y: 0 }); // Update local state when props change (for editing) useEffect(() => { setImage(initialImage); if(initialConfig) setConfig(initialConfig); }, [initialImage, initialConfig]); const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const url = URL.createObjectURL(e.target.files[0]); setImage(url); setConfig({ x: 50, y: 50, scale: 1 }); // Reset config for new image } }; const handleMouseDown = (e: React.MouseEvent) => { setIsDragging(true); dragStart.current = { x: e.clientX, y: e.clientY }; }; const handleMouseMove = (e: React.MouseEvent) => { if (!isDragging) return; const deltaX = (e.clientX - dragStart.current.x) * 0.5; // Sensitivity const deltaY = (e.clientY - dragStart.current.y) * 0.5; setConfig(prev => ({ ...prev, x: Math.min(100, Math.max(0, prev.x - deltaX * 0.2)), // Invert direction for natural feel or keep normal y: Math.min(100, Math.max(0, prev.y - deltaY * 0.2)) })); dragStart.current = { x: e.clientX, y: e.clientY }; }; const handleMouseUp = () => setIsDragging(false); return (
{image ? ( Avatar ) : (
)}
{image && (
Zoom setConfig({...config, scale: parseFloat(e.target.value)})} className="flex-1 h-1 bg-gray-300 rounded-lg appearance-none cursor-pointer" />
)}

Trascina l'immagine per centrarla

); }; // --- MAIN COMPONENT --- export const AgentDashboard: React.FC = ({ currentUser, tickets, articles, agents, queues, surveys = [], clientUsers, settings, updateTicketStatus, updateTicketAgent, addArticle, updateArticle, addAgent, updateAgent, removeAgent, addClientUser, updateClientUser, removeClientUser, updateSettings, addQueue, removeQueue, onLogout, showToast }) => { const [view, setView] = useState<'tickets' | 'kb' | 'ai' | 'analytics' | 'settings'>('tickets'); const [selectedTicketId, setSelectedTicketId] = useState(null); // ROLE BASED PERMISSIONS const canManageGlobalSettings = currentUser.role === 'superadmin'; const canManageTeam = currentUser.role === 'superadmin' || currentUser.role === 'supervisor'; const canAccessSettings = canManageTeam || canManageGlobalSettings; // Initialize correct tab based on permissions const [settingsTab, setSettingsTab] = useState<'general' | 'system' | 'ai' | 'users' | 'agents' | 'queues' | 'email'>('users'); const [isSaving, setIsSaving] = useState(false); useEffect(() => { if (view === 'settings') { if (canManageGlobalSettings) setSettingsTab('system'); // Default to system for superadmin else if (canManageTeam) setSettingsTab('users'); } }, [view, canManageGlobalSettings, canManageTeam]); // KB Editor State const [isEditingKB, setIsEditingKB] = useState(false); const [newArticle, setNewArticle] = useState>({ type: 'article', category: 'General' }); const [isFetchingUrl, setIsFetchingUrl] = useState(false); // AI State const [isAiAnalyzing, setIsAiAnalyzing] = useState(false); const [aiSuggestion, setAiSuggestion] = useState<{ title: string; content: string; category: string } | null>(null); // Forms State for Settings const [newAgentForm, setNewAgentForm] = useState>({ name: '', email: '', password: '', skills: [], queues: [], role: 'agent', avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} }); const [editingAgent, setEditingAgent] = useState(null); const [newUserForm, setNewUserForm] = useState>({ name: '', email: '', status: 'active', company: '' }); const [editingUser, setEditingUser] = useState(null); const [newQueueForm, setNewQueueForm] = useState>({ name: '', description: '' }); const [tempSettings, setTempSettings] = useState(settings); // Email Template Editor State const [editingTemplate, setEditingTemplate] = useState(null); const [isTestingSmtp, setIsTestingSmtp] = useState(false); const selectedTicket = tickets.find(t => t.id === selectedTicketId); // Stats for Quotas const currentAgents = agents.filter(a => a.role === 'agent').length; const currentSupervisors = agents.filter(a => a.role === 'supervisor').length; const currentArticles = articles.length; const currentAiArticles = articles.filter(a => a.source === 'ai').length; const isKbFull = currentArticles >= settings.features.maxKbArticles; const isAgentQuotaFull = currentAgents >= settings.features.maxAgents; const isSupervisorQuotaFull = currentSupervisors >= settings.features.maxSupervisors; // Filter Agents for Assignment based on Role const getAssignableAgents = (ticketQueue: string) => { // Superadmin and Supervisor can assign to anyone if (canManageTeam) return agents; // Agents can only assign to agents in the same queue return agents.filter(a => a.queues.includes(ticketQueue)); }; // Helper function to fetch URL content via proxy const fetchUrlContent = async (url: string): Promise => { const parseContent = (html: string) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const scripts = doc.querySelectorAll('script, style, nav, footer, header, svg, noscript, iframe'); scripts.forEach(node => node.remove()); let text = doc.body?.textContent || ""; text = text.replace(/\s+/g, ' ').trim(); return text.substring(0, 5000); }; try { const res1 = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(url)}`); if (res1.ok) { const data = await res1.json(); if (data.contents) return parseContent(data.contents); } } catch (e) { console.warn("Primary scraping failed, trying fallback..."); } try { const res2 = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`); if (res2.ok) { const html = await res2.text(); return parseContent(html); } } catch (e) { console.error("Scraping error:", e); } return ""; }; // Handlers const handleAiAnalysis = async () => { if (!settings.features.aiKnowledgeAgentEnabled) { showToast("L'Agente Knowledge AI è disabilitato dall'amministratore.", 'error'); return; } if (currentAiArticles >= settings.features.maxAiGeneratedArticles) { showToast("Quota creazione articoli AI raggiunta. Impossibile generare nuovi suggerimenti.", 'error'); return; } setIsAiAnalyzing(true); setAiSuggestion(null); const suggestion = await generateNewKBArticle(tickets, articles); setAiSuggestion(suggestion); setIsAiAnalyzing(false); }; const saveAiArticle = () => { if (aiSuggestion) { addArticle({ id: `kb-${Date.now()}`, title: aiSuggestion.title, content: aiSuggestion.content, category: aiSuggestion.category, type: 'article', source: 'ai', lastUpdated: new Date().toISOString().split('T')[0] }); setAiSuggestion(null); setView('kb'); } }; const handleSaveArticle = async () => { if (newArticle.title && (newArticle.content || newArticle.url)) { let finalContent = newArticle.content || ''; if (newArticle.type === 'url' && newArticle.url) { setIsFetchingUrl(true); const scrapedText = await fetchUrlContent(newArticle.url); setIsFetchingUrl(false); if (scrapedText) { finalContent = `[CONTENUTO PAGINA WEB SCARICATO]\n\n${scrapedText}\n\n[NOTE MANUALI]\n${newArticle.content || ''}`; } else { finalContent = newArticle.content || 'Contenuto non recuperabile automaticamente. Fare riferimento al link.'; } } const articleToSave: KBArticle = { id: newArticle.id || `kb-${Date.now()}`, title: newArticle.title, content: finalContent, category: newArticle.category || 'General', type: newArticle.type || 'article', url: newArticle.url, source: newArticle.source || 'manual', lastUpdated: new Date().toISOString().split('T')[0] }; if (newArticle.id) { updateArticle(articleToSave); } else { addArticle(articleToSave); } setIsEditingKB(false); setNewArticle({ type: 'article', category: 'General' }); } }; const handleAvatarSaved = (image: string, config: AgentAvatarConfig, isEditing: boolean) => { if (isEditing && editingAgent) { setEditingAgent({ ...editingAgent, avatar: image, avatarConfig: config }); } else { setNewAgentForm({ ...newAgentForm, avatar: image, avatarConfig: config }); } }; const handleAddAgent = () => { if(newAgentForm.name && newAgentForm.email && newAgentForm.queues && newAgentForm.queues.length > 0) { addAgent({ id: `a${Date.now()}`, name: newAgentForm.name, email: newAgentForm.email, password: newAgentForm.password || 'password', role: newAgentForm.role || 'agent', avatar: newAgentForm.avatar || 'https://via.placeholder.com/200', avatarConfig: newAgentForm.avatarConfig, skills: newAgentForm.skills || ['General'], queues: newAgentForm.queues } as Agent); setNewAgentForm({ name: '', email: '', password: '', role: 'agent', skills: [], queues: [], avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} }); } else { showToast("Compila nome, email e seleziona almeno una coda.", 'error'); } }; const handleUpdateAgent = () => { if (editingAgent && newAgentForm.name && newAgentForm.email) { const updatedAgent: Agent = { ...editingAgent, name: newAgentForm.name!, email: newAgentForm.email!, password: newAgentForm.password || editingAgent.password, role: newAgentForm.role as AgentRole, avatar: newAgentForm.avatar || editingAgent.avatar, avatarConfig: newAgentForm.avatarConfig, queues: newAgentForm.queues || [], skills: newAgentForm.skills || [] }; updateAgent(updatedAgent); setEditingAgent(null); setNewAgentForm({ name: '', email: '', password: '', role: 'agent', skills: [], queues: [], avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} }); } }; const handleEditAgentClick = (agent: Agent) => { setEditingAgent(agent); setNewAgentForm({ name: agent.name, email: agent.email, password: agent.password, role: agent.role, avatar: agent.avatar, avatarConfig: agent.avatarConfig, skills: agent.skills, queues: agent.queues }); }; const cancelEditAgent = () => { setEditingAgent(null); setNewAgentForm({ name: '', email: '', password: '', role: 'agent', skills: [], queues: [], avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} }); }; const toggleQueueInForm = (queueName: string, isEditing: boolean) => { const currentQueues = newAgentForm.queues || []; const newQueues = currentQueues.includes(queueName) ? currentQueues.filter(q => q !== queueName) : [...currentQueues, queueName]; setNewAgentForm({ ...newAgentForm, queues: newQueues }); }; // --- USER MANAGEMENT HANDLERS --- const handleAddUser = () => { if(newUserForm.name && newUserForm.email) { addClientUser({ id: `u${Date.now()}`, name: newUserForm.name, email: newUserForm.email, company: newUserForm.company, status: newUserForm.status || 'active', password: 'user' // Default password } as ClientUser); setNewUserForm({ name: '', email: '', status: 'active', company: '' }); } }; const handleUpdateUser = () => { if (editingUser && newUserForm.name && newUserForm.email) { updateClientUser({ ...editingUser, name: newUserForm.name, email: newUserForm.email, company: newUserForm.company, status: newUserForm.status as 'active' | 'inactive' }); setEditingUser(null); setNewUserForm({ name: '', email: '', status: 'active', company: '' }); } }; const handleEditUserClick = (user: ClientUser) => { setEditingUser(user); setNewUserForm({ name: user.name, email: user.email, company: user.company, status: user.status }); }; const cancelEditUser = () => { setEditingUser(null); setNewUserForm({ name: '', email: '', status: 'active', company: '' }); }; const handleSendPasswordReset = (email: string) => { // Simulate sending email showToast(`Link di reset password inviato a ${email}`, 'success'); }; const handleAddQueue = () => { if (newQueueForm.name) { addQueue({ id: `q-${Date.now()}`, name: newQueueForm.name, description: newQueueForm.description } as TicketQueue); setNewQueueForm({ name: '', description: '' }); } }; // Email & SMTP Handlers const handleTestSmtp = () => { setIsTestingSmtp(true); setTimeout(() => { setIsTestingSmtp(false); showToast(`Test Connessione SMTP Riuscito! Host: ${tempSettings.smtp.host}`, 'success'); }, 1500); }; const handleSaveTemplate = () => { if (editingTemplate) { let updatedTemplates = [...tempSettings.emailTemplates]; if (editingTemplate.id === 'new') { updatedTemplates.push({ ...editingTemplate, id: `t${Date.now()}` }); } else { updatedTemplates = updatedTemplates.map(t => t.id === editingTemplate.id ? editingTemplate : t); } setTempSettings({ ...tempSettings, emailTemplates: updatedTemplates }); setEditingTemplate(null); } }; const handleDeleteTemplate = (id: string) => { setTempSettings({ ...tempSettings, emailTemplates: tempSettings.emailTemplates.filter(t => t.id !== id) }); }; const handleSaveSettings = () => { setIsSaving(true); // Simulate API call setTimeout(() => { updateSettings(tempSettings); setIsSaving(false); showToast("Impostazioni salvate con successo!", 'success'); }, 800); }; // Analytics Helpers const avgRating = surveys.length > 0 ? (surveys.reduce((acc, curr) => acc + curr.rating, 0) / surveys.length).toFixed(1) : 'N/A'; const totalTickets = tickets.length; const resolvedTickets = tickets.filter(t => t.status === TicketStatus.RESOLVED).length; const resolutionRate = totalTickets > 0 ? ((resolvedTickets / totalTickets) * 100).toFixed(0) : 0; const techCount = tickets.filter(t => t.queue === 'Tech Support').length; const billingCount = tickets.filter(t => t.queue === 'Billing').length; const generalCount = tickets.filter(t => t.queue === 'General').length; const maxQueue = Math.max(techCount, billingCount, generalCount); // Template Variables Legend const getTemplateVariables = (trigger?: EmailTrigger) => { const common = ['{app_name}', '{ticket_id}', '{ticket_subject}', '{customer_name}', '{queue_name}']; if (trigger === EmailTrigger.STATUS_CHANGED) return [...common, '{status}', '{old_status}']; if (trigger === EmailTrigger.AGENT_ASSIGNED) return [...common, '{agent_name}', '{agent_email}']; if (trigger === EmailTrigger.SURVEY_REQUEST) return [...common, '{survey_link}']; if (trigger === EmailTrigger.NEW_REPLY) return [...common, '{last_message}', '{reply_link}']; return [...common, '{ticket_priority}', '{ticket_description}']; }; return (
{/* Sidebar */}

{settings.branding.appName}

{currentUser.role === 'superadmin' ? 'Super Admin' : currentUser.role === 'supervisor' ? 'Supervisor Workspace' : 'Agent Workspace'}

{currentUser.name}

{currentUser.name}

{currentUser.queues.join(', ')}

{/* Main Content */}
{/* SETTINGS VIEW - PERMISSION GATED */} {view === 'settings' && canAccessSettings && (
{/* Sidebar Settings Tabs */}

Impostazioni

{/* SYSTEM TAB - SUPERADMIN ONLY */} {settingsTab === 'system' && canManageGlobalSettings && (

Limiti e Quote di Sistema

Knowledge Base

Stato Modulo KB

Abilita o disabilita l'accesso alla KB

setTempSettings({...tempSettings, features: {...tempSettings.features, maxKbArticles: parseInt(e.target.value)}})} />

Utilizzo corrente: {currentArticles}

Personale

setTempSettings({...tempSettings, features: {...tempSettings.features, maxSupervisors: parseInt(e.target.value)}})} />

Utilizzo corrente: {currentSupervisors}

setTempSettings({...tempSettings, features: {...tempSettings.features, maxAgents: parseInt(e.target.value)}})} />

Utilizzo corrente: {currentAgents}

AI Knowledge Agent

Stato Agente AI

Auto-generazione articoli da ticket

setTempSettings({...tempSettings, features: {...tempSettings.features, maxAiGeneratedArticles: parseInt(e.target.value)}})} />

Generati finora: {currentAiArticles}

)} {/* AI CONFIG TAB - NEW */} {settingsTab === 'ai' && canManageTeam && (

Integrazione AI

Configurazione Provider AI

Scegli il motore di intelligenza artificiale per l'assistente chat e l'analisi della KB. Puoi usare provider cloud o self-hosted.

setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, model: e.target.value}})} />
setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, baseUrl: e.target.value}})} />
)} {settingsTab === 'general' && canManageGlobalSettings && (

Personalizzazione Branding

setTempSettings({...tempSettings, branding: {...tempSettings.branding, appName: e.target.value}})} />
setTempSettings({...tempSettings, branding: {...tempSettings.branding, primaryColor: e.target.value}})} /> {tempSettings.branding.primaryColor}
setTempSettings({...tempSettings, branding: {...tempSettings.branding, logoUrl: e.target.value}})} />
)} {settingsTab === 'users' && canManageTeam && (

Gestione Utenti Frontend

{editingUser ? 'Modifica Utente' : 'Aggiungi Nuovo Utente'}

setNewUserForm({...newUserForm, name: e.target.value})} /> setNewUserForm({...newUserForm, email: e.target.value})} /> setNewUserForm({...newUserForm, company: e.target.value})} /> {editingUser && ( )}
{editingUser && ( )}
{clientUsers.map(user => ( ))}
Nome Email Azienda Stato Azioni
{user.name} {user.email} {user.company || '-'} {user.status}
)} {settingsTab === 'agents' && canManageTeam && (

Gestione Team di Supporto

{/* Add/Edit Agent Form */}

{editingAgent ? 'Modifica Agente' : 'Aggiungi Nuovo Agente'}

{!editingAgent && (
Agenti: {currentAgents}/{settings.features.maxAgents} Supervisor: {currentSupervisors}/{settings.features.maxSupervisors}
)}
{/* Avatar Editor Component */} handleAvatarSaved(img, config, !!editingAgent)} />
setNewAgentForm({...newAgentForm, name: e.target.value})} /> setNewAgentForm({...newAgentForm, email: e.target.value})} /> setNewAgentForm({...newAgentForm, password: e.target.value})} />
{queues.map(q => ( ))}
{editingAgent && ( )}
{agents.map(agent => (
{agent.name}

{agent.name} {agent.role === 'superadmin' && } {agent.role === 'supervisor' && }

{agent.email}

Code Assegnate

{agent.queues.map(q => ( {q} ))}
))}
)} {settingsTab === 'queues' && canManageTeam && (

Code di Smistamento

setNewQueueForm({...newQueueForm, name: e.target.value})} /> setNewQueueForm({...newQueueForm, description: e.target.value})} />
{queues.map(queue => ( ))}
Nome Coda Descrizione Agenti Assegnati Azioni
{queue.name} {queue.description || '-'} {agents.filter(a => a.queues.includes(queue.name)).length} agenti
)} {settingsTab === 'email' && canManageGlobalSettings && (

Notifiche & Email

Configurazione SMTP

Imposta il server di posta in uscita.

setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, host: e.target.value}})} />
setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, port: parseInt(e.target.value)}})} />
setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, user: e.target.value}})} />
setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, pass: e.target.value}})} />

{/* Template Manager */} {!editingTemplate ? (

Modelli Email

{tempSettings.emailTemplates.map(template => (

{template.name}

{template.audience}

Trigger: {template.trigger}

))}
) : ( /* Template Editor */

{editingTemplate.id === 'new' ? 'Nuovo Modello' : 'Modifica Modello'}

setEditingTemplate({...editingTemplate, name: e.target.value})} />
setEditingTemplate({...editingTemplate, subject: e.target.value})} />
) : (
Seleziona un ticket per vedere i dettagli
)}
)} {view === 'kb' && (

Gestione Knowledge Base

{settings.features.kbEnabled ? ( ) : ( KB Disabilitata )}
{isEditingKB && (

{newArticle.id ? 'Modifica Elemento' : 'Nuovo Elemento'}

{/* ... (Existing KB Form Content remains the same) ... */}
setNewArticle({...newArticle, title: e.target.value})} />
setNewArticle({...newArticle, category: e.target.value})} />
{newArticle.type === 'article' ? (