"use client"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ChartContainer, ChartTooltip, type ChartConfig, } from "@/components/ui/chart"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { WidgetEmptyState } from "@/components/widget-empty-state"; import type { CategoryHistoryData } from "@/lib/dashboard/categories/category-history"; import { getIconComponent } from "@/lib/utils/icons"; import { RiArrowDownSLine, RiBarChartBoxLine, RiCloseLine, } from "@remixicon/react"; import { useEffect, useMemo, useState } from "react"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; type CategoryHistoryWidgetProps = { data: CategoryHistoryData; }; const STORAGE_KEY_SELECTED = "dashboard-category-history-selected"; // Vibrant colors for categories const CHART_COLORS = [ "#ef4444", // red-500 "#3b82f6", // blue-500 "#10b981", // emerald-500 "#f59e0b", // amber-500 "#8b5cf6", // violet-500 ]; export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) { const [selectedCategories, setSelectedCategories] = useState([]); const [isClient, setIsClient] = useState(false); const [open, setOpen] = useState(false); // Load selected categories from sessionStorage on mount useEffect(() => { setIsClient(true); const stored = sessionStorage.getItem(STORAGE_KEY_SELECTED); if (stored) { try { const parsed = JSON.parse(stored); if (Array.isArray(parsed)) { const validCategories = parsed.filter((id) => data.allCategories.some((cat) => cat.id === id) ); setSelectedCategories(validCategories.slice(0, 5)); } } catch (e) { // Invalid JSON, ignore } } }, [data.allCategories]); // Save to sessionStorage when selection changes useEffect(() => { if (isClient) { sessionStorage.setItem( STORAGE_KEY_SELECTED, JSON.stringify(selectedCategories) ); } }, [selectedCategories, isClient]); // Filter data to show only selected categories with vibrant colors const filteredCategories = useMemo(() => { return selectedCategories .map((id, index) => { const cat = data.categories.find((c) => c.id === id); if (!cat) return null; return { ...cat, color: CHART_COLORS[index % CHART_COLORS.length], }; }) .filter(Boolean) as Array<{ id: string; name: string; icon: string | null; color: string; data: Record; }>; }, [data.categories, selectedCategories]); // Filter chart data to include only selected categories const filteredChartData = useMemo(() => { if (filteredCategories.length === 0) { return data.chartData.map((item) => ({ month: item.month })); } return data.chartData.map((item) => { const filtered: Record = { month: item.month }; filteredCategories.forEach((category) => { filtered[category.name] = item[category.name] || 0; }); return filtered; }); }, [data.chartData, filteredCategories]); // Build chart config dynamically from filtered categories const chartConfig = useMemo(() => { const config: ChartConfig = {}; filteredCategories.forEach((category) => { config[category.name] = { label: category.name, color: category.color, }; }); return config; }, [filteredCategories]); const formatCurrency = (value: number) => { return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(value); }; const formatCurrencyCompact = (value: number) => { if (value >= 1000) { return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", minimumFractionDigits: 0, maximumFractionDigits: 0, notation: "compact", }).format(value); } return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; const handleAddCategory = (categoryId: string) => { if ( categoryId && !selectedCategories.includes(categoryId) && selectedCategories.length < 5 ) { setSelectedCategories([...selectedCategories, categoryId]); setOpen(false); } }; const handleRemoveCategory = (categoryId: string) => { setSelectedCategories(selectedCategories.filter((id) => id !== categoryId)); }; const handleClearAll = () => { setSelectedCategories([]); }; const availableCategories = useMemo(() => { return data.allCategories.filter( (cat) => !selectedCategories.includes(cat.id) ); }, [data.allCategories, selectedCategories]); const selectedCategoryDetails = useMemo(() => { return selectedCategories .map((id) => data.allCategories.find((cat) => cat.id === id)) .filter(Boolean); }, [selectedCategories, data.allCategories]); const isEmpty = filteredCategories.length === 0; // Group available categories by type const { despesaCategories, receitaCategories } = useMemo(() => { const despesa = availableCategories.filter((cat) => cat.type === "despesa"); const receita = availableCategories.filter((cat) => cat.type === "receita"); return { despesaCategories: despesa, receitaCategories: receita }; }, [availableCategories]); if (!isClient) { return null; } return (
{selectedCategoryDetails.length > 0 && (
{selectedCategoryDetails.map((category) => { if (!category) return null; const IconComponent = category.icon ? getIconComponent(category.icon) : null; const colorIndex = selectedCategories.indexOf(category.id); const color = CHART_COLORS[colorIndex % CHART_COLORS.length]; return (
{IconComponent ? ( ) : (
)} {category.name}
); })}
{selectedCategories.length}/5 selecionadas
)} {selectedCategories.length < 5 && availableCategories.length > 0 && ( Nenhuma categoria encontrada. {despesaCategories.length > 0 && ( {despesaCategories.map((category) => { const IconComponent = category.icon ? getIconComponent(category.icon) : null; return ( handleAddCategory(category.id)} className="gap-2" > {IconComponent ? ( ) : (
)} {category.name} ); })} )} {receitaCategories.length > 0 && ( {receitaCategories.map((category) => { const IconComponent = category.icon ? getIconComponent(category.icon) : null; return ( handleAddCategory(category.id)} className="gap-2" > {IconComponent ? ( ) : (
)} {category.name} ); })} )} )}
{isEmpty ? (
} title="Selecione categorias para visualizar" description="Escolha até 5 categorias para acompanhar o histórico dos últimos 8 meses, mês atual e próximo mês." />
) : ( {filteredCategories.map((category) => ( ))} { if (!active || !payload || payload.length === 0) { return null; } // Sort payload by value (descending) const sortedPayload = [...payload].sort( (a, b) => (b.value as number) - (a.value as number) ); return (
{payload[0].payload.month}
{sortedPayload .filter((entry) => (entry.value as number) > 0) .map((entry) => { const config = chartConfig[ entry.dataKey as keyof typeof chartConfig ]; const value = entry.value as number; return (
{config?.label}
{formatCurrency(value)}
); })}
); }} cursor={{ stroke: "hsl(var(--muted-foreground))", strokeWidth: 1, }} /> {filteredCategories.map((category) => ( ))} )} ); }