diff --git a/components/relatorios/cartoes/cards-overview.tsx b/components/relatorios/cartoes/cards-overview.tsx
index 5f0f156..c4f71f2 100644
--- a/components/relatorios/cartoes/cards-overview.tsx
+++ b/components/relatorios/cartoes/cards-overview.tsx
@@ -67,7 +67,7 @@ export function CardsOverview({ data }: CardsOverviewProps) {
const params = new URLSearchParams();
if (periodoParam) params.set("periodo", periodoParam);
params.set("cartao", cardId);
- return `/relatorios/cartoes?${params.toString()}`;
+ return `/relatorios/uso-cartoes?${params.toString()}`;
};
const summaryCards = [
@@ -140,7 +140,7 @@ export function CardsOverview({ data }: CardsOverviewProps) {
alt={card.name}
width={32}
height={32}
- className="rounded object-contain"
+ className="rounded-sm object-contain"
/>
) : (
diff --git a/components/relatorios/category-cell.tsx b/components/relatorios/category-cell.tsx
index 94fe252..dc9ec96 100644
--- a/components/relatorios/category-cell.tsx
+++ b/components/relatorios/category-cell.tsx
@@ -1,6 +1,11 @@
"use client";
import { RiArrowDownLine, RiArrowUpLine } from "@remixicon/react";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import { formatCurrency, formatPercentageChange } from "@/lib/relatorios/utils";
import { cn } from "@/lib/utils/ui";
@@ -22,25 +27,55 @@ export function CategoryCell({
? ((value - previousValue) / previousValue) * 100
: null;
+ const absoluteChange = !isFirstMonth ? value - previousValue : null;
+
const isIncrease = percentageChange !== null && percentageChange > 0;
const isDecrease = percentageChange !== null && percentageChange < 0;
return (
-
-
{formatCurrency(value)}
- {!isFirstMonth && percentageChange !== null && (
-
+
+
+
{formatCurrency(value)}
+ {!isFirstMonth && percentageChange !== null && (
+
+ {isIncrease && }
+ {isDecrease && }
+ {formatPercentageChange(percentageChange)}
+
)}
- >
- {isIncrease &&
}
- {isDecrease &&
}
-
{formatPercentageChange(percentageChange)}
- )}
-
+
+
+
+
{formatCurrency(value)}
+ {!isFirstMonth && absoluteChange !== null && (
+ <>
+
+ Mês anterior: {formatCurrency(previousValue)}
+
+
+ Diferença:{" "}
+ {absoluteChange >= 0
+ ? `+${formatCurrency(absoluteChange)}`
+ : formatCurrency(absoluteChange)}
+
+ >
+ )}
+
+
+
);
}
diff --git a/components/relatorios/category-report-cards.tsx b/components/relatorios/category-report-cards.tsx
index d470857..af37930 100644
--- a/components/relatorios/category-report-cards.tsx
+++ b/components/relatorios/category-report-cards.tsx
@@ -1,63 +1,161 @@
"use client";
-import { TypeBadge } from "@/components/type-badge";
+import Link from "next/link";
+import { useMemo } from "react";
+import { CategoryIconBadge } from "@/components/categorias/category-icon-badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import type { CategoryReportData } from "@/lib/relatorios/types";
+import type {
+ CategoryReportData,
+ CategoryReportItem,
+} from "@/lib/relatorios/types";
import { formatCurrency, formatPeriodLabel } from "@/lib/relatorios/utils";
-import { getIconComponent } from "@/lib/utils/icons";
+import { formatPeriodForUrl } from "@/lib/utils/period";
import { CategoryCell } from "./category-cell";
interface CategoryReportCardsProps {
data: CategoryReportData;
}
-export function CategoryReportCards({ data }: CategoryReportCardsProps) {
- const { categories, periods } = data;
+interface CategoryCardProps {
+ category: CategoryReportItem;
+ periods: string[];
+ colorIndex: number;
+}
+
+function CategoryCard({ category, periods, colorIndex }: CategoryCardProps) {
+ const periodParam = formatPeriodForUrl(periods[periods.length - 1]);
return (
-
- {categories.map((category) => {
- const Icon = category.icon ? getIconComponent(category.icon) : null;
+
+
+
+
+
+ {category.name}
+
+
+
+
+ {periods.map((period, periodIndex) => {
+ const monthData = category.monthlyData.get(period);
+ const isFirstMonth = periodIndex === 0;
- return (
-
-
-
- {Icon && }
- {category.name}
-
-
-
-
- {periods.map((period, periodIndex) => {
- const monthData = category.monthlyData.get(period);
- const isFirstMonth = periodIndex === 0;
+ return (
+
+
+ {formatPeriodLabel(period)}
+
+
+
+ );
+ })}
+
+ Total
+ {formatCurrency(category.total)}
+
+
+
+ );
+}
- return (
-
-
- {formatPeriodLabel(period)}
-
-
-
- );
- })}
-
- Total
- {formatCurrency(category.total)}
-
-
-
- );
- })}
+interface SectionProps {
+ title: string;
+ categories: CategoryReportItem[];
+ periods: string[];
+ colorIndexOffset: number;
+ total: number;
+}
+
+function Section({
+ title,
+ categories,
+ periods,
+ colorIndexOffset,
+ total,
+}: SectionProps) {
+ if (categories.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+ {title}
+
+
+ {formatCurrency(total)}
+
+
+ {categories.map((category, index) => (
+
+ ))}
+
+ );
+}
+
+export function CategoryReportCards({ data }: CategoryReportCardsProps) {
+ const { categories, periods } = data;
+
+ // Separate categories by type and calculate totals
+ const { receitas, despesas, receitasTotal, despesasTotal } = useMemo(() => {
+ const receitas: CategoryReportItem[] = [];
+ const despesas: CategoryReportItem[] = [];
+ let receitasTotal = 0;
+ let despesasTotal = 0;
+
+ for (const category of categories) {
+ if (category.type === "receita") {
+ receitas.push(category);
+ receitasTotal += category.total;
+ } else {
+ despesas.push(category);
+ despesasTotal += category.total;
+ }
+ }
+
+ return { receitas, despesas, receitasTotal, despesasTotal };
+ }, [categories]);
+
+ return (
+
+ {/* Despesas Section */}
+
+
+ {/* Receitas Section */}
+
);
}
diff --git a/components/relatorios/category-report-table.tsx b/components/relatorios/category-report-table.tsx
index c5b3803..6fae5d8 100644
--- a/components/relatorios/category-report-table.tsx
+++ b/components/relatorios/category-report-table.tsx
@@ -1,110 +1,49 @@
"use client";
-import {
- Table,
- TableBody,
- TableCell,
- TableFooter,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import type { CategoryReportData } from "@/lib/relatorios/types";
-import { formatCurrency, formatPeriodLabel } from "@/lib/relatorios/utils";
-import { getIconComponent } from "@/lib/utils/icons";
-import DotIcon from "../dot-icon";
-import { Card } from "../ui/card";
-import { CategoryCell } from "./category-cell";
+import { useMemo } from "react";
+import type { CategoryReportData, CategoryReportItem } from "@/lib/relatorios/types";
+import { CategoryTable } from "./category-table";
interface CategoryReportTableProps {
data: CategoryReportData;
}
export function CategoryReportTable({ data }: CategoryReportTableProps) {
- const { categories, periods, totals, grandTotal } = data;
+ const { categories, periods } = data;
+
+ // Separate categories by type
+ const { receitas, despesas } = useMemo(() => {
+ const receitas: CategoryReportItem[] = [];
+ const despesas: CategoryReportItem[] = [];
+
+ for (const category of categories) {
+ if (category.type === "receita") {
+ receitas.push(category);
+ } else {
+ despesas.push(category);
+ }
+ }
+
+ return { receitas, despesas };
+ }, [categories]);
return (
-
-
-
-
-
- Categoria
-
- {periods.map((period) => (
-
- {formatPeriodLabel(period)}
-
- ))}
-
- Total
-
-
-
+
+ {/* Despesas Table */}
+
-
- {categories.map((category) => {
- const Icon = category.icon ? getIconComponent(category.icon) : null;
- const isReceita = category.type.toLowerCase() === "receita";
- const dotColor = isReceita
- ? "bg-green-600 dark:bg-green-400"
- : "bg-red-600 dark:bg-red-400";
-
- return (
-
-
-
-
- {Icon && }
- {category.name}
-
-
- {periods.map((period, periodIndex) => {
- const monthData = category.monthlyData.get(period);
- const isFirstMonth = periodIndex === 0;
-
- return (
-
-
-
- );
- })}
-
- {formatCurrency(category.total)}
-
-
- );
- })}
-
-
-
-
- Total Geral
- {periods.map((period) => {
- const periodTotal = totals.get(period) ?? 0;
- return (
-
- {formatCurrency(periodTotal)}
-
- );
- })}
-
- {formatCurrency(grandTotal)}
-
-
-
-
-
+ {/* Receitas Table */}
+
+
);
}
diff --git a/components/relatorios/category-table.tsx b/components/relatorios/category-table.tsx
new file mode 100644
index 0000000..6a88464
--- /dev/null
+++ b/components/relatorios/category-table.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+import Link from "next/link";
+import { useMemo } from "react";
+import { CategoryIconBadge } from "@/components/categorias/category-icon-badge";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import type { CategoryReportItem } from "@/lib/relatorios/types";
+import { formatCurrency, formatPeriodLabel } from "@/lib/relatorios/utils";
+import { formatPeriodForUrl } from "@/lib/utils/period";
+import DotIcon from "../dot-icon";
+import { Card } from "../ui/card";
+import { CategoryCell } from "./category-cell";
+
+export interface CategoryTableProps {
+ title: string;
+ categories: CategoryReportItem[];
+ periods: string[];
+ colorIndexOffset: number;
+}
+
+export function CategoryTable({
+ title,
+ categories,
+ periods,
+ colorIndexOffset,
+}: CategoryTableProps) {
+ // Calculate section totals
+ const sectionTotals = useMemo(() => {
+ const totalsMap = new Map
();
+ let grandTotal = 0;
+
+ for (const category of categories) {
+ grandTotal += category.total;
+ for (const period of periods) {
+ const monthData = category.monthlyData.get(period);
+ const current = totalsMap.get(period) ?? 0;
+ totalsMap.set(period, current + (monthData?.amount ?? 0));
+ }
+ }
+
+ return { totalsMap, grandTotal };
+ }, [categories, periods]);
+
+ if (categories.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ Categoria
+
+ {periods.map((period) => (
+
+ {formatPeriodLabel(period)}
+
+ ))}
+
+ Total
+
+
+
+
+
+ {categories.map((category, index) => {
+ const colorIndex = colorIndexOffset + index;
+ const periodParam = formatPeriodForUrl(periods[periods.length - 1]);
+
+ return (
+
+
+
+
+
+
+
+ {category.name}
+
+
+
+ {periods.map((period, periodIndex) => {
+ const monthData = category.monthlyData.get(period);
+ const isFirstMonth = periodIndex === 0;
+
+ return (
+
+
+
+ );
+ })}
+
+ {formatCurrency(category.total)}
+
+
+ );
+ })}
+
+
+
+
+ Total
+ {periods.map((period) => {
+ const periodTotal = sectionTotals.totalsMap.get(period) ?? 0;
+ return (
+
+ {formatCurrency(periodTotal)}
+
+ );
+ })}
+
+ {formatCurrency(sectionTotals.grandTotal)}
+
+
+
+
+
+ );
+}
diff --git a/components/relatorios/index.ts b/components/relatorios/index.ts
index 28b3b0f..42f6a4f 100644
--- a/components/relatorios/index.ts
+++ b/components/relatorios/index.ts
@@ -5,6 +5,7 @@ export { CategoryReportExport } from "./category-report-export";
export { CategoryReportFilters } from "./category-report-filters";
export { CategoryReportPage } from "./category-report-page";
export { CategoryReportTable } from "./category-report-table";
+export { CategoryTable } from "./category-table";
export type {
CategoryOption,
CategoryReportFiltersProps,
diff --git a/components/sidebar/nav-link.tsx b/components/sidebar/nav-link.tsx
index 2c9a15d..f1757a7 100644
--- a/components/sidebar/nav-link.tsx
+++ b/components/sidebar/nav-link.tsx
@@ -184,12 +184,12 @@ export function createSidebarNavData(
},
{
title: "Tendências",
- url: "/relatorios/categorias",
+ url: "/relatorios/tendencias",
icon: RiFileChartLine,
},
{
title: "Uso de Cartões",
- url: "/relatorios/cartoes",
+ url: "/relatorios/uso-cartoes",
icon: RiBankCard2Line,
},
],
diff --git a/components/top-estabelecimentos/top-categories.tsx b/components/top-estabelecimentos/top-categories.tsx
index 93ace64..aae24dc 100644
--- a/components/top-estabelecimentos/top-categories.tsx
+++ b/components/top-estabelecimentos/top-categories.tsx
@@ -1,16 +1,11 @@
"use client";
import { RiPriceTag3Line } from "@remixicon/react";
+import { CategoryIconBadge } from "@/components/categorias/category-icon-badge";
import MoneyValues from "@/components/money-values";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { WidgetEmptyState } from "@/components/widget-empty-state";
import type { TopEstabelecimentosData } from "@/lib/top-estabelecimentos/fetch-data";
-import {
- buildCategoryInitials,
- getCategoryBgColor,
- getCategoryColor,
-} from "@/lib/utils/category-colors";
-import { getIconComponent } from "@/lib/utils/icons";
import { title_font } from "@/public/fonts/font_index";
import { Progress } from "../ui/progress";
@@ -56,12 +51,6 @@ export function TopCategories({ categories }: TopCategoriesProps) {
{categories.map((category, index) => {
- const IconComponent = category.icon
- ? getIconComponent(category.icon)
- : null;
- const color = getCategoryColor(index);
- const bgColor = getCategoryBgColor(index);
- const initials = buildCategoryInitials(category.name);
const percent =
totalAmount > 0 ? (category.totalAmount / totalAmount) * 100 : 0;
@@ -72,21 +61,11 @@ export function TopCategories({ categories }: TopCategoriesProps) {
>
-
- {IconComponent ? (
-
- ) : (
-
- {initials}
-
- )}
-
+
{/* Name and percentage */}
@@ -110,7 +89,7 @@ export function TopCategories({ categories }: TopCategoriesProps) {
{/* Progress bar */}
-