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,178 +0,0 @@
import { execSync } from "child_process";
import fs from "fs";
import path from "path";
interface ChangelogEntry {
id: string;
type: string;
title: string;
date: string;
icon: string;
category: string;
}
function getIcon(type: string): string {
const icons: Record<string, string> = {
feat: "✨",
fix: "🐛",
perf: "🚀",
docs: "📝",
style: "🎨",
refactor: "♻️",
test: "🧪",
chore: "🔧",
};
return icons[type] || "📦";
}
function getCategory(type: string): string {
const categories: Record<string, string> = {
feat: "feature",
fix: "bugfix",
perf: "performance",
docs: "documentation",
style: "style",
refactor: "refactor",
test: "test",
chore: "chore",
};
return categories[type] || "other";
}
function getCategoryLabel(category: string): string {
const labels: Record<string, string> = {
feature: "Novidades",
bugfix: "Correções",
performance: "Performance",
documentation: "Documentação",
style: "Interface",
refactor: "Melhorias",
test: "Testes",
chore: "Manutenção",
other: "Outros",
};
return labels[category] || "Outros";
}
function generateChangelog() {
try {
console.log("🔍 Gerando changelog dos últimos commits...\n");
// Pega commits dos últimos 30 dias
const gitCommand =
'git log --since="30 days ago" --pretty=format:"%H|%s|%ai" --no-merges';
let output: string;
try {
output = execSync(gitCommand, { encoding: "utf-8" });
} catch (error) {
console.warn("⚠️ Não foi possível acessar o Git. Gerando changelog vazio.");
output = "";
}
if (!output.trim()) {
console.log(" Nenhum commit encontrado nos últimos 30 dias.");
const emptyChangelog = {
version: "1.0.0",
generatedAt: new Date().toISOString(),
entries: [],
};
const publicDir = path.join(process.cwd(), "public");
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
fs.writeFileSync(
path.join(publicDir, "changelog.json"),
JSON.stringify(emptyChangelog, null, 2)
);
return;
}
const commits = output
.split("\n")
.filter((line) => line.trim())
.map((line) => {
const [hash, message, date] = line.split("|");
return { hash, message, date };
});
console.log(`📝 Processando ${commits.length} commits...\n`);
// Parseia conventional commits
const entries: ChangelogEntry[] = commits
.map((commit) => {
// Match conventional commit format: type: message or type(scope): message
const match = commit.message.match(
/^(feat|fix|perf|docs|style|refactor|test|chore)(\(.+\))?:\s*(.+)$/
);
if (!match) {
// Ignora commits que não seguem o padrão
return null;
}
const [, type, , title] = match;
return {
id: commit.hash,
type,
title: title.trim(),
date: commit.date,
icon: getIcon(type),
category: getCategory(type),
};
})
.filter((entry): entry is ChangelogEntry => entry !== null);
console.log(`${entries.length} commits válidos encontrados\n`);
// Agrupa por categoria
const grouped = entries.reduce(
(acc, entry) => {
if (!acc[entry.category]) {
acc[entry.category] = [];
}
acc[entry.category].push(entry);
return acc;
},
{} as Record<string, ChangelogEntry[]>
);
// Mostra resumo
Object.entries(grouped).forEach(([category, items]) => {
console.log(
`${getIcon(items[0].type)} ${getCategoryLabel(category)}: ${items.length}`
);
});
// Pega versão do package.json
const packageJson = JSON.parse(
fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8")
);
const changelog = {
version: packageJson.version || "1.0.0",
generatedAt: new Date().toISOString(),
entries: entries.slice(0, 20), // Limita a 20 mais recentes
};
// Salva em public/changelog.json
const publicDir = path.join(process.cwd(), "public");
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
const changelogPath = path.join(publicDir, "changelog.json");
fs.writeFileSync(changelogPath, JSON.stringify(changelog, null, 2));
console.log(`\n✅ Changelog gerado com sucesso em: ${changelogPath}`);
} catch (error) {
console.error("❌ Erro ao gerar changelog:", error);
// Não falha o build, apenas avisa
process.exit(0);
}
}
generateChangelog();