forked from git.gladyson/openmonetis
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
108 lines
2.7 KiB
TypeScript
108 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { RiBarChartLine } from "@remixicon/react";
|
|
import {
|
|
Bar,
|
|
BarChart,
|
|
CartesianGrid,
|
|
LabelList,
|
|
type LabelProps,
|
|
XAxis,
|
|
} from "recharts";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import {
|
|
ChartContainer,
|
|
ChartTooltip,
|
|
ChartTooltipContent,
|
|
} from "@/components/ui/chart";
|
|
import { WidgetEmptyState } from "@/components/widget-empty-state";
|
|
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
|
import type { PagadorHistoryPoint } from "@/lib/pagadores/details";
|
|
|
|
const chartConfig = {
|
|
despesas: {
|
|
label: "Despesas",
|
|
color: "hsl(356, 72%, 50%)",
|
|
},
|
|
};
|
|
|
|
type PagadorHistoryCardProps = {
|
|
data: PagadorHistoryPoint[];
|
|
};
|
|
|
|
const ValueLabel = (props: LabelProps) => {
|
|
const { x, y, value, width } = props;
|
|
if (typeof x !== "number" || typeof y !== "number" || width === undefined) {
|
|
return null;
|
|
}
|
|
const labelX = x + (Number(width) ?? 0) / 2;
|
|
const amount =
|
|
typeof value === "number" ? currencyFormatter.format(value) : value;
|
|
const labelY = Math.max(y - 6, 12);
|
|
return (
|
|
<text
|
|
x={labelX}
|
|
y={labelY}
|
|
fill="currentColor"
|
|
textAnchor="middle"
|
|
className="text-[11px] font-semibold text-muted-foreground"
|
|
>
|
|
{amount}
|
|
</text>
|
|
);
|
|
};
|
|
|
|
export function PagadorHistoryCard({ data }: PagadorHistoryCardProps) {
|
|
const hasData = data.length > 0;
|
|
|
|
return (
|
|
<Card className="border">
|
|
<CardHeader className="gap-1.5 pb-3">
|
|
<CardTitle className="text-lg font-semibold">
|
|
Evolução (últimos 6 meses)
|
|
</CardTitle>
|
|
<p className="text-xs text-muted-foreground">
|
|
Despesas registradas para este pagador ao longo do tempo.
|
|
</p>
|
|
</CardHeader>
|
|
|
|
<CardContent className="pt-0">
|
|
{hasData ? (
|
|
<ChartContainer
|
|
config={chartConfig}
|
|
className="mx-auto flex h-[210px] w-full max-w-[520px] items-center justify-center aspect-auto"
|
|
>
|
|
<BarChart
|
|
data={data}
|
|
barCategoryGap={16}
|
|
margin={{ top: 28, right: 8, left: 8, bottom: 0 }}
|
|
>
|
|
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
|
<XAxis
|
|
dataKey="label"
|
|
tickLine={false}
|
|
axisLine={false}
|
|
tickMargin={8}
|
|
/>
|
|
<ChartTooltip content={<ChartTooltipContent />} />
|
|
<Bar
|
|
dataKey="despesas"
|
|
fill="var(--color-despesas)"
|
|
radius={[6, 6, 0, 0]}
|
|
>
|
|
<LabelList dataKey="despesas" content={<ValueLabel />} />
|
|
</Bar>
|
|
</BarChart>
|
|
</ChartContainer>
|
|
) : (
|
|
<WidgetEmptyState
|
|
icon={<RiBarChartLine className="size-6 text-muted-foreground" />}
|
|
title="Sem dados para exibir"
|
|
description="Ainda não há movimentações suficientes para gerar este gráfico."
|
|
/>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|