"use client" import * as React from "react" import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" import { useQuery } from "convex/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 { useIsMobile } from "@/hooks/use-mobile" import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart" import { cn, formatDateDM, formatDateDMY } from "@/lib/utils" import { Skeleton } from "@/components/ui/skeleton" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { usePersistentCompanyFilter } from "@/lib/use-company-filter" import { ToggleGroup, ToggleGroupItem, } from "@/components/ui/toggle-group" import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox" export const description = "Distribuição semanal de tickets por canal" type ChartRange = "7d" | "30d" | "90d" type ChartAreaInteractiveProps = { range?: ChartRange onRangeChange?: (value: ChartRange) => void companyId?: string onCompanyChange?: (value: string) => void hideControls?: boolean title?: string description?: React.ReactNode className?: string } export function ChartAreaInteractive({ range, onRangeChange, companyId, onCompanyChange, hideControls = false, title = "Entrada de tickets por canal", description: descriptionOverride, className, }: ChartAreaInteractiveProps = {}) { const [mounted, setMounted] = React.useState(false) const isMobile = useIsMobile() const [internalRange, setInternalRange] = React.useState(range ?? "7d") const timeRange = range ?? internalRange // Persistir seleção de empresa globalmente const [internalCompanyId, setInternalCompanyId] = usePersistentCompanyFilter("all") const selectedCompanyId = companyId ?? internalCompanyId const { session, convexUserId, isStaff } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID React.useEffect(() => { setMounted(true) }, []) React.useEffect(() => { if (!range && isMobile) { setInternalRange("7d") } }, [isMobile, range]) const handleRangeChange = (value: ChartRange) => { if (!value) return onRangeChange?.(value) if (!range) { setInternalRange(value) } } const handleCompanyChange = (value: string) => { onCompanyChange?.(value) if (!companyId) { setInternalCompanyId(value) } } const reportsEnabled = Boolean(isStaff && convexUserId) const report = useQuery( api.reports.ticketsByChannel, reportsEnabled ? ({ tenantId, viewerId: convexUserId as Id<"users">, range: timeRange, companyId: selectedCompanyId === "all" ? undefined : (selectedCompanyId as Id<"companies">), }) : "skip" ) const companies = useQuery( api.companies.list, reportsEnabled ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip" ) as Array<{ id: Id<"companies">; name: string }> | undefined const companyOptions = React.useMemo(() => { const base: SearchableComboboxOption[] = [{ value: "all", label: "Todas as empresas" }] if (!companies || companies.length === 0) { return base } const sorted = [...companies].sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) return [ base[0], ...sorted.map((company) => ({ value: company.id, label: company.name, })), ] }, [companies]) const channels = React.useMemo(() => report?.channels ?? [], [report]) const palette = React.useMemo( () => [ "var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)", ], [] ) const chartConfig = React.useMemo(() => { const entries = channels.map((channel: string, index: number) => [ channel, { label: channel .toLowerCase() .replace(/_/g, " ") .replace(/\b\w/g, (letter) => letter.toUpperCase()), color: palette[index % palette.length], }, ]) return Object.fromEntries(entries) as ChartConfig }, [channels, palette]) const chartData = React.useMemo(() => { if (!report?.points) return [] return report.points.map((point: { date: string; values: Record }) => { const entry: Record = { date: point.date } for (const channel of channels) { entry[channel] = point.values[channel] ?? 0 } return entry }) }, [channels, report]) if (!mounted) { return (
Carregando gráfico...
) } const defaultDescription = ( <> Distribuição dos canais nos últimos {timeRange.replace("d", " dias")} Período: {timeRange} ) return ( {title} {descriptionOverride ?? defaultDescription}
{!hideControls ? ( <> handleCompanyChange(next ?? "all")} options={companyOptions} placeholder="Todas as empresas" className="w-full min-w-56 sm:w-64" /> handleRangeChange((value as ChartRange) ?? timeRange)} variant="outline" className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex" > 90 dias 30 dias 7 dias ) : null}
{report === undefined ? (
) : chartData.length === 0 || channels.length === 0 ? (
Sem dados suficientes no período selecionado.
) : ( {channels.map((channel: string) => ( ))} formatDateDM(new Date(value))} /> formatDateDMY(new Date(value as string))} indicator="dot" /> } /> {channels .slice() .reverse() .map((channel: string) => ( ))} )}
) } export default ChartAreaInteractive