Files
omnisupport-ai/components/AgentDashboard.tsx
2026-02-17 12:51:16 +01:00

1771 lines
108 KiB
TypeScript

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,
Archive,
Inbox,
Send,
Eye,
EyeOff,
Globe,
Sliders,
ChevronRight
} 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;
onReplyTicket: (ticketId: string, message: string) => void;
addArticle: (article: KBArticle) => void;
updateArticle: (article: KBArticle) => void;
deleteArticle?: (id: string) => void;
markTicketsAsAnalyzed?: (ticketIds: string[]) => 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<AvatarEditorProps> = ({ initialImage, initialConfig, onSave }) => {
const [image, setImage] = useState(initialImage);
const [config, setConfig] = useState<AgentAvatarConfig>(initialConfig || { x: 50, y: 50, scale: 1 });
const [isDragging, setIsDragging] = useState(false);
const dragStart = useRef<{x: number, y: number}>({ x: 0, y: 0 });
useEffect(() => {
setImage(initialImage);
if(initialConfig) setConfig(initialConfig);
}, [initialImage, initialConfig]);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 });
}
};
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;
const deltaY = (e.clientY - dragStart.current.y) * 0.5;
setConfig(prev => ({
...prev,
x: Math.min(100, Math.max(0, prev.x - deltaX * 0.2)),
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 (
<div className="flex flex-col items-center p-4 bg-gray-50 rounded-xl border border-dashed border-gray-300 hover:border-brand-300 transition-colors">
<div
className="w-32 h-32 rounded-full overflow-hidden border-4 border-white shadow-lg cursor-move relative bg-gray-200 mb-4 ring-2 ring-gray-100"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
{image ? (
<img
src={image}
alt="Avatar"
className="w-full h-full object-cover"
style={{
objectPosition: `${config.x}% ${config.y}%`,
transform: `scale(${config.scale})`
}}
draggable={false}
/>
) : (
<div className="flex items-center justify-center h-full text-gray-400"><Users className="w-8 h-8" /></div>
)}
<div className="absolute inset-0 bg-black bg-opacity-0 hover:bg-opacity-10 transition pointer-events-none flex items-center justify-center">
<Move className="text-white opacity-0 hover:opacity-100 drop-shadow-md w-6 h-6" />
</div>
</div>
<div className="w-full space-y-3">
<div className="flex justify-center">
<label className="cursor-pointer bg-white border border-gray-300 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-50 flex items-center shadow-sm transition-all hover:shadow text-gray-700">
<Camera className="w-4 h-4 mr-2 text-gray-500" />
Carica Foto
<input type="file" className="hidden" accept="image/*" onChange={handleFileChange} />
</label>
</div>
{image && (
<div className="flex items-center space-x-2 px-2">
<span className="text-xs text-gray-500 font-medium">Zoom</span>
<input
type="range"
min="1"
max="3"
step="0.1"
value={config.scale}
onChange={(e) => setConfig({...config, scale: parseFloat(e.target.value)})}
className="flex-1 h-1.5 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-brand-600"
/>
</div>
)}
<p className="text-[10px] text-gray-400 text-center uppercase tracking-wider font-semibold">Trascina per centrare</p>
<button
type="button"
onClick={() => onSave(image, config)}
className="w-full bg-brand-600 text-white py-2 rounded-lg text-sm font-bold hover:bg-brand-700 shadow-md transition-all active:scale-95"
>
Applica Modifiche
</button>
</div>
</div>
);
};
// --- MAIN COMPONENT ---
export const AgentDashboard: React.FC<AgentDashboardProps> = ({
currentUser,
tickets,
articles,
agents,
queues,
surveys = [],
clientUsers,
settings,
updateTicketStatus,
updateTicketAgent,
onReplyTicket,
addArticle,
updateArticle,
deleteArticle,
markTicketsAsAnalyzed,
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<string | null>(null);
const [selectedQueue, setSelectedQueue] = useState<string | null>(null);
const [isViewingArchive, setIsViewingArchive] = useState(false);
const [replyText, setReplyText] = useState('');
// ROLE BASED PERMISSIONS
const canManageGlobalSettings = currentUser.role === 'superadmin';
const canManageTeam = currentUser.role === 'superadmin' || currentUser.role === 'supervisor';
const canAccessSettings = canManageTeam || canManageGlobalSettings;
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');
else if (canManageTeam) setSettingsTab('users');
}
}, [view, canManageGlobalSettings, canManageTeam]);
useEffect(() => {
if (view === 'tickets' && !selectedQueue && !isViewingArchive && queues.length > 0) {
const firstAssignedQueue = queues.find(q => currentUser.queues.includes(q.name));
if (firstAssignedQueue) {
setSelectedQueue(firstAssignedQueue.name);
} else if (queues.length > 0) {
setSelectedQueue(queues[0].name);
}
}
}, [view, queues, currentUser]);
// KB Editor State
const [isEditingKB, setIsEditingKB] = useState(false);
const [newArticle, setNewArticle] = useState<Partial<KBArticle>>({ type: 'article', category: 'General', visibility: 'public' });
const [isFetchingUrl, setIsFetchingUrl] = useState(false);
// AI State
const [isAiAnalyzing, setIsAiAnalyzing] = useState(false);
const [aiSuggestions, setAiSuggestions] = useState<Array<{ title: string; content: string; category: string }>>([]);
// Forms State for Settings
const [newAgentForm, setNewAgentForm] = useState<Partial<Agent>>({ name: '', email: '', password: '', skills: [], queues: [], role: 'agent', avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} });
const [editingAgent, setEditingAgent] = useState<Agent | null>(null);
const [newUserForm, setNewUserForm] = useState<Partial<ClientUser>>({ name: '', email: '', status: 'active', company: '' });
const [editingUser, setEditingUser] = useState<ClientUser | null>(null);
const [newQueueForm, setNewQueueForm] = useState<Partial<TicketQueue>>({ name: '', description: '' });
const [tempSettings, setTempSettings] = useState<AppSettings>(settings);
useEffect(() => {
setTempSettings(settings);
}, [settings]);
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;
const getAssignableAgents = (ticketQueue: string) => {
if (canManageTeam) return agents;
return agents.filter(a => a.queues.includes(ticketQueue));
};
const fetchUrlContent = async (url: string): Promise<string> => {
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 handleReplySubmit = () => {
if (selectedTicketId && replyText.trim()) {
onReplyTicket(selectedTicketId, replyText);
setReplyText('');
showToast('Risposta inviata', 'success');
}
};
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;
}
if (!settings.aiConfig.apiKey) {
showToast("Chiave API mancante. Configura l'AI nelle impostazioni.", 'error');
return;
}
setIsAiAnalyzing(true);
setAiSuggestions([]);
const suggestions = await generateNewKBArticle(
settings.aiConfig.apiKey,
tickets,
articles,
settings.aiConfig.provider,
settings.aiConfig.model
);
if (suggestions) {
setAiSuggestions(suggestions);
if (suggestions.length === 0) {
showToast("Nessuna nuova lacuna identificata dall'AI nei ticket recenti.", 'info');
}
const ticketsToMark = tickets
.filter(t => t.status === TicketStatus.RESOLVED && !t.hasBeenAnalyzed)
.map(t => t.id);
if (ticketsToMark.length > 0 && markTicketsAsAnalyzed) {
markTicketsAsAnalyzed(ticketsToMark);
}
} else {
showToast("Errore durante l'analisi AI.", 'error');
}
setIsAiAnalyzing(false);
};
const saveAiArticle = (suggestion: { title: string; content: string; category: string }, index: number) => {
addArticle({
id: `kb-${Date.now()}-${index}`,
title: suggestion.title,
content: suggestion.content,
category: suggestion.category,
type: 'article',
source: 'ai',
visibility: 'internal',
lastUpdated: new Date().toISOString().split('T')[0]
});
setAiSuggestions(prev => prev.filter((_, i) => i !== index));
showToast("Articolo aggiunto alla KB (Visibilità: Interna)", 'success');
};
const discardAiArticle = (index: number) => {
setAiSuggestions(prev => prev.filter((_, i) => i !== index));
};
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',
visibility: newArticle.visibility || 'public',
lastUpdated: new Date().toISOString().split('T')[0]
};
if (newArticle.id) {
updateArticle(articleToSave);
} else {
addArticle(articleToSave);
}
setIsEditingKB(false);
setNewArticle({ type: 'article', category: 'General', visibility: 'public' });
}
};
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 });
};
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'
} 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) => {
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: '' });
}
};
const handleTestSmtp = () => {
setIsTestingSmtp(true);
setTimeout(() => {
setIsTestingSmtp(false);
showToast(`Test Connessione SMTP Riuscito! Host: ${tempSettings.smtp.host}`, 'success');
}, 1500);
};
const handleSaveSettings = () => {
setIsSaving(true);
updateSettings(tempSettings);
setTimeout(() => {
setIsSaving(false);
showToast("Impostazioni salvate con successo!", "success");
}, 800);
};
const getFilteredTickets = () => {
if (isViewingArchive) {
return tickets.filter(t => t.status === TicketStatus.RESOLVED || t.status === TicketStatus.CLOSED);
}
if (selectedQueue) {
return tickets.filter(t =>
t.queue === selectedQueue &&
(t.status === TicketStatus.OPEN || t.status === TicketStatus.IN_PROGRESS)
);
}
return [];
};
const filteredTickets = getFilteredTickets();
const queueCounts: Record<string, number> = {};
queues.forEach(q => {
queueCounts[q.name] = tickets.filter(t =>
t.queue === q.name &&
(t.status === TicketStatus.OPEN || t.status === TicketStatus.IN_PROGRESS)
).length;
});
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;
const validSurveys = surveys || [];
const ratedSurveys = validSurveys.filter(s => s.rating > 0);
const avgRating = ratedSurveys.length > 0
? (ratedSurveys.reduce((acc, curr) => acc + curr.rating, 0) / ratedSurveys.length).toFixed(1)
: '0.0';
let maxQueue = 0;
queues.forEach(q => {
const count = tickets.filter(t => t.queue === q.name).length;
if (count > maxQueue) maxQueue = count;
});
return (
<div className="flex h-screen bg-gray-100">
{/* Sidebar */}
<div className="w-64 bg-slate-900 text-white flex flex-col flex-shrink-0">
<div className="p-6">
<h2 className="text-2xl font-bold tracking-tight">{settings.branding.appName}</h2>
<p className="text-slate-400 text-sm">{currentUser.role === 'superadmin' ? 'Super Admin' : currentUser.role === 'supervisor' ? 'Supervisor Workspace' : 'Agent Workspace'}</p>
</div>
<nav className="flex-1 px-4 space-y-2">
<button onClick={() => setView('tickets')} className={`flex items-center w-full px-4 py-3 rounded-lg transition ${view === 'tickets' ? 'bg-brand-600 text-white' : 'text-slate-300 hover:bg-slate-800'}`} style={view === 'tickets' ? { backgroundColor: settings.branding.primaryColor } : {}}><LayoutDashboard className="w-5 h-5 mr-3" />Ticket</button>
<button onClick={() => setView('kb')} className={`flex items-center w-full px-4 py-3 rounded-lg transition ${view === 'kb' ? 'bg-brand-600 text-white' : 'text-slate-300 hover:bg-slate-800'}`} style={view === 'kb' ? { backgroundColor: settings.branding.primaryColor } : {}}><BookOpen className="w-5 h-5 mr-3" />Knowledge Base</button>
<button onClick={() => setView('ai')} className={`flex items-center w-full px-4 py-3 rounded-lg transition ${view === 'ai' ? 'bg-purple-600 text-white' : 'text-slate-300 hover:bg-slate-800'}`}><Sparkles className="w-5 h-5 mr-3" />AI Knowledge Agent</button>
<button onClick={() => setView('analytics')} className={`flex items-center w-full px-4 py-3 rounded-lg transition ${view === 'analytics' ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800'}`}><BarChart3 className="w-5 h-5 mr-3" />Analytics</button>
{canAccessSettings && <button onClick={() => setView('settings')} className={`flex items-center w-full px-4 py-3 rounded-lg transition ${view === 'settings' ? 'bg-brand-600 text-white' : 'text-slate-300 hover:bg-slate-800'}`} style={view === 'settings' ? { backgroundColor: settings.branding.primaryColor } : {}}><Settings className="w-5 h-5 mr-3" />Impostazioni</button>}
</nav>
<div className="p-4 border-t border-slate-800">
<div className="flex items-center justify-between">
<div className="flex items-center">
<div className="w-8 h-8 rounded-full overflow-hidden mr-3 bg-gray-600 border border-slate-600">
<img src={currentUser.avatar || 'https://via.placeholder.com/200'} alt={currentUser.name} className="w-full h-full object-cover" style={currentUser.avatarConfig ? { objectPosition: `${currentUser.avatarConfig.x}% ${currentUser.avatarConfig.y}%`, transform: `scale(${currentUser.avatarConfig.scale})` } : {}} />
</div>
<div className="min-w-0">
<p className="text-sm font-medium truncate">{currentUser.name}</p>
<p className="text-xs text-slate-400 truncate w-24" title={currentUser.queues.join(', ')}>{currentUser.queues.join(', ')}</p>
</div>
</div>
<button onClick={onLogout} className="text-slate-400 hover:text-white"><LogOut className="w-4 h-4" /></button>
</div>
</div>
</div>
{/* Main Content */}
<div className="flex-1 overflow-hidden flex flex-col h-screen">
{/* SETTINGS VIEW */}
{view === 'settings' && canAccessSettings && (
<div className="flex-1 overflow-auto p-8 bg-gray-50">
<div className="max-w-6xl mx-auto flex bg-white rounded-xl shadow-lg border border-gray-100 overflow-hidden min-h-[700px]">
{/* Sidebar Settings Tabs */}
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0">
<div className="p-6 border-b border-gray-200 bg-gray-100/50">
<h3 className="font-bold text-gray-800 text-lg flex items-center"><Sliders className="w-5 h-5 mr-2 text-brand-600"/> Configurazione</h3>
<p className="text-xs text-gray-500 mt-1">Gestisci le preferenze globali</p>
</div>
<nav className="flex-1 p-3 space-y-1 overflow-y-auto">
{canManageGlobalSettings && (
<>
<div className="px-3 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">Sistema</div>
<button onClick={() => setSettingsTab('system')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'system' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Cpu className="w-4 h-4 mr-3 opacity-70" /> Sistema & Quote
</button>
<button onClick={() => setSettingsTab('general')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'general' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Palette className="w-4 h-4 mr-3 opacity-70" /> Branding
</button>
</>
)}
{(canManageTeam) && (
<>
<div className="px-3 py-2 mt-4 text-xs font-semibold text-gray-400 uppercase tracking-wider">Team & AI</div>
<button onClick={() => setSettingsTab('ai')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'ai' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Bot className="w-4 h-4 mr-3 opacity-70" /> Configurazione AI
</button>
<button onClick={() => setSettingsTab('users')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'users' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Users className="w-4 h-4 mr-3 opacity-70" /> Utenti Frontend
</button>
<button onClick={() => setSettingsTab('agents')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'agents' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Shield className="w-4 h-4 mr-3 opacity-70" /> Agenti Reali
</button>
<button onClick={() => setSettingsTab('queues')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'queues' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Layers className="w-4 h-4 mr-3 opacity-70" /> Gestione Code
</button>
</>
)}
{canManageGlobalSettings && (
<>
<div className="px-3 py-2 mt-4 text-xs font-semibold text-gray-400 uppercase tracking-wider">Integrazioni</div>
<button onClick={() => setSettingsTab('email')} className={`w-full px-4 py-2.5 text-sm font-medium flex items-center rounded-lg transition-all ${settingsTab === 'email' ? 'bg-white text-brand-600 shadow-sm border border-gray-100' : 'text-gray-600 hover:bg-gray-200/50 hover:text-gray-900'}`}>
<Mail className="w-4 h-4 mr-3 opacity-70" /> Email & SMTP
</button>
</>
)}
</nav>
</div>
<div className="flex-1 p-8 overflow-y-auto bg-gray-50/50">
{/* SYSTEM SETTINGS TAB */}
{settingsTab === 'system' && canManageGlobalSettings && (
<div className="space-y-8 animate-fade-in max-w-4xl mx-auto">
<div>
<h2 className="text-2xl font-bold text-gray-800">Limiti e Quote di Sistema</h2>
<p className="text-gray-500 text-sm mt-1">Configura le restrizioni operative della piattaforma.</p>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="p-2 bg-blue-50 rounded-lg text-blue-600"><BookOpen className="w-5 h-5"/></div>
<span className="text-xs font-bold text-gray-400 uppercase">Knowledge Base</span>
</div>
<label className="block text-sm font-bold text-gray-700 mb-2">Max Articoli</label>
<input type="number" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 focus:border-transparent outline-none transition-all"
value={tempSettings.features.maxKbArticles}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, maxKbArticles: parseInt(e.target.value)}})} />
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="p-2 bg-green-50 rounded-lg text-green-600"><Users className="w-5 h-5"/></div>
<span className="text-xs font-bold text-gray-400 uppercase">Team</span>
</div>
<label className="block text-sm font-bold text-gray-700 mb-2">Max Agenti</label>
<input type="number" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 focus:border-transparent outline-none transition-all"
value={tempSettings.features.maxAgents}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, maxAgents: parseInt(e.target.value)}})} />
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="p-2 bg-purple-50 rounded-lg text-purple-600"><Shield className="w-5 h-5"/></div>
<span className="text-xs font-bold text-gray-400 uppercase">Sicurezza</span>
</div>
<label className="block text-sm font-bold text-gray-700 mb-2">Max Supervisor</label>
<input type="number" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 focus:border-transparent outline-none transition-all"
value={tempSettings.features.maxSupervisors}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, maxSupervisors: parseInt(e.target.value)}})} />
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="p-2 bg-amber-50 rounded-lg text-amber-600"><Sparkles className="w-5 h-5"/></div>
<span className="text-xs font-bold text-gray-400 uppercase">Automazione</span>
</div>
<label className="block text-sm font-bold text-gray-700 mb-2">Quota Giornaliera AI</label>
<input type="number" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 focus:border-transparent outline-none transition-all"
value={tempSettings.features.maxAiGeneratedArticles}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, maxAiGeneratedArticles: parseInt(e.target.value)}})} />
</div>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
<h3 className="font-bold text-gray-800 mb-4">Interruttori Funzionalità</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-100">
<div>
<label htmlFor="kbEnabled" className="font-medium text-gray-800 block">Knowledge Base Pubblica</label>
<span className="text-xs text-gray-500">Permetti ai clienti di accedere agli articoli pubblici</span>
</div>
<div className="relative inline-block w-12 mr-2 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" id="kbEnabled" className="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer checked:right-0 checked:border-brand-600"
style={{right: tempSettings.features.kbEnabled ? '0' : 'auto', left: tempSettings.features.kbEnabled ? 'auto' : '0', borderColor: tempSettings.features.kbEnabled ? '#0284c7' : '#e5e7eb'}}
checked={tempSettings.features.kbEnabled}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, kbEnabled: e.target.checked}})} />
<label htmlFor="kbEnabled" className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer ${tempSettings.features.kbEnabled ? 'bg-brand-600' : 'bg-gray-300'}`}></label>
</div>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-100">
<div>
<label htmlFor="aiAgent" className="font-medium text-gray-800 block">Agente AI Knowledge Extraction</label>
<span className="text-xs text-gray-500">Analisi automatica dei ticket risolti per suggerire nuovi articoli</span>
</div>
<div className="relative inline-block w-12 mr-2 align-middle select-none">
<input type="checkbox" id="aiAgent" className="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer checked:right-0 checked:border-brand-600"
style={{right: tempSettings.features.aiKnowledgeAgentEnabled ? '0' : 'auto', left: tempSettings.features.aiKnowledgeAgentEnabled ? 'auto' : '0', borderColor: tempSettings.features.aiKnowledgeAgentEnabled ? '#0284c7' : '#e5e7eb'}}
checked={tempSettings.features.aiKnowledgeAgentEnabled}
onChange={e => setTempSettings({...tempSettings, features: {...tempSettings.features, aiKnowledgeAgentEnabled: e.target.checked}})} />
<label htmlFor="aiAgent" className={`toggle-label block overflow-hidden h-6 rounded-full cursor-pointer ${tempSettings.features.aiKnowledgeAgentEnabled ? 'bg-brand-600' : 'bg-gray-300'}`}></label>
</div>
</div>
</div>
</div>
</div>
)}
{/* GENERAL (BRANDING) TAB */}
{settingsTab === 'general' && canManageGlobalSettings && (
<div className="space-y-8 max-w-4xl mx-auto animate-fade-in">
<div>
<h2 className="text-2xl font-bold text-gray-800">Identità del Brand</h2>
<p className="text-gray-500 text-sm mt-1">Personalizza l'aspetto della piattaforma per i tuoi clienti.</p>
</div>
<div className="bg-white p-8 rounded-xl border border-gray-200 shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-6">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Nome Applicazione</label>
<input type="text" className="w-full border border-gray-300 rounded-lg px-4 py-2.5 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none"
value={tempSettings.branding.appName}
onChange={e => setTempSettings({...tempSettings, branding: {...tempSettings.branding, appName: e.target.value}})} />
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">URL Logo</label>
<input type="text" className="w-full border border-gray-300 rounded-lg px-4 py-2.5 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none text-sm font-mono"
value={tempSettings.branding.logoUrl}
onChange={e => setTempSettings({...tempSettings, branding: {...tempSettings.branding, logoUrl: e.target.value}})} />
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Colore Primario</label>
<div className="flex items-center space-x-3">
<div className="h-10 w-10 rounded-lg shadow-sm border border-gray-200 p-1 bg-white">
<input type="color" className="w-full h-full cursor-pointer rounded"
value={tempSettings.branding.primaryColor}
onChange={e => setTempSettings({...tempSettings, branding: {...tempSettings.branding, primaryColor: e.target.value}})} />
</div>
<input type="text" className="flex-1 border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 uppercase font-mono"
value={tempSettings.branding.primaryColor}
onChange={e => setTempSettings({...tempSettings, branding: {...tempSettings.branding, primaryColor: e.target.value}})} />
</div>
</div>
</div>
<div className="flex flex-col items-center justify-center p-6 bg-gray-50 rounded-xl border border-dashed border-gray-300">
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-widest mb-4">Anteprima Live</h4>
<div className="w-64 bg-white rounded-lg shadow-md overflow-hidden border border-gray-100">
<div className="h-12 flex items-center px-4" style={{backgroundColor: tempSettings.branding.primaryColor}}>
<div className="w-6 h-6 bg-white/20 rounded mr-2"></div>
<div className="w-20 h-3 bg-white/20 rounded"></div>
</div>
<div className="p-4 space-y-3">
<div className="h-4 w-3/4 bg-gray-100 rounded"></div>
<div className="h-4 w-1/2 bg-gray-100 rounded"></div>
<button className="px-3 py-1.5 rounded text-xs text-white mt-2 font-medium" style={{backgroundColor: tempSettings.branding.primaryColor}}>
Bottone Principale
</button>
</div>
</div>
<p className="text-xs text-gray-400 mt-4 italic">Questo è come appariranno i componenti principali.</p>
</div>
</div>
</div>
</div>
)}
{/* AI CONFIG TAB WITH CUSTOMIZATION */}
{settingsTab === 'ai' && canManageTeam && (
<div className="space-y-8 max-w-4xl mx-auto animate-fade-in">
<div>
<h2 className="text-2xl font-bold text-gray-800">Intelligenza Artificiale</h2>
<p className="text-gray-500 text-sm mt-1">Configura il cervello e la personalità del tuo assistente virtuale.</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Technical Config */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm h-full">
<h3 className="font-bold text-gray-700 border-b border-gray-100 pb-3 mb-5 flex items-center">
<Cpu className="w-5 h-5 mr-2 text-brand-600" /> Motore & Connessione
</h3>
<div className="space-y-5">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Provider AI</label>
<div className="relative">
<select
className="w-full appearance-none border border-gray-300 rounded-lg px-4 py-2.5 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none"
value={tempSettings.aiConfig.provider}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, provider: e.target.value as AiProvider}})}
>
<option value="gemini">Google Gemini</option>
<option value="openrouter">OpenRouter (Multi-Model)</option>
<option value="openai">OpenAI (GPT)</option>
<option value="anthropic">Anthropic (Claude)</option>
<option value="ollama">Ollama (Locale)</option>
</select>
<ChevronRight className="w-4 h-4 absolute right-3 top-3 text-gray-400 rotate-90 pointer-events-none"/>
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Modello (es. gemini-1.5-pro)</label>
<input
type="text"
placeholder="Inserisci ID modello..."
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none font-mono text-sm"
value={tempSettings.aiConfig.model}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, model: e.target.value}})}
/>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">API Key</label>
<div className="relative">
<input
type="password"
placeholder="sk-..."
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none font-mono text-sm pr-10"
value={tempSettings.aiConfig.apiKey}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, apiKey: e.target.value}})}
/>
<Lock className="w-4 h-4 absolute right-3 top-3 text-gray-400"/>
</div>
<p className="text-[10px] text-gray-400 mt-1">La chiave viene salvata in modo sicuro nel database.</p>
</div>
</div>
</div>
{/* Persona Config */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm h-full flex flex-col">
<h3 className="font-bold text-gray-700 border-b border-gray-100 pb-3 mb-5 flex items-center">
<Bot className="w-5 h-5 mr-2 text-brand-600" /> Identità Chatbot
</h3>
<div className="flex items-start space-x-4 mb-6">
<div className="flex-shrink-0">
<div className="w-16 h-16 rounded-full overflow-hidden bg-gray-100 border-2 border-brand-100 shadow-sm relative group cursor-pointer">
<img src={tempSettings.aiConfig.agentAvatar || 'https://via.placeholder.com/150'} alt="AI" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/20 hidden group-hover:flex items-center justify-center text-white text-xs">Modifica URL</div>
</div>
</div>
<div className="flex-1">
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">Nome Pubblico</label>
<input
type="text"
className="w-full border border-gray-300 rounded px-3 py-1.5 bg-gray-50 text-sm font-semibold text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none"
value={tempSettings.aiConfig.agentName || ''}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, agentName: e.target.value}})}
/>
<div className="mt-2">
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">URL Avatar</label>
<input
type="text"
className="w-full border border-gray-300 rounded px-3 py-1.5 bg-gray-50 text-xs text-gray-600 focus:ring-2 focus:ring-brand-500 outline-none"
placeholder="https://..."
value={tempSettings.aiConfig.agentAvatar || ''}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, agentAvatar: e.target.value}})}
/>
</div>
</div>
</div>
<div className="flex-1 flex flex-col">
<label className="block text-sm font-bold text-gray-700 mb-2">Prompt di Sistema (Personalità)</label>
<textarea
rows={5}
placeholder="Es. Sei un assistente empatico e professionale. Rispondi sempre citando le fonti..."
className="w-full border border-gray-300 rounded-lg px-4 py-3 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none text-sm resize-none flex-1"
value={tempSettings.aiConfig.customPrompt || ''}
onChange={(e) => setTempSettings({...tempSettings, aiConfig: {...tempSettings.aiConfig, customPrompt: e.target.value}})}
/>
<div className="mt-3 bg-blue-50 p-3 rounded-lg flex items-start border border-blue-100">
<InfoIcon className="w-4 h-4 text-blue-500 mt-0.5 mr-2 flex-shrink-0" />
<p className="text-xs text-blue-700 leading-tight">Questo prompt verrà aggiunto alle istruzioni base. Definisci tono, stile e regole specifiche.</p>
</div>
</div>
</div>
</div>
</div>
)}
{/* USERS TAB */}
{settingsTab === 'users' && canManageTeam && (
<div className="animate-fade-in max-w-5xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h2 className="text-2xl font-bold text-gray-800">Utenti Frontend</h2>
<p className="text-gray-500 text-sm mt-1">Gestisci i clienti che hanno accesso al portale di supporto.</p>
</div>
<button
onClick={() => { setEditingUser(null); setNewUserForm({ name: '', email: '', status: 'active', company: '' }); }}
className="text-sm bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-lg transition shadow-sm font-medium flex items-center"
>
<RotateCcw className="w-4 h-4 mr-2"/> Reset Form
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Form Card */}
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm h-fit">
<h3 className="font-bold text-gray-800 mb-4 pb-4 border-b border-gray-100 flex items-center">
{editingUser ? <Edit3 className="w-5 h-5 mr-2 text-blue-600"/> : <UserPlus className="w-5 h-5 mr-2 text-green-600"/>}
{editingUser ? 'Modifica Cliente' : 'Nuovo Cliente'}
</h3>
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">Nome Completo</label>
<input type="text" className="w-full border border-gray-300 rounded-lg p-2.5 bg-gray-50 focus:ring-2 focus:ring-brand-500 outline-none text-sm" value={newUserForm.name} onChange={e => setNewUserForm({...newUserForm, name: e.target.value})} />
</div>
<div>
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">Email</label>
<input type="email" className="w-full border border-gray-300 rounded-lg p-2.5 bg-gray-50 focus:ring-2 focus:ring-brand-500 outline-none text-sm" value={newUserForm.email} onChange={e => setNewUserForm({...newUserForm, email: e.target.value})} />
</div>
<div>
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">Azienda</label>
<input type="text" className="w-full border border-gray-300 rounded-lg p-2.5 bg-gray-50 focus:ring-2 focus:ring-brand-500 outline-none text-sm" value={newUserForm.company} onChange={e => setNewUserForm({...newUserForm, company: e.target.value})} />
</div>
<div>
<label className="block text-xs font-bold text-gray-500 uppercase mb-1">Stato Account</label>
<select className="w-full border border-gray-300 rounded-lg p-2.5 bg-gray-50 focus:ring-2 focus:ring-brand-500 outline-none text-sm" value={newUserForm.status} onChange={e => setNewUserForm({...newUserForm, status: e.target.value as any})}>
<option value="active">Attivo</option>
<option value="inactive">Sospeso</option>
</select>
</div>
<div className="pt-4 flex gap-2">
{editingUser && <button onClick={cancelEditUser} className="flex-1 px-4 py-2 text-sm text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium">Annulla</button>}
<button onClick={editingUser ? handleUpdateUser : handleAddUser} className={`flex-1 text-white px-4 py-2 rounded-lg text-sm font-bold shadow-md transition-all ${editingUser ? 'bg-blue-600 hover:bg-blue-700' : 'bg-green-600 hover:bg-green-700'}`}>
{editingUser ? 'Salva Modifiche' : 'Aggiungi Cliente'}
</button>
</div>
</div>
</div>
{/* List Table */}
<div className="lg:col-span-2 bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden flex flex-col">
<div className="overflow-x-auto">
<table className="min-w-full text-left">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-6 py-3 text-xs font-bold text-gray-500 uppercase tracking-wider">Utente</th>
<th className="px-6 py-3 text-xs font-bold text-gray-500 uppercase tracking-wider">Azienda</th>
<th className="px-6 py-3 text-xs font-bold text-gray-500 uppercase tracking-wider">Stato</th>
<th className="px-6 py-3 text-right text-xs font-bold text-gray-500 uppercase tracking-wider">Azioni</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{clientUsers.map(user => (
<tr key={user.id} className="hover:bg-gray-50 transition-colors group">
<td className="px-6 py-4">
<div className="flex items-center">
<div className="w-8 h-8 rounded-full bg-brand-100 text-brand-600 flex items-center justify-center font-bold text-xs mr-3">
{user.name.substring(0,2).toUpperCase()}
</div>
<div>
<div className="text-sm font-medium text-gray-900">{user.name}</div>
<div className="text-xs text-gray-500">{user.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 text-sm text-gray-500">{user.company || '-'}</td>
<td className="px-6 py-4">
<span className={`px-2.5 py-1 rounded-full text-xs font-bold ${user.status === 'active' ? 'bg-green-100 text-green-700 border border-green-200' : 'bg-red-100 text-red-700 border border-red-200'}`}>
{user.status === 'active' ? 'Attivo' : 'Inattivo'}
</span>
</td>
<td className="px-6 py-4 text-right space-x-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button onClick={() => handleSendPasswordReset(user.email)} className="p-1.5 text-gray-400 hover:text-brand-600 bg-white hover:bg-gray-100 border border-transparent hover:border-gray-200 rounded transition-all" title="Reset Password"><Key className="w-4 h-4" /></button>
<button onClick={() => handleEditUserClick(user)} className="p-1.5 text-gray-400 hover:text-blue-600 bg-white hover:bg-gray-100 border border-transparent hover:border-gray-200 rounded transition-all"><Edit3 className="w-4 h-4" /></button>
<button onClick={() => removeClientUser(user.id)} className="p-1.5 text-gray-400 hover:text-red-600 bg-white hover:bg-gray-100 border border-transparent hover:border-gray-200 rounded transition-all"><Trash2 className="w-4 h-4" /></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)}
{/* AGENTS TAB */}
{settingsTab === 'agents' && canManageTeam && (
<div className="animate-fade-in max-w-5xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h2 className="text-2xl font-bold text-gray-800">Team di Supporto</h2>
<p className="text-gray-500 text-sm mt-1">Gestisci gli account degli agenti e dei supervisori.</p>
</div>
{isAgentQuotaFull && <span className="bg-red-50 text-red-600 px-3 py-1 rounded-full text-xs font-bold border border-red-100">Quota Raggiunta</span>}
<button onClick={() => { setEditingAgent(null); setNewAgentForm({ name: '', email: '', password: '', role: 'agent', skills: [], queues: [], avatar: '', avatarConfig: {x: 50, y: 50, scale: 1} }); }} className="text-sm bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-lg transition shadow-sm font-medium flex items-center">
<Plus className="w-4 h-4 mr-2"/> Nuovo Agente
</button>
</div>
<div className="bg-white p-8 rounded-xl border border-gray-200 shadow-sm mb-10">
<div className="flex flex-col md:flex-row gap-8">
<div className="w-full md:w-1/3 flex flex-col items-center">
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 w-full text-center">Foto Profilo</h4>
<AvatarEditor
initialImage={newAgentForm.avatar || 'https://via.placeholder.com/200'}
initialConfig={newAgentForm.avatarConfig}
onSave={(img, cfg) => handleAvatarSaved(img, cfg, !!editingAgent)}
/>
</div>
<div className="w-full md:w-2/3 space-y-6">
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider border-b border-gray-100 pb-2">Dettagli Account</h4>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Nome Completo</label>
<input type="text" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={newAgentForm.name} onChange={e => setNewAgentForm({...newAgentForm, name: e.target.value})} />
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Email Aziendale</label>
<input type="email" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={newAgentForm.email} onChange={e => setNewAgentForm({...newAgentForm, email: e.target.value})} />
</div>
</div>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Ruolo</label>
<select className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={newAgentForm.role} onChange={e => setNewAgentForm({...newAgentForm, role: e.target.value as any})}>
<option value="agent">Agente Semplice</option>
<option value="supervisor">Supervisore</option>
<option value="superadmin">Super Admin</option>
</select>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Password</label>
<input type="password" placeholder={editingAgent ? "Invariata" : "Password provvisoria"} className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={newAgentForm.password} onChange={e => setNewAgentForm({...newAgentForm, password: e.target.value})} />
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-3">Assegnazione Code</label>
<div className="flex flex-wrap gap-3">
{queues.map(q => (
<button
key={q.id}
onClick={() => toggleQueueInForm(q.name, !!editingAgent)}
className={`px-4 py-2 rounded-lg text-sm border transition-all flex items-center ${newAgentForm.queues?.includes(q.name) ? 'bg-brand-50 border-brand-200 text-brand-700 font-bold shadow-sm' : 'bg-white border-gray-200 text-gray-500 hover:border-gray-300 hover:bg-gray-50'}`}
>
{newAgentForm.queues?.includes(q.name) && <Check className="w-3.5 h-3.5 mr-2" />}
{q.name}
</button>
))}
</div>
</div>
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-100">
{editingAgent && <button onClick={cancelEditAgent} className="px-6 py-2.5 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium transition-colors">Annulla</button>}
<button onClick={editingAgent ? handleUpdateAgent : handleAddAgent} className="bg-brand-600 text-white px-8 py-2.5 rounded-lg font-bold hover:bg-brand-700 shadow-md transition-all">
{editingAgent ? 'Salva Modifiche' : 'Crea Account Agente'}
</button>
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{agents.map(agent => (
<div key={agent.id} className="bg-white p-5 rounded-xl border border-gray-200 shadow-sm flex items-start hover:shadow-md transition-shadow group">
<div className="w-14 h-14 rounded-full overflow-hidden mr-5 bg-gray-100 flex-shrink-0 border-2 border-white shadow-sm ring-1 ring-gray-100">
<img src={agent.avatar || 'https://via.placeholder.com/200'} alt={agent.name} className="w-full h-full object-cover"
style={agent.avatarConfig ? { objectPosition: `${agent.avatarConfig.x}% ${agent.avatarConfig.y}%`, transform: `scale(${agent.avatarConfig.scale})` } : {}}
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-start">
<h4 className="font-bold text-gray-900 text-base truncate">{agent.name}</h4>
<span className={`text-[10px] uppercase font-bold px-2 py-1 rounded-md ${agent.role === 'superadmin' ? 'bg-purple-100 text-purple-700' : agent.role === 'supervisor' ? 'bg-amber-100 text-amber-700' : 'bg-blue-100 text-blue-700'}`}>{agent.role}</span>
</div>
<p className="text-sm text-gray-500 mb-3 truncate">{agent.email}</p>
<div className="flex flex-wrap gap-1.5 mb-3">
{agent.queues.map(q => <span key={q} className="text-[10px] bg-gray-100 text-gray-600 px-2 py-0.5 rounded border border-gray-200 font-medium">{q}</span>)}
</div>
<div className="flex space-x-4 text-xs font-medium text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity">
<button onClick={() => handleEditAgentClick(agent)} className="hover:text-blue-600 flex items-center transition-colors"><Edit3 className="w-3.5 h-3.5 mr-1" /> Modifica</button>
{agent.role !== 'superadmin' && <button onClick={() => removeAgent(agent.id)} className="hover:text-red-600 flex items-center transition-colors"><Trash2 className="w-3.5 h-3.5 mr-1" /> Rimuovi</button>}
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* QUEUES TAB */}
{settingsTab === 'queues' && canManageTeam && (
<div className="animate-fade-in max-w-4xl mx-auto">
<div className="mb-8">
<h2 className="text-2xl font-bold text-gray-800">Code di Smistamento</h2>
<p className="text-gray-500 text-sm mt-1">Organizza i ticket in base alla tipologia o al dipartimento.</p>
</div>
<div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm mb-8">
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4">Aggiungi Nuova Coda</h4>
<div className="flex flex-col md:flex-row gap-4 items-end">
<div className="flex-1 w-full">
<label className="block text-sm font-bold text-gray-700 mb-1">Nome Coda</label>
<div className="relative">
<Layers className="absolute left-3 top-2.5 w-4 h-4 text-gray-400"/>
<input type="text" className="w-full border border-gray-300 rounded-lg pl-9 pr-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" placeholder="Es. Supporto Tecnico" value={newQueueForm.name} onChange={e => setNewQueueForm({...newQueueForm, name: e.target.value})} />
</div>
</div>
<div className="flex-[2] w-full">
<label className="block text-sm font-bold text-gray-700 mb-1">Descrizione</label>
<input type="text" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" placeholder="Descrizione interna per gli agenti..." value={newQueueForm.description} onChange={e => setNewQueueForm({...newQueueForm, description: e.target.value})} />
</div>
<button onClick={handleAddQueue} className="bg-brand-600 text-white px-6 py-2 rounded-lg text-sm font-bold hover:bg-brand-700 h-10 shadow-md transition-all active:scale-95 whitespace-nowrap">
<Plus className="w-4 h-4 inline mr-1"/> Aggiungi
</button>
</div>
</div>
<div className="space-y-4">
{queues.map(q => (
<div key={q.id} className="bg-white p-5 rounded-xl border border-gray-200 shadow-sm flex justify-between items-center group hover:border-brand-200 transition-colors">
<div className="flex items-start">
<div className="p-3 bg-brand-50 text-brand-600 rounded-lg mr-4">
<Layers className="w-6 h-6" />
</div>
<div>
<h4 className="font-bold text-gray-900 text-lg">{q.name}</h4>
<p className="text-sm text-gray-500">{q.description || 'Nessuna descrizione'}</p>
<div className="mt-2 flex items-center text-xs text-gray-400">
<Users className="w-3 h-3 mr-1"/> {agents.filter(a => a.queues.includes(q.name)).length} agenti assegnati
</div>
</div>
</div>
<button onClick={() => removeQueue(q.id)} className="text-gray-300 hover:text-red-500 p-2 rounded-full hover:bg-red-50 transition-all opacity-0 group-hover:opacity-100">
<Trash2 className="w-5 h-5" />
</button>
</div>
))}
</div>
</div>
)}
{/* EMAIL TAB */}
{settingsTab === 'email' && canManageGlobalSettings && (
<div className="animate-fade-in max-w-4xl mx-auto">
<div className="mb-8">
<h2 className="text-2xl font-bold text-gray-800">Email & Notifiche</h2>
<p className="text-gray-500 text-sm mt-1">Configura il server SMTP per l'invio delle notifiche.</p>
</div>
<div className="bg-white p-8 rounded-xl border border-gray-200 shadow-sm">
<div className="grid grid-cols-2 gap-6 mb-6">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Host SMTP</label>
<div className="relative">
<Globe className="absolute left-3 top-2.5 w-4 h-4 text-gray-400"/>
<input type="text" className="w-full border border-gray-300 rounded-lg pl-9 pr-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={tempSettings.smtp.host} onChange={e => setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, host: e.target.value}})} />
</div>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Porta</label>
<input type="number" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none font-mono" value={tempSettings.smtp.port} onChange={e => setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, port: parseInt(e.target.value)}})} />
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Utente</label>
<input type="text" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={tempSettings.smtp.user} onChange={e => setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, user: e.target.value}})} />
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">Password</label>
<input type="password" className="w-full border border-gray-300 rounded-lg px-4 py-2 bg-gray-50 text-gray-900 focus:ring-2 focus:ring-brand-500 outline-none" value={tempSettings.smtp.pass} onChange={e => setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, pass: e.target.value}})} />
</div>
</div>
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center">
<input type="checkbox" id="secure" className="rounded border-gray-300 text-brand-600 focus:ring-brand-500 h-4 w-4" checked={tempSettings.smtp.secure} onChange={e => setTempSettings({...tempSettings, smtp: {...tempSettings.smtp, secure: e.target.checked}})} />
<label htmlFor="secure" className="ml-2 block text-sm text-gray-700 font-medium">Usa SSL/TLS (Secure Connection)</label>
</div>
<button onClick={handleTestSmtp} disabled={isTestingSmtp} className="text-brand-600 bg-brand-50 px-4 py-2 rounded-lg text-sm font-bold hover:bg-brand-100 transition-colors flex items-center">
{isTestingSmtp ? <Loader2 className="w-4 h-4 animate-spin mr-2"/> : <Zap className="w-4 h-4 mr-2"/>}
{isTestingSmtp ? 'Test in corso...' : 'Test Connessione'}
</button>
</div>
</div>
</div>
)}
{settingsTab !== 'users' && settingsTab !== 'agents' && settingsTab !== 'queues' && (
<div className="mt-8 flex justify-end border-t border-gray-200 pt-6 sticky bottom-0 bg-gray-50/95 backdrop-blur py-4 -mx-8 px-8 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)]">
<button
onClick={handleSaveSettings}
disabled={isSaving}
className="bg-brand-600 text-white px-8 py-3 rounded-xl font-bold flex items-center hover:bg-brand-700 shadow-lg shadow-brand-500/30 disabled:opacity-70 disabled:cursor-wait transition-all active:scale-95"
>
{isSaving ? <Loader2 className="w-5 h-5 mr-2 animate-spin" /> : <Save className="w-5 h-5 mr-2" />}
{isSaving ? 'Salvataggio...' : 'Salva Impostazioni'}
</button>
</div>
)}
</div>
</div>
</div>
)}
{view === 'tickets' && (
<div className="flex h-full border-t border-gray-200">
{/* COLUMN 1: QUEUES & ARCHIVE */}
<div className="w-64 bg-white border-r border-gray-200 flex flex-col">
<div className="p-4 border-b border-gray-100 bg-gray-50">
<h3 className="font-bold text-gray-700 text-sm uppercase tracking-wide">Code Attive</h3>
</div>
<div className="flex-1 overflow-y-auto">
<ul className="py-2">
{queues.map(q => {
const count = queueCounts[q.name] || 0;
const isActive = selectedQueue === q.name && !isViewingArchive;
return (
<li key={q.id}>
<button
onClick={() => { setSelectedQueue(q.name); setIsViewingArchive(false); setSelectedTicketId(null); }}
className={`w-full text-left px-4 py-3 flex justify-between items-center text-sm font-medium transition ${isActive ? 'bg-blue-50 text-blue-700 border-r-4 border-blue-600' : 'text-gray-600 hover:bg-gray-50'}`}
>
<div className="flex items-center">
<Inbox className={`w-4 h-4 mr-3 ${isActive ? 'text-blue-500' : 'text-gray-400'}`} />
{q.name}
</div>
{count > 0 && (
<span className={`text-xs px-2 py-0.5 rounded-full ${isActive ? 'bg-blue-200 text-blue-800' : 'bg-gray-200 text-gray-600'}`}>
{count}
</span>
)}
</button>
</li>
);
})}
</ul>
<div className="mt-4 pt-4 border-t border-gray-100">
<div className="px-4 pb-2 text-xs font-bold text-gray-400 uppercase">Storico</div>
<button
onClick={() => { setIsViewingArchive(true); setSelectedQueue(null); setSelectedTicketId(null); }}
className={`w-full text-left px-4 py-3 flex items-center text-sm font-medium transition ${isViewingArchive ? 'bg-purple-50 text-purple-700 border-r-4 border-purple-600' : 'text-gray-600 hover:bg-gray-50'}`}
>
<Archive className={`w-4 h-4 mr-3 ${isViewingArchive ? 'text-purple-500' : 'text-gray-400'}`} />
Archivio Risolti
</button>
</div>
</div>
</div>
{/* COLUMN 2: TICKET LIST */}
<div className="w-80 bg-white border-r border-gray-200 flex flex-col">
<div className="p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center">
<h3 className="font-bold text-gray-700 text-sm">
{isViewingArchive ? 'Archivio Ticket' : selectedQueue ? selectedQueue : 'Seleziona Coda'}
</h3>
<span className="text-xs text-gray-500">{filteredTickets.length} ticket</span>
</div>
<div className="flex-1 overflow-y-auto">
{filteredTickets.length === 0 ? (
<div className="p-8 text-center text-gray-400">
<div className="bg-gray-50 rounded-full p-4 w-16 h-16 flex items-center justify-center mx-auto mb-3">
{isViewingArchive ? <Archive className="w-8 h-8 opacity-20" /> : <Inbox className="w-8 h-8 opacity-20" />}
</div>
<p className="text-sm">Nessun ticket {isViewingArchive ? 'in archivio' : 'in questa coda'}.</p>
</div>
) : (
filteredTickets.map(ticket => (
<div
key={ticket.id}
onClick={() => setSelectedTicketId(ticket.id)}
className={`p-4 border-b border-gray-100 cursor-pointer hover:bg-blue-50 transition relative group ${selectedTicketId === ticket.id ? 'bg-blue-50 border-l-4 border-brand-500' : ''}`}
>
<div className="absolute right-2 top-2 hidden group-hover:flex flex-col gap-1 z-10">
{!isViewingArchive && (
<div className="relative" onClick={e => e.stopPropagation()}>
<select
className="appearance-none w-full bg-white border border-gray-200 text-xs rounded shadow-sm py-1 pl-6 pr-2 cursor-pointer hover:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
value={ticket.assignedAgentId || ''}
onChange={(e) => updateTicketAgent(ticket.id, e.target.value)}
>
<option value="">Non assegnato</option>
{getAssignableAgents(ticket.queue).map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
</select>
<UserPlus className="w-3 h-3 absolute left-1.5 top-1.5 text-gray-400 pointer-events-none" />
</div>
)}
<div className="relative" onClick={e => e.stopPropagation()}>
<select
className={`appearance-none w-full border text-xs rounded shadow-sm py-1 pl-6 pr-2 cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-500 ${
ticket.status === TicketStatus.RESOLVED ? 'bg-green-50 border-green-200 text-green-700' :
ticket.status === TicketStatus.OPEN ? 'bg-blue-50 border-blue-200 text-blue-700' :
'bg-white border-gray-200 text-gray-700'
}`}
value={ticket.status}
onChange={(e) => updateTicketStatus(ticket.id, e.target.value as TicketStatus)}
>
{Object.values(TicketStatus).map(s => <option key={s} value={s}>{s}</option>)}
</select>
<div className="absolute left-1.5 top-1.5 pointer-events-none text-current opacity-70">
<Activity className="w-3 h-3" />
</div>
</div>
</div>
<div className="flex justify-between items-start mb-1">
<span className="font-bold text-gray-800 text-sm">{ticket.id}</span>
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold uppercase ${
ticket.priority === TicketPriority.HIGH || ticket.priority === TicketPriority.CRITICAL ? 'bg-red-100 text-red-600' : 'bg-green-100 text-green-600'
}`}>{ticket.priority}</span>
</div>
<h4 className="text-sm font-medium text-gray-900 truncate mb-1 pr-6">{ticket.subject}</h4>
<p className="text-xs text-gray-500">{ticket.customerName} • {ticket.status}</p>
<div className="flex items-center justify-between mt-1">
{ticket.attachments && ticket.attachments.length > 0 && (
<div className="flex items-center text-xs text-gray-400">
<Paperclip className="w-3 h-3 mr-1" />
{ticket.attachments.length}
</div>
)}
<span className="text-[10px] bg-gray-100 text-gray-500 px-1 rounded">{(ticket.createdAt || '').split('T')[0]}</span>
</div>
</div>
))
)}
</div>
</div>
{/* COLUMN 3: DETAIL */}
<div className="flex-1 bg-white rounded-xl shadow-sm p-6 overflow-y-auto m-4 ml-0">
{selectedTicket ? (
<div>
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-2xl font-bold text-gray-900">{selectedTicket.subject}</h2>
<div className="flex items-center space-x-3 mt-2 text-sm text-gray-500">
<span>{selectedTicket.customerName}</span>
<span>•</span>
<span>{selectedTicket.createdAt.split('T')[0]}</span>
<span>•</span>
<span className="bg-gray-100 px-2 py-0.5 rounded text-gray-700">{selectedTicket.queue}</span>
</div>
</div>
<div className="flex space-x-2">
<select
value={selectedTicket.assignedAgentId || ''}
onChange={(e) => updateTicketAgent(selectedTicket.id, e.target.value)}
className="border border-gray-300 rounded-md text-sm px-2 py-1 bg-white text-gray-900"
disabled={isViewingArchive}
>
<option value="">Non assegnato</option>
{getAssignableAgents(selectedTicket.queue).map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
</select>
<select
value={selectedTicket.status}
onChange={(e) => updateTicketStatus(selectedTicket.id, e.target.value as TicketStatus)}
className="border border-gray-300 rounded-md text-sm px-2 py-1 bg-white text-gray-900"
>
{Object.values(TicketStatus).map(s => <option key={s} value={s}>{s}</option>)}
</select>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-lg mb-6 border border-gray-100">
<p className="text-gray-800">{selectedTicket.description}</p>
</div>
{/* Attachments Section */}
{selectedTicket.attachments && selectedTicket.attachments.length > 0 && (
<div className="mb-6">
<h3 className="font-semibold text-gray-700 text-sm mb-2 flex items-center">
<Paperclip className="w-4 h-4 mr-1" /> Allegati
</h3>
<div className="grid grid-cols-2 gap-2">
{selectedTicket.attachments.map(att => (
<div key={att.id} className="flex items-center p-2 bg-gray-50 border border-gray-200 rounded text-sm text-blue-600 hover:underline cursor-pointer">
<FileText className="w-4 h-4 mr-2 text-gray-500" />
{att.name}
</div>
))}
</div>
</div>
)}
<div className="space-y-4 mb-6">
<h3 className="font-semibold text-gray-700">Cronologia Messaggi</h3>
{selectedTicket.messages.length === 0 ? (
<p className="text-sm text-gray-400 italic">Nessun messaggio.</p>
) : (
selectedTicket.messages.map(m => (
<div key={m.id} className={`p-3 rounded-lg max-w-[80%] ${m.role === 'assistant' ? 'ml-auto bg-brand-50 border border-brand-100' : 'bg-white border border-gray-200'}`}>
<p className="text-xs text-gray-500 mb-1 font-semibold">{m.role === 'assistant' ? 'Agente' : 'Cliente'} <span className="font-normal opacity-70 ml-2">{(m.timestamp || '').split(/[T ]/)[1]?.substring(0, 5) || ''}</span></p>
<p className="text-sm text-gray-800">{m.content}</p>
</div>
))
)}
</div>
<div className="mt-auto pt-4 border-t border-gray-100">
<textarea
className="w-full border border-gray-300 rounded-lg p-3 text-sm focus:ring-2 focus:ring-brand-500 focus:outline-none bg-white text-gray-900"
placeholder="Scrivi una risposta interna o pubblica..."
rows={3}
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
/>
<div className="flex justify-end mt-2">
<button
onClick={handleReplySubmit}
className="bg-brand-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-brand-700 flex items-center"
>
<Send className="w-4 h-4 mr-2" />
Rispondi
</button>
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
<div className="text-center">
<LayoutDashboard className="w-12 h-12 mx-auto mb-2 opacity-20" />
<p>Seleziona un ticket dalla lista</p>
</div>
</div>
)}
</div>
</div>
)}
{view === 'kb' && (
<div className="bg-white rounded-xl shadow-sm p-6 min-h-full overflow-y-auto m-8">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-800">Gestione Knowledge Base</h2>
{settings.features.kbEnabled ? (
<button
onClick={() => {
setNewArticle({ type: 'article', category: 'General', visibility: 'public' });
setIsEditingKB(true);
}}
disabled={isKbFull}
className="bg-brand-600 text-white px-4 py-2 rounded-lg flex items-center hover:bg-brand-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="w-4 h-4 mr-2" />
{isKbFull ? 'Limite Raggiunto' : 'Nuovo Articolo'}
</button>
) : (
<span className="text-red-500 font-bold flex items-center bg-red-50 px-3 py-1 rounded">
<AlertTriangle className="w-4 h-4 mr-2" /> KB Disabilitata
</span>
)}
</div>
{isEditingKB && (
<div className="bg-gray-50 p-6 rounded-xl border border-gray-200 mb-8 animate-fade-in">
<h3 className="font-bold text-gray-700 mb-4">{newArticle.id ? 'Modifica Elemento' : 'Nuovo Elemento'}</h3>
{/* KB Form Inputs */}
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Titolo</label>
<input type="text" className="w-full border border-gray-300 rounded px-3 py-2 bg-white text-gray-900" value={newArticle.title || ''} onChange={e => setNewArticle({...newArticle, title: e.target.value})} />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Categoria</label>
<input type="text" className="w-full border border-gray-300 rounded px-3 py-2 bg-white text-gray-900" value={newArticle.category || ''} onChange={e => setNewArticle({...newArticle, category: e.target.value})} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Visibilità</label>
<select
className="w-full border border-gray-300 rounded px-3 py-2 bg-white text-gray-900"
value={newArticle.visibility || 'public'}
onChange={e => setNewArticle({...newArticle, visibility: e.target.value as any})}
>
<option value="public">Pubblico (Clienti)</option>
<option value="internal">Interno (Solo Agenti)</option>
</select>
</div>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">Tipo</label>
<div className="flex space-x-4">
<label className="flex items-center"><input type="radio" name="type" checked={newArticle.type === 'article'} onChange={() => setNewArticle({...newArticle, type: 'article'})} className="mr-2" />Articolo</label>
<label className="flex items-center"><input type="radio" name="type" checked={newArticle.type === 'url'} onChange={() => setNewArticle({...newArticle, type: 'url'})} className="mr-2" />Link Esterno</label>
</div>
</div>
{newArticle.type === 'article' ? (
<div className="mb-4"><label className="block text-sm font-medium text-gray-700 mb-1">Contenuto</label><textarea rows={6} className="w-full border border-gray-300 rounded px-3 py-2 bg-white text-gray-900" value={newArticle.content || ''} onChange={e => setNewArticle({...newArticle, content: e.target.value})} /></div>
) : (
<div className="mb-4"><label className="block text-sm font-medium text-gray-700 mb-1">URL</label><input type="url" className="w-full border border-gray-300 rounded px-3 py-2 bg-white text-gray-900" value={newArticle.url || ''} onChange={e => setNewArticle({...newArticle, url: e.target.value})} /></div>
)}
<div className="flex justify-end space-x-3">
<button onClick={() => { setIsEditingKB(false); setNewArticle({ type: 'article', category: 'General', visibility: 'public' }); }} className="px-4 py-2 text-gray-600 hover:text-gray-800">Annulla</button>
<button onClick={handleSaveArticle} disabled={isFetchingUrl} className="px-4 py-2 bg-brand-600 text-white rounded hover:bg-brand-700 flex items-center disabled:opacity-70">{isFetchingUrl && <Loader2 className="w-4 h-4 mr-2 animate-spin" />}{isFetchingUrl ? 'Scaricamento...' : 'Salva'}</button>
</div>
</div>
)}
<div className="overflow-x-auto">
<table className="min-w-full text-left">
<thead><tr className="border-b border-gray-200 text-gray-500 text-sm"><th className="py-3 px-4">Titolo</th><th className="py-3 px-4">Categoria</th><th className="py-3 px-4">Visibilità</th><th className="py-3 px-4">Tipo</th><th className="py-3 px-4">Ultimo Agg.</th><th className="py-3 px-4">Azioni</th></tr></thead>
<tbody>
{articles.map(article => (
<tr key={article.id} className="border-b border-gray-100 hover:bg-gray-50">
<td className="py-3 px-4 font-medium text-gray-800">{article.title}</td>
<td className="py-3 px-4"><span className="bg-gray-100 text-gray-600 text-xs px-2 py-1 rounded">{article.category}</span></td>
<td className="py-3 px-4">
{article.visibility === 'internal' ? (
<span className="flex items-center text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded w-fit"><EyeOff className="w-3 h-3 mr-1"/> Interno</span>
) : (
<span className="flex items-center text-xs text-green-600 bg-green-50 px-2 py-1 rounded w-fit"><Eye className="w-3 h-3 mr-1"/> Pubblico</span>
)}
</td>
<td className="py-3 px-4 text-sm text-gray-500 flex items-center">{article.type === 'url' ? <ExternalLink className="w-4 h-4 mr-1 text-blue-500" /> : <FileText className="w-4 h-4 mr-1 text-gray-400" />}{article.type === 'url' ? 'Link' : 'Articolo'}</td>
<td className="py-3 px-4 text-sm text-gray-500">{article.lastUpdated}</td>
<td className="py-3 px-4 flex space-x-2">
<button onClick={() => { setNewArticle(article); setIsEditingKB(true); }} className="text-brand-600 hover:text-brand-800"><Edit3 className="w-4 h-4" /></button>
{deleteArticle && (
<button onClick={() => { if(confirm('Sei sicuro di voler eliminare questo articolo?')) deleteArticle(article.id); }} className="text-red-400 hover:text-red-600"><Trash2 className="w-4 h-4" /></button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{view === 'ai' && (
<div className="max-w-4xl mx-auto p-8 overflow-y-auto">
<div className="bg-gradient-to-r from-purple-600 to-indigo-600 rounded-2xl p-8 text-white shadow-lg mb-8">
<div className="flex items-start justify-between">
<div>
<h2 className="text-3xl font-bold mb-2">Knowledge Agent AI</h2>
<p className="text-purple-100 max-w-xl">
Questo agente analizza automaticamente TUTTI i ticket "Risolti" per trovare lacune nella Knowledge Base.
</p>
</div>
<Sparkles className="w-16 h-16 text-purple-300 opacity-50" />
</div>
<div className="mt-8">
{settings.features.aiKnowledgeAgentEnabled ? (
<button
onClick={handleAiAnalysis}
disabled={isAiAnalyzing}
className="bg-white text-purple-700 px-6 py-3 rounded-xl font-bold hover:bg-purple-50 transition shadow-lg flex items-center disabled:opacity-70"
>
{isAiAnalyzing ? (
<>
<div className="animate-spin h-5 w-5 border-2 border-purple-700 border-t-transparent rounded-full mr-3"></div>
Analisi Completa in corso...
</>
) : (
<>
<CheckCircle className="w-5 h-5 mr-2" />
Scansiona Tutti i Ticket Risolti
</>
)}
</button>
) : (
<div className="bg-white/20 p-4 rounded-lg flex items-center text-sm font-medium">
<AlertTriangle className="w-5 h-5 mr-3 text-yellow-300" />
Funzionalità disabilitata dall'amministratore.
</div>
)}
</div>
</div>
{/* LIST OF SUGGESTIONS */}
{aiSuggestions.length > 0 ? (
<div className="space-y-6">
<h3 className="text-xl font-bold text-gray-800 flex items-center">
<Sparkles className="w-5 h-5 mr-2 text-purple-600" />
{aiSuggestions.length} Nuovi Articoli Suggeriti
</h3>
{aiSuggestions.map((suggestion, index) => (
<div key={index} className="bg-white rounded-2xl shadow-md border border-purple-100 overflow-hidden animate-fade-in-up">
<div className="bg-purple-50 p-4 border-b border-purple-100 flex justify-between items-center">
<h3 className="text-purple-800 font-bold">{suggestion.title}</h3>
<span className="text-xs bg-purple-200 text-purple-800 px-2 py-1 rounded-full">{suggestion.category}</span>
</div>
<div className="p-6">
<div className="mb-4">
<label className="text-xs font-bold text-gray-500 uppercase tracking-wide">Contenuto Bozza</label>
<div className="mt-2 p-4 bg-gray-50 rounded-lg border border-gray-100 text-sm text-gray-700 font-mono whitespace-pre-wrap max-h-60 overflow-y-auto">
{suggestion.content}
</div>
</div>
<div className="flex justify-end space-x-4">
<button onClick={() => discardAiArticle(index)} className="px-4 py-2 text-gray-500 hover:text-gray-700 font-medium">Scarta</button>
<button onClick={() => saveAiArticle(suggestion, index)} className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 font-bold shadow-md">Approva (Interno)</button>
</div>
</div>
</div>
))}
</div>
) : (
!isAiAnalyzing && (
<div className="text-center text-gray-400 mt-12">
<Clock className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p>Nessun suggerimento attivo. Avvia una scansione per trovare nuove lacune.</p>
</div>
)
)}
</div>
)}
{view === 'analytics' && (
<div className="max-w-6xl mx-auto space-y-6 p-8 overflow-y-auto">
<h2 className="text-2xl font-bold text-gray-800 mb-6">Dashboard Analitica</h2>
{/* Key Metrics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<div className="flex justify-between items-start mb-4"><div className="p-2 bg-blue-50 rounded-lg text-blue-600"><Inbox className="w-5 h-5"/></div><span className="text-xs font-semibold text-green-500 flex items-center"><TrendingUp className="w-3 h-3 mr-1" /> +12%</span></div>
<h3 className="text-slate-500 text-sm font-medium">Volume Ticket</h3><p className="text-3xl font-bold text-gray-900">{totalTickets}</p>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<div className="flex justify-between items-start mb-4"><div className="p-2 bg-amber-50 rounded-lg text-amber-600"><Star className="w-5 h-5" /></div></div>
<h3 className="text-slate-500 text-sm font-medium">CSAT (Soddisfazione)</h3><p className="text-3xl font-bold text-gray-900">{avgRating}/5</p>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<div className="flex justify-between items-start mb-4"><div className="p-2 bg-green-50 rounded-lg text-green-600"><CheckCircle className="w-5 h-5" /></div></div>
<h3 className="text-slate-500 text-sm font-medium">Tasso Risoluzione</h3><p className="text-3xl font-bold text-gray-900">{resolutionRate}%</p>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<div className="flex justify-between items-start mb-4"><div className="p-2 bg-purple-50 rounded-lg text-purple-600"><MessageCircle className="w-5 h-5" /></div></div>
<h3 className="text-slate-500 text-sm font-medium">Chat AI</h3><p className="text-3xl font-bold text-gray-900">{surveys ? surveys.filter(s => s.source === 'chat').length : 0} <span className="text-sm text-gray-400 font-normal">sessioni</span></p>
</div>
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100"><h3 className="font-bold text-gray-800 mb-6">Distribuzione Code</h3>
<div className="space-y-4">
{queues.map(q => {
const count = tickets.filter(t => t.queue === q.name).length;
return (
<div key={q.id} className="group">
<div className="flex justify-between text-sm mb-1"><span>{q.name}</span><span className="font-bold">{count}</span></div>
<div className="h-3 bg-gray-100 rounded-full overflow-hidden"><div className="h-full bg-blue-500 rounded-full" style={{ width: `${maxQueue > 0 ? (count/maxQueue)*100 : 0}%` }}></div></div>
</div>
)
})}
</div>
</div>
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-100 flex flex-col"><h3 className="font-bold text-gray-800 mb-4">Feedback Recenti</h3>
<div className="flex-1 overflow-y-auto pr-2 space-y-3 max-h-[250px]">
{validSurveys.slice().reverse().map(survey => (
<div key={survey.id} className="p-3 bg-gray-50 rounded-lg border border-gray-100">
<div className="flex justify-between mb-1">
<div className="flex text-amber-400">{[...Array(5)].map((_, i) => (<Star key={i} className={`w-3 h-3 ${i < survey.rating ? 'fill-current' : 'text-gray-200'}`} />))}</div>
<span className="text-[10px] uppercase font-bold text-gray-400 bg-white px-2 rounded border border-gray-200">{survey.source}</span>
</div>
<p className="text-sm text-gray-700 italic">"{survey.comment || 'Nessun commento'}"</p>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
};
// Simple Icon component for the info box
const InfoIcon: React.FC<{className?: string}> = ({className}) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
);