import { useState, useEffect, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Calendar, CalendarDays, AlertTriangle, Clock, Zap, RefreshCw, XCircle, FolderKanban, TrendingUp, FileText, Phone, MessageSquare, CreditCard, Coffee, TestTube, ArrowUpRight, Ticket, CheckCircle2, Timer, Sparkles, LayoutDashboard, Activity, Target, } from 'lucide-react' // Types interface Evento { titulo: string hora: string data?: string tipo: 'personal' | 'work' link: string } interface Tarefa { id: number name: string projeto: string dias?: number dias_atraso?: number } interface Lead { id: number name: string company: string source: string dias: number dias_sem_contacto?: number } interface Projeto { id: number name: string cliente: string total: number concluidas: number } interface Cliente360 { client_name: string desk_project_id: number total_invoiced: number total_delivered: number balance: number status: 'credit' | 'debt' | 'ok' } interface DashboardData { data_formatada: string is_monday: boolean eventos_hoje: Evento[] eventos_semana: Evento[] monday_mood: Tarefa[] urgente: Tarefa[] alta: Tarefa[] vencidas: Tarefa[] em_testes: Tarefa[] esta_semana: Tarefa[] tickets: { ticketid: number; subject: string }[] proposta: Lead[] contactar: Lead[] followup: Lead[] projectos: Projeto[] billing_360: Cliente360[] resumo: { tarefas: number tickets: number projectos: number leads: number horas: number horas_pct: number pipeline_valor: number } } // Animation variants const containerVariants = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.06 } } } const itemVariants = { hidden: { opacity: 0, y: 20, scale: 0.95 }, show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring' as const, stiffness: 300, damping: 30 } } } // Sparkline Mini Chart const Sparkline = ({ data, color }: { data: number[], color: string }) => { const max = Math.max(...data) const min = Math.min(...data) const range = max - min || 1 const points = data.map((v, i) => `${(i / (data.length - 1)) * 100},${100 - ((v - min) / range) * 80}`).join(' ') return ( ) } // Hero Stat Card - Big impact numbers const HeroStat = ({ icon: Icon, label, value, sub, trend, sparkData, status, gradient, }: { icon: React.ElementType label: string value: string | number sub: string trend?: string sparkData?: number[] status?: 'ok' | 'warning' | 'critical' gradient: string }) => { const statusGlow = { ok: 'shadow-emerald-500/20', warning: 'shadow-amber-500/20', critical: 'shadow-red-500/20', } const trendColor = trend?.startsWith('+') ? 'text-emerald-400' : trend?.startsWith('-') ? 'text-red-400' : 'text-zinc-400' return ( {sparkData && } {label} {value} {trend && {trend}} {sub} ) } // Status Pill const StatusPill = ({ children, variant }: { children: React.ReactNode; variant: string }) => { const styles: Record = { project: 'bg-blue-500/20 text-blue-300 ring-blue-500/30', lead: 'bg-emerald-500/20 text-emerald-300 ring-emerald-500/30', followup: 'bg-violet-500/20 text-violet-300 ring-violet-500/30', overdue: 'bg-red-500/20 text-red-300 ring-red-500/30', testing: 'bg-cyan-500/20 text-cyan-300 ring-cyan-500/30', personal: 'bg-violet-500/20 text-violet-300 ring-violet-500/30', work: 'bg-amber-500/20 text-amber-300 ring-amber-500/30', credit: 'bg-emerald-500/20 text-emerald-300 ring-emerald-500/30', debt: 'bg-red-500/20 text-red-300 ring-red-500/30', ok: 'bg-amber-500/20 text-amber-300 ring-amber-500/30', days: 'bg-zinc-700/50 text-zinc-300 ring-zinc-600/30', } return ( {children} ) } // Glass Card with accent const GlassCard = ({ title, icon: Icon, count, value, accent, size = 'normal', children, }: { title: string icon: React.ElementType count?: number value?: string accent?: 'amber' | 'cyan' | 'violet' | 'emerald' | 'red' | 'blue' size?: 'normal' | 'tall' | 'wide' children: React.ReactNode }) => { const accentColors = { amber: 'from-amber-500/10 border-amber-500/30 hover:border-amber-500/50', cyan: 'from-cyan-500/10 border-cyan-500/30 hover:border-cyan-500/50', violet: 'from-violet-500/10 border-violet-500/30 hover:border-violet-500/50', emerald: 'from-emerald-500/10 border-emerald-500/30 hover:border-emerald-500/50', red: 'from-red-500/10 border-red-500/30 hover:border-red-500/50', blue: 'from-blue-500/10 border-blue-500/30 hover:border-blue-500/50', } const sizeClasses = { normal: '', tall: 'md:row-span-2', wide: 'md:col-span-2', } return ( {/* Glow effect on hover */} {title} {count !== undefined && ( {count} )} {value && ( {value} )} {children} ) } // List Item with hover effect const ListItem = ({ href, children, badges, }: { href: string children: React.ReactNode badges?: React.ReactNode }) => ( {children} {badges && {badges}} ) // Progress Ring const ProgressRing = ({ progress, size = 120 }: { progress: number; size?: number }) => { const strokeWidth = 8 const radius = (size - strokeWidth) / 2 const circumference = radius * 2 * Math.PI const offset = circumference - (progress / 100) * circumference return ( {progress}% completo ) } // Summary Widget - Bento style const SummaryWidget = ({ data }: { data: DashboardData['resumo'] }) => ( Resumo da Semana {[ { label: 'Tarefas', value: data.tarefas, icon: CheckCircle2, color: 'text-brand-400' }, { label: 'Tickets', value: data.tickets, icon: Ticket, color: 'text-amber-400' }, { label: 'Projectos', value: data.projectos, icon: FolderKanban, color: 'text-cyan-400' }, { label: 'Leads', value: data.leads, icon: TrendingUp, color: 'text-emerald-400' }, ].map((item) => ( {item.value} {item.label} ))} Horas trabalhadas {data.horas}h / 40h ) // Main App function App() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const fetchData = useCallback(async () => { setRefreshing(true) try { const response = await fetch('/api.php') if (!response.ok) throw new Error('Failed to fetch') const json = await response.json() setData(json) } catch { setData(getMockData()) } finally { setLoading(false) setRefreshing(false) } }, []) useEffect(() => { fetchData() }, [fetchData]) const getGreeting = () => { const hour = new Date().getHours() if (hour < 12) return 'Bom dia' if (hour < 19) return 'Boa tarde' return 'Boa noite' } if (loading) { return ( A preparar o dashboard... ) } if (!data) return null return ( {/* Header */} Plan EAL Descomplicar Dashboard SDK Dashboard Monitor {/* Main Content */} {/* Welcome */} {getGreeting()}, Emanuel {data.data_formatada} {/* Hero Stats - Bento Grid */} 0 ? `-${data.vencidas.length}` : undefined} sparkData={[3, 5, 4, 7, 6, 8, data.resumo.tarefas]} status={data.vencidas.length > 0 ? 'critical' : data.urgente.length > 0 ? 'warning' : 'ok'} gradient="from-zinc-800 to-zinc-900" /> 0 ? 'warning' : 'ok'} gradient="from-amber-900/30 to-zinc-900" /> {/* Cards Grid - Bento Layout */} {/* Agenda Hoje */} {data.eventos_hoje.length > 0 && ( {data.eventos_hoje.map((e, i) => ( {e.hora}}> {e.titulo} ))} )} {/* Restante Semana */} {data.eventos_semana.length > 0 && ( {data.eventos_semana.map((e, i) => ( {e.data}}> {e.titulo} ))} )} {/* Monday Mood */} {data.is_monday && data.monday_mood.length > 0 && ( {data.monday_mood.map((t) => ( {t.projeto}}> {t.name} ))} )} {/* Vencidas */} {data.vencidas.length > 0 && ( {data.vencidas.map((t) => ( -{t.dias_atraso}d {t.projeto} > } > {t.name} ))} )} {/* Urgente */} {data.urgente.length > 0 && ( {data.urgente.map((t) => ( {t.projeto}}> {t.name} ))} )} {/* Alta Prioridade */} {data.alta.length > 0 && ( {data.alta.map((t) => ( {t.projeto} {t.dias !== undefined && {t.dias >= 0 ? '+' : ''}{t.dias}d} > } > {t.name} ))} )} {/* Em Testes */} {data.em_testes.length > 0 && ( {data.em_testes.map((t) => ( {t.projeto}}> {t.name} ))} )} {/* Esta Semana */} {data.esta_semana.length > 0 && ( {data.esta_semana.map((t) => ( {t.projeto} +{t.dias}d > } > {t.name} ))} )} {/* Fazer Proposta */} {data.proposta.length > 0 && ( {data.proposta.map((l) => ( {l.source} {l.dias}d > } > {l.company || l.name} ))} )} {/* Contactar */} {data.contactar.length > 0 && ( {data.contactar.map((l) => ( {l.source} {l.dias}d > } > {l.company || l.name} ))} )} {/* FollowUp */} {data.followup.length > 0 && ( {data.followup.map((l) => ( {l.dias_sem_contacto}d sem contacto {l.source} > } > {l.company || l.name} ))} )} {/* Projectos Activos */} {data.projectos.length > 0 && ( {data.projectos.map((p) => ( {p.cliente} {p.concluidas}/{p.total} > } > {p.name} ))} )} {/* Clientes 360 */} {data.billing_360.length > 0 && ( {data.billing_360.map((b) => ( {b.client_name} {b.balance > 0 ? '+' : ''}{b.balance}h Fact: {b.total_invoiced}h · Entregue: {b.total_delivered}h ))} )} {/* Summary Widget */} {/* Footer */} ) } // Mock data function getMockData(): DashboardData { return { data_formatada: 'Segunda-feira, 3 de Fevereiro', is_monday: true, eventos_hoje: [ { titulo: 'Buscar Tomás', hora: '12:55', tipo: 'personal', link: '#' }, { titulo: 'Reunião Carstuff', hora: '15:00', tipo: 'work', link: '#' }, ], eventos_semana: [ { titulo: 'Entrega projecto SolarFV', data: 'Qua 05', hora: '10:00', tipo: 'work', link: '#' }, { titulo: 'Buscar Tomás', data: 'Sex 07', hora: '13:30', tipo: 'personal', link: '#' }, ], monday_mood: [ { id: 100, name: 'Revisão semanal de métricas', projeto: 'DES 360º' }, { id: 101, name: 'Planeamento sprint Q1', projeto: 'DES Stack Workflow' }, ], urgente: [], alta: [ { id: 1, name: 'Sistema métricas - tráfego e conversões', projeto: 'CTF Carstuff 360º', dias: 2 }, { id: 2, name: 'Sistema métricas - tráfego e conversões', projeto: 'SFV 360º', dias: 2 }, { id: 3, name: 'SDK MCP - Biblioteca de Desenvolvimento MCP', projeto: 'DES Stack Workflow', dias: 5 }, { id: 4, name: 'Skill /sdk - Gestão de SDKs e PDCA', projeto: 'DES Stack Workflow', dias: 5 }, { id: 5, name: 'Sistema de Gestão de Informação Descomplicar', projeto: 'DES Stack Workflow', dias: 7 }, { id: 6, name: 'SDK Dashboard - Biblioteca de Componentes', projeto: 'DES Stack Workflow', dias: 7 }, ], vencidas: [ { id: 7, name: 'Implementar plugin Catálogos PDF', projeto: 'CTF Carstuff 360º', dias_atraso: 3 }, ], em_testes: [ { id: 8, name: 'Decisão reCAPTCHA - reactivar ou manter honeypot', projeto: 'CTF Carstuff 360º' }, { id: 9, name: 'MCP Gateway - HTTP Streamable Proxy', projeto: 'DES Stack Workflow' }, { id: 10, name: 'MCP Gateway', projeto: 'DES Stack Workflow' }, ], esta_semana: [ { id: 11, name: 'Mapeamento de Dados', projeto: 'CTF Carstuff 360º', dias: 3 }, ], tickets: [], proposta: [ { id: 1, name: 'MegaSport', company: 'MegaSport Travel', source: 'Mail Mkt', dias: 71 }, ], contactar: [ { id: 2, name: 'DSI', company: 'DSI Credito DS Seguros', source: 'Google', dias: 6 }, { id: 3, name: 'Century', company: 'CENTURY 21 Via', source: 'Google', dias: 6 }, { id: 4, name: 'Rui', company: 'Rui Marques', source: 'Google', dias: 12 }, { id: 5, name: 'Entreescolha', company: 'Entreescolha', source: 'Google', dias: 12 }, ], followup: [ { id: 7, name: 'Grupo Criativo', company: 'Grupo Criativo', source: 'Google', dias: 12, dias_sem_contacto: 1 }, { id: 8, name: 'Casa Aleixo', company: 'Casa Aleixo Lda', source: 'Mail Mkt', dias: 15, dias_sem_contacto: 1 }, { id: 9, name: 'Hidraulicentro', company: 'Hidraulicentro, Lda', source: 'Mail Mkt', dias: 20, dias_sem_contacto: 1 }, ], projectos: [ { id: 1, name: 'DES 360º', cliente: 'DES - Marketing', total: 115, concluidas: 99 }, { id: 2, name: 'DES Stack Workflow', cliente: 'DES - Inovação', total: 225, concluidas: 55 }, { id: 3, name: 'SFV 360º', cliente: 'Solar FV', total: 19, concluidas: 10 }, { id: 4, name: 'CTF Carstuff 360º', cliente: 'CTF Carstuff', total: 21, concluidas: 5 }, ], billing_360: [ { client_name: 'Carstuff', desk_project_id: 67, total_invoiced: 150, total_delivered: 133.1, balance: 16.9, status: 'credit' }, { client_name: 'SolarFV', desk_project_id: 68, total_invoiced: 120, total_delivered: 72.4, balance: 47.6, status: 'credit' }, ], resumo: { tarefas: 7, tickets: 0, projectos: 4, leads: 8, horas: 22.8, horas_pct: 57, pipeline_valor: 12842, }, } } export default App
A preparar o dashboard...
Descomplicar Dashboard SDK
{data.data_formatada}