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

@@ -48,6 +48,20 @@ const deleteAccountSchema = z.object({
}),
});
const updatePreferencesSchema = z.object({
disableMagnetlines: z.boolean(),
periodMonthsBefore: z
.number()
.int("Deve ser um número inteiro")
.min(1, "Mínimo de 1 mês")
.max(24, "Máximo de 24 meses"),
periodMonthsAfter: z
.number()
.int("Deve ser um número inteiro")
.min(1, "Mínimo de 1 mês")
.max(24, "Máximo de 24 meses"),
});
// Actions
export async function updateNameAction(
@@ -327,3 +341,73 @@ export async function deleteAccountAction(
};
}
}
export async function updatePreferencesAction(
data: z.infer<typeof updatePreferencesSchema>
): Promise<ActionResponse> {
try {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session?.user?.id) {
return {
success: false,
error: "Não autenticado",
};
}
const validated = updatePreferencesSchema.parse(data);
// Check if preferences exist, if not create them
const existingResult = await db
.select()
.from(schema.userPreferences)
.where(eq(schema.userPreferences.userId, session.user.id))
.limit(1);
const existing = existingResult[0] || null;
if (existing) {
// Update existing preferences
await db
.update(schema.userPreferences)
.set({
disableMagnetlines: validated.disableMagnetlines,
periodMonthsBefore: validated.periodMonthsBefore,
periodMonthsAfter: validated.periodMonthsAfter,
updatedAt: new Date(),
})
.where(eq(schema.userPreferences.userId, session.user.id));
} else {
// Create new preferences
await db.insert(schema.userPreferences).values({
userId: session.user.id,
disableMagnetlines: validated.disableMagnetlines,
periodMonthsBefore: validated.periodMonthsBefore,
periodMonthsAfter: validated.periodMonthsAfter,
});
}
// Revalidar o layout do dashboard
revalidatePath("/", "layout");
return {
success: true,
message: "Preferências atualizadas com sucesso",
};
} catch (error) {
if (error instanceof z.ZodError) {
return {
success: false,
error: error.issues[0]?.message || "Dados inválidos",
};
}
console.error("Erro ao atualizar preferências:", error);
return {
success: false,
error: "Erro ao atualizar preferências. Tente novamente.",
};
}
}