feat: implementar sistema de preferências do usuário e refatorar changelog

Adiciona sistema completo de preferências de usuário:
  - Cria tabela userPreferences no schema com campos disableMagnetlines, periodMonthsBefore e periodMonthsAfter
  - Implementa página de Ajustes com abas (Preferências, Alterar nome, Senha, E-mail, Deletar conta)
  - Adiciona componente PreferencesForm para configuração de magnetlines e períodos de exibição
  - Propaga periodPreferences para todos os componentes de lançamentos e calendário

  Refatora sistema de changelog:
  - Remove implementação anterior baseada em JSON estático
  - Adiciona nova página de changelog dinâmica em app/(dashboard)/changelog
  - Adiciona componente changelog-list.tsx
  - Remove arquivos obsoletos (changelog-notification, actions, data, utils, scripts)

  Adiciona controle de saldo inicial em contas:
  - Novo campo excludeInitialBalanceFromIncome em contas
  - Permite excluir saldo inicial do cálculo de receitas
  - Atualiza queries de lançamentos para respeitar esta configuração

  Melhorias adicionais:
  - Adiciona componente ui/accordion.tsx do shadcn/ui
  - Refatora formatPeriodLabel para displayPeriod centralizado
  - Propaga estabelecimentos para componentes de lançamentos
  - Remove variável DB_PROVIDER obsoleta do .env.example e documentação
  - Adiciona 6 migrações de banco de dados (0003-0008)
This commit is contained in:
Felipe Coutinho
2026-01-03 14:18:03 +00:00
parent 3eca48c71a
commit fd817683ca
87 changed files with 13582 additions and 1445 deletions

View File

@@ -1,3 +1,4 @@
import { getRecentEstablishmentsAction } from "@/app/(dashboard)/lancamentos/actions";
import { AccountDialog } from "@/components/contas/account-dialog";
import { AccountStatementCard } from "@/components/contas/account-statement-card";
import type { Account } from "@/components/contas/types";
@@ -19,6 +20,7 @@ import {
type ResolvedSearchParams,
} from "@/lib/lancamentos/page-helpers";
import { loadLogoOptions } from "@/lib/logo/options";
import { fetchUserPeriodPreferences } from "@/lib/user-preferences/period";
import { parsePeriodParam } from "@/lib/utils/period";
import { RiPencilLine } from "@remixicon/react";
import { and, desc, eq } from "drizzle-orm";
@@ -55,10 +57,18 @@ export default async function Page({ params, searchParams }: PageProps) {
notFound();
}
const [filterSources, logoOptions, accountSummary] = await Promise.all([
const [
filterSources,
logoOptions,
accountSummary,
estabelecimentos,
periodPreferences,
] = await Promise.all([
fetchLancamentoFilterSources(userId),
loadLogoOptions(),
fetchAccountSummary(userId, contaId, selectedPeriod),
getRecentEstablishmentsAction(),
fetchUserPeriodPreferences(userId),
]);
const sluggedFilters = buildSluggedFilters(filterSources);
const slugMaps = buildSlugMaps(sluggedFilters);
@@ -165,6 +175,8 @@ export default async function Page({ params, searchParams }: PageProps) {
categoriaFilterOptions={categoriaFilterOptions}
contaCartaoFilterOptions={contaCartaoFilterOptions}
selectedPeriod={selectedPeriod}
estabelecimentos={estabelecimentos}
periodPreferences={periodPreferences}
allowCreate={false}
/>
</section>

View File

@@ -56,6 +56,9 @@ const accountBaseSchema = z.object({
excludeFromBalance: z
.union([z.boolean(), z.string()])
.transform((value) => value === true || value === "true"),
excludeInitialBalanceFromIncome: z
.union([z.boolean(), z.string()])
.transform((value) => value === true || value === "true"),
});
const createAccountSchema = accountBaseSchema;
@@ -93,6 +96,7 @@ export async function createAccountAction(
logo: logoFile,
initialBalance: formatDecimalForDbRequired(data.initialBalance),
excludeFromBalance: data.excludeFromBalance,
excludeInitialBalanceFromIncome: data.excludeInitialBalanceFromIncome,
userId: user.id,
})
.returning({ id: contas.id, name: contas.name });
@@ -183,6 +187,7 @@ export async function updateAccountAction(
logo: logoFile,
initialBalance: formatDecimalForDbRequired(data.initialBalance),
excludeFromBalance: data.excludeFromBalance,
excludeInitialBalanceFromIncome: data.excludeInitialBalanceFromIncome,
})
.where(and(eq(contas.id, data.id), eq(contas.userId, user.id)))
.returning();

View File

@@ -15,6 +15,7 @@ export type AccountData = {
initialBalance: number;
balance: number;
excludeFromBalance: boolean;
excludeInitialBalanceFromIncome: boolean;
};
export async function fetchAccountsForUser(
@@ -31,6 +32,7 @@ export async function fetchAccountsForUser(
logo: contas.logo,
initialBalance: contas.initialBalance,
excludeFromBalance: contas.excludeFromBalance,
excludeInitialBalanceFromIncome: contas.excludeInitialBalanceFromIncome,
balanceMovements: sql<number>`
coalesce(
sum(
@@ -67,7 +69,8 @@ export async function fetchAccountsForUser(
contas.note,
contas.logo,
contas.initialBalance,
contas.excludeFromBalance
contas.excludeFromBalance,
contas.excludeInitialBalanceFromIncome
),
loadLogoOptions(),
]);
@@ -84,6 +87,7 @@ export async function fetchAccountsForUser(
Number(account.initialBalance ?? 0) +
Number(account.balanceMovements ?? 0),
excludeFromBalance: account.excludeFromBalance,
excludeInitialBalanceFromIncome: account.excludeInitialBalanceFromIncome,
}));
return { accounts, logoOptions };