mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
feat(finance): refina fluxos de transacoes e pagadores
This commit is contained in:
@@ -20,7 +20,10 @@ import {
|
||||
PERIOD_FORMAT_REGEX,
|
||||
} from "@/lib/faturas";
|
||||
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
||||
import { parseLocalDateString } from "@/lib/utils/date";
|
||||
import { getBusinessTodayDate, parseLocalDateString } from "@/lib/utils/date";
|
||||
|
||||
const isValidPaymentDate = (value: string) =>
|
||||
!Number.isNaN(parseLocalDateString(value).getTime());
|
||||
|
||||
const updateInvoicePaymentStatusSchema = z.object({
|
||||
cartaoId: z.string({ message: "Cartão inválido." }).uuid("Cartão inválido."),
|
||||
@@ -30,7 +33,12 @@ const updateInvoicePaymentStatusSchema = z.object({
|
||||
status: z.enum(
|
||||
INVOICE_STATUS_VALUES as [InvoicePaymentStatus, ...InvoicePaymentStatus[]],
|
||||
),
|
||||
paymentDate: z.string().optional(),
|
||||
paymentDate: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((value) => !value || isValidPaymentDate(value), {
|
||||
message: "Data de pagamento inválida.",
|
||||
}),
|
||||
});
|
||||
|
||||
type UpdateInvoicePaymentStatusInput = z.infer<
|
||||
@@ -157,7 +165,7 @@ export async function updateInvoicePaymentStatusAction(
|
||||
// Usar a data customizada ou a data atual como data de pagamento
|
||||
const invoiceDate = data.paymentDate
|
||||
? parseLocalDateString(data.paymentDate)
|
||||
: new Date();
|
||||
: getBusinessTodayDate();
|
||||
|
||||
const amount = `-${formatDecimal(adminShare)}`;
|
||||
const payload = {
|
||||
@@ -229,7 +237,11 @@ const updatePaymentDateSchema = z.object({
|
||||
period: z
|
||||
.string({ message: "Período inválido." })
|
||||
.regex(PERIOD_FORMAT_REGEX, "Período inválido."),
|
||||
paymentDate: z.string({ message: "Data de pagamento inválida." }),
|
||||
paymentDate: z
|
||||
.string({ message: "Data de pagamento inválida." })
|
||||
.refine((value) => isValidPaymentDate(value), {
|
||||
message: "Data de pagamento inválida.",
|
||||
}),
|
||||
});
|
||||
|
||||
type UpdatePaymentDateInput = z.infer<typeof updatePaymentDateSchema>;
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import {
|
||||
INITIAL_BALANCE_CONDITION,
|
||||
@@ -30,8 +29,10 @@ import {
|
||||
sendPagadorAutoEmails,
|
||||
} from "@/lib/pagadores/notifications";
|
||||
import { noteSchema, uuidSchema } from "@/lib/schemas/common";
|
||||
import type { ActionResult } from "@/lib/types/actions";
|
||||
import { formatDecimalForDbRequired } from "@/lib/utils/currency";
|
||||
import { getTodayDate, parseLocalDateString } from "@/lib/utils/date";
|
||||
import { getBusinessTodayDate, parseLocalDateString } from "@/lib/utils/date";
|
||||
import { addMonthsToPeriod } from "@/lib/utils/period";
|
||||
|
||||
// ============================================================================
|
||||
// Authorization Validation Functions
|
||||
@@ -108,11 +109,14 @@ const resolvePeriod = (purchaseDate: string, period?: string | null) => {
|
||||
return `${year}-${month}`;
|
||||
};
|
||||
|
||||
const isValidDateInput = (value: string) =>
|
||||
!Number.isNaN(parseLocalDateString(value).getTime());
|
||||
|
||||
const baseFields = z.object({
|
||||
purchaseDate: z
|
||||
.string({ message: "Informe a data da transação." })
|
||||
.trim()
|
||||
.refine((value) => !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => isValidDateInput(value), {
|
||||
message: "Data da transação inválida.",
|
||||
}),
|
||||
period: z
|
||||
@@ -164,14 +168,14 @@ const baseFields = z.object({
|
||||
dueDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => !value || isValidDateInput(value), {
|
||||
message: "Informe uma data de vencimento válida.",
|
||||
})
|
||||
.optional(),
|
||||
boletoPaymentDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => !value || isValidDateInput(value), {
|
||||
message: "Informe uma data de pagamento válida.",
|
||||
})
|
||||
.optional(),
|
||||
@@ -353,23 +357,6 @@ const splitAmount = (totalCents: number, parts: number) => {
|
||||
);
|
||||
};
|
||||
|
||||
const addMonthsToPeriod = (period: string, offset: number) => {
|
||||
const [yearStr, monthStr] = period.split("-");
|
||||
const baseYear = Number(yearStr);
|
||||
const baseMonth = Number(monthStr);
|
||||
|
||||
if (!baseYear || !baseMonth) {
|
||||
throw new Error("Período inválido.");
|
||||
}
|
||||
|
||||
const date = new Date(baseYear, baseMonth - 1, 1);
|
||||
date.setMonth(date.getMonth() + offset);
|
||||
|
||||
const nextYear = date.getFullYear();
|
||||
const nextMonth = String(date.getMonth() + 1).padStart(2, "0");
|
||||
return `${nextYear}-${nextMonth}`;
|
||||
};
|
||||
|
||||
const addMonthsToDate = (value: Date, offset: number) => {
|
||||
const result = new Date(value);
|
||||
const originalDay = result.getDate();
|
||||
@@ -648,7 +635,7 @@ export async function createLancamentoAction(
|
||||
const boletoPaymentDate = shouldSetBoletoPaymentDate
|
||||
? data.boletoPaymentDate
|
||||
? parseLocalDateString(data.boletoPaymentDate)
|
||||
: getTodayDate()
|
||||
: getBusinessTodayDate()
|
||||
: null;
|
||||
|
||||
const amountSign: 1 | -1 = data.transactionType === "Despesa" ? -1 : 1;
|
||||
@@ -828,7 +815,7 @@ export async function updateLancamentoAction(
|
||||
const boletoPaymentDateValue = shouldSetBoletoPaymentDate
|
||||
? data.boletoPaymentDate
|
||||
? parseLocalDateString(data.boletoPaymentDate)
|
||||
: getTodayDate()
|
||||
: getBusinessTodayDate()
|
||||
: null;
|
||||
|
||||
await db
|
||||
@@ -982,7 +969,7 @@ export async function toggleLancamentoSettlementAction(
|
||||
const isBoleto = existing.paymentMethod === "Boleto";
|
||||
const boletoPaymentDate = isBoleto
|
||||
? data.value
|
||||
? getTodayDate()
|
||||
? getBusinessTodayDate()
|
||||
: null
|
||||
: null;
|
||||
|
||||
@@ -1118,7 +1105,7 @@ const updateBulkSchema = z.object({
|
||||
dueDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => !value || isValidDateInput(value), {
|
||||
message: "Informe uma data de vencimento válida.",
|
||||
})
|
||||
.optional()
|
||||
@@ -1126,7 +1113,7 @@ const updateBulkSchema = z.object({
|
||||
boletoPaymentDate: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((value) => !value || !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => !value || isValidDateInput(value), {
|
||||
message: "Informe uma data de pagamento válida.",
|
||||
})
|
||||
.optional()
|
||||
@@ -1284,7 +1271,7 @@ export async function updateLancamentoBulkAction(
|
||||
});
|
||||
|
||||
await applyUpdates(
|
||||
futureLancamentos.map((item) => ({
|
||||
futureLancamentos.map((item: (typeof futureLancamentos)[number]) => ({
|
||||
id: item.id,
|
||||
purchaseDate: item.purchaseDate ?? null,
|
||||
})),
|
||||
@@ -1311,7 +1298,7 @@ export async function updateLancamentoBulkAction(
|
||||
});
|
||||
|
||||
await applyUpdates(
|
||||
allLancamentos.map((item) => ({
|
||||
allLancamentos.map((item: (typeof allLancamentos)[number]) => ({
|
||||
id: item.id,
|
||||
purchaseDate: item.purchaseDate ?? null,
|
||||
})),
|
||||
@@ -1335,7 +1322,7 @@ const massAddTransactionSchema = z.object({
|
||||
purchaseDate: z
|
||||
.string({ message: "Informe a data da transação." })
|
||||
.trim()
|
||||
.refine((value) => !Number.isNaN(new Date(value).getTime()), {
|
||||
.refine((value) => isValidDateInput(value), {
|
||||
message: "Data da transação inválida.",
|
||||
}),
|
||||
name: z
|
||||
@@ -1598,12 +1585,12 @@ export async function deleteMultipleLancamentosAction(
|
||||
const notificationData = existing
|
||||
.filter(
|
||||
(
|
||||
item,
|
||||
item: (typeof existing)[number],
|
||||
): item is typeof item & {
|
||||
pagadorId: NonNullable<typeof item.pagadorId>;
|
||||
} => Boolean(item.pagadorId),
|
||||
)
|
||||
.map((item) => ({
|
||||
.map((item: (typeof existing)[number]) => ({
|
||||
pagadorId: item.pagadorId,
|
||||
name: item.name ?? null,
|
||||
amount: item.amount ?? null,
|
||||
@@ -1662,11 +1649,11 @@ export async function getRecentEstablishmentsAction(): Promise<string[]> {
|
||||
|
||||
// Remove duplicates and filter empty names
|
||||
const uniqueNames = Array.from(
|
||||
new Set(
|
||||
new Set<string>(
|
||||
results
|
||||
.map((r) => r.name)
|
||||
.map((r: (typeof results)[number]) => r.name)
|
||||
.filter(
|
||||
(name): name is string =>
|
||||
(name: string | null): name is string =>
|
||||
name != null &&
|
||||
name.trim().length > 0 &&
|
||||
!name.toLowerCase().startsWith("pagamento fatura"),
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
pagadores,
|
||||
} from "@/db/schema";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import type { ActionResult } from "@/lib/types/actions";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
|
||||
@@ -3,18 +3,16 @@
|
||||
import { and, eq, ne } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { categorias, orcamentos } from "@/db/schema";
|
||||
import {
|
||||
type ActionResult,
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
} from "@/lib/actions/helpers";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
import { periodSchema, uuidSchema } from "@/lib/schemas/common";
|
||||
import type { ActionResult } from "@/lib/types/actions";
|
||||
import {
|
||||
formatDecimalForDbRequired,
|
||||
normalizeDecimalInput,
|
||||
} from "@/lib/utils/currency";
|
||||
import { getPreviousPeriod } from "@/lib/utils/period";
|
||||
|
||||
const budgetBaseSchema = z.object({
|
||||
categoriaId: uuidSchema("Categoria"),
|
||||
@@ -43,9 +41,13 @@ const deleteBudgetSchema = z.object({
|
||||
id: uuidSchema("Orçamento"),
|
||||
});
|
||||
|
||||
type BudgetCreateInput = z.infer<typeof createBudgetSchema>;
|
||||
type BudgetUpdateInput = z.infer<typeof updateBudgetSchema>;
|
||||
type BudgetDeleteInput = z.infer<typeof deleteBudgetSchema>;
|
||||
type BudgetCreateInput = z.input<typeof createBudgetSchema>;
|
||||
type BudgetUpdateInput = z.input<typeof updateBudgetSchema>;
|
||||
type BudgetDeleteInput = z.input<typeof deleteBudgetSchema>;
|
||||
type BudgetCopyRow = {
|
||||
categoriaId: string | null;
|
||||
amount: unknown;
|
||||
};
|
||||
|
||||
const ensureCategory = async (userId: string, categoriaId: string) => {
|
||||
const category = await db.query.categorias.findFirst({
|
||||
@@ -193,7 +195,7 @@ const duplicatePreviousMonthSchema = z.object({
|
||||
period: periodSchema,
|
||||
});
|
||||
|
||||
type DuplicatePreviousMonthInput = z.infer<typeof duplicatePreviousMonthSchema>;
|
||||
type DuplicatePreviousMonthInput = z.input<typeof duplicatePreviousMonthSchema>;
|
||||
|
||||
export async function duplicatePreviousMonthBudgetsAction(
|
||||
input: DuplicatePreviousMonthInput,
|
||||
@@ -203,22 +205,15 @@ export async function duplicatePreviousMonthBudgetsAction(
|
||||
const data = duplicatePreviousMonthSchema.parse(input);
|
||||
|
||||
// Calcular mês anterior
|
||||
const [year, month] = data.period.split("-").map(Number);
|
||||
const currentDate = new Date(year, month - 1, 1);
|
||||
const previousDate = new Date(currentDate);
|
||||
previousDate.setMonth(previousDate.getMonth() - 1);
|
||||
|
||||
const prevYear = previousDate.getFullYear();
|
||||
const prevMonth = String(previousDate.getMonth() + 1).padStart(2, "0");
|
||||
const previousPeriod = `${prevYear}-${prevMonth}`;
|
||||
const previousPeriod = getPreviousPeriod(data.period);
|
||||
|
||||
// Buscar orçamentos do mês anterior
|
||||
const previousBudgets = await db.query.orcamentos.findMany({
|
||||
const previousBudgets = (await db.query.orcamentos.findMany({
|
||||
where: and(
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, previousPeriod),
|
||||
),
|
||||
});
|
||||
})) as BudgetCopyRow[];
|
||||
|
||||
if (previousBudgets.length === 0) {
|
||||
return {
|
||||
@@ -228,12 +223,12 @@ export async function duplicatePreviousMonthBudgetsAction(
|
||||
}
|
||||
|
||||
// Buscar orçamentos existentes do mês atual
|
||||
const currentBudgets = await db.query.orcamentos.findMany({
|
||||
const currentBudgets = (await db.query.orcamentos.findMany({
|
||||
where: and(
|
||||
eq(orcamentos.userId, user.id),
|
||||
eq(orcamentos.period, data.period),
|
||||
),
|
||||
});
|
||||
})) as BudgetCopyRow[];
|
||||
|
||||
// Filtrar para evitar duplicatas
|
||||
const existingCategoryIds = new Set(
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
fetchPagadorHistory,
|
||||
fetchPagadorMonthlyBreakdown,
|
||||
} from "@/lib/pagadores/details";
|
||||
import { formatCurrency } from "@/lib/utils/currency";
|
||||
import { formatDateTime } from "@/lib/utils/date";
|
||||
import { displayPeriod } from "@/lib/utils/period";
|
||||
|
||||
const inputSchema = z.object({
|
||||
@@ -27,20 +29,14 @@ type ActionResult =
|
||||
| { success: true; message: string }
|
||||
| { success: false; error: string };
|
||||
|
||||
const formatCurrency = (value: number) =>
|
||||
value.toLocaleString("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const formatDate = (value: Date | null | undefined) => {
|
||||
if (!value) return "—";
|
||||
return value.toLocaleDateString("pt-BR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
return (
|
||||
formatDateTime(value, {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
}) ?? "—"
|
||||
);
|
||||
};
|
||||
|
||||
// Escapa HTML para prevenir XSS
|
||||
@@ -515,25 +511,47 @@ export async function sendPagadorSummaryAction(
|
||||
.orderBy(desc(lancamentos.purchaseDate)),
|
||||
]);
|
||||
|
||||
const normalizedBoletos: BoletoItem[] = boletoRows.map((row) => ({
|
||||
const normalizedBoletos: BoletoItem[] = (
|
||||
boletoRows as Array<{
|
||||
name: string | null;
|
||||
amount: unknown;
|
||||
dueDate: Date | null;
|
||||
}>
|
||||
).map((row) => ({
|
||||
name: row.name ?? "Sem descrição",
|
||||
amount: Math.abs(Number(row.amount ?? 0)),
|
||||
dueDate: row.dueDate,
|
||||
}));
|
||||
|
||||
const normalizedLancamentos: LancamentoRow[] = lancamentoRows.map(
|
||||
(row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
paymentMethod: row.paymentMethod,
|
||||
condition: row.condition,
|
||||
transactionType: row.transactionType,
|
||||
purchaseDate: row.purchaseDate,
|
||||
amount: Number(row.amount ?? 0),
|
||||
}),
|
||||
);
|
||||
const normalizedLancamentos: LancamentoRow[] = (
|
||||
lancamentoRows as Array<{
|
||||
id: string;
|
||||
name: string | null;
|
||||
paymentMethod: string | null;
|
||||
condition: string | null;
|
||||
transactionType: string | null;
|
||||
purchaseDate: Date | null;
|
||||
amount: unknown;
|
||||
}>
|
||||
).map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
paymentMethod: row.paymentMethod,
|
||||
condition: row.condition,
|
||||
transactionType: row.transactionType,
|
||||
purchaseDate: row.purchaseDate,
|
||||
amount: Number(row.amount ?? 0),
|
||||
}));
|
||||
|
||||
const normalizedParcelados: ParceladoItem[] = parceladoRows.map((row) => {
|
||||
const normalizedParcelados: ParceladoItem[] = (
|
||||
parceladoRows as Array<{
|
||||
name: string | null;
|
||||
amount: unknown;
|
||||
installmentCount: number | null;
|
||||
currentInstallment: number | null;
|
||||
purchaseDate: Date | null;
|
||||
}>
|
||||
).map((row) => {
|
||||
const installmentAmount = Math.abs(Number(row.amount ?? 0));
|
||||
const installmentCount = row.installmentCount ?? 1;
|
||||
const totalAmount = installmentAmount * installmentCount;
|
||||
|
||||
@@ -1,56 +1,47 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
/**
|
||||
* Loading state para a página de detalhes do pagador
|
||||
* Layout: MonthPicker + Info do pagador + Tabs (Visão Geral / Lançamentos)
|
||||
* Loading state para a página de detalhes do pagador.
|
||||
* Layout: navegação mensal + tabs com card compartilhado do pagador.
|
||||
*/
|
||||
export default function PagadorDetailsLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
{/* Month Picker placeholder */}
|
||||
<div className="h-[60px] animate-pulse rounded-2xl bg-foreground/10" />
|
||||
|
||||
{/* Info do Pagador (sempre visível) */}
|
||||
<div className="rounded-2xl border p-6 space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Avatar */}
|
||||
<Skeleton className="size-20 rounded-full bg-foreground/10" />
|
||||
|
||||
<div className="flex-1 space-y-3">
|
||||
{/* Nome + Badge */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="h-7 w-48 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-6 w-20 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<Skeleton className="h-5 w-64 rounded-2xl bg-foreground/10" />
|
||||
|
||||
{/* Status */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="size-2 rounded-full bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-16 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botões de ação */}
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-9 w-9 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-9 w-9 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="space-y-6 pt-4">
|
||||
<div className="flex gap-2 border-b">
|
||||
<Skeleton className="h-10 w-32 rounded-t-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-32 rounded-t-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-36 rounded-t-2xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border p-6 space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<Skeleton className="size-20 rounded-full bg-foreground/10" />
|
||||
|
||||
<div className="flex-1 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="h-7 w-48 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-6 w-20 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
<Skeleton className="h-5 w-64 rounded-2xl bg-foreground/10" />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="size-2 rounded-full bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-16 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-9 w-9 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-9 w-9 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Conteúdo da aba Visão Geral (grid de cards) */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{/* Card de resumo mensal */}
|
||||
<div className="rounded-2xl border p-6 space-y-4 lg:col-span-2">
|
||||
<Skeleton className="h-6 w-48 rounded-2xl bg-foreground/10" />
|
||||
<div className="grid grid-cols-3 gap-4 pt-4">
|
||||
@@ -63,7 +54,6 @@ export default function PagadorDetailsLoading() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Outros cards */}
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="rounded-2xl border p-6 space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { notFound } from "next/navigation";
|
||||
import { fetchUserPreferences } from "@/app/(dashboard)/ajustes/data";
|
||||
import { getRecentEstablishmentsAction } from "@/app/(dashboard)/lancamentos/actions";
|
||||
import { ExpandableWidgetCard } from "@/components/shared/expandable-widget-card";
|
||||
import { LancamentosPage as LancamentosSection } from "@/components/lancamentos/page/lancamentos-page";
|
||||
import type {
|
||||
ContaCartaoFilterOption,
|
||||
@@ -15,6 +16,7 @@ import type {
|
||||
} from "@/components/lancamentos/types";
|
||||
import MonthNavigation from "@/components/month-picker/month-navigation";
|
||||
import { PagadorCardUsageCard } from "@/components/pagadores/details/pagador-card-usage-card";
|
||||
import { PagadorHeaderCard } from "@/components/pagadores/details/pagador-header-card";
|
||||
import { PagadorHistoryCard } from "@/components/pagadores/details/pagador-history-card";
|
||||
import { PagadorInfoCard } from "@/components/pagadores/details/pagador-info-card";
|
||||
import { PagadorLeaveShareCard } from "@/components/pagadores/details/pagador-leave-share-card";
|
||||
@@ -25,7 +27,6 @@ import {
|
||||
} from "@/components/pagadores/details/pagador-payment-method-cards";
|
||||
import { PagadorSharingCard } from "@/components/pagadores/details/pagador-sharing-card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import WidgetCard from "@/components/widget-card";
|
||||
import type { pagadores } from "@/db/schema";
|
||||
import { getUserId } from "@/lib/auth/server";
|
||||
import {
|
||||
@@ -50,6 +51,7 @@ import {
|
||||
fetchPagadorHistory,
|
||||
fetchPagadorMonthlyBreakdown,
|
||||
fetchPagadorPaymentStatus,
|
||||
type PagadorCardUsageItem,
|
||||
} from "@/lib/pagadores/details";
|
||||
import { parsePeriodParam } from "@/lib/utils/period";
|
||||
import {
|
||||
@@ -232,6 +234,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
label: pagador.name,
|
||||
slug: pagador.id,
|
||||
role: pagador.role,
|
||||
avatarUrl: pagador.avatarUrl,
|
||||
},
|
||||
],
|
||||
categoriaFiltersRaw: [],
|
||||
@@ -284,7 +287,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
periodLabel,
|
||||
totalExpenses: monthlyBreakdown.totalExpenses,
|
||||
paymentSplits: monthlyBreakdown.paymentSplits,
|
||||
cardUsage: cardUsage.slice(0, 3).map((item) => ({
|
||||
cardUsage: cardUsage.slice(0, 3).map((item: PagadorCardUsageItem) => ({
|
||||
name: item.name,
|
||||
amount: item.amount,
|
||||
})),
|
||||
@@ -308,15 +311,14 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
<TabsTrigger value="painel">Painel</TabsTrigger>
|
||||
<TabsTrigger value="lancamentos">Lançamentos</TabsTrigger>
|
||||
</TabsList>
|
||||
<PagadorHeaderCard
|
||||
pagador={pagadorData}
|
||||
selectedPeriod={selectedPeriod}
|
||||
summary={summaryPreview}
|
||||
/>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<section>
|
||||
<PagadorInfoCard
|
||||
pagador={pagadorData}
|
||||
selectedPeriod={selectedPeriod}
|
||||
summary={summaryPreview}
|
||||
/>
|
||||
</section>
|
||||
<PagadorInfoCard pagador={pagadorData} />
|
||||
{canEdit && pagadorData.shareCode ? (
|
||||
<PagadorSharingCard
|
||||
pagadorId={pagador.id}
|
||||
@@ -343,27 +345,27 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
</section>
|
||||
|
||||
<section className="grid gap-3 lg:grid-cols-3">
|
||||
<WidgetCard
|
||||
<ExpandableWidgetCard
|
||||
title="Minhas Faturas"
|
||||
subtitle="Valores por cartão neste período"
|
||||
icon={<RiBankCard2Line className="size-4" />}
|
||||
>
|
||||
<PagadorCardUsageCard items={cardUsage} />
|
||||
</WidgetCard>
|
||||
<WidgetCard
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Boletos"
|
||||
subtitle="Boletos registrados neste período"
|
||||
icon={<RiBarcodeLine className="size-4" />}
|
||||
>
|
||||
<PagadorBoletoCard items={boletoItems} />
|
||||
</WidgetCard>
|
||||
<WidgetCard
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Status de Pagamento"
|
||||
subtitle="Situação das despesas no período"
|
||||
icon={<RiWallet3Line className="size-4" />}
|
||||
>
|
||||
<PagadorPaymentStatusCard data={paymentStatus} />
|
||||
</WidgetCard>
|
||||
</ExpandableWidgetCard>
|
||||
</section>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { compartilhamentosPagador, pagadores, user } from "@/db/schema";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import type { ActionResult } from "@/lib/types/actions";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { and, eq, inArray } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { preLancamentos } from "@/db/schema";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import type { ActionResult } from "@/lib/types/actions";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user