"use client"; import MoneyValues from "@/components/money-values"; import { ChartContainer, type ChartConfig } from "@/components/ui/chart"; import type { IncomeByCategoryData } from "@/lib/dashboard/categories/income-by-category"; import { getIconComponent } from "@/lib/utils/icons"; import { formatPeriodForUrl } from "@/lib/utils/period"; import { RiArrowDownLine, RiArrowUpLine, RiExternalLinkLine, RiListUnordered, RiPieChart2Line, RiPieChartLine, RiWallet3Line, } from "@remixicon/react"; import Link from "next/link"; import { useMemo, useState } from "react"; import { Pie, PieChart, Tooltip } from "recharts"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { WidgetEmptyState } from "../widget-empty-state"; type IncomeByCategoryWidgetWithChartProps = { data: IncomeByCategoryData; period: string; }; const buildInitials = (value: string) => { const parts = value.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) { return "CT"; } if (parts.length === 1) { const firstPart = parts[0]; return firstPart ? firstPart.slice(0, 2).toUpperCase() : "CT"; } const firstChar = parts[0]?.[0] ?? ""; const secondChar = parts[1]?.[0] ?? ""; return `${firstChar}${secondChar}`.toUpperCase() || "CT"; }; const formatPercentage = (value: number) => { return `${Math.abs(value).toFixed(1)}%`; }; const formatCurrency = (value: number) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(value); export function IncomeByCategoryWidgetWithChart({ data, period, }: IncomeByCategoryWidgetWithChartProps) { const [activeTab, setActiveTab] = useState<"list" | "chart">("list"); const periodParam = formatPeriodForUrl(period); // Configuração do chart com cores do CSS const chartConfig = useMemo(() => { const config: ChartConfig = {}; const colors = [ "var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)", "var(--chart-1)", "var(--chart-2)", ]; if (data.categories.length <= 7) { data.categories.forEach((category, index) => { config[category.categoryId] = { label: category.categoryName, color: colors[index % colors.length], }; }); } else { // Top 7 + Outros const top7 = data.categories.slice(0, 7); top7.forEach((category, index) => { config[category.categoryId] = { label: category.categoryName, color: colors[index % colors.length], }; }); config["outros"] = { label: "Outros", color: "var(--chart-6)", }; } return config; }, [data.categories]); // Preparar dados para o gráfico de pizza - Top 7 + Outros const chartData = useMemo(() => { if (data.categories.length <= 7) { return data.categories.map((category) => ({ category: category.categoryId, name: category.categoryName, value: category.currentAmount, percentage: category.percentageOfTotal, fill: chartConfig[category.categoryId]?.color, })); } // Pegar top 7 categorias const top7 = data.categories.slice(0, 7); const others = data.categories.slice(7); // Somar o restante const othersTotal = others.reduce((sum, cat) => sum + cat.currentAmount, 0); const othersPercentage = others.reduce( (sum, cat) => sum + cat.percentageOfTotal, 0 ); const top7Data = top7.map((category) => ({ category: category.categoryId, name: category.categoryName, value: category.currentAmount, percentage: category.percentageOfTotal, fill: chartConfig[category.categoryId]?.color, })); // Adicionar "Outros" se houver if (others.length > 0) { top7Data.push({ category: "outros", name: "Outros", value: othersTotal, percentage: othersPercentage, fill: chartConfig["outros"]?.color, }); } return top7Data; }, [data.categories, chartConfig]); if (data.categories.length === 0) { return ( } title="Nenhuma receita encontrada" description="Quando houver receitas registradas, elas aparecerão aqui." /> ); } return ( setActiveTab(v as "list" | "chart")} className="w-full" >
Lista Gráfico
{data.categories.map((category) => { const IconComponent = category.categoryIcon ? getIconComponent(category.categoryIcon) : null; const initials = buildInitials(category.categoryName); const hasIncrease = category.percentageChange !== null && category.percentageChange > 0; const hasDecrease = category.percentageChange !== null && category.percentageChange < 0; const hasBudget = category.budgetAmount !== null; const budgetExceeded = hasBudget && category.budgetUsedPercentage !== null && category.budgetUsedPercentage > 100; const exceededAmount = budgetExceeded && category.budgetAmount ? category.currentAmount - category.budgetAmount : 0; return (
{IconComponent ? ( ) : ( {initials} )}
{category.categoryName}
{formatPercentage(category.percentageOfTotal)} da receita total
{category.percentageChange !== null && ( {hasIncrease && } {hasDecrease && } {formatPercentage(category.percentageChange)} )}
{hasBudget && category.budgetUsedPercentage !== null && category.budgetAmount !== null && (
{budgetExceeded ? ( <> {formatPercentage(category.budgetUsedPercentage)} do limite {formatCurrency(category.budgetAmount)} - excedeu em {formatCurrency(exceededAmount)} ) : ( <> {formatPercentage(category.budgetUsedPercentage)} do limite {formatCurrency(category.budgetAmount)} )}
)}
); })}
formatPercentage(entry.percentage)} outerRadius={75} dataKey="value" nameKey="category" /> { if (active && payload && payload.length) { const data = payload[0].payload; return (
{data.name} {formatCurrency(data.value)} {formatPercentage(data.percentage)} do total
); } return null; }} />
{chartData.map((entry, index) => (
{entry.name}
))}
); }