Explorar el Código

add the new screen

gdias hace 1 mes
padre
commit
d4c363a6aa

+ 23 - 1
.claude/settings.json

@@ -5,7 +5,29 @@
       "Bash(cd /mnt/c/Users/gabri/.claude/projects/--wsl-localhost-Ubuntu-home-gabrielrd-workspace2-nettown-front && pwd)",
       "Read(//mnt/c/Users/gabri/.claude/projects/**)",
       "Bash(find /home -name \"nettown_front\" -type d 2>/dev/null | head -5)",
-      "Read(///wsl.localhost/Ubuntu//**)"
+      "Read(///wsl.localhost/Ubuntu//**)",
+      "Read(//home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/**)",
+      "Read(///wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/**)",
+      "Read(//home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/**)",
+      "Read(///wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/**)",
+      "Read(//home/gabrielrd/workspace2/nettown_front/src/lib/core/models/**)",
+      "Read(///wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/lib/core/models/**)",
+      "Bash(mkdir -p /home/gabrielrd/workspace2/nettown_front/src/routes/\\\\\\(app\\\\\\)/dashboard/executive /home/gabrielrd/workspace2/nettown_front/src/routes/\\\\\\(app\\\\\\)/dashboard/agents /home/gabrielrd/workspace2/nettown_front/src/routes/\\\\\\(app\\\\\\)/dashboard/sla)",
+      "Bash(ls //wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/)",
+      "Read(///wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/**)",
+      "Bash(New-Item -ItemType Directory -Force \"//wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/executive\")",
+      "Bash(Out-Null)",
+      "Bash(New-Item -ItemType Directory -Force \"//wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/agents\")",
+      "Bash(New-Item -ItemType Directory -Force \"//wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/sla\")",
+      "Bash(Write-Output \"Directories created\")",
+      "Bash(ls \"//wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front/src/routes/\\(app\\)/dashboard/\")",
+      "Bash(cd \"//wsl.localhost/Ubuntu/home/gabrielrd/workspace2/nettown_front\" && npm run build 2>&1 | tail -30)",
+      "Read(///wsl.localhost/Ubuntu/home/gabrielrd/workspace2/**)",
+      "PowerShell($env:WSLENV = \"\"; wsl -e bash -c \"cd /home/gabrielrd/workspace2/nettown_front && npm run build 2>&1 | tail -35\")",
+      "PowerShell(wsl -e bash -c \"cd /home/gabrielrd/workspace2/nettown_front && npx svelte-check --tsconfig ./tsconfig.json 2>&1 | tail -20\")"
+    ],
+    "additionalDirectories": [
+      "\\home\\gabrielrd\\workspace2\\nettown_front\\src\\routes\\(app)\\dashboard"
     ]
   }
 }

+ 8 - 2
src/routes/(app)/+layout.svelte

@@ -18,7 +18,10 @@
 		UserRound,
 		User,
 		CreditCard,
-		HelpCircle
+		HelpCircle,
+		PieChart,
+		Users,
+		ShieldCheck
 	} from 'lucide-svelte';
 	import { goto } from '$app/navigation';
 	import logoWhite from '$lib/assets/images/nettown_white_logo.svg';
@@ -44,7 +47,10 @@
 		{ name: 'Análise de Sentimento', href: '/dashboard/analytics', icon: BarChart2 },
 		{ name: 'Personas', href: '/dashboard/personas', icon: UserRound },
 		{ name: 'Evolução', href: '/dashboard/evolucao', icon: TrendingUp },
-		{ name: 'Configurações', href: '/dashboard/settings', icon: Settings }
+		{ name: 'Configurações', href: '/dashboard/settings', icon: Settings },
+		{ name: 'Dashboard Executivo', href: '/dashboard/executive', icon: PieChart },
+		{ name: 'Agentes', href: '/dashboard/agents', icon: Users },
+		{ name: 'Config. SLA', href: '/dashboard/sla', icon: ShieldCheck }
 	];
 
 	function handleLogout() {

+ 750 - 0
src/routes/(app)/dashboard/agents/+page.svelte

@@ -0,0 +1,750 @@
+<script>
+	import {
+		Users,
+		UserPlus,
+		Search,
+		CheckCircle,
+		XCircle,
+		Clock,
+		Edit,
+		Power,
+		X,
+		ShieldCheck,
+		MessageSquare
+	} from 'lucide-svelte';
+	import { onMount } from 'svelte';
+
+	let isLoading = $state(true);
+	onMount(() => {
+		setTimeout(() => {
+			isLoading = false;
+		}, 700);
+	});
+
+	// ── Mock agents ───────────────────────────────────────────────────────────
+	let agents = $state([
+		{
+			id: 1,
+			name: 'Maria Santos',
+			initials: 'MS',
+			department: 'SAC',
+			channels: ['whatsapp'],
+			status: 'Ativo',
+			availableForEscalation: true,
+			todayAttendances: 24,
+			avgResponseTime: '3m 12s',
+			responseTimeTrend: 'down',
+			slaPct: 94
+		},
+		{
+			id: 2,
+			name: 'Lívia Ferreira',
+			initials: 'LF',
+			department: 'Vendas',
+			channels: ['whatsapp', 'instagram'],
+			status: 'Em Atendimento',
+			availableForEscalation: false,
+			todayAttendances: 18,
+			avgResponseTime: '5m 44s',
+			responseTimeTrend: 'up',
+			slaPct: 88
+		},
+		{
+			id: 3,
+			name: 'Julia Costa',
+			initials: 'JC',
+			department: 'Suporte',
+			channels: ['whatsapp'],
+			status: 'Ativo',
+			availableForEscalation: true,
+			todayAttendances: 31,
+			avgResponseTime: '2m 08s',
+			responseTimeTrend: 'down',
+			slaPct: 97
+		},
+		{
+			id: 4,
+			name: 'Fernanda Lima',
+			initials: 'FL',
+			department: 'SAC',
+			channels: ['instagram'],
+			status: 'Inativo',
+			availableForEscalation: false,
+			todayAttendances: 0,
+			avgResponseTime: '—',
+			responseTimeTrend: 'stable',
+			slaPct: 0
+		},
+		{
+			id: 5,
+			name: 'Roberto Souza',
+			initials: 'RS',
+			department: 'Vendas',
+			channels: ['whatsapp', 'instagram'],
+			status: 'Em Atendimento',
+			availableForEscalation: true,
+			todayAttendances: 12,
+			avgResponseTime: '8m 30s',
+			responseTimeTrend: 'up',
+			slaPct: 72
+		},
+		{
+			id: 6,
+			name: 'Amanda Rocha',
+			initials: 'AR',
+			department: 'Suporte',
+			channels: ['whatsapp'],
+			status: 'Ativo',
+			availableForEscalation: true,
+			todayAttendances: 29,
+			avgResponseTime: '4m 15s',
+			responseTimeTrend: 'down',
+			slaPct: 91
+		},
+		{
+			id: 7,
+			name: 'Carlos Mendes',
+			initials: 'CM',
+			department: 'SAC',
+			channels: ['whatsapp', 'instagram'],
+			status: 'Ativo',
+			availableForEscalation: false,
+			todayAttendances: 20,
+			avgResponseTime: '6m 00s',
+			responseTimeTrend: 'stable',
+			slaPct: 83
+		},
+		{
+			id: 8,
+			name: 'Patrícia Nunes',
+			initials: 'PN',
+			department: 'Vendas',
+			channels: ['whatsapp'],
+			status: 'Disponível',
+			availableForEscalation: true,
+			todayAttendances: 8,
+			avgResponseTime: '3m 50s',
+			responseTimeTrend: 'down',
+			slaPct: 96
+		}
+	]);
+
+	// ── Filters ───────────────────────────────────────────────────────────────
+	let searchTerm = $state('');
+	let filterDept = $state('Todos');
+	let filterChannel = $state('Todos');
+	let filterStatus = $state('Todos');
+
+	const filteredAgents = $derived(
+		agents.filter((a) => {
+			if (searchTerm && !a.name.toLowerCase().includes(searchTerm.toLowerCase())) return false;
+			if (filterDept !== 'Todos' && a.department !== filterDept) return false;
+			if (filterChannel === 'WhatsApp' && !a.channels.includes('whatsapp')) return false;
+			if (filterChannel === 'Instagram' && !a.channels.includes('instagram')) return false;
+			if (filterStatus === 'Disponível para Escalonamento' && !a.availableForEscalation)
+				return false;
+			if (
+				filterStatus !== 'Todos' &&
+				filterStatus !== 'Disponível para Escalonamento' &&
+				a.status !== filterStatus
+			)
+				return false;
+			return true;
+		})
+	);
+
+	// ── Stats ─────────────────────────────────────────────────────────────────
+	const stats = $derived({
+		total: agents.length,
+		active: agents.filter((a) => a.status === 'Ativo' || a.status === 'Em Atendimento' || a.status === 'Disponível').length,
+		inAttendance: agents.filter((a) => a.status === 'Em Atendimento').length,
+		availableForEscalation: agents.filter((a) => a.availableForEscalation).length
+	});
+
+	// ── Actions ───────────────────────────────────────────────────────────────
+	function toggleEscalation(id) {
+		const agent = agents.find((a) => a.id === id);
+		if (agent) agent.availableForEscalation = !agent.availableForEscalation;
+	}
+
+	function toggleAgentActive(id) {
+		const agent = agents.find((a) => a.id === id);
+		if (agent) agent.status = agent.status === 'Inativo' ? 'Ativo' : 'Inativo';
+	}
+
+	// ── New Agent Modal ───────────────────────────────────────────────────────
+	let showModal = $state(false);
+	let editingId = $state(null);
+
+	const emptyForm = () => ({
+		name: '',
+		email: '',
+		department: 'SAC',
+		channels: [],
+		status: 'Ativo',
+		availableForEscalation: true
+	});
+
+	let form = $state(emptyForm());
+
+	function openNewAgent() {
+		form = emptyForm();
+		editingId = null;
+		showModal = true;
+	}
+
+	function openEditAgent(agent) {
+		form = {
+			name: agent.name,
+			email: agent.email ?? '',
+			department: agent.department,
+			channels: [...agent.channels],
+			status: agent.status,
+			availableForEscalation: agent.availableForEscalation
+		};
+		editingId = agent.id;
+		showModal = true;
+	}
+
+	function toggleChannel(ch) {
+		if (form.channels.includes(ch)) {
+			form.channels = form.channels.filter((c) => c !== ch);
+		} else {
+			form.channels = [...form.channels, ch];
+		}
+	}
+
+	function saveAgent() {
+		if (!form.name.trim()) return;
+		if (editingId !== null) {
+			const agent = agents.find((a) => a.id === editingId);
+			if (agent) {
+				agent.name = form.name;
+				agent.initials = form.name
+					.split(' ')
+					.slice(0, 2)
+					.map((n) => n[0])
+					.join('')
+					.toUpperCase();
+				agent.department = form.department;
+				agent.channels = [...form.channels];
+				agent.status = form.status;
+				agent.availableForEscalation = form.availableForEscalation;
+			}
+		} else {
+			const id = Math.max(0, ...agents.map((a) => a.id)) + 1;
+			agents = [
+				...agents,
+				{
+					id,
+					name: form.name,
+					initials: form.name
+						.split(' ')
+						.slice(0, 2)
+						.map((n) => n[0])
+						.join('')
+						.toUpperCase(),
+					department: form.department,
+					channels: [...form.channels],
+					status: form.status,
+					availableForEscalation: form.availableForEscalation,
+					todayAttendances: 0,
+					avgResponseTime: '—',
+					responseTimeTrend: 'stable',
+					slaPct: 100
+				}
+			];
+		}
+		showModal = false;
+	}
+
+	// ── Helpers ───────────────────────────────────────────────────────────────
+	function statusConfig(status) {
+		const map = {
+			Ativo: { dot: 'bg-emerald-500', text: 'text-emerald-700 dark:text-emerald-400' },
+			'Em Atendimento': { dot: 'bg-sky-500', text: 'text-sky-700 dark:text-sky-400' },
+			Inativo: { dot: 'bg-slate-400', text: 'text-slate-500 dark:text-slate-400' },
+			Disponível: { dot: 'bg-indigo-500', text: 'text-indigo-700 dark:text-indigo-400' }
+		};
+		return map[status] ?? map['Inativo'];
+	}
+
+	function deptBadgeClass(dept) {
+		const map = {
+			SAC: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-400/10 dark:text-sky-400 dark:border-sky-400/20',
+			Vendas:
+				'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-400/10 dark:text-emerald-400 dark:border-emerald-400/20',
+			Suporte:
+				'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-400/10 dark:text-purple-400 dark:border-purple-400/20'
+		};
+		return (
+			map[dept] ??
+			'bg-slate-50 text-slate-700 border-slate-200 dark:bg-slate-700 dark:text-slate-300 dark:border-slate-600'
+		);
+	}
+
+	function trendArrow(trend) {
+		if (trend === 'down') return { arrow: '↓', cls: 'text-emerald-600 dark:text-emerald-400' };
+		if (trend === 'up') return { arrow: '↑', cls: 'text-red-600 dark:text-red-400' };
+		return { arrow: '→', cls: 'text-slate-400' };
+	}
+
+	function slaBarClass(pct) {
+		if (pct >= 90) return 'bg-emerald-500';
+		if (pct >= 70) return 'bg-amber-500';
+		return 'bg-red-500';
+	}
+</script>
+
+<svelte:head>
+	<title>Agentes - Nettown Analytics</title>
+</svelte:head>
+
+<!-- New Agent Modal -->
+{#if showModal}
+	<div
+		class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 p-4"
+		onclick={(e) => e.target === e.currentTarget && (showModal = false)}
+		onkeydown={(e) => e.key === 'Escape' && (showModal = false)}
+		role="dialog"
+		aria-modal="true"
+		tabindex="-1"
+	>
+		<div
+			class="w-full max-w-lg rounded-xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-[#1e293b]"
+		>
+			<div
+				class="flex items-center justify-between border-b border-slate-200 p-5 dark:border-slate-700"
+			>
+				<h2 class="text-base font-bold text-slate-900 dark:text-white">
+					{editingId !== null ? 'Editar Agente' : 'Cadastrar Novo Agente'}
+				</h2>
+				<button
+					onclick={() => (showModal = false)}
+					class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-white"
+				>
+					<X size={18} />
+				</button>
+			</div>
+
+			<div class="space-y-4 p-5">
+				<!-- Nome -->
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+						Nome completo
+					</label>
+					<input
+						type="text"
+						bind:value={form.name}
+						placeholder="Ex: Carolina Ribeiro"
+						class="w-full rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm text-slate-900 placeholder-slate-400 transition-colors focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:placeholder-slate-500 dark:focus:bg-slate-900"
+					/>
+				</div>
+
+				<!-- E-mail -->
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+						E-mail
+					</label>
+					<input
+						type="email"
+						bind:value={form.email}
+						placeholder="agente@empresa.com.br"
+						class="w-full rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm text-slate-900 placeholder-slate-400 transition-colors focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:placeholder-slate-500 dark:focus:bg-slate-900"
+					/>
+				</div>
+
+				<!-- Departamento -->
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+						Departamento
+					</label>
+					<select
+						bind:value={form.department}
+						class="w-full rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm text-slate-900 transition-colors focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
+					>
+						<option>SAC</option>
+						<option>Vendas</option>
+						<option>Suporte</option>
+					</select>
+				</div>
+
+				<!-- Canal -->
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+						Canal (multi-seleção)
+					</label>
+					<div class="flex gap-2">
+						{#each ['whatsapp', 'instagram'] as ch}
+							<button
+								type="button"
+								onclick={() => toggleChannel(ch)}
+								class="rounded-lg border px-4 py-2 text-sm font-medium transition-colors {form.channels.includes(ch)
+									? 'border-indigo-600 bg-indigo-600 text-white dark:border-indigo-500 dark:bg-indigo-500'
+									: 'border-slate-300 bg-white text-slate-700 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800'}"
+							>
+								{ch === 'whatsapp' ? 'WhatsApp' : 'Instagram'}
+							</button>
+						{/each}
+					</div>
+				</div>
+
+				<!-- Status -->
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+						Status inicial
+					</label>
+					<select
+						bind:value={form.status}
+						class="w-full rounded-lg border border-slate-300 bg-slate-50 px-3 py-2 text-sm text-slate-900 transition-colors focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
+					>
+						<option>Ativo</option>
+						<option>Inativo</option>
+						<option>Disponível</option>
+					</select>
+				</div>
+
+				<!-- Disponível para escalonamento -->
+				<div class="flex items-center justify-between">
+					<div>
+						<div class="text-sm font-medium text-slate-900 dark:text-white">
+							Disponível para escalonamento
+						</div>
+						<div class="text-xs text-slate-500 dark:text-slate-400">
+							Permite receber conversas escaladas de outros agentes
+						</div>
+					</div>
+					<button
+						type="button"
+						onclick={() => (form.availableForEscalation = !form.availableForEscalation)}
+						class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 focus:outline-none {form.availableForEscalation
+							? 'bg-indigo-600 dark:bg-indigo-500'
+							: 'bg-slate-200 dark:bg-slate-700'}"
+					>
+						<span
+							class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 {form.availableForEscalation
+								? 'translate-x-5'
+								: 'translate-x-0'}"
+						></span>
+					</button>
+				</div>
+			</div>
+
+			<div
+				class="flex justify-end gap-3 border-t border-slate-200 p-5 dark:border-slate-700"
+			>
+				<button
+					onclick={() => (showModal = false)}
+					class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700"
+				>
+					Cancelar
+				</button>
+				<button
+					onclick={saveAgent}
+					disabled={!form.name.trim()}
+					class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-600"
+				>
+					Salvar
+				</button>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<div class="mx-auto max-w-[1600px] space-y-6">
+	<!-- Page header -->
+	<div
+		class="flex flex-wrap items-center justify-between gap-4 rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+	>
+		<div class="flex items-center gap-3">
+			<div
+				class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-indigo-500/20 text-indigo-500"
+			>
+				<Users size={20} strokeWidth={2.5} />
+			</div>
+			<div>
+				<h1 class="text-xl font-bold text-slate-900 dark:text-white">Gestão de Agentes</h1>
+				<p class="text-sm text-slate-500 dark:text-slate-400">
+					Gerencie a equipe de atendimento e configure escalonamentos
+				</p>
+			</div>
+		</div>
+		<button
+			onclick={openNewAgent}
+			class="flex items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-all hover:-translate-y-0.5 hover:bg-indigo-700 hover:shadow-md dark:bg-indigo-500 dark:hover:bg-indigo-600"
+		>
+			<UserPlus size={16} strokeWidth={2.5} />
+			+ Cadastrar Agente
+		</button>
+	</div>
+
+	<!-- Stats row -->
+	{#if isLoading}
+		<div class="grid grid-cols-2 gap-4 xl:grid-cols-4">
+			{#each [1, 2, 3, 4] as _}
+				<div
+					class="h-20 animate-pulse rounded-xl border border-slate-200 bg-slate-100 dark:border-slate-800 dark:bg-slate-800"
+				></div>
+			{/each}
+		</div>
+	{:else}
+		<div class="grid grid-cols-2 gap-4 xl:grid-cols-4">
+			{#each [
+				{ label: 'Total de Agentes', value: stats.total, color: 'text-slate-900 dark:text-white', icon: Users, bg: 'bg-slate-100 dark:bg-slate-800', iconCls: 'text-slate-600 dark:text-slate-400' },
+				{ label: 'Ativos agora', value: stats.active, color: 'text-emerald-600 dark:text-emerald-400', icon: CheckCircle, bg: 'bg-emerald-50 dark:bg-emerald-400/10', iconCls: 'text-emerald-600 dark:text-emerald-400' },
+				{ label: 'Em atendimento', value: stats.inAttendance, color: 'text-sky-600 dark:text-sky-400', icon: MessageSquare, bg: 'bg-sky-50 dark:bg-sky-400/10', iconCls: 'text-sky-600 dark:text-sky-400' },
+				{ label: 'Disp. para escalonamento', value: stats.availableForEscalation, color: 'text-indigo-600 dark:text-indigo-400', icon: ShieldCheck, bg: 'bg-indigo-50 dark:bg-indigo-400/10', iconCls: 'text-indigo-600 dark:text-indigo-400' }
+			] as stat}
+				{@const Icon = stat.icon}
+				<div
+					class="flex items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-colors dark:border-slate-800 dark:bg-[#1e293b]"
+				>
+					<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg {stat.bg} {stat.iconCls}">
+						<Icon size={18} strokeWidth={2.5} />
+					</div>
+					<div>
+						<div class="text-2xl font-bold {stat.color}">{stat.value}</div>
+						<div class="text-xs text-slate-500 dark:text-slate-400">{stat.label}</div>
+					</div>
+				</div>
+			{/each}
+		</div>
+	{/if}
+
+	<!-- Filter bar -->
+	<div
+		class="flex flex-wrap items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 text-sm shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+	>
+		<div class="relative flex-1 min-w-48">
+			<Search size={16} class="absolute top-1/2 left-3 -translate-y-1/2 text-slate-400" />
+			<input
+				type="text"
+				placeholder="Buscar por nome..."
+				bind:value={searchTerm}
+				class="w-full rounded-lg border border-slate-300 bg-slate-50 py-1.5 pr-3 pl-9 text-slate-900 placeholder-slate-400 focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:placeholder-slate-500"
+			/>
+		</div>
+
+		<div class="flex items-center gap-2">
+			<span class="text-xs font-medium text-slate-500 dark:text-slate-400">Departamento:</span>
+			<select
+				bind:value={filterDept}
+				class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 text-slate-900 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
+			>
+				{#each ['Todos', 'SAC', 'Vendas', 'Suporte'] as opt}
+					<option>{opt}</option>
+				{/each}
+			</select>
+		</div>
+
+		<div class="flex items-center gap-2">
+			<span class="text-xs font-medium text-slate-500 dark:text-slate-400">Canal:</span>
+			<select
+				bind:value={filterChannel}
+				class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 text-slate-900 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
+			>
+				{#each ['Todos', 'WhatsApp', 'Instagram'] as opt}
+					<option>{opt}</option>
+				{/each}
+			</select>
+		</div>
+
+		<div class="flex items-center gap-2">
+			<span class="text-xs font-medium text-slate-500 dark:text-slate-400">Status:</span>
+			<select
+				bind:value={filterStatus}
+				class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 text-slate-900 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
+			>
+				{#each ['Todos', 'Ativo', 'Inativo', 'Em Atendimento', 'Disponível para Escalonamento'] as opt}
+					<option>{opt}</option>
+				{/each}
+			</select>
+		</div>
+	</div>
+
+	<!-- Agents table -->
+	<div
+		class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+	>
+		{#if isLoading}
+			<div class="space-y-3 p-4">
+				{#each [1, 2, 3, 4, 5] as _}
+					<div
+						class="h-14 animate-pulse rounded-lg bg-slate-100 dark:bg-slate-800"
+					></div>
+				{/each}
+			</div>
+		{:else}
+			<div class="overflow-x-auto">
+				<table class="w-full min-w-[960px] text-sm">
+					<thead>
+						<tr class="border-b border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/50">
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Agente</th>
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Depto</th>
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Canal</th>
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Status</th>
+							<th class="px-4 py-3 text-center text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Escalonamento</th>
+							<th class="px-4 py-3 text-center text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Hoje</th>
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">T. Médio Resp.</th>
+							<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">SLA Cumprido</th>
+							<th class="px-4 py-3 text-center text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Ações</th>
+						</tr>
+					</thead>
+					<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
+						{#if filteredAgents.length === 0}
+							<tr>
+								<td colspan="9" class="py-16 text-center">
+									<div class="flex flex-col items-center gap-3">
+										<div class="flex h-12 w-12 items-center justify-center rounded-full bg-slate-100 dark:bg-slate-800">
+											<Users size={20} class="text-slate-400" />
+										</div>
+										<div class="text-sm font-medium text-slate-500 dark:text-slate-400">Nenhum agente encontrado</div>
+										<div class="text-xs text-slate-400 dark:text-slate-500">Tente ajustar os filtros acima</div>
+									</div>
+								</td>
+							</tr>
+						{:else}
+							{#each filteredAgents as agent (agent.id)}
+								{@const sc = statusConfig(agent.status)}
+								{@const trend = trendArrow(agent.responseTimeTrend)}
+								<tr
+									class="transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/40 {agent.status === 'Inativo'
+										? 'opacity-60'
+										: ''}"
+								>
+									<!-- Nome -->
+									<td class="px-4 py-3">
+										<div class="flex items-center gap-3">
+											<div
+												class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-xs font-bold text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300"
+											>
+												{agent.initials}
+											</div>
+											<span class="font-medium text-slate-900 dark:text-white">{agent.name}</span>
+										</div>
+									</td>
+
+									<!-- Departamento -->
+									<td class="px-4 py-3">
+										<span
+											class="rounded-md border px-2 py-0.5 text-xs font-semibold {deptBadgeClass(agent.department)}"
+										>
+											{agent.department}
+										</span>
+									</td>
+
+									<!-- Canal -->
+									<td class="px-4 py-3">
+										<div class="flex flex-wrap gap-1">
+											{#if agent.channels.includes('whatsapp')}
+												<span
+													class="rounded-md border border-emerald-200 bg-emerald-50 px-1.5 py-0.5 text-[10px] font-bold text-emerald-700 dark:border-emerald-400/20 dark:bg-emerald-400/10 dark:text-emerald-400"
+													>WA</span
+												>
+											{/if}
+											{#if agent.channels.includes('instagram')}
+												<span
+													class="rounded-md border border-purple-200 bg-purple-50 px-1.5 py-0.5 text-[10px] font-bold text-purple-700 dark:border-purple-400/20 dark:bg-purple-400/10 dark:text-purple-400"
+													>IG</span
+												>
+											{/if}
+										</div>
+									</td>
+
+									<!-- Status -->
+									<td class="px-4 py-3">
+										<div class="flex items-center gap-2">
+											<span
+												class="inline-block h-2 w-2 shrink-0 rounded-full {sc.dot}"
+											></span>
+											<span class="text-xs font-medium {sc.text}">{agent.status}</span>
+										</div>
+									</td>
+
+									<!-- Escalonamento toggle -->
+									<td class="px-4 py-3 text-center">
+										<button
+											type="button"
+											onclick={() => toggleEscalation(agent.id)}
+											class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 focus:outline-none {agent.availableForEscalation
+												? 'bg-indigo-600 dark:bg-indigo-500'
+												: 'bg-slate-200 dark:bg-slate-700'}"
+											title={agent.availableForEscalation
+												? 'Disponível para escalonamento'
+												: 'Indisponível para escalonamento'}
+										>
+											<span
+												class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition duration-200 {agent.availableForEscalation
+													? 'translate-x-4'
+													: 'translate-x-0'}"
+											></span>
+										</button>
+									</td>
+
+									<!-- Atendimentos hoje -->
+									<td class="px-4 py-3 text-center">
+										<span class="font-semibold text-slate-900 dark:text-white"
+											>{agent.todayAttendances}</span
+										>
+									</td>
+
+									<!-- Tempo médio -->
+									<td class="px-4 py-3">
+										<div class="flex items-center gap-1.5">
+											<Clock size={13} class="shrink-0 text-slate-400" />
+											<span class="text-slate-700 dark:text-slate-300">{agent.avgResponseTime}</span>
+											<span class="text-xs font-semibold {trend.cls}">{trend.arrow}</span>
+										</div>
+									</td>
+
+									<!-- SLA progress -->
+									<td class="px-4 py-3">
+										<div class="flex items-center gap-2">
+											<div class="h-2 w-20 overflow-hidden rounded-full bg-slate-100 dark:bg-slate-800">
+												<div
+													class="h-full rounded-full {slaBarClass(agent.slaPct)}"
+													style="width: {agent.slaPct}%"
+												></div>
+											</div>
+											<span
+												class="text-xs font-semibold {agent.slaPct >= 90
+													? 'text-emerald-600 dark:text-emerald-400'
+													: agent.slaPct >= 70
+													? 'text-amber-600 dark:text-amber-400'
+													: 'text-red-600 dark:text-red-400'}"
+											>
+												{agent.slaPct}%
+											</span>
+										</div>
+									</td>
+
+									<!-- Ações -->
+									<td class="px-4 py-3">
+										<div class="flex items-center justify-center gap-2">
+											<button
+												onclick={() => openEditAgent(agent)}
+												class="rounded-md p-1.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200"
+												title="Editar agente"
+											>
+												<Edit size={15} />
+											</button>
+											<button
+												onclick={() => toggleAgentActive(agent.id)}
+												class="rounded-md p-1.5 transition-colors {agent.status === 'Inativo'
+													? 'text-emerald-500 hover:bg-emerald-50 hover:text-emerald-700 dark:hover:bg-emerald-400/10'
+													: 'text-slate-400 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-400/10 dark:hover:text-red-400'}"
+												title={agent.status === 'Inativo' ? 'Reativar agente' : 'Desativar agente'}
+											>
+												<Power size={15} />
+											</button>
+										</div>
+									</td>
+								</tr>
+							{/each}
+						{/if}
+					</tbody>
+				</table>
+			</div>
+		{/if}
+	</div>
+</div>

+ 557 - 0
src/routes/(app)/dashboard/executive/+page.svelte

@@ -0,0 +1,557 @@
+<script>
+	import {
+		TrendingUp,
+		AlertTriangle,
+		DollarSign,
+		Users,
+		ShieldCheck,
+		Activity,
+		MessageSquare,
+		UserRound,
+		Settings,
+		BarChart2,
+		BookOpen
+	} from 'lucide-svelte';
+	import { onMount } from 'svelte';
+
+	let isLoading = $state(true);
+	onMount(() => {
+		setTimeout(() => {
+			isLoading = false;
+		}, 700);
+	});
+
+	// ── Section 1: Top KPIs ──────────────────────────────────────────────────
+	const topKpis = [
+		{
+			title: 'Venda Atual',
+			value: 'R$ 879',
+			trendLabel: '↑ +12% vs ontem',
+			trendColor: 'text-emerald-600 dark:text-emerald-400',
+			icon: DollarSign,
+			danger: false,
+			color: 'text-emerald-600 dark:text-emerald-400',
+			bg: 'bg-emerald-50 dark:bg-emerald-400/10',
+			border: 'border-emerald-200 dark:border-emerald-400/20'
+		},
+		{
+			title: 'Ticket Médio',
+			value: 'R$ 655,00',
+			trendLabel: '→ estável',
+			trendColor: 'text-slate-500 dark:text-slate-400',
+			icon: BarChart2,
+			danger: false,
+			color: 'text-sky-600 dark:text-sky-400',
+			bg: 'bg-sky-50 dark:bg-sky-400/10',
+			border: 'border-sky-200 dark:border-sky-400/20'
+		},
+		{
+			title: 'Lifetime em Risco',
+			value: 'R$ 8.639',
+			trendLabel: '↑ +5% esta semana',
+			trendColor: 'text-red-600 dark:text-red-400',
+			icon: AlertTriangle,
+			danger: true,
+			color: 'text-red-600 dark:text-red-400',
+			bg: 'bg-red-50 dark:bg-red-400/10',
+			border: 'border-red-200 dark:border-red-400/20'
+		},
+		{
+			title: 'Clientes em Risco Crítico',
+			value: '34',
+			trendLabel: '↑ +3 desde ontem',
+			trendColor: 'text-red-600 dark:text-red-400',
+			icon: Users,
+			danger: true,
+			color: 'text-red-600 dark:text-red-400',
+			bg: 'bg-red-50 dark:bg-red-400/10',
+			border: 'border-red-200 dark:border-red-400/20'
+		}
+	];
+
+	// ── Section 2: Churn donut ───────────────────────────────────────────────
+	const churnData = [
+		{ label: 'Baixo', value: 45, color: '#10b981' },
+		{ label: 'Moderado', value: 30, color: '#f59e0b' },
+		{ label: 'Alto', value: 15, color: '#f97316' },
+		{ label: 'Crítico', value: 10, color: '#ef4444' }
+	];
+
+	function buildDonut(data, cx, cy, r, ri) {
+		const total = data.reduce((s, d) => s + d.value, 0);
+		let a = -Math.PI / 2;
+		return data.map((d) => {
+			const sweep = (d.value / total) * 2 * Math.PI;
+			const a1 = a;
+			const a2 = a + sweep;
+			a = a2;
+			const x1 = cx + r * Math.cos(a1),
+				y1 = cy + r * Math.sin(a1);
+			const x2 = cx + r * Math.cos(a2),
+				y2 = cy + r * Math.sin(a2);
+			const ix1 = cx + ri * Math.cos(a2),
+				iy1 = cy + ri * Math.sin(a2);
+			const ix2 = cx + ri * Math.cos(a1),
+				iy2 = cy + ri * Math.sin(a1);
+			const lg = sweep > Math.PI ? 1 : 0;
+			return {
+				...d,
+				pct: Math.round((d.value / total) * 100),
+				path: `M${x1},${y1} A${r},${r},0,${lg},1,${x2},${y2} L${ix1},${iy1} A${ri},${ri},0,${lg},0,${ix2},${iy2} Z`
+			};
+		});
+	}
+
+	const donutPaths = buildDonut(churnData, 60, 60, 50, 28);
+
+	// ── Section 2: LTV em Risco ──────────────────────────────────────────────
+	const ltvTotal = 285000;
+	const ltvAtRisk = 34556;
+	const ltvRiskPct = Math.round((ltvAtRisk / ltvTotal) * 100);
+
+	// ── Section 2: SLA ──────────────────────────────────────────────────────
+	const slaWithinPct = 78;
+	const slaBreachPct = 22;
+
+	// ── Section 3: Sentiment ─────────────────────────────────────────────────
+	const emotionData = [
+		{ label: 'Alegria', value: 38, color: '#10b981', count: 1524 },
+		{ label: 'Confiança', value: 22, color: '#6366f1', count: 882 },
+		{ label: 'Medo', value: 12, color: '#f97316', count: 481 },
+		{ label: 'Raiva', value: 5, color: '#ef4444', count: 201 },
+		{ label: 'Surpresa', value: 15, color: '#f59e0b', count: 602 },
+		{ label: 'Tristeza', value: 8, color: '#64748b', count: 321 }
+	];
+
+	const avgSentimentScore = 0.28;
+	const scorePosition = ((avgSentimentScore + 1) / 2) * 100;
+	let hoveredEmotion = $state(null);
+
+	// ── Section 4: Quick access ──────────────────────────────────────────────
+	const quickAccessItems = [
+		{
+			label: 'Conversas',
+			href: '/dashboard/interactions',
+			icon: MessageSquare,
+			metric: '418 hoje',
+			trend: '↑',
+			trendColor: 'text-emerald-600 dark:text-emerald-400',
+			color: 'text-sky-600 dark:text-sky-400',
+			bg: 'bg-sky-50 dark:bg-sky-400/10',
+			border: 'border-sky-200 dark:border-sky-400/20'
+		},
+		{
+			label: 'Personas',
+			href: '/dashboard/personas',
+			icon: UserRound,
+			metric: '5 ativas',
+			trend: '→',
+			trendColor: 'text-slate-500 dark:text-slate-400',
+			color: 'text-purple-600 dark:text-purple-400',
+			bg: 'bg-purple-50 dark:bg-purple-400/10',
+			border: 'border-purple-200 dark:border-purple-400/20'
+		},
+		{
+			label: 'Agentes',
+			href: '/dashboard/agents',
+			icon: Users,
+			metric: '12 ativos',
+			trend: '↑',
+			trendColor: 'text-emerald-600 dark:text-emerald-400',
+			color: 'text-emerald-600 dark:text-emerald-400',
+			bg: 'bg-emerald-50 dark:bg-emerald-400/10',
+			border: 'border-emerald-200 dark:border-emerald-400/20'
+		},
+		{
+			label: 'Playbooks',
+			href: '/dashboard/evolucao',
+			icon: BookOpen,
+			metric: '3 ativos',
+			trend: '→',
+			trendColor: 'text-slate-500 dark:text-slate-400',
+			color: 'text-amber-600 dark:text-amber-400',
+			bg: 'bg-amber-50 dark:bg-amber-400/10',
+			border: 'border-amber-200 dark:border-amber-400/20'
+		},
+		{
+			label: 'Configurações',
+			href: '/dashboard/settings',
+			icon: Settings,
+			metric: '2 pendentes',
+			trend: '↓',
+			trendColor: 'text-amber-600 dark:text-amber-400',
+			color: 'text-slate-600 dark:text-slate-400',
+			bg: 'bg-slate-100 dark:bg-slate-700/50',
+			border: 'border-slate-200 dark:border-slate-700'
+		},
+		{
+			label: 'Evolução',
+			href: '/dashboard/evolucao',
+			icon: TrendingUp,
+			metric: '+12% vs ontem',
+			trend: '↑',
+			trendColor: 'text-emerald-600 dark:text-emerald-400',
+			color: 'text-indigo-600 dark:text-indigo-400',
+			bg: 'bg-indigo-50 dark:bg-indigo-400/10',
+			border: 'border-indigo-200 dark:border-indigo-400/20'
+		}
+	];
+</script>
+
+<svelte:head>
+	<title>Executive Dashboard - Nettown Analytics</title>
+</svelte:head>
+
+<div class="mx-auto max-w-[1600px] space-y-6">
+	<!-- Page header -->
+	<div
+		class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+	>
+		<div class="flex items-center gap-3">
+			<div
+				class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-indigo-500/20 text-indigo-500"
+			>
+				<Activity size={20} strokeWidth={2.5} />
+			</div>
+			<h1 class="text-xl font-bold text-slate-900 dark:text-white">Executive Dashboard</h1>
+		</div>
+		<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
+			Visão executiva do desempenho comercial e saúde da base de clientes — atualizado agora há
+			pouco
+		</p>
+	</div>
+
+	<!-- Section 1: KPI Bar -->
+	{#if isLoading}
+		<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
+			{#each [1, 2, 3, 4] as _}
+				<div
+					class="h-28 animate-pulse rounded-xl border border-slate-200 bg-slate-100 dark:border-slate-800 dark:bg-slate-800"
+				></div>
+			{/each}
+		</div>
+	{:else}
+		<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
+			{#each topKpis as kpi}
+				{@const Icon = kpi.icon}
+				<div
+					class="rounded-xl border bg-white p-5 shadow-sm transition-colors duration-200 dark:bg-[#1e293b] {kpi.border}"
+				>
+					<div class="flex items-start gap-4">
+						<div
+							class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg {kpi.bg} {kpi.color}"
+						>
+							<Icon size={20} strokeWidth={2.5} />
+						</div>
+						<div class="min-w-0 flex-1 space-y-1">
+							<div class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
+								{kpi.title}
+							</div>
+							<div
+								class="text-2xl font-bold {kpi.danger
+									? kpi.color
+									: 'text-slate-900 dark:text-white'}"
+							>
+								{kpi.value}
+							</div>
+							<div class="text-xs font-medium {kpi.trendColor}">{kpi.trendLabel}</div>
+						</div>
+					</div>
+				</div>
+			{/each}
+		</div>
+	{/if}
+
+	<!-- Section 2: Risk Overview -->
+	{#if isLoading}
+		<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
+			{#each [1, 2, 3] as _}
+				<div
+					class="h-64 animate-pulse rounded-xl border border-slate-200 bg-slate-100 dark:border-slate-800 dark:bg-slate-800"
+				></div>
+			{/each}
+		</div>
+	{:else}
+		<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
+			<!-- Churn Donut -->
+			<div
+				class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+			>
+				<div class="mb-4 flex items-center gap-2">
+					<div
+						class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400"
+					>
+						<Activity size={18} strokeWidth={2} />
+					</div>
+					<h2 class="text-base font-bold text-slate-900 dark:text-white">
+						Índice de Churn Relacional
+					</h2>
+				</div>
+				<div class="flex items-center gap-6">
+					<svg viewBox="0 0 120 120" class="h-36 w-36 shrink-0">
+						{#each donutPaths as seg}
+							<path d={seg.path} fill={seg.color} class="transition-opacity hover:opacity-80">
+								<title>{seg.label}: {seg.pct}%</title>
+							</path>
+						{/each}
+						<text
+							x="60"
+							y="55"
+							text-anchor="middle"
+							dominant-baseline="middle"
+							class="fill-slate-900 dark:fill-white"
+							font-size="11"
+							font-weight="bold"
+						>Churn</text>
+						<text
+							x="60"
+							y="67"
+							text-anchor="middle"
+							dominant-baseline="middle"
+							class="fill-slate-500 dark:fill-slate-400"
+							font-size="8"
+						>Relacional</text>
+					</svg>
+					<div class="flex-1 space-y-2.5">
+						{#each donutPaths as seg}
+							<div class="flex items-center gap-2 text-xs">
+								<span
+									class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
+									style="background-color: {seg.color}"
+								></span>
+								<span class="text-slate-700 dark:text-slate-300">{seg.label}</span>
+								<span class="ml-auto font-bold text-slate-900 dark:text-white">{seg.pct}%</span>
+							</div>
+						{/each}
+					</div>
+				</div>
+			</div>
+
+			<!-- LTV em Risco -->
+			<div
+				class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+			>
+				<div class="mb-4 flex items-center gap-2">
+					<div
+						class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-red-50 text-red-600 dark:bg-red-400/10 dark:text-red-400"
+					>
+						<AlertTriangle size={18} strokeWidth={2} />
+					</div>
+					<h2 class="text-base font-bold text-slate-900 dark:text-white">LTV em Risco</h2>
+				</div>
+				<div class="space-y-4">
+					<div>
+						<div class="text-2xl font-bold text-red-600 dark:text-red-400">
+							R$ {ltvAtRisk.toLocaleString('pt-BR')}
+						</div>
+						<div class="text-xs text-slate-500 dark:text-slate-400">
+							de R$ {ltvTotal.toLocaleString('pt-BR')} em LTV total
+						</div>
+					</div>
+					<div>
+						<div class="mb-1.5 flex items-center justify-between text-xs">
+							<span class="text-slate-600 dark:text-slate-300">{ltvRiskPct}% da base em risco</span>
+							<span class="font-semibold text-red-600 dark:text-red-400">↑ aumentando</span>
+						</div>
+						<div class="h-3 w-full overflow-hidden rounded-full bg-slate-100 dark:bg-slate-800">
+							<div
+								class="h-full rounded-full bg-red-500 transition-all"
+								style="width: {ltvRiskPct}%"
+							></div>
+						</div>
+					</div>
+					<div
+						class="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-400/20 dark:bg-amber-400/10"
+					>
+						<div class="text-xs font-medium text-amber-700 dark:text-amber-400">
+							34 clientes críticos · Ticket médio R$ 1.016
+						</div>
+					</div>
+					<div class="text-xs text-slate-500 dark:text-slate-400">
+						↑ +3 clientes entraram em risco crítico desde ontem
+					</div>
+				</div>
+			</div>
+
+			<!-- SLA Status -->
+			<div
+				class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+			>
+				<div class="mb-4 flex items-center gap-2">
+					<div
+						class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-emerald-50 text-emerald-600 dark:bg-emerald-400/10 dark:text-emerald-400"
+					>
+						<ShieldCheck size={18} strokeWidth={2} />
+					</div>
+					<h2 class="text-base font-bold text-slate-900 dark:text-white">SLA Status</h2>
+				</div>
+				<div class="space-y-4">
+					<div class="flex items-end gap-6">
+						<div>
+							<div class="text-3xl font-bold text-emerald-600 dark:text-emerald-400">
+								{slaWithinPct}%
+							</div>
+							<div class="text-xs text-slate-500 dark:text-slate-400">dentro do SLA</div>
+						</div>
+						<div>
+							<div class="text-2xl font-bold text-red-600 dark:text-red-400">{slaBreachPct}%</div>
+							<div class="text-xs text-slate-500 dark:text-slate-400">estourado</div>
+						</div>
+					</div>
+					<div class="h-3 w-full overflow-hidden rounded-full bg-red-100 dark:bg-red-400/20">
+						<div
+							class="h-full rounded-full bg-emerald-500 transition-all"
+							style="width: {slaWithinPct}%"
+						></div>
+					</div>
+					<div class="grid grid-cols-3 gap-2 text-center">
+						<div class="rounded-lg bg-emerald-50 p-2.5 dark:bg-emerald-400/10">
+							<div class="text-xs font-bold text-emerald-700 dark:text-emerald-400">SAC</div>
+							<div class="text-sm font-semibold text-slate-900 dark:text-white">72%</div>
+						</div>
+						<div class="rounded-lg bg-amber-50 p-2.5 dark:bg-amber-400/10">
+							<div class="text-xs font-bold text-amber-700 dark:text-amber-400">Vendas</div>
+							<div class="text-sm font-semibold text-slate-900 dark:text-white">85%</div>
+						</div>
+						<div class="rounded-lg bg-emerald-50 p-2.5 dark:bg-emerald-400/10">
+							<div class="text-xs font-bold text-emerald-700 dark:text-emerald-400">Suporte</div>
+							<div class="text-sm font-semibold text-slate-900 dark:text-white">91%</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	{/if}
+
+	<!-- Section 3: Sentiment Summary -->
+	{#if isLoading}
+		<div
+			class="h-56 animate-pulse rounded-xl border border-slate-200 bg-slate-100 dark:border-slate-800 dark:bg-slate-800"
+		></div>
+	{:else}
+		<div
+			class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+		>
+			<div class="mb-5 flex items-center gap-2">
+				<div
+					class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 dark:bg-indigo-400/10 dark:text-indigo-400"
+				>
+					<Activity size={18} strokeWidth={2} />
+				</div>
+				<h2 class="text-base font-bold text-slate-900 dark:text-white">Emoção Geral da Base</h2>
+			</div>
+
+			<!-- Stacked bar with hover tooltip -->
+			<div class="relative mb-2 pt-10">
+				{#if hoveredEmotion}
+					<div
+						class="pointer-events-none absolute top-0 left-0 right-0 flex justify-center"
+					>
+						<div
+							class="rounded-lg border border-slate-200 bg-white px-3 py-2 shadow-lg dark:border-slate-700 dark:bg-[#0f172a]"
+						>
+							<span class="text-sm font-semibold text-slate-900 dark:text-white"
+								>{hoveredEmotion.label}</span
+							>
+							<span class="ml-2 text-xs text-slate-500 dark:text-slate-400">
+								{hoveredEmotion.count.toLocaleString('pt-BR')} interações · {hoveredEmotion.value}%
+							</span>
+						</div>
+					</div>
+				{/if}
+				<div class="flex h-10 w-full overflow-hidden rounded-lg">
+					{#each emotionData as emotion}
+						<button
+							type="button"
+							class="relative h-full cursor-default transition-opacity hover:opacity-85"
+							style="width: {emotion.value}%; background-color: {emotion.color}"
+							onmouseenter={() => (hoveredEmotion = emotion)}
+							onmouseleave={() => (hoveredEmotion = null)}
+							aria-label="{emotion.label}: {emotion.value}%"
+						>
+							{#if emotion.value >= 10}
+								<span
+									class="absolute inset-0 flex items-center justify-center text-xs font-bold text-white drop-shadow-sm"
+									>{emotion.value}%</span
+								>
+							{/if}
+						</button>
+					{/each}
+				</div>
+			</div>
+
+			<!-- Legend -->
+			<div class="mb-6 mt-3 flex flex-wrap gap-4">
+				{#each emotionData as emotion}
+					<div class="flex items-center gap-1.5 text-xs">
+						<span
+							class="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
+							style="background-color: {emotion.color}"
+						></span>
+						<span class="text-slate-700 dark:text-slate-300"
+							>{emotion.label} <span class="font-semibold">{emotion.value}%</span></span
+						>
+					</div>
+				{/each}
+			</div>
+
+			<!-- Sentiment score scale -->
+			<div class="space-y-2">
+				<div class="flex items-center justify-between text-xs">
+					<span class="font-medium text-slate-600 dark:text-slate-400">Score médio de sentimento</span>
+					<span class="font-bold text-emerald-600 dark:text-emerald-400">
+						+{avgSentimentScore} — Positivo ↑
+					</span>
+				</div>
+				<div
+					class="relative h-4 w-full overflow-visible rounded-full"
+					style="background: linear-gradient(to right, #ef4444 0%, #f59e0b 35%, #10b981 100%)"
+				>
+					<div
+						class="absolute top-1/2 h-5 w-5 -translate-y-1/2 -translate-x-1/2 rounded-full border-2 border-white bg-white shadow-md dark:border-slate-900 dark:bg-slate-900"
+						style="left: {scorePosition}%"
+					>
+						<div
+							class="absolute inset-0.5 rounded-full"
+							style="background-color: #10b981"
+						></div>
+					</div>
+				</div>
+				<div class="flex justify-between text-[10px] text-slate-500 dark:text-slate-400">
+					<span>−1 Muito Negativo</span>
+					<span>0 Neutro</span>
+					<span>+1 Muito Positivo</span>
+				</div>
+			</div>
+		</div>
+	{/if}
+
+	<!-- Section 4: Quick Access -->
+	{#if !isLoading}
+		<div
+			class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+		>
+			<h2 class="mb-4 text-base font-bold text-slate-900 dark:text-white">Acesso Rápido</h2>
+			<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-6">
+				{#each quickAccessItems as item}
+					{@const Icon = item.icon}
+					<a
+						href={item.href}
+						class="group flex flex-col items-center gap-3 rounded-xl border p-4 text-center transition-all hover:border-indigo-200 hover:shadow-sm dark:hover:border-indigo-500/30 {item.border}"
+					>
+						<div
+							class="flex h-12 w-12 items-center justify-center rounded-xl {item.bg} {item.color} transition-transform duration-150 group-hover:scale-105"
+						>
+							<Icon size={22} strokeWidth={2} />
+						</div>
+						<div>
+							<div class="text-sm font-semibold text-slate-900 dark:text-white">{item.label}</div>
+							<div class="mt-0.5 text-xs text-slate-500 dark:text-slate-400">{item.metric}</div>
+							<div class="mt-0.5 text-xs font-medium {item.trendColor}">{item.trend}</div>
+						</div>
+					</a>
+				{/each}
+			</div>
+		</div>
+	{/if}
+</div>

+ 489 - 0
src/routes/(app)/dashboard/sla/+page.svelte

@@ -0,0 +1,489 @@
+<script>
+	import {
+		ShieldCheck,
+		Clock,
+		CheckCircle,
+		AlertTriangle,
+		XCircle,
+		Plus,
+		X
+	} from 'lucide-svelte';
+	import { onMount } from 'svelte';
+
+	let isLoading = $state(true);
+	onMount(() => {
+		setTimeout(() => {
+			isLoading = false;
+		}, 600);
+	});
+
+	// ── Department configs ────────────────────────────────────────────────────
+	let departments = $state([
+		{
+			id: 'sac',
+			name: 'SAC',
+			firstResponseH: 1,
+			firstResponseM: 30,
+			resolutionH: 24,
+			alertPct: 80,
+			liveStatus: 'breach',
+			liveDetail: '14h estourado',
+			lastUpdated: 'há 8 minutos'
+		},
+		{
+			id: 'vendas',
+			name: 'Vendas',
+			firstResponseH: 0,
+			firstResponseM: 45,
+			resolutionH: 8,
+			alertPct: 80,
+			liveStatus: 'warning',
+			liveDetail: 'próximo de estourar (18% restante)',
+			lastUpdated: 'há 3 minutos'
+		},
+		{
+			id: 'suporte',
+			name: 'Suporte',
+			firstResponseH: 2,
+			firstResponseM: 0,
+			resolutionH: 48,
+			alertPct: 80,
+			liveStatus: 'ok',
+			liveDetail: 'dentro do SLA',
+			lastUpdated: 'há 1 minuto'
+		}
+	]);
+
+	let savedDeptIds = $state([]);
+
+	function saveDepartment(deptId, deptName) {
+		savedDeptIds = [...savedDeptIds, deptId];
+		setTimeout(() => {
+			savedDeptIds = savedDeptIds.filter((id) => id !== deptId);
+		}, 3000);
+	}
+
+	// ── Add department modal ──────────────────────────────────────────────────
+	let showAddModal = $state(false);
+	let newDept = $state({ name: '', firstResponseH: 1, firstResponseM: 0, resolutionH: 24, alertPct: 80 });
+
+	function addDepartment() {
+		if (!newDept.name.trim()) return;
+		departments = [
+			...departments,
+			{
+				id: newDept.name.toLowerCase().replace(/\s+/g, '-'),
+				name: newDept.name,
+				firstResponseH: newDept.firstResponseH,
+				firstResponseM: newDept.firstResponseM,
+				resolutionH: newDept.resolutionH,
+				alertPct: newDept.alertPct,
+				liveStatus: 'ok',
+				liveDetail: 'dentro do SLA',
+				lastUpdated: 'agora'
+			}
+		];
+		newDept = { name: '', firstResponseH: 1, firstResponseM: 0, resolutionH: 24, alertPct: 80 };
+		showAddModal = false;
+	}
+
+	// ── Live status helpers ───────────────────────────────────────────────────
+	function statusBadge(status) {
+		const map = {
+			ok: {
+				label: 'OK',
+				cls: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-400/10 dark:text-emerald-400 dark:border-emerald-400/20'
+			},
+			warning: {
+				label: 'ALERTA',
+				cls: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-400/10 dark:text-amber-400 dark:border-amber-400/20'
+			},
+			breach: {
+				label: 'CRÍTICO',
+				cls: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-400/10 dark:text-red-400 dark:border-red-400/20'
+			}
+		};
+		return map[status] ?? map['ok'];
+	}
+
+	function statusIndicatorCls(status) {
+		if (status === 'ok') return 'bg-emerald-500';
+		if (status === 'warning') return 'bg-amber-500';
+		return 'bg-red-500';
+	}
+
+	function statusCardBorder(status) {
+		if (status === 'ok') return 'border-emerald-200 dark:border-emerald-400/20';
+		if (status === 'warning') return 'border-amber-200 dark:border-amber-400/20';
+		return 'border-red-200 dark:border-red-400/20';
+	}
+
+	function statusIcon(status) {
+		if (status === 'ok') return CheckCircle;
+		if (status === 'warning') return AlertTriangle;
+		return XCircle;
+	}
+
+	function statusIconCls(status) {
+		if (status === 'ok') return 'text-emerald-600 dark:text-emerald-400';
+		if (status === 'warning') return 'text-amber-600 dark:text-amber-400';
+		return 'text-red-600 dark:text-red-400';
+	}
+
+	const inputCls =
+		'rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 text-sm text-slate-900 transition-colors focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 w-full';
+</script>
+
+<svelte:head>
+	<title>Configuração de SLA - Nettown Analytics</title>
+</svelte:head>
+
+<!-- Add Department Modal -->
+{#if showAddModal}
+	<div
+		class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 p-4"
+		onclick={(e) => e.target === e.currentTarget && (showAddModal = false)}
+		onkeydown={(e) => e.key === 'Escape' && (showAddModal = false)}
+		role="dialog"
+		aria-modal="true"
+		tabindex="-1"
+	>
+		<div
+			class="w-full max-w-md rounded-xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-[#1e293b]"
+		>
+			<div
+				class="flex items-center justify-between border-b border-slate-200 p-5 dark:border-slate-700"
+			>
+				<h2 class="text-base font-bold text-slate-900 dark:text-white">Adicionar Departamento</h2>
+				<button
+					onclick={() => (showAddModal = false)}
+					class="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-white"
+				>
+					<X size={18} />
+				</button>
+			</div>
+			<div class="space-y-4 p-5">
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Nome do departamento</label>
+					<input type="text" bind:value={newDept.name} placeholder="Ex: Financeiro" class={inputCls} />
+				</div>
+				<div class="grid grid-cols-2 gap-4">
+					<div>
+						<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Primeira resposta (h)</label>
+						<input type="number" min="0" max="72" bind:value={newDept.firstResponseH} class={inputCls} />
+					</div>
+					<div>
+						<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Primeira resposta (min)</label>
+						<input type="number" min="0" max="59" bind:value={newDept.firstResponseM} class={inputCls} />
+					</div>
+				</div>
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Tempo máx. de resolução (h)</label>
+					<input type="number" min="1" max="720" bind:value={newDept.resolutionH} class={inputCls} />
+				</div>
+				<div>
+					<label class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Alerta de estouro em (% do tempo)</label>
+					<input type="number" min="1" max="99" bind:value={newDept.alertPct} class={inputCls} />
+				</div>
+			</div>
+			<div class="flex justify-end gap-3 border-t border-slate-200 p-5 dark:border-slate-700">
+				<button
+					onclick={() => (showAddModal = false)}
+					class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:hover:bg-slate-700"
+				>
+					Cancelar
+				</button>
+				<button
+					onclick={addDepartment}
+					disabled={!newDept.name.trim()}
+					class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-indigo-700 disabled:opacity-50 dark:bg-indigo-500 dark:hover:bg-indigo-600"
+				>
+					Adicionar
+				</button>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<div class="mx-auto max-w-[1600px] space-y-6">
+	<!-- Page header -->
+	<div
+		class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+	>
+		<div class="flex items-center gap-3">
+			<div
+				class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-indigo-500/20 text-indigo-500"
+			>
+				<ShieldCheck size={20} strokeWidth={2.5} />
+			</div>
+			<h1 class="text-xl font-bold text-slate-900 dark:text-white">Configuração de SLA</h1>
+		</div>
+		<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
+			Defina os tempos de atendimento por departamento. O sistema alertará automaticamente quando os
+			limites estiverem próximos de estourar.
+		</p>
+	</div>
+
+	<!-- Department cards -->
+	{#if isLoading}
+		<div class="space-y-4">
+			{#each [1, 2, 3] as _}
+				<div
+					class="h-56 animate-pulse rounded-xl border border-slate-200 bg-slate-100 dark:border-slate-800 dark:bg-slate-800"
+				></div>
+			{/each}
+		</div>
+	{:else}
+		<div class="space-y-4">
+			{#each departments as dept, i}
+				{@const badge = statusBadge(dept.liveStatus)}
+				{@const StatusIcon = statusIcon(dept.liveStatus)}
+				<div
+					class="overflow-hidden rounded-xl border bg-white shadow-sm transition-colors duration-200 dark:bg-[#1e293b] {statusCardBorder(dept.liveStatus)}"
+				>
+					<!-- Card header -->
+					<div
+						class="flex items-center justify-between border-b border-slate-200 bg-slate-50 px-5 py-4 dark:border-slate-800 dark:bg-slate-900/30"
+					>
+						<div class="flex items-center gap-3">
+							<div class="h-3 w-3 shrink-0 rounded-full {statusIndicatorCls(dept.liveStatus)}"></div>
+							<h2 class="text-base font-bold text-slate-900 dark:text-white">{dept.name}</h2>
+							<span class="rounded-md border px-2 py-0.5 text-xs font-bold {badge.cls}"
+								>{badge.label}</span
+							>
+						</div>
+						<div class="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
+							<StatusIcon size={14} class={statusIconCls(dept.liveStatus)} />
+							<span>{dept.liveDetail}</span>
+						</div>
+					</div>
+
+					<!-- Form fields -->
+					<div class="grid grid-cols-1 gap-6 p-5 sm:grid-cols-2 xl:grid-cols-4">
+						<!-- Primeira resposta horas -->
+						<div>
+							<label
+								class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+							>
+								Primeira resposta — horas
+							</label>
+							<div class="flex items-center gap-2">
+								<input
+									type="number"
+									min="0"
+									max="72"
+									bind:value={departments[i].firstResponseH}
+									class={inputCls}
+								/>
+								<span class="shrink-0 text-xs text-slate-500 dark:text-slate-400">h</span>
+							</div>
+						</div>
+
+						<!-- Primeira resposta minutos -->
+						<div>
+							<label
+								class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+							>
+								Primeira resposta — minutos
+							</label>
+							<div class="flex items-center gap-2">
+								<input
+									type="number"
+									min="0"
+									max="59"
+									bind:value={departments[i].firstResponseM}
+									class={inputCls}
+								/>
+								<span class="shrink-0 text-xs text-slate-500 dark:text-slate-400">min</span>
+							</div>
+						</div>
+
+						<!-- Tempo máx. de resolução -->
+						<div>
+							<label
+								class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+							>
+								Tempo máx. de resolução
+							</label>
+							<div class="flex items-center gap-2">
+								<input
+									type="number"
+									min="1"
+									max="720"
+									bind:value={departments[i].resolutionH}
+									class={inputCls}
+								/>
+								<span class="shrink-0 text-xs text-slate-500 dark:text-slate-400">horas</span>
+							</div>
+						</div>
+
+						<!-- Alerta de estouro -->
+						<div>
+							<label
+								class="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+							>
+								Alertar quando atingir
+							</label>
+							<div class="flex items-center gap-2">
+								<input
+									type="number"
+									min="1"
+									max="99"
+									bind:value={departments[i].alertPct}
+									class={inputCls}
+								/>
+								<span class="shrink-0 text-xs text-slate-500 dark:text-slate-400">% do tempo</span>
+							</div>
+						</div>
+					</div>
+
+					<!-- Summary + save -->
+					<div
+						class="flex flex-wrap items-center justify-between gap-4 border-t border-slate-100 px-5 py-4 dark:border-slate-800"
+					>
+						<div class="flex flex-wrap gap-4 text-xs text-slate-500 dark:text-slate-400">
+							<span>
+								Primeira resposta:
+								<strong class="text-slate-900 dark:text-slate-200">
+									{dept.firstResponseH > 0 ? `${dept.firstResponseH}h ` : ''}{dept.firstResponseM > 0
+										? `${dept.firstResponseM}min`
+										: dept.firstResponseH === 0
+										? '0min'
+										: ''}
+								</strong>
+							</span>
+							<span>
+								Resolução: <strong class="text-slate-900 dark:text-slate-200">{dept.resolutionH}h</strong>
+							</span>
+							<span>
+								Alerta em: <strong class="text-slate-900 dark:text-slate-200">{dept.alertPct}%</strong>
+							</span>
+						</div>
+
+						<div class="flex items-center gap-3">
+							{#if savedDeptIds.includes(dept.id)}
+								<span class="flex items-center gap-1.5 text-sm font-medium text-emerald-600 dark:text-emerald-400">
+									<CheckCircle size={16} />
+									SLA do {dept.name} atualizado
+								</span>
+							{/if}
+							<button
+								onclick={() => saveDepartment(dept.id, dept.name)}
+								class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-all hover:bg-indigo-700 dark:bg-indigo-500 dark:hover:bg-indigo-600"
+							>
+								Salvar
+							</button>
+						</div>
+					</div>
+				</div>
+			{/each}
+		</div>
+	{/if}
+
+	<!-- Add Department button -->
+	{#if !isLoading}
+		<div class="flex justify-center">
+			<button
+				onclick={() => (showAddModal = true)}
+				class="flex items-center gap-2 rounded-lg border border-dashed border-slate-300 bg-white px-6 py-3 text-sm font-medium text-slate-600 shadow-sm transition-all hover:border-indigo-400 hover:text-indigo-600 dark:border-slate-700 dark:bg-[#1e293b] dark:text-slate-400 dark:hover:border-indigo-500 dark:hover:text-indigo-400"
+			>
+				<Plus size={16} strokeWidth={2.5} />
+				+ Adicionar Departamento
+			</button>
+		</div>
+	{/if}
+
+	<!-- Live SLA Status panel -->
+	{#if !isLoading}
+		<div
+			class="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
+		>
+			<div
+				class="flex items-center gap-3 border-b border-slate-200 bg-slate-50 px-5 py-4 dark:border-slate-800 dark:bg-slate-900/30"
+			>
+				<div
+					class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400"
+				>
+					<Clock size={18} strokeWidth={2} />
+				</div>
+				<h2 class="text-base font-bold text-slate-900 dark:text-white">
+					Status de SLA em Tempo Real
+				</h2>
+			</div>
+
+			<div class="overflow-x-auto">
+				<table class="w-full text-sm">
+					<thead>
+						<tr
+							class="border-b border-slate-100 bg-slate-50/50 dark:border-slate-800 dark:bg-slate-900/20"
+						>
+							<th
+								class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Departamento</th
+							>
+							<th
+								class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Status atual</th
+							>
+							<th
+								class="px-5 py-3 text-center text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Situação</th
+							>
+							<th
+								class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Primeira resposta máx.</th
+							>
+							<th
+								class="px-5 py-3 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Resolução máx.</th
+							>
+							<th
+								class="px-5 py-3 text-right text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"
+								>Atualizado</th
+							>
+						</tr>
+					</thead>
+					<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
+						{#each departments as dept}
+							{@const badge = statusBadge(dept.liveStatus)}
+							{@const StatusIcon = statusIcon(dept.liveStatus)}
+							<tr class="transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/40">
+								<td class="px-5 py-4">
+									<div class="flex items-center gap-2">
+										<div
+											class="h-2.5 w-2.5 shrink-0 rounded-full {statusIndicatorCls(dept.liveStatus)}"
+										></div>
+										<span class="font-semibold text-slate-900 dark:text-white">{dept.name}</span>
+									</div>
+								</td>
+								<td class="px-5 py-4">
+									<div class="flex items-center gap-2 text-sm">
+										<StatusIcon size={15} class={statusIconCls(dept.liveStatus)} />
+										<span class="text-slate-700 dark:text-slate-300">{dept.liveDetail}</span>
+									</div>
+								</td>
+								<td class="px-5 py-4 text-center">
+									<span class="rounded-md border px-2.5 py-1 text-xs font-bold {badge.cls}"
+										>{badge.label}</span
+									>
+								</td>
+								<td class="px-5 py-4 text-slate-700 dark:text-slate-300">
+									{dept.firstResponseH > 0 ? `${dept.firstResponseH}h` : ''}{dept.firstResponseM > 0
+										? ` ${dept.firstResponseM}min`
+										: ''}
+									{dept.firstResponseH === 0 && dept.firstResponseM === 0 ? '—' : ''}
+								</td>
+								<td class="px-5 py-4 text-slate-700 dark:text-slate-300">
+									{dept.resolutionH}h
+								</td>
+								<td class="px-5 py-4 text-right text-xs text-slate-400 dark:text-slate-500">
+									{dept.lastUpdated}
+								</td>
+							</tr>
+						{/each}
+					</tbody>
+				</table>
+			</div>
+		</div>
+	{/if}
+</div>