feat: implement category history widget and loading state for category history page
This commit is contained in:
@@ -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));
|
||||
});
|
||||
|
||||
33
app/(dashboard)/categorias/historico/loading.tsx
Normal file
33
app/(dashboard)/categorias/historico/loading.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
21
app/(dashboard)/categorias/historico/page.tsx
Normal file
21
app/(dashboard)/categorias/historico/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user