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

@@ -0,0 +1,23 @@
import PageDescription from "@/components/page-description";
import { RiGitCommitLine } from "@remixicon/react";
export const metadata = {
title: "Changelog | Opensheets",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="space-y-6 px-6">
<PageDescription
icon={<RiGitCommitLine />}
title="Changelog"
subtitle="Histórico completo de alterações e atualizações do projeto."
/>
{children}
</section>
);
}

View File

@@ -0,0 +1,31 @@
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
<main className="container mx-auto px-4 py-8 max-w-4xl">
<div className="mb-8">
<Skeleton className="h-9 w-48 mb-2" />
<Skeleton className="h-5 w-96" />
</div>
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<Card key={i}>
<CardHeader className="pb-3">
<Skeleton className="h-6 w-3/4 mb-2" />
<div className="flex gap-3">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-24" />
</div>
</CardHeader>
<CardContent className="pt-0">
<Skeleton className="h-4 w-32" />
</CardContent>
</Card>
))}
</div>
</main>
);
}

View File

@@ -0,0 +1,102 @@
import { ChangelogList } from "@/components/changelog/changelog-list";
import { execSync } from "child_process";
type GitCommit = {
hash: string;
shortHash: string;
author: string;
date: string;
message: string;
body: string;
filesChanged: string[];
};
function getGitRemoteUrl(): string | null {
try {
const remoteUrl = execSync("git config --get remote.origin.url", {
encoding: "utf-8",
cwd: process.cwd(),
}).trim();
// Converter SSH para HTTPS se necessário
if (remoteUrl.startsWith("git@")) {
return remoteUrl
.replace("git@github.com:", "https://github.com/")
.replace("git@gitlab.com:", "https://gitlab.com/")
.replace(".git", "");
}
return remoteUrl.replace(".git", "");
} catch (error) {
console.error("Error fetching git remote URL:", error);
return null;
}
}
function getGitCommits(): GitCommit[] {
try {
// Buscar os últimos 50 commits
const commits = execSync(
'git log -50 --pretty=format:"%H|%h|%an|%ad|%s|%b" --date=iso --name-only',
{
encoding: "utf-8",
cwd: process.cwd(),
}
)
.trim()
.split("\n\n");
return commits
.map((commitBlock) => {
const lines = commitBlock.split("\n");
const [hash, shortHash, author, date, message, ...rest] =
lines[0].split("|");
// Separar body e arquivos
const bodyLines: string[] = [];
const filesChanged: string[] = [];
let isBody = true;
rest.forEach((line) => {
if (line && !line.includes("/") && !line.includes(".")) {
bodyLines.push(line);
} else {
isBody = false;
}
});
lines.slice(1).forEach((line) => {
if (line.trim()) {
filesChanged.push(line.trim());
}
});
return {
hash,
shortHash,
author,
date,
message,
body: bodyLines.join("\n").trim(),
filesChanged: filesChanged.filter(
(f) => f && !f.startsWith("git log")
),
};
})
.filter((commit) => commit.hash && commit.message);
} catch (error) {
console.error("Error fetching git commits:", error);
return [];
}
}
export default async function ChangelogPage() {
const commits = getGitCommits();
const repoUrl = getGitRemoteUrl();
return (
<main>
<ChangelogList commits={commits} repoUrl={repoUrl} />
</main>
);
}