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,16 +1,16 @@
"use client";
import { cn } from "@/lib/utils/ui";
import {
RiArrowLeftRightLine,
RiDeleteBin5Line,
RiEyeOffLine,
RiFileList2Line,
RiPencilLine,
RiInformationLine,
} from "@remixicon/react";
import type React from "react";
import MoneyValues from "../money-values";
import { Card, CardContent, CardFooter } from "../ui/card";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
interface AccountCardProps {
accountName: string;
@@ -19,6 +19,7 @@ interface AccountCardProps {
status?: string;
icon?: React.ReactNode;
excludeFromBalance?: boolean;
excludeInitialBalanceFromIncome?: boolean;
onViewStatement?: () => void;
onEdit?: () => void;
onRemove?: () => void;
@@ -33,6 +34,7 @@ export function AccountCard({
status,
icon,
excludeFromBalance,
excludeInitialBalanceFromIncome,
onViewStatement,
onEdit,
onRemove,
@@ -85,21 +87,39 @@ export function AccountCard({
<h2 className="text-lg font-semibold text-foreground">
{accountName}
</h2>
{excludeFromBalance ? (
<div
className="flex items-center gap-1 text-muted-foreground"
title="Excluída do saldo geral"
>
<RiEyeOffLine className="size-4" aria-hidden />
</div>
) : null}
{(excludeFromBalance || excludeInitialBalanceFromIncome) && (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<RiInformationLine className="size-5 text-muted-foreground hover:text-foreground transition-colors cursor-help" />
</div>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs">
<div className="space-y-1">
{excludeFromBalance && (
<p className="text-xs">
<strong>Desconsiderado do saldo total:</strong> Esta conta
não é incluída no cálculo do saldo total geral.
</p>
)}
{excludeInitialBalanceFromIncome && (
<p className="text-xs">
<strong>
Saldo inicial desconsiderado das receitas:
</strong>{" "}
O saldo inicial desta conta não é contabilizado como
receita nas métricas.
</p>
)}
</div>
</TooltipContent>
</Tooltip>
)}
</div>
<div className="space-y-1">
<p className="text-sm text-muted-foreground">Saldo</p>
<p className="text-3xl text-foreground">
<MoneyValues amount={balance} className="text-3xl" />
</p>
<div className="space-y-2">
<MoneyValues amount={balance} className="text-3xl" />
<p className="text-sm text-muted-foreground">{accountType}</p>
</div>
</CardContent>

View File

@@ -38,7 +38,7 @@ const DEFAULT_ACCOUNT_TYPES = [
"Conta Poupança",
"Carteira Digital",
"Conta Investimento",
"Cartão Pré-pago",
"Pré-Pago | VR/VA",
] as const;
const DEFAULT_ACCOUNT_STATUS = ["Ativa", "Inativa"] as const;
@@ -75,6 +75,8 @@ const buildInitialValues = ({
logo: selectedLogo,
initialBalance: formatInitialBalanceInput(account?.initialBalance ?? 0),
excludeFromBalance: account?.excludeFromBalance ?? false,
excludeInitialBalanceFromIncome:
account?.excludeInitialBalanceFromIncome ?? false,
};
};

View File

@@ -106,17 +106,39 @@ export function AccountFormFields({
/>
</div>
<div className="flex items-center gap-2 sm:col-span-2">
<Checkbox
id="exclude-from-balance"
checked={values.excludeFromBalance}
onCheckedChange={(checked) =>
onChange("excludeFromBalance", checked ? "true" : "false")
}
/>
<Label htmlFor="exclude-from-balance" className="cursor-pointer text-sm font-normal">
Excluir do saldo total (útil para contas de investimento ou reserva)
</Label>
<div className="flex flex-col gap-3 sm:col-span-2">
<div className="flex items-center gap-2">
<Checkbox
id="exclude-from-balance"
checked={values.excludeFromBalance === true || values.excludeFromBalance === "true"}
onCheckedChange={(checked) =>
onChange("excludeFromBalance", !!checked ? "true" : "false")
}
/>
<Label
htmlFor="exclude-from-balance"
className="cursor-pointer text-sm font-normal leading-tight"
>
Desconsiderar do saldo total (útil para contas de investimento ou
reserva)
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox
id="exclude-initial-balance-from-income"
checked={values.excludeInitialBalanceFromIncome === true || values.excludeInitialBalanceFromIncome === "true"}
onCheckedChange={(checked) =>
onChange("excludeInitialBalanceFromIncome", !!checked ? "true" : "false")
}
/>
<Label
htmlFor="exclude-initial-balance-from-income"
className="cursor-pointer text-sm font-normal leading-tight"
>
Desconsiderar o saldo inicial ao calcular o total de receitas
</Label>
</div>
</div>
</div>
);

View File

@@ -137,10 +137,13 @@ export function AccountsPage({ accounts, logoOptions }: AccountsPageProps) {
<AccountCard
key={account.id}
accountName={account.name}
accountType={`${account.accountType} - ${account.status}`}
accountType={`${account.accountType}`}
balance={account.balance ?? account.initialBalance ?? 0}
status={account.status}
excludeFromBalance={account.excludeFromBalance}
excludeInitialBalanceFromIncome={
account.excludeInitialBalanceFromIncome
}
icon={
logoSrc ? (
<Image

View File

@@ -8,6 +8,7 @@ export type Account = {
initialBalance: number;
balance?: number | null;
excludeFromBalance?: boolean;
excludeInitialBalanceFromIncome?: boolean;
};
export type AccountFormValues = {
@@ -18,4 +19,5 @@ export type AccountFormValues = {
logo: string;
initialBalance: string;
excludeFromBalance: boolean;
excludeInitialBalanceFromIncome: boolean;
};