|
|
@@ -1,316 +1,552 @@
|
|
|
<script>
|
|
|
- import { Users, MessageSquare, AlertTriangle, ThumbsUp, Activity, Smartphone, Monitor } from 'lucide-svelte';
|
|
|
- import { Chart, Svg, Axis, Bar, Line, Spline, Highlight, Group } from 'layerchart';
|
|
|
- import { scaleTime, scaleLinear, scaleBand } from 'd3-scale';
|
|
|
- import { format } from 'date-fns';
|
|
|
- import { ptBR } from 'date-fns/locale';
|
|
|
-
|
|
|
- import {
|
|
|
- mockKpis,
|
|
|
- mockPriorityQueue,
|
|
|
- mockRadarData,
|
|
|
- mockVolumeData,
|
|
|
- mockSentimentDistribution,
|
|
|
- mockAspectsData
|
|
|
- } from '$lib/core/models/mock-data.js';
|
|
|
+ import {
|
|
|
+ Users,
|
|
|
+ MessageSquare,
|
|
|
+ AlertTriangle,
|
|
|
+ ThumbsUp,
|
|
|
+ Activity,
|
|
|
+ UserRoundCog,
|
|
|
+ UserX
|
|
|
+ } from 'lucide-svelte';
|
|
|
+ import { Chart, Svg, Axis, Bar, Line, Spline, Highlight, Group } from 'layerchart';
|
|
|
+ import { scaleTime, scaleLinear, scaleBand } from 'd3-scale';
|
|
|
+ import { format } from 'date-fns';
|
|
|
+ import { ptBR } from 'date-fns/locale';
|
|
|
|
|
|
- // Map KPIs to UI format
|
|
|
- const kpis = [
|
|
|
- { title: 'Mensagens WhatsApp', value: mockKpis.whatsappMessages.current, subvalue: `${mockKpis.whatsappMessages.total} Total`, icon: MessageSquare, 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: 'Linhas CRM', value: mockKpis.crmLines.current, subvalue: `${mockKpis.crmLines.total} Total`, icon: Activity, 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' },
|
|
|
- { title: 'Pessoas Novas', value: mockKpis.newPeople.new, subvalue: `${mockKpis.newPeople.recurring} Recorrente`, icon: Users, 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: 'Sentimento Geral', value: mockKpis.generalSentiment.value, subvalue: 'Média', icon: ThumbsUp, 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' },
|
|
|
- { title: 'Fontes Ativas', value: mockKpis.activeSources.value, subvalue: 'Conectadas', icon: Smartphone, color: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-400/10', border: 'border-blue-200 dark:border-blue-400/20' },
|
|
|
- { title: 'Atendimento Ativo', value: mockKpis.activeService.value, subvalue: 'Agentes online', icon: Monitor, 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' },
|
|
|
- ];
|
|
|
+ import {
|
|
|
+ mockKpis,
|
|
|
+ mockPriorityQueue,
|
|
|
+ mockRadarData,
|
|
|
+ mockVolumeData,
|
|
|
+ mockSentimentDistribution,
|
|
|
+ mockAspectsData,
|
|
|
+ mockAspectsDrilldown
|
|
|
+ } from '$lib/core/models/mock-data.js';
|
|
|
|
|
|
- // Data-driven Radar Chart logic
|
|
|
- const radarRadius = 80;
|
|
|
- const radarCenter = 100;
|
|
|
- const angleStep = (Math.PI * 2) / mockRadarData.length;
|
|
|
-
|
|
|
- const radarPoints = $derived(mockRadarData.map((d, i) => {
|
|
|
- const angle = i * angleStep - Math.PI / 2;
|
|
|
- const r = (d.value / 100) * radarRadius;
|
|
|
- return {
|
|
|
- ...d,
|
|
|
- x: radarCenter + r * Math.cos(angle),
|
|
|
- y: radarCenter + r * Math.sin(angle),
|
|
|
- labelX: radarCenter + (radarRadius + 18) * Math.cos(angle),
|
|
|
- labelY: radarCenter + (radarRadius + 15) * Math.sin(angle),
|
|
|
- axisX: radarCenter + radarRadius * Math.cos(angle),
|
|
|
- axisY: radarCenter + radarRadius * Math.sin(angle),
|
|
|
- };
|
|
|
- }));
|
|
|
+ function getSentimentLabel(sentimentScore) {
|
|
|
+ if (sentimentScore >= 0.35) return 'Positivo';
|
|
|
+ if (sentimentScore <= -0.35) return 'Negativo';
|
|
|
+ return 'Neutro';
|
|
|
+ }
|
|
|
|
|
|
- const radarPath = $derived(radarPoints.map(p => `${p.x},${p.y}`).join(' '));
|
|
|
+ let selectedAspectDrilldown = $state(null);
|
|
|
|
|
|
- const gridLevels = [0.2, 0.4, 0.6, 0.8, 1].map(level => {
|
|
|
- return mockRadarData.map((_, i) => {
|
|
|
- const angle = i * angleStep - Math.PI / 2;
|
|
|
- const r = level * radarRadius;
|
|
|
- return `${radarCenter + r * Math.cos(angle)},${radarCenter + r * Math.sin(angle)}`;
|
|
|
- }).join(' ');
|
|
|
- });
|
|
|
+ function openAspectDrilldown(aspect, tone) {
|
|
|
+ selectedAspectDrilldown = { aspect, tone };
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearAspectDrilldown() {
|
|
|
+ selectedAspectDrilldown = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const drilldownItems = $derived(
|
|
|
+ selectedAspectDrilldown
|
|
|
+ ? (mockAspectsDrilldown[selectedAspectDrilldown.aspect]?.[selectedAspectDrilldown.tone] ?? []).slice(
|
|
|
+ 0,
|
|
|
+ 6
|
|
|
+ )
|
|
|
+ : []
|
|
|
+ );
|
|
|
+
|
|
|
+ const maxDrilldownValue = $derived(Math.max(1, ...drilldownItems.map((item) => item.value)));
|
|
|
+
|
|
|
+ const drilldownTitle = $derived(
|
|
|
+ selectedAspectDrilldown
|
|
|
+ ? `${selectedAspectDrilldown.aspect} • ${
|
|
|
+ selectedAspectDrilldown.tone === 'positive' ? 'Pontos Positivos' : 'Pontos Negativos'
|
|
|
+ }`
|
|
|
+ : 'Distribuição de Aspectos'
|
|
|
+ );
|
|
|
+
|
|
|
+ // Map KPIs to UI format
|
|
|
+ const kpis = [
|
|
|
+ {
|
|
|
+ title: 'Usuários Cadastrados',
|
|
|
+ value: mockKpis.newPeople.new,
|
|
|
+ // subvalue: `${mockKpis.newPeople.recurring} entrada / 0 saída`,
|
|
|
+ icon: Users,
|
|
|
+ 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: 'Atendentes Ativos',
|
|
|
+ value: mockKpis.activeService.value,
|
|
|
+ // subvalue: 'Com conversas no período',
|
|
|
+ icon: UserRoundCog,
|
|
|
+ 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'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: 'Conversas Ativas',
|
|
|
+ value: mockKpis.whatsappMessages.current,
|
|
|
+ // subvalue: `${mockKpis.whatsappMessages.total} total geral`,
|
|
|
+ icon: MessageSquare,
|
|
|
+ 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: 'Sentimento Geral',
|
|
|
+ value: getSentimentLabel(mockKpis.generalSentiment.value),
|
|
|
+ // subvalue: `Score ${mockKpis.generalSentiment.value.toFixed(1).replace('.', ',')}`,
|
|
|
+ icon: ThumbsUp,
|
|
|
+ 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'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: 'Usuários Não Cadastrados',
|
|
|
+ value: mockKpis.crmLines.total,
|
|
|
+ // subvalue: 'Base sem cadastro no CRM',
|
|
|
+ icon: UserX,
|
|
|
+ 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'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // Data-driven Radar Chart logic
|
|
|
+ const radarRadius = 80;
|
|
|
+ const radarCenter = 100;
|
|
|
+ const angleStep = (Math.PI * 2) / mockRadarData.length;
|
|
|
+
|
|
|
+ const radarPoints = $derived(
|
|
|
+ mockRadarData.map((d, i) => {
|
|
|
+ const angle = i * angleStep - Math.PI / 2;
|
|
|
+ const r = (d.value / 100) * radarRadius;
|
|
|
+ return {
|
|
|
+ ...d,
|
|
|
+ x: radarCenter + r * Math.cos(angle),
|
|
|
+ y: radarCenter + r * Math.sin(angle),
|
|
|
+ labelX: radarCenter + (radarRadius + 18) * Math.cos(angle),
|
|
|
+ labelY: radarCenter + (radarRadius + 15) * Math.sin(angle),
|
|
|
+ axisX: radarCenter + radarRadius * Math.cos(angle),
|
|
|
+ axisY: radarCenter + radarRadius * Math.sin(angle)
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ const radarPath = $derived(radarPoints.map((p) => `${p.x},${p.y}`).join(' '));
|
|
|
+
|
|
|
+ const gridLevels = [0.2, 0.4, 0.6, 0.8, 1].map((level) => {
|
|
|
+ return mockRadarData
|
|
|
+ .map((_, i) => {
|
|
|
+ const angle = i * angleStep - Math.PI / 2;
|
|
|
+ const r = level * radarRadius;
|
|
|
+ return `${radarCenter + r * Math.cos(angle)},${radarCenter + r * Math.sin(angle)}`;
|
|
|
+ })
|
|
|
+ .join(' ');
|
|
|
+ });
|
|
|
</script>
|
|
|
|
|
|
<svelte:head>
|
|
|
- <title>Dashboard - Nettown Analytics</title>
|
|
|
+ <title>Dashboard - Nettown Analytics</title>
|
|
|
</svelte:head>
|
|
|
|
|
|
-<div class="space-y-6 max-w-[1600px] mx-auto">
|
|
|
- <!-- Header/Filters -->
|
|
|
- <div class="bg-white dark:bg-[#1e293b] p-4 rounded-xl border border-slate-200 dark:border-slate-800 flex flex-wrap items-center gap-4 text-sm shadow-sm transition-colors duration-200">
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <span class="text-slate-600 dark:text-slate-400 font-medium">Período:</span>
|
|
|
- <select class="bg-slate-50 dark:bg-slate-900 border border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 rounded-lg px-3 py-1.5 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors">
|
|
|
- <option>Hoje (24h)</option>
|
|
|
- <option>Ontem</option>
|
|
|
- <option>Últimos 7 dias</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <span class="text-slate-600 dark:text-slate-400 font-medium">Unidade:</span>
|
|
|
- <select class="bg-slate-50 dark:bg-slate-900 border border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 rounded-lg px-3 py-1.5 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors">
|
|
|
- <option>Sem segmento</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <span class="text-slate-600 dark:text-slate-400 font-medium">Área:</span>
|
|
|
- <select class="bg-slate-50 dark:bg-slate-900 border border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 rounded-lg px-3 py-1.5 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors">
|
|
|
- <option>Sem segmento de setor</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <span class="text-slate-600 dark:text-slate-400 font-medium">Sentimento:</span>
|
|
|
- <select class="bg-slate-50 dark:bg-slate-900 border border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 rounded-lg px-3 py-1.5 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors">
|
|
|
- <option>Todos</option>
|
|
|
- <option>Positivo</option>
|
|
|
- <option>Neutro</option>
|
|
|
- <option>Negativo</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+<div class="mx-auto max-w-[1600px] space-y-6">
|
|
|
+ <!-- Header/Filters -->
|
|
|
+ <div
|
|
|
+ class="flex flex-wrap items-center gap-4 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="flex items-center gap-2">
|
|
|
+ <span class="font-medium text-slate-600 dark:text-slate-400">Período:</span>
|
|
|
+ <select
|
|
|
+ class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 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>Hoje (24h)</option>
|
|
|
+ <option>Ontem</option>
|
|
|
+ <option>Últimos 7 dias</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <span class="font-medium text-slate-600 dark:text-slate-400">Unidade:</span>
|
|
|
+ <select
|
|
|
+ class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 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>Sem segmento</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <span class="font-medium text-slate-600 dark:text-slate-400">Área:</span>
|
|
|
+ <select
|
|
|
+ class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 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>Sem segmento de setor</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <span class="font-medium text-slate-600 dark:text-slate-400">Sentimento:</span>
|
|
|
+ <select
|
|
|
+ class="rounded-lg border border-slate-300 bg-slate-50 px-3 py-1.5 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>Todos</option>
|
|
|
+ <option>Positivo</option>
|
|
|
+ <option>Neutro</option>
|
|
|
+ <option>Negativo</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- KPIs -->
|
|
|
+ <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
|
|
+ {#each kpis as kpi}
|
|
|
+ {@const Icon = kpi.icon}
|
|
|
+ <div
|
|
|
+ class="rounded-xl border bg-white p-5 dark:bg-[#1e293b] {kpi.border} shadow-sm transition-colors duration-200 hover:border-indigo-200 dark:hover:border-slate-600"
|
|
|
+ >
|
|
|
+ <div class="flex items-start gap-4">
|
|
|
+ <div
|
|
|
+ class="h-10 w-10 rounded-lg {kpi.bg} {kpi.color} flex shrink-0 items-center justify-center"
|
|
|
+ >
|
|
|
+ <Icon size={20} strokeWidth={2.5} />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div
|
|
|
+ class="mb-1 text-xs font-semibold tracking-wider text-slate-500 uppercase dark:text-slate-400"
|
|
|
+ >
|
|
|
+ {kpi.title}
|
|
|
+ </div>
|
|
|
+ <div class="mb-0.5 text-2xl font-bold text-slate-900 dark:text-white">{kpi.value}</div>
|
|
|
+ <div class="text-xs text-slate-500">{kpi.subvalue}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {/each}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Main Content Area -->
|
|
|
+ <div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
|
|
+ <!-- Fila Priorizada / Alertas -->
|
|
|
+ <div
|
|
|
+ class="flex h-[500px] flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm transition-colors duration-200 xl:col-span-2 dark:border-slate-800 dark:bg-[#1e293b]"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="flex items-center justify-between border-b border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-[#1e293b]"
|
|
|
+ >
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <AlertTriangle size={18} class="text-amber-600 dark:text-amber-500" />
|
|
|
+ <h2 class="text-base font-bold text-slate-900 dark:text-white">
|
|
|
+ Fila Priorizada • Conversas Inacabadas
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <span
|
|
|
+ class="rounded-md border border-red-200 bg-red-50 px-2.5 py-1 text-xs font-medium text-red-600 dark:border-red-500/20 dark:bg-red-500/10 dark:text-red-400"
|
|
|
+ >22 fora da janela</span
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700 dark:border-amber-500/20 dark:bg-amber-500/10 dark:text-amber-400"
|
|
|
+ >Potencial: R$ 81.004</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="flex gap-2 border-b border-slate-200 bg-slate-50/50 p-4 dark:border-slate-800 dark:bg-slate-900"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ class="rounded-lg border border-slate-300 bg-white px-4 py-1.5 text-sm font-medium text-slate-900 shadow-sm dark:border-slate-700 dark:bg-[#1e293b] dark:text-white dark:shadow-none"
|
|
|
+ >Todas (22)</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ class="rounded-lg border border-transparent bg-transparent px-4 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 dark:text-slate-400 dark:hover:border-slate-700 dark:hover:text-white"
|
|
|
+ >Pela vendedora (13)</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ class="rounded-lg border border-transparent bg-transparent px-4 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 dark:text-slate-400 dark:hover:border-slate-700 dark:hover:text-white"
|
|
|
+ >Pelo cliente (9)</button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex-1 space-y-4 overflow-y-auto p-4">
|
|
|
+ {#each mockPriorityQueue as item}
|
|
|
+ <div
|
|
|
+ class="group relative overflow-hidden rounded-lg border border-red-200 bg-white p-4 shadow-sm transition-colors hover:border-red-300 dark:border-red-500/30 dark:bg-slate-900 dark:shadow-none dark:hover:border-red-500/60"
|
|
|
+ >
|
|
|
+ <div class="absolute top-0 bottom-0 left-0 w-1 bg-red-500"></div>
|
|
|
+ <div class="mb-3 flex items-start justify-between">
|
|
|
+ <div>
|
|
|
+ <div class="mb-1 flex items-center gap-2">
|
|
|
+ <span class="font-bold text-slate-900 dark:text-white"
|
|
|
+ >#{item.id} {item.customerName}</span
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="rounded border border-slate-200 bg-slate-100 px-2 py-0.5 text-[10px] font-bold text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300"
|
|
|
+ >{item.segment}</span
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="rounded border border-red-100 bg-red-50 px-2 py-0.5 text-[10px] font-bold text-red-600 dark:border-transparent dark:bg-red-500/20 dark:text-red-400"
|
|
|
+ >{item.status}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-slate-600 dark:text-slate-400">
|
|
|
+ Vendedora: <span class="font-medium text-slate-900 dark:text-slate-200"
|
|
|
+ >{item.sellerName}</span
|
|
|
+ >
|
|
|
+ · {item.lastMessage}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-right">
|
|
|
+ <div class="mb-1 text-xs font-medium text-red-600 dark:text-red-400">
|
|
|
+ {item.slaStatus}
|
|
|
+ </div>
|
|
|
+ <div class="text-xs text-slate-500">{item.timeAgo}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="mb-3 flex items-center gap-2 rounded-md bg-slate-50 p-2 text-sm text-slate-600 dark:bg-transparent dark:p-0 dark:text-slate-300"
|
|
|
+ >
|
|
|
+ <Activity size={14} class="shrink-0 text-slate-400 dark:text-slate-500" />
|
|
|
+ <span>Motivo: <span class="font-medium">{item.motive}</span></span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center justify-between text-xs">
|
|
|
+ <div class="flex gap-4">
|
|
|
+ <span class="text-slate-500 dark:text-slate-400"
|
|
|
+ >Impacto: <span class="font-bold text-red-600 dark:text-red-400"
|
|
|
+ >R$ {item.impact.toLocaleString('pt-BR')}</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ <span class="text-slate-500 dark:text-slate-400"
|
|
|
+ >Ticket: <span class="font-medium text-slate-900 dark:text-slate-200"
|
|
|
+ >R$ {item.ticket}</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ <span class="text-slate-500 dark:text-slate-400"
|
|
|
+ >Chance: <span class="font-bold text-emerald-600 dark:text-emerald-400"
|
|
|
+ >{item.chance}%</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button
|
|
|
+ class="rounded-md border border-slate-300 bg-white px-3 py-1.5 font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:shadow-none dark:hover:bg-slate-700"
|
|
|
+ >Sugerir mensagem</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ class="rounded-md border border-emerald-200 bg-emerald-50 px-3 py-1.5 font-medium text-emerald-700 transition-colors hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-400 dark:hover:bg-emerald-500/20"
|
|
|
+ >Resolver</button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {/each}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Radar Chart / Humor da base -->
|
|
|
+ <div
|
|
|
+ class="flex h-[500px] flex-col rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
|
|
|
+ >
|
|
|
+ <div class="mb-6 flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div
|
|
|
+ class="flex h-8 w-8 items-center justify-center rounded-lg border border-indigo-100 bg-indigo-50 text-indigo-600 dark:border-transparent dark:bg-indigo-500/10 dark:text-indigo-400"
|
|
|
+ >
|
|
|
+ <Activity size={18} />
|
|
|
+ </div>
|
|
|
+ <h2 class="text-base font-bold text-slate-900 dark:text-white">Humor da Base</h2>
|
|
|
+ </div>
|
|
|
+ <span
|
|
|
+ class="rounded bg-slate-100 px-2 py-1 text-xs font-medium text-slate-600 dark:bg-slate-800 dark:text-slate-400"
|
|
|
+ >Hoje</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="relative flex h-full w-full flex-1 items-center justify-center">
|
|
|
+ <!-- Pure SVG Data-driven Radar Chart -->
|
|
|
+ <svg viewBox="0 0 200 200" class="h-auto w-full max-w-[300px] overflow-visible">
|
|
|
+ <defs>
|
|
|
+ <linearGradient id="radarArea" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
|
+ <stop offset="0%" stop-color="#10b981" stop-opacity="0.4" />
|
|
|
+ <stop offset="100%" stop-color="#3b82f6" stop-opacity="0.1" />
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+
|
|
|
+ <!-- Background Grid -->
|
|
|
+ {#each gridLevels as points, i}
|
|
|
+ <polygon
|
|
|
+ {points}
|
|
|
+ class="fill-none stroke-slate-300 dark:stroke-slate-700"
|
|
|
+ stroke-width={i === 4 ? '1.5' : '0.5'}
|
|
|
+ stroke-dasharray={i === 4 ? 'none' : '2 2'}
|
|
|
+ />
|
|
|
+ {/each}
|
|
|
|
|
|
- <!-- KPIs -->
|
|
|
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4">
|
|
|
- {#each kpis as kpi}
|
|
|
- {@const Icon = kpi.icon}
|
|
|
- <div class="bg-white dark:bg-[#1e293b] p-5 rounded-xl border {kpi.border} hover:border-indigo-200 dark:hover:border-slate-600 transition-colors shadow-sm duration-200">
|
|
|
- <div class="flex items-start gap-4">
|
|
|
- <div class="w-10 h-10 rounded-lg {kpi.bg} {kpi.color} flex items-center justify-center shrink-0">
|
|
|
- <Icon size={20} strokeWidth={2.5} />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <div class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-1">{kpi.title}</div>
|
|
|
- <div class="text-2xl font-bold text-slate-900 dark:text-white mb-0.5">{kpi.value}</div>
|
|
|
- <div class="text-xs text-slate-500">{kpi.subvalue}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/each}
|
|
|
- </div>
|
|
|
+ <!-- Axes -->
|
|
|
+ {#each radarPoints as point}
|
|
|
+ <line
|
|
|
+ x1={radarCenter}
|
|
|
+ y1={radarCenter}
|
|
|
+ x2={point.axisX}
|
|
|
+ y2={point.axisY}
|
|
|
+ class="stroke-slate-300 dark:stroke-slate-700"
|
|
|
+ stroke-width="1"
|
|
|
+ />
|
|
|
+ <!-- Labels -->
|
|
|
+ <text
|
|
|
+ x={point.labelX}
|
|
|
+ y={point.labelY}
|
|
|
+ text-anchor="middle"
|
|
|
+ dominant-baseline="middle"
|
|
|
+ class="fill-slate-500 text-[8px] font-medium dark:fill-slate-400"
|
|
|
+ >
|
|
|
+ {point.name}
|
|
|
+ </text>
|
|
|
+ <!-- Value Labels -->
|
|
|
+ <text
|
|
|
+ x={point.labelX}
|
|
|
+ y={point.labelY + 12}
|
|
|
+ text-anchor="middle"
|
|
|
+ dominant-baseline="middle"
|
|
|
+ class="fill-slate-900 text-[9px] font-bold dark:fill-white"
|
|
|
+ >
|
|
|
+ {point.value}%
|
|
|
+ </text>
|
|
|
+ {/each}
|
|
|
|
|
|
- <!-- Main Content Area -->
|
|
|
- <div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
|
|
- <!-- Fila Priorizada / Alertas -->
|
|
|
- <div class="xl:col-span-2 bg-white dark:bg-[#1e293b] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden flex flex-col h-[500px] transition-colors duration-200">
|
|
|
- <div class="p-4 border-b border-slate-200 dark:border-slate-800 flex justify-between items-center bg-slate-50 dark:bg-[#1e293b]">
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <AlertTriangle size={18} class="text-amber-600 dark:text-amber-500" />
|
|
|
- <h2 class="text-base font-bold text-slate-900 dark:text-white">Fila Priorizada • Conversas Inacabadas</h2>
|
|
|
- </div>
|
|
|
- <div class="flex gap-2">
|
|
|
- <span class="px-2.5 py-1 rounded-md bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400 text-xs font-medium border border-red-200 dark:border-red-500/20">22 fora da janela</span>
|
|
|
- <span class="px-2.5 py-1 rounded-md bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 text-xs font-medium border border-amber-200 dark:border-amber-500/20">Potencial: R$ 81.004</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="p-4 bg-slate-50/50 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 flex gap-2">
|
|
|
- <button class="px-4 py-1.5 bg-white dark:bg-[#1e293b] text-slate-900 dark:text-white text-sm font-medium rounded-lg border border-slate-300 dark:border-slate-700 shadow-sm dark:shadow-none">Todas (22)</button>
|
|
|
- <button class="px-4 py-1.5 bg-transparent text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white text-sm font-medium rounded-lg border border-transparent hover:border-slate-300 dark:hover:border-slate-700 transition-colors">Pela vendedora (13)</button>
|
|
|
- <button class="px-4 py-1.5 bg-transparent text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white text-sm font-medium rounded-lg border border-transparent hover:border-slate-300 dark:hover:border-slate-700 transition-colors">Pelo cliente (9)</button>
|
|
|
- </div>
|
|
|
+ <!-- Data Polygon -->
|
|
|
+ <polygon
|
|
|
+ points={radarPath}
|
|
|
+ class="origin-center fill-[url(#radarArea)] stroke-emerald-500 drop-shadow-md transition-all duration-700 ease-out hover:scale-105 dark:stroke-emerald-400"
|
|
|
+ stroke-width="2.5"
|
|
|
+ />
|
|
|
|
|
|
- <div class="flex-1 overflow-y-auto p-4 space-y-4">
|
|
|
- {#each mockPriorityQueue as item}
|
|
|
- <div class="p-4 rounded-lg bg-white dark:bg-slate-900 border border-red-200 dark:border-red-500/30 relative overflow-hidden group hover:border-red-300 dark:hover:border-red-500/60 shadow-sm dark:shadow-none transition-colors">
|
|
|
- <div class="absolute left-0 top-0 bottom-0 w-1 bg-red-500"></div>
|
|
|
- <div class="flex justify-between items-start mb-3">
|
|
|
- <div>
|
|
|
- <div class="flex items-center gap-2 mb-1">
|
|
|
- <span class="font-bold text-slate-900 dark:text-white">#{item.id} {item.customerName}</span>
|
|
|
- <span class="px-2 py-0.5 rounded text-[10px] font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700">{item.segment}</span>
|
|
|
- <span class="px-2 py-0.5 rounded text-[10px] font-bold bg-red-50 dark:bg-red-500/20 text-red-600 dark:text-red-400 border border-red-100 dark:border-transparent">{item.status}</span>
|
|
|
- </div>
|
|
|
- <div class="text-sm text-slate-600 dark:text-slate-400">Vendedora: <span class="text-slate-900 dark:text-slate-200 font-medium">{item.sellerName}</span> · {item.lastMessage}</div>
|
|
|
- </div>
|
|
|
- <div class="text-right">
|
|
|
- <div class="text-xs text-red-600 dark:text-red-400 font-medium mb-1">{item.slaStatus}</div>
|
|
|
- <div class="text-xs text-slate-500">{item.timeAgo}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-2 text-sm text-slate-600 dark:text-slate-300 mb-3 bg-slate-50 dark:bg-transparent p-2 dark:p-0 rounded-md">
|
|
|
- <Activity size={14} class="text-slate-400 dark:text-slate-500 shrink-0" />
|
|
|
- <span>Motivo: <span class="font-medium">{item.motive}</span></span>
|
|
|
- </div>
|
|
|
- <div class="flex items-center justify-between text-xs">
|
|
|
- <div class="flex gap-4">
|
|
|
- <span class="text-slate-500 dark:text-slate-400">Impacto: <span class="text-red-600 dark:text-red-400 font-bold">R$ {item.impact.toLocaleString('pt-BR')}</span></span>
|
|
|
- <span class="text-slate-500 dark:text-slate-400">Ticket: <span class="text-slate-900 dark:text-slate-200 font-medium">R$ {item.ticket}</span></span>
|
|
|
- <span class="text-slate-500 dark:text-slate-400">Chance: <span class="text-emerald-600 dark:text-emerald-400 font-bold">{item.chance}%</span></span>
|
|
|
- </div>
|
|
|
- <div class="flex gap-2">
|
|
|
- <button class="px-3 py-1.5 bg-white dark:bg-slate-800 hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 rounded-md transition-colors font-medium border border-slate-300 dark:border-slate-700 shadow-sm dark:shadow-none">Sugerir mensagem</button>
|
|
|
- <button class="px-3 py-1.5 bg-emerald-50 dark:bg-emerald-500/10 hover:bg-emerald-100 dark:hover:bg-emerald-500/20 text-emerald-700 dark:text-emerald-400 rounded-md transition-colors font-medium border border-emerald-200 dark:border-emerald-500/20">Resolver</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/each}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <!-- Data Points -->
|
|
|
+ {#each radarPoints as point}
|
|
|
+ <circle
|
|
|
+ cx={point.x}
|
|
|
+ cy={point.y}
|
|
|
+ r="3"
|
|
|
+ class="hover:r-[5] fill-white stroke-emerald-500 transition-all duration-300 dark:fill-slate-900 dark:stroke-emerald-400"
|
|
|
+ stroke-width="2"
|
|
|
+ >
|
|
|
+ <title>{point.name}: {point.value}%</title>
|
|
|
+ </circle>
|
|
|
+ {/each}
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Radar Chart / Humor da base -->
|
|
|
- <div class="bg-white dark:bg-[#1e293b] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm p-4 flex flex-col h-[500px] transition-colors duration-200">
|
|
|
- <div class="flex justify-between items-center mb-6">
|
|
|
- <div class="flex items-center gap-2">
|
|
|
- <div class="w-8 h-8 rounded-lg bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 flex items-center justify-center border border-indigo-100 dark:border-transparent">
|
|
|
- <Activity size={18} />
|
|
|
- </div>
|
|
|
- <h2 class="text-base font-bold text-slate-900 dark:text-white">Humor da Base</h2>
|
|
|
- </div>
|
|
|
- <span class="text-xs font-medium text-slate-600 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded">Hoje</span>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="flex-1 w-full h-full relative flex items-center justify-center">
|
|
|
- <!-- Pure SVG Data-driven Radar Chart -->
|
|
|
- <svg viewBox="0 0 200 200" class="w-full max-w-[300px] h-auto overflow-visible">
|
|
|
- <defs>
|
|
|
- <linearGradient id="radarArea" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
|
- <stop offset="0%" stop-color="#10b981" stop-opacity="0.4" />
|
|
|
- <stop offset="100%" stop-color="#3b82f6" stop-opacity="0.1" />
|
|
|
- </linearGradient>
|
|
|
- </defs>
|
|
|
-
|
|
|
- <!-- Background Grid -->
|
|
|
- {#each gridLevels as points, i}
|
|
|
- <polygon
|
|
|
- {points}
|
|
|
- class="stroke-slate-300 dark:stroke-slate-700 fill-none"
|
|
|
- stroke-width={i === 4 ? "1.5" : "0.5"}
|
|
|
- stroke-dasharray={i === 4 ? "none" : "2 2"}
|
|
|
- />
|
|
|
- {/each}
|
|
|
+ <!-- Secondary Charts Row -->
|
|
|
+ <div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
|
+ <!-- Volume over time -->
|
|
|
+ <div
|
|
|
+ class="flex h-[400px] flex-col rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
|
|
|
+ >
|
|
|
+ <div class="mb-6 flex items-center justify-between">
|
|
|
+ <h2 class="text-base font-bold text-slate-900 dark:text-white">Volume por Canal</h2>
|
|
|
+ <select
|
|
|
+ class="rounded-lg border border-slate-300 bg-slate-50 px-2 py-1 text-xs text-slate-900 focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200"
|
|
|
+ >
|
|
|
+ <option>Por Hora</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Axes -->
|
|
|
- {#each radarPoints as point}
|
|
|
- <line
|
|
|
- x1={radarCenter}
|
|
|
- y1={radarCenter}
|
|
|
- x2={point.axisX}
|
|
|
- y2={point.axisY}
|
|
|
- class="stroke-slate-300 dark:stroke-slate-700"
|
|
|
- stroke-width="1"
|
|
|
- />
|
|
|
- <!-- Labels -->
|
|
|
- <text
|
|
|
- x={point.labelX}
|
|
|
- y={point.labelY}
|
|
|
- text-anchor="middle"
|
|
|
- dominant-baseline="middle"
|
|
|
- class="text-[8px] font-medium fill-slate-500 dark:fill-slate-400"
|
|
|
- >
|
|
|
- {point.name}
|
|
|
- </text>
|
|
|
- <!-- Value Labels -->
|
|
|
- <text
|
|
|
- x={point.labelX}
|
|
|
- y={point.labelY + 12}
|
|
|
- text-anchor="middle"
|
|
|
- dominant-baseline="middle"
|
|
|
- class="text-[9px] font-bold fill-slate-900 dark:fill-white"
|
|
|
- >
|
|
|
- {point.value}%
|
|
|
- </text>
|
|
|
- {/each}
|
|
|
+ <div class="h-full w-full flex-1">
|
|
|
+ <Chart
|
|
|
+ data={mockVolumeData}
|
|
|
+ x="date"
|
|
|
+ y="whatsapp"
|
|
|
+ yDomain={[0, 250]}
|
|
|
+ padding={{ top: 10, right: 10, bottom: 20, left: 30 }}
|
|
|
+ >
|
|
|
+ <Svg>
|
|
|
+ <Axis
|
|
|
+ placement="left"
|
|
|
+ grid={{ class: 'stroke-slate-200 dark:stroke-slate-700', strokeDasharray: '2 2' }}
|
|
|
+ class="fill-slate-500 text-xs dark:fill-slate-400"
|
|
|
+ />
|
|
|
+ <Axis
|
|
|
+ placement="bottom"
|
|
|
+ format={(d) => format(d, 'HH:mm')}
|
|
|
+ class="fill-slate-500 text-xs dark:fill-slate-400"
|
|
|
+ />
|
|
|
+ <Spline stroke="#38bdf8" strokeWidth={2} />
|
|
|
+ <Highlight
|
|
|
+ points={{
|
|
|
+ fill: '#38bdf8',
|
|
|
+ class: 'stroke-white dark:stroke-slate-900',
|
|
|
+ strokeWidth: 2
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Svg>
|
|
|
+ </Chart>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Data Polygon -->
|
|
|
- <polygon
|
|
|
- points={radarPath}
|
|
|
- class="fill-[url(#radarArea)] stroke-emerald-500 dark:stroke-emerald-400 drop-shadow-md origin-center transition-all duration-700 ease-out hover:scale-105"
|
|
|
- stroke-width="2.5"
|
|
|
- />
|
|
|
+ <!-- Sentiment and Aspects distribution -->
|
|
|
+ <div
|
|
|
+ class="flex h-[400px] flex-col rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition-colors duration-200 dark:border-slate-800 dark:bg-[#1e293b]"
|
|
|
+ >
|
|
|
+ <div class="mb-6 flex items-center justify-between">
|
|
|
+ <h2 class="text-base font-bold text-slate-900 dark:text-white">
|
|
|
+ {drilldownTitle}
|
|
|
+ </h2>
|
|
|
+ {#if selectedAspectDrilldown}
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onclick={clearAspectDrilldown}
|
|
|
+ class="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
|
|
|
+ >
|
|
|
+ Voltar
|
|
|
+ </button>
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Data Points -->
|
|
|
- {#each radarPoints as point}
|
|
|
- <circle
|
|
|
- cx={point.x}
|
|
|
- cy={point.y}
|
|
|
- r="3"
|
|
|
- class="fill-white dark:fill-slate-900 stroke-emerald-500 dark:stroke-emerald-400 transition-all duration-300 hover:r-[5]"
|
|
|
- stroke-width="2"
|
|
|
- >
|
|
|
- <title>{point.name}: {point.value}%</title>
|
|
|
- </circle>
|
|
|
- {/each}
|
|
|
- </svg>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- Secondary Charts Row -->
|
|
|
- <div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
|
|
- <!-- Volume over time -->
|
|
|
- <div class="bg-white dark:bg-[#1e293b] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm p-4 flex flex-col h-[400px] transition-colors duration-200">
|
|
|
- <div class="flex justify-between items-center mb-6">
|
|
|
- <h2 class="text-base font-bold text-slate-900 dark:text-white">Volume por Canal</h2>
|
|
|
- <select class="bg-slate-50 dark:bg-slate-900 border border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 text-xs rounded-lg px-2 py-1 focus:outline-none focus:border-indigo-500">
|
|
|
- <option>Por Hora</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="flex-1 w-full h-full">
|
|
|
- <Chart data={mockVolumeData} x="date" y="whatsapp" yDomain={[0, 250]} padding={{ top: 10, right: 10, bottom: 20, left: 30 }}>
|
|
|
- <Svg>
|
|
|
- <Axis placement="left" grid={{ class: 'stroke-slate-200 dark:stroke-slate-700', strokeDasharray: '2 2' }} class="text-xs fill-slate-500 dark:fill-slate-400" />
|
|
|
- <Axis
|
|
|
- placement="bottom"
|
|
|
- format={(d) => format(d, 'HH:mm')}
|
|
|
- class="text-xs fill-slate-500 dark:fill-slate-400"
|
|
|
- />
|
|
|
- <Spline stroke="#38bdf8" strokeWidth={2} />
|
|
|
- <Highlight points={{ fill: '#38bdf8', class: 'stroke-white dark:stroke-slate-900', strokeWidth: 2 }} />
|
|
|
- </Svg>
|
|
|
- </Chart>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- Sentiment and Aspects distribution -->
|
|
|
- <div class="bg-white dark:bg-[#1e293b] rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm p-4 flex flex-col h-[400px] transition-colors duration-200">
|
|
|
- <h2 class="text-base font-bold text-slate-900 dark:text-white mb-6">Distribuição de Aspectos</h2>
|
|
|
-
|
|
|
- <div class="flex-1 w-full h-full relative overflow-y-auto pr-2 custom-scrollbar">
|
|
|
- <div class="space-y-4">
|
|
|
- {#each mockAspectsData as aspect}
|
|
|
- <div>
|
|
|
- <div class="flex justify-between text-xs text-slate-600 dark:text-slate-300 mb-1">
|
|
|
- <span class="font-medium">{aspect.aspect}</span>
|
|
|
- <span>{aspect.positive + aspect.negative} interações</span>
|
|
|
- </div>
|
|
|
- <div class="h-6 w-full bg-slate-100 dark:bg-slate-800 rounded overflow-hidden flex shadow-inner">
|
|
|
- <div
|
|
|
- class="h-full bg-emerald-500"
|
|
|
- style="width: {(aspect.positive / (aspect.positive + aspect.negative)) * 100}%"
|
|
|
- ></div>
|
|
|
- <div
|
|
|
- class="h-full bg-red-500"
|
|
|
- style="width: {(aspect.negative / (aspect.positive + aspect.negative)) * 100}%"
|
|
|
- ></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- {/each}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="custom-scrollbar relative h-full w-full flex-1 overflow-y-auto pr-2">
|
|
|
+ {#if !selectedAspectDrilldown}
|
|
|
+ <div class="space-y-4">
|
|
|
+ {#each mockAspectsData as aspect}
|
|
|
+ <div>
|
|
|
+ <div class="mb-1 flex justify-between text-xs text-slate-600 dark:text-slate-300">
|
|
|
+ <span class="font-medium">{aspect.aspect}</span>
|
|
|
+ <span>{aspect.positive + aspect.negative} interações</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="flex h-7 w-full overflow-hidden rounded bg-slate-100 shadow-inner dark:bg-slate-800"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="h-full bg-emerald-500 transition-opacity hover:opacity-90"
|
|
|
+ style="width: {(aspect.positive / (aspect.positive + aspect.negative)) * 100}%"
|
|
|
+ onclick={() => openAspectDrilldown(aspect.aspect, 'positive')}
|
|
|
+ aria-label={`Ver pontos positivos de ${aspect.aspect}`}
|
|
|
+ ></button>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="h-full bg-red-500 transition-opacity hover:opacity-90"
|
|
|
+ style="width: {(aspect.negative / (aspect.positive + aspect.negative)) * 100}%"
|
|
|
+ onclick={() => openAspectDrilldown(aspect.aspect, 'negative')}
|
|
|
+ aria-label={`Ver pontos negativos de ${aspect.aspect}`}
|
|
|
+ ></button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {/each}
|
|
|
+ </div>
|
|
|
+ {:else}
|
|
|
+ <div class="space-y-3">
|
|
|
+ {#each drilldownItems as item}
|
|
|
+ <div>
|
|
|
+ <div class="mb-1 flex justify-between text-xs text-slate-600 dark:text-slate-300">
|
|
|
+ <span class="font-medium">{item.label}</span>
|
|
|
+ <span>{item.value}</span>
|
|
|
+ </div>
|
|
|
+ <div class="h-5 w-full rounded bg-slate-100 dark:bg-slate-800">
|
|
|
+ <div
|
|
|
+ class="h-full rounded {selectedAspectDrilldown.tone === 'positive'
|
|
|
+ ? 'bg-emerald-500'
|
|
|
+ : 'bg-red-500'}"
|
|
|
+ style="width: {(item.value / maxDrilldownValue) * 100}%"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {/each}
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|