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

@@ -35,6 +35,8 @@ import { Textarea } from "@/components/ui/textarea";
import { useControlledState } from "@/hooks/use-controlled-state";
import { useFormState } from "@/hooks/use-form-state";
import type { EligibleInstallment } from "@/lib/installments/anticipation-types";
import type { PeriodPreferences } from "@/lib/user-preferences/period";
import { createMonthOptions } from "@/lib/utils/period";
import { RiLoader4Line } from "@remixicon/react";
import {
useCallback,
@@ -53,6 +55,7 @@ interface AnticipateInstallmentsDialogProps {
categorias: Array<{ id: string; name: string; icon: string | null }>;
pagadores: Array<{ id: string; name: string }>;
defaultPeriod: string;
periodPreferences: PeriodPreferences;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
@@ -65,57 +68,6 @@ type AnticipationFormValues = {
note: string;
};
type SelectOption = {
value: string;
label: string;
};
const monthFormatter = new Intl.DateTimeFormat("pt-BR", {
month: "long",
year: "numeric",
});
const formatPeriodLabel = (period: string) => {
const [year, month] = period.split("-").map(Number);
if (!year || !month) {
return period;
}
const date = new Date(year, month - 1, 1);
if (Number.isNaN(date.getTime())) {
return period;
}
const label = monthFormatter.format(date);
return label.charAt(0).toUpperCase() + label.slice(1);
};
const buildPeriodOptions = (currentValue?: string): SelectOption[] => {
const now = new Date();
const options: SelectOption[] = [];
// Adiciona opções de 3 meses no passado até 6 meses no futuro
for (let offset = -3; offset <= 6; offset += 1) {
const date = new Date(now.getFullYear(), now.getMonth() + offset, 1);
const value = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
2,
"0"
)}`;
options.push({ value, label: formatPeriodLabel(value) });
}
// Adiciona o valor atual se não estiver na lista
if (
currentValue &&
!options.some((option) => option.value === currentValue)
) {
options.push({
value: currentValue,
label: formatPeriodLabel(currentValue),
});
}
return options.sort((a, b) => a.value.localeCompare(b.value));
};
export function AnticipateInstallmentsDialog({
trigger,
seriesId,
@@ -123,6 +75,7 @@ export function AnticipateInstallmentsDialog({
categorias,
pagadores,
defaultPeriod,
periodPreferences,
open,
onOpenChange,
}: AnticipateInstallmentsDialogProps) {
@@ -152,8 +105,13 @@ export function AnticipateInstallmentsDialog({
});
const periodOptions = useMemo(
() => buildPeriodOptions(formState.anticipationPeriod),
[formState.anticipationPeriod]
() =>
createMonthOptions(
formState.anticipationPeriod,
periodPreferences.monthsBefore,
periodPreferences.monthsAfter
),
[formState.anticipationPeriod, periodPreferences.monthsBefore, periodPreferences.monthsAfter]
);
// Buscar parcelas elegíveis ao abrir o dialog