"use client" import { useMemo } from "react" import { useQuery } from "convex/react" import { IconClockHour4, IconTrendingDown, IconTrendingUp } from "@tabler/icons-react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { useAuth } from "@/lib/auth-client" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" function formatMinutes(value: number | null) { if (value === null) return "—" if (value < 60) return `${Math.round(value)} min` const hours = Math.floor(value / 60) const minutes = Math.round(value % 60) if (minutes === 0) return `${hours}h` return `${hours}h ${minutes}min` } export function SectionCards() { const { session, convexUserId, isStaff } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const dashboardEnabled = Boolean(isStaff && convexUserId) const dashboard = useQuery( api.reports.dashboardOverview, dashboardEnabled ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" ) const trendInfo = useMemo(() => { if (!dashboard?.newTickets) return { value: null, label: "Aguardando dados", icon: IconTrendingUp } const trend = dashboard.newTickets.trendPercentage if (trend === null) { return { value: null, label: "Sem histórico", icon: IconTrendingUp } } const positive = trend >= 0 const icon = positive ? IconTrendingUp : IconTrendingDown const label = `${positive ? "+" : ""}${trend.toFixed(1)}%` return { value: trend, label, icon } }, [dashboard]) const responseDelta = useMemo(() => { if (!dashboard?.firstResponse) return { delta: null, label: "Sem dados", positive: false } const delta = dashboard.firstResponse.deltaMinutes if (delta === null) return { delta: null, label: "Sem comparação", positive: false } const positive = delta <= 0 const value = `${delta > 0 ? "+" : ""}${Math.round(delta)} min` return { delta, label: value, positive } }, [dashboard]) const TrendIcon = trendInfo.icon const resolutionInfo = useMemo(() => { if (!dashboard?.resolution) { return { positive: true, badgeLabel: "Sem histórico", rateLabel: "Taxa indisponível", } } const current = dashboard.resolution.resolvedLast7d ?? 0 const previous = dashboard.resolution.previousResolved ?? 0 const deltaPercentage = dashboard.resolution.deltaPercentage ?? null const positive = deltaPercentage !== null ? deltaPercentage >= 0 : current >= previous const badgeLabel = deltaPercentage !== null ? `${deltaPercentage >= 0 ? "+" : ""}${deltaPercentage.toFixed(1)}%` : previous > 0 ? `${current - previous >= 0 ? "+" : ""}${current - previous}` : "Sem histórico" const rateLabel = dashboard.resolution.rate !== null ? `${dashboard.resolution.rate.toFixed(1)}% dos tickets foram resolvidos` : "Taxa indisponível" return { positive, badgeLabel, rateLabel } }, [dashboard]) return (
Tickets novos {dashboard ? dashboard.newTickets.last24h : } {trendInfo.label}
{trendInfo.value === null ? "Aguardando histórico" : trendInfo.value >= 0 ? "Volume acima do período anterior" : "Volume abaixo do período anterior"}
Comparação com as 24h anteriores.
Tempo médio da 1ª resposta {dashboard ? formatMinutes(dashboard.firstResponse.averageMinutes) : } {responseDelta.delta !== null && responseDelta.delta > 0 ? ( ) : ( )} {responseDelta.label} {dashboard ? `${dashboard.firstResponse.responsesCount} tickets com primeira resposta` : "Carregando amostra"} Média móvel dos últimos 7 dias. Tickets aguardando ação {dashboard ? dashboard.awaitingAction.total : } {dashboard ? `${dashboard.awaitingAction.atRisk} em risco` : "—"} Inclui status "Novo", "Aberto" e "Em espera". Atrasos calculados com base no prazo de SLA. Tickets resolvidos (7 dias) {dashboard ? dashboard.resolution.resolvedLast7d : } {resolutionInfo.positive ? ( ) : ( )} {resolutionInfo.badgeLabel} {resolutionInfo.rateLabel} Comparação com os 7 dias anteriores.
) }