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

@@ -20,6 +20,7 @@ import {
buildDashboardAdminFilters,
excludeAutoInvoiceEntries,
excludeInitialBalanceWhenConfigured,
excludeRefundEntries,
excludeTransactionsFromExcludedAccounts,
} from "@/features/dashboard/transaction-filters";
import { db } from "@/shared/lib/db";
@@ -168,6 +169,7 @@ export async function fetchDashboardCategoryOverview(
eq(transactions.transactionType, "Receita"),
eq(categories.type, "receita"),
excludeAutoInvoiceEntries(),
excludeRefundEntries(),
excludeInitialBalanceWhenConfigured(),
),
),

View File

@@ -330,7 +330,7 @@ export function DashboardGridEditable({
>
<div className="relative">
{isEditing && (
<div className="absolute inset-0 z-10 bg-background/50 backdrop-blur-[1px] rounded-lg border-2 border-dashed border-primary/50 flex items-center justify-center">
<div className="absolute inset-0 z-10 bg-background/50 backdrop-blur-xs rounded-lg border border-dashed border-primary flex items-center justify-center">
<div className="flex flex-col items-center gap-2">
<RiDragMove2Line className="size-8 text-primary" />
<span className="text-xs font-medium">

View File

@@ -41,6 +41,7 @@ const CARDS = [
"Consideramos lançamentos efetivados e não efetivados da pessoa principal (admin).",
"Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.",
"Não entram transferências internas nem lançamentos automáticos de fatura.",
"Reembolsos não entram como receita; eles abatem despesas e afetam o balanço líquido.",
"Saldo inicial também fica fora quando a conta está marcada para desconsiderá-lo das receitas.",
],
},
@@ -57,6 +58,7 @@ const CARDS = [
"Consideramos lançamentos efetivados e não efetivados da pessoa principal (admin).",
"Movimentações de contas marcadas como não consideradas no saldo total ficam fora deste card.",
"Não entram transferências internas nem lançamentos automáticos de fatura.",
"Reembolsos do período reduzem o total de despesas, sem deixar o card negativo.",
"O valor mostrado é a saída efetiva do período, sempre em número positivo no card.",
],
},
@@ -70,6 +72,7 @@ const CARDS = [
helpTitle: "Como calculamos o balanço",
helpLines: [
"Partimos de receitas menos despesas do período.",
"Reembolsos entram no resultado líquido, mas não inflam receitas nem despesas.",
"Receitas e despesas de contas marcadas como não consideradas no saldo total ficam fora do cálculo base.",
"Depois aplicamos ajustes de transferências entre contas consideradas e não consideradas no saldo total.",
"Se a transferência entra em conta considerada, soma. Se sai de conta considerada para conta não considerada, subtrai.",

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);
}

View File

@@ -3,6 +3,7 @@ import { financialAccounts, transactions } from "@/db/schema";
import {
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
INITIAL_BALANCE_NOTE,
REFUND_NOTE_PREFIX,
} from "@/shared/lib/accounts/constants";
export { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters";
@@ -27,6 +28,12 @@ export const excludeAutoInvoiceEntries = () =>
not(ilike(transactions.note, `${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`)),
);
export const excludeRefundEntries = () =>
or(
isNull(transactions.note),
not(ilike(transactions.note, `${REFUND_NOTE_PREFIX}%`)),
);
export const excludeInitialBalanceWhenConfigured = () =>
or(
isNull(transactions.note),