diff --git a/components/AgentDashboard.tsx b/components/AgentDashboard.tsx index c32d5df..4837c63 100644 --- a/components/AgentDashboard.tsx +++ b/components/AgentDashboard.tsx @@ -45,7 +45,10 @@ import { Inbox, Send, Eye, - EyeOff + EyeOff, + Globe, + Sliders, + ChevronRight } from 'lucide-react'; interface AgentDashboardProps { @@ -62,8 +65,8 @@ interface AgentDashboardProps { onReplyTicket: (ticketId: string, message: string) => void; addArticle: (article: KBArticle) => void; updateArticle: (article: KBArticle) => void; - deleteArticle?: (id: string) => void; // New Prop - markTicketsAsAnalyzed?: (ticketIds: string[]) => void; // New Prop + deleteArticle?: (id: string) => void; + markTicketsAsAnalyzed?: (ticketIds: string[]) => void; addAgent: (agent: Agent) => void; updateAgent: (agent: Agent) => void; removeAgent: (id: string) => void; @@ -90,7 +93,6 @@ const AvatarEditor: React.FC = ({ initialImage, initialConfig 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); @@ -100,7 +102,7 @@ const AvatarEditor: React.FC = ({ initialImage, initialConfig 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 + setConfig({ x: 50, y: 50, scale: 1 }); } }; @@ -111,12 +113,12 @@ const AvatarEditor: React.FC = ({ initialImage, initialConfig const handleMouseMove = (e: React.MouseEvent) => { if (!isDragging) return; - const deltaX = (e.clientX - dragStart.current.x) * 0.5; // Sensitivity + const deltaX = (e.clientX - dragStart.current.x) * 0.5; 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 + x: Math.min(100, Math.max(0, prev.x - deltaX * 0.2)), y: Math.min(100, Math.max(0, prev.y - deltaY * 0.2)) })); @@ -126,9 +128,9 @@ const AvatarEditor: React.FC = ({ initialImage, initialConfig const handleMouseUp = () => setIsDragging(false); return ( -
+
= ({ initialImage, initialConfig
)}
- +
-
{image && ( -
- Zoom +
+ Zoom = ({ initialImage, initialConfig step="0.1" value={config.scale} onChange={(e) => setConfig({...config, scale: parseFloat(e.target.value)})} - className="flex-1 h-1 bg-gray-300 rounded-lg appearance-none cursor-pointer" + className="flex-1 h-1.5 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-brand-600" />
)} -

Trascina l'immagine per centrarla

+

Trascina per centrare

@@ -222,7 +224,7 @@ export const AgentDashboard: React.FC = ({ }) => { const [view, setView] = useState<'tickets' | 'kb' | 'ai' | 'analytics' | 'settings'>('tickets'); const [selectedTicketId, setSelectedTicketId] = useState(null); - const [selectedQueue, setSelectedQueue] = useState(null); // Name of the queue + const [selectedQueue, setSelectedQueue] = useState(null); const [isViewingArchive, setIsViewingArchive] = useState(false); const [replyText, setReplyText] = useState(''); @@ -231,21 +233,18 @@ export const AgentDashboard: React.FC = ({ 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 + if (canManageGlobalSettings) setSettingsTab('system'); else if (canManageTeam) setSettingsTab('users'); } }, [view, canManageGlobalSettings, canManageTeam]); - // Set default queue when entering tickets view useEffect(() => { if (view === 'tickets' && !selectedQueue && !isViewingArchive && queues.length > 0) { - // Find the first queue assigned to the agent, or just the first queue const firstAssignedQueue = queues.find(q => currentUser.queues.includes(q.name)); if (firstAssignedQueue) { setSelectedQueue(firstAssignedQueue.name); @@ -275,13 +274,10 @@ export const AgentDashboard: React.FC = ({ const [newQueueForm, setNewQueueForm] = useState>({ name: '', description: '' }); const [tempSettings, setTempSettings] = useState(settings); - // Sync tempSettings with settings prop useEffect(() => { setTempSettings(settings); }, [settings]); - // Email Template Editor State - const [editingTemplate, setEditingTemplate] = useState(null); const [isTestingSmtp, setIsTestingSmtp] = useState(false); const selectedTicket = tickets.find(t => t.id === selectedTicketId); @@ -296,16 +292,11 @@ export const AgentDashboard: React.FC = ({ 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(); @@ -370,7 +361,6 @@ export const AgentDashboard: React.FC = ({ setIsAiAnalyzing(true); setAiSuggestions([]); - // Pass ALL tickets. The service filters resolved ones. const suggestions = await generateNewKBArticle( settings.aiConfig.apiKey, tickets, @@ -385,7 +375,6 @@ export const AgentDashboard: React.FC = ({ showToast("Nessuna nuova lacuna identificata dall'AI nei ticket recenti.", 'info'); } - // Mark current resolved & unanalyzed tickets as analyzed so we don't process them again const ticketsToMark = tickets .filter(t => t.status === TicketStatus.RESOLVED && !t.hasBeenAnalyzed) .map(t => t.id); @@ -407,7 +396,7 @@ export const AgentDashboard: React.FC = ({ category: suggestion.category, type: 'article', source: 'ai', - visibility: 'internal', // Default AI suggestions to internal for review + visibility: 'internal', lastUpdated: new Date().toISOString().split('T')[0] }); setAiSuggestions(prev => prev.filter((_, i) => i !== index)); @@ -530,7 +519,6 @@ export const AgentDashboard: React.FC = ({ setNewAgentForm({ ...newAgentForm, queues: newQueues }); }; - // --- USER MANAGEMENT HANDLERS --- const handleAddUser = () => { if(newUserForm.name && newUserForm.email) { addClientUser({ @@ -539,7 +527,7 @@ export const AgentDashboard: React.FC = ({ email: newUserForm.email, company: newUserForm.company, status: newUserForm.status || 'active', - password: 'user' // Default password + password: 'user' } as ClientUser); setNewUserForm({ name: '', email: '', status: 'active', company: '' }); } @@ -575,7 +563,6 @@ export const AgentDashboard: React.FC = ({ }; const handleSendPasswordReset = (email: string) => { - // Simulate sending email showToast(`Link di reset password inviato a ${email}`, 'success'); }; @@ -590,7 +577,6 @@ export const AgentDashboard: React.FC = ({ } }; - // Email & SMTP Handlers const handleTestSmtp = () => { setIsTestingSmtp(true); setTimeout(() => { @@ -608,12 +594,10 @@ export const AgentDashboard: React.FC = ({ }, 800); }; - // Filter Logic for Ticket View const getFilteredTickets = () => { if (isViewingArchive) { return tickets.filter(t => t.status === TicketStatus.RESOLVED || t.status === TicketStatus.CLOSED); } - // Viewing a specific queue (Open tickets only) if (selectedQueue) { return tickets.filter(t => t.queue === selectedQueue && @@ -626,7 +610,6 @@ export const AgentDashboard: React.FC = ({ const filteredTickets = getFilteredTickets(); const queueCounts: Record = {}; - // Calculate open tickets per queue for the sidebar queues.forEach(q => { queueCounts[q.name] = tickets.filter(t => t.queue === q.name && @@ -634,7 +617,6 @@ export const AgentDashboard: React.FC = ({ ).length; }); - // ANALYTICS & STATS CALCULATION const totalTickets = tickets.length; const resolvedCount = tickets.filter(t => t.status === TicketStatus.RESOLVED || t.status === TicketStatus.CLOSED).length; const resolutionRate = totalTickets > 0 ? Math.round((resolvedCount / totalTickets) * 100) : 0; @@ -660,14 +642,12 @@ export const AgentDashboard: React.FC = ({

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

- {/* ... Profile Footer ... */}
@@ -689,191 +669,621 @@ export const AgentDashboard: React.FC = ({ {/* SETTINGS VIEW */} {view === 'settings' && canAccessSettings && ( -
-
+
+
{/* Sidebar Settings Tabs */}
-
-

Impostazioni

+
+

Configurazione

+

Gestisci le preferenze globali

-
-
- {/* AI CONFIG TAB WITH CUSTOMIZATION */} - {settingsTab === 'ai' && canManageTeam && ( -
-

Integrazione AI

- - {/* Provider & Key */} -
-

Motore AI

-
- - -
-
- - setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, model: e.target.value}})} - /> -
-
- - setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, apiKey: e.target.value}})} - /> -
-
- - {/* Chat Agent Customization */} -
-

- Personalizzazione Chatbot -

-
-
- -
-
- AI Avatar -
- setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, agentAvatar: e.target.value}})} - /> -
-
-
-
- - setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, agentName: e.target.value}})} - /> -
-
- -