chore: ajustes de componentes, estilos, dependências e métricas do dashboard

- dashboard: melhorias em métricas, filtros de transações e overview de período
- transactions: colunas, tabela e página com novos campos e ajustes de exibição
- ui: card, table, navigation-menu, navbar, month-picker, logo-picker, theme-toggler
- calculator: ajustes de display, keypad e estado
- calendar: melhorias de grid e day-cell
- insights: atualização de constantes
- settings: pequenos ajustes
- pnpm-lock: atualização de dependências
- pdf.worker: atualização do worker

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-05-02 22:08:53 +00:00
parent d55173e8c1
commit 94bf93194f
40 changed files with 4699 additions and 477 deletions

View File

@@ -21,9 +21,11 @@ import { excludeTransactionsFromExcludedAccounts } from "@/features/dashboard/tr
import {
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
INITIAL_BALANCE_NOTE,
isRefundNote,
} from "@/shared/lib/accounts/constants";
import { db } from "@/shared/lib/db";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
import { TRANSFER_CATEGORY_NAME } from "@/shared/lib/transfers/constants";
import {
compareDateOnly,
getBusinessDateString,
@@ -58,6 +60,7 @@ type CurrentPeriodTransactionRow = {
categoryId: string | null;
categoryName: string | null;
categoryType: string | null;
accountId: string | null;
cardLogo: string | null;
accountLogo: string | null;
accountExcludeInitialBalanceFromIncome: boolean | null;
@@ -119,6 +122,9 @@ const shouldIncludeWithoutAutoInvoice = (note: string | null | undefined) =>
const shouldIncludeWithoutAutoGenerated = (note: string | null | undefined) =>
!isInitialBalanceNote(note) && !isAutoInvoiceNote(note);
const shouldIncludeWithoutRefund = (note: string | null | undefined) =>
!isRefundNote(note);
const shouldIncludeInPaymentStatus = (row: CurrentPeriodTransactionRow) => {
if (!shouldIncludeWithoutAutoInvoice(row.note)) {
return false;
@@ -183,6 +189,7 @@ const buildBillsSnapshot = (
? row.boletoPaymentDate.toISOString().slice(0, 10)
: null,
isSettled: Boolean(row.isSettled),
accountId: row.accountId ?? null,
}))
.sort((a, b) => {
if (a.isSettled !== b.isSettled) {
@@ -259,6 +266,14 @@ const buildPaymentStatusData = (
}
const amount = toNumber(row.amount);
const isRefund = isRefundNote(row.note);
if (isRefund) {
const targetKey = row.isSettled === true ? "confirmed" : "pending";
result.expenses[targetKey] -= Math.abs(amount);
continue;
}
const target =
row.transactionType === TRANSACTION_TYPE_INCOME
? result.income
@@ -271,6 +286,8 @@ const buildPaymentStatusData = (
}
}
result.expenses.confirmed = Math.max(0, result.expenses.confirmed);
result.expenses.pending = Math.max(0, result.expenses.pending);
result.income.total = result.income.confirmed + result.income.pending;
result.expenses.total = result.expenses.confirmed + result.expenses.pending;
@@ -495,7 +512,9 @@ const buildPurchasesByCategoryData = (
!row.categoryName ||
!row.categoryType ||
!["despesa", "receita"].includes(row.categoryType) ||
row.categoryName === TRANSFER_CATEGORY_NAME ||
!shouldIncludeWithoutAutoGenerated(row.note) ||
!shouldIncludeWithoutRefund(row.note) ||
!shouldIncludeNamedItem(row.name)
) {
continue;
@@ -564,6 +583,7 @@ export async function fetchDashboardCurrentPeriodOverview(
categoryId: transactions.categoryId,
categoryName: categories.name,
categoryType: categories.type,
accountId: transactions.accountId,
cardLogo: cards.logo,
accountLogo: financialAccounts.logo,
accountExcludeInitialBalanceFromIncome:

View File

@@ -1,4 +1,4 @@
import { and, asc, eq, gte, inArray, lte, sum } from "drizzle-orm";
import { and, asc, eq, gte, inArray, lte, sql } from "drizzle-orm";
import { financialAccounts, transactions } from "@/db/schema";
import type { DashboardCardMetrics } from "@/features/dashboard/overview/dashboard-metrics-queries";
import type {
@@ -11,6 +11,7 @@ import {
excludeInitialBalanceWhenConfigured,
excludeTransactionsFromExcludedAccounts,
} from "@/features/dashboard/transaction-filters";
import { REFUND_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
import { db } from "@/shared/lib/db";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
import { safeToNumber } from "@/shared/utils/number";
@@ -31,6 +32,7 @@ const TRANSACTION_TYPE_TRANSFER = "Transferência";
type PeriodTotals = {
receitas: number;
despesas: number;
reembolsos: number;
transferAdjustment: number;
balanco: number;
};
@@ -39,6 +41,7 @@ type PeriodSummaryRow = {
period: string | null;
transactionType: string;
totalAmount: string | number | null;
refundAmount: string | number | null;
accountExcludeFromBalance: boolean | null;
};
@@ -50,6 +53,7 @@ type DashboardPeriodOverview = {
const createEmptyTotals = (): PeriodTotals => ({
receitas: 0,
despesas: 0,
reembolsos: 0,
transferAdjustment: 0,
balanco: 0,
});
@@ -105,11 +109,17 @@ export async function fetchDashboardPeriodOverview(
const chartPeriods = generateLast6Months(period);
const startPeriod = addMonthsToPeriod(period, -24);
const refundPattern = `${REFUND_NOTE_PREFIX}%`;
const rows = (await db
.select({
period: transactions.period,
transactionType: transactions.transactionType,
totalAmount: sum(transactions.amount).as("total"),
totalAmount: sql<number>`coalesce(sum(case when ${transactions.note} ilike ${refundPattern} then 0 else ${transactions.amount} end), 0)`.as(
"total",
),
refundAmount: sql<number>`coalesce(sum(case when ${transactions.note} ilike ${refundPattern} then ${transactions.amount} else 0 end), 0)`.as(
"refund",
),
accountExcludeFromBalance: financialAccounts.excludeFromBalance,
})
.from(transactions)
@@ -151,6 +161,9 @@ export async function fetchDashboardPeriodOverview(
const totals = ensurePeriodTotals(periodTotals, row.period);
const total = safeToNumber(row.totalAmount);
const refund = safeToNumber(row.refundAmount);
totals.reembolsos += Math.abs(refund);
if (row.transactionType === TRANSACTION_TYPE_INCOME) {
totals.receitas += total;
@@ -179,9 +192,14 @@ export async function fetchDashboardPeriodOverview(
for (const key of periodRange) {
const totals = ensurePeriodTotals(periodTotals, key);
const netExpenses = Math.max(0, totals.despesas - totals.reembolsos);
totals.balanco =
totals.receitas - totals.despesas + totals.transferAdjustment;
totals.receitas -
totals.despesas +
totals.reembolsos +
totals.transferAdjustment;
runningForecast += totals.balanco;
totals.despesas = netExpenses;
forecastByPeriod.set(key, runningForecast);
}