feat: implement category history widget and loading state for category history page

This commit is contained in:
Felipe Coutinho
2025-11-28 13:42:21 +00:00
parent 302521ce14
commit cf5a0b7745
12 changed files with 808 additions and 34 deletions

View File

@@ -17,6 +17,7 @@ import {
type InvoicePaymentStatus,
} from "@/lib/faturas";
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
import { parseLocalDateString } from "@/lib/utils/date";
import { and, eq, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
@@ -157,7 +158,7 @@ export async function updateInvoicePaymentStatusAction(
if (adminPagador) {
// Usar a data customizada ou a data atual como data de pagamento
const invoiceDate = data.paymentDate
? new Date(data.paymentDate)
? parseLocalDateString(data.paymentDate)
: new Date();
const amount = `-${formatDecimal(adminShare)}`;
@@ -273,7 +274,7 @@ export async function updatePaymentDateAction(
await tx
.update(lancamentos)
.set({
purchaseDate: new Date(data.paymentDate),
purchaseDate: parseLocalDateString(data.paymentDate),
})
.where(eq(lancamentos.id, existingPayment.id));
});

View File

@@ -0,0 +1,33 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent } from "@/components/ui/card";
export default function Loading() {
return (
<main className="flex flex-col gap-6 px-6">
<Card className="h-auto">
<CardContent className="space-y-2.5">
<div className="space-y-2">
{/* Selected categories and counter */}
<div className="flex items-start justify-between gap-4">
<div className="flex flex-wrap gap-2">
<Skeleton className="h-8 w-32 rounded-md" />
<Skeleton className="h-8 w-40 rounded-md" />
<Skeleton className="h-8 w-36 rounded-md" />
</div>
<div className="flex items-center gap-2 shrink-0 pt-1.5">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-6 w-14" />
</div>
</div>
{/* Category selector button */}
<Skeleton className="h-9 w-full rounded-md" />
</div>
{/* Chart */}
<Skeleton className="h-[450px] w-full rounded-lg" />
</CardContent>
</Card>
</main>
);
}

View File

@@ -0,0 +1,21 @@
import { CategoryHistoryWidget } from "@/components/dashboard/category-history-widget";
import { getUser } from "@/lib/auth/server";
import { fetchCategoryHistory } from "@/lib/dashboard/categories/category-history";
import { getCurrentPeriod } from "@/lib/utils/period";
export default async function HistoricoCategoriasPage() {
const user = await getUser();
const currentPeriod = getCurrentPeriod();
const data = await fetchCategoryHistory(user.id, currentPeriod);
return (
<main className="flex flex-col gap-6">
<p className="text-muted-foreground">
Acompanhe o histórico de desempenho das suas categorias ao longo de 9
meses.
</p>
<CategoryHistoryWidget data={data} />
</main>
);
}

View File

@@ -22,7 +22,11 @@ import {
} from "@/lib/pagadores/notifications";
import { noteSchema, uuidSchema } from "@/lib/schemas/common";
import { formatDecimalForDbRequired } from "@/lib/utils/currency";
import { getTodayDateString } from "@/lib/utils/date";
import {
getTodayDate,
getTodayDateString,
parseLocalDateString,
} from "@/lib/utils/date";
import { and, asc, desc, eq, gte, inArray, sql } from "drizzle-orm";
import { randomUUID } from "node:crypto";
import { z } from "zod";
@@ -32,7 +36,7 @@ const resolvePeriod = (purchaseDate: string, period?: string | null) => {
return period;
}
const date = new Date(purchaseDate);
const date = parseLocalDateString(purchaseDate);
if (Number.isNaN(date.getTime())) {
throw new Error("Data da transação inválida.");
}
@@ -42,8 +46,6 @@ const resolvePeriod = (purchaseDate: string, period?: string | null) => {
return `${year}-${month}`;
};
const getTodayDate = () => new Date(getTodayDateString());
const baseFields = z.object({
purchaseDate: z
.string({ message: "Informe a data da transação." })
@@ -471,13 +473,13 @@ export async function createLancamentoAction(
const data = createSchema.parse(input);
const period = resolvePeriod(data.purchaseDate, data.period);
const purchaseDate = new Date(data.purchaseDate);
const dueDate = data.dueDate ? new Date(data.dueDate) : null;
const purchaseDate = parseLocalDateString(data.purchaseDate);
const dueDate = data.dueDate ? parseLocalDateString(data.dueDate) : null;
const shouldSetBoletoPaymentDate =
data.paymentMethod === "Boleto" && (data.isSettled ?? false);
const boletoPaymentDate = shouldSetBoletoPaymentDate
? data.boletoPaymentDate
? new Date(data.boletoPaymentDate)
? parseLocalDateString(data.boletoPaymentDate)
: getTodayDate()
: null;
@@ -603,7 +605,7 @@ export async function updateLancamentoAction(
data.paymentMethod === "Boleto" && Boolean(normalizedSettled);
const boletoPaymentDateValue = shouldSetBoletoPaymentDate
? data.boletoPaymentDate
? new Date(data.boletoPaymentDate)
? parseLocalDateString(data.boletoPaymentDate)
: getTodayDate()
: null;
@@ -611,7 +613,7 @@ export async function updateLancamentoAction(
.update(lancamentos)
.set({
name: data.name,
purchaseDate: new Date(data.purchaseDate),
purchaseDate: parseLocalDateString(data.purchaseDate),
transactionType: data.transactionType,
amount: normalizedAmount,
condition: data.condition,
@@ -624,7 +626,7 @@ export async function updateLancamentoAction(
isSettled: normalizedSettled,
installmentCount: data.installmentCount ?? null,
recurrenceCount: data.recurrenceCount ?? null,
dueDate: data.dueDate ? new Date(data.dueDate) : null,
dueDate: data.dueDate ? parseLocalDateString(data.dueDate) : null,
boletoPaymentDate: boletoPaymentDateValue,
period,
})
@@ -963,14 +965,14 @@ export async function updateLancamentoBulkAction(
const baseDueDate =
hasDueDateUpdate && data.dueDate
? new Date(data.dueDate)
? parseLocalDateString(data.dueDate)
: hasDueDateUpdate
? null
: undefined;
const baseBoletoPaymentDate =
hasBoletoPaymentDateUpdate && data.boletoPaymentDate
? new Date(data.boletoPaymentDate)
? parseLocalDateString(data.boletoPaymentDate)
: hasBoletoPaymentDateUpdate
? null
: undefined;
@@ -1192,7 +1194,7 @@ export async function createMassLancamentosAction(
const period =
data.fixedFields.period ?? resolvePeriod(transaction.purchaseDate);
const purchaseDate = new Date(transaction.purchaseDate);
const purchaseDate = parseLocalDateString(transaction.purchaseDate);
const amountSign: 1 | -1 = transactionType === "Despesa" ? -1 : 1;
const totalCents = Math.round(Math.abs(transaction.amount) * 100);
const amount = centsToDecimalString(totalCents * amountSign);