Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78c3ed5995 | ||
|
|
c34adba587 | ||
|
|
99bc049cf4 | ||
|
|
35abe1b0bf | ||
|
|
402f0072af | ||
|
|
02ee5bb758 | ||
|
|
41eecc2538 | ||
|
|
cdcc677787 | ||
|
|
e50eeba36e | ||
|
|
26cb18a9ad | ||
|
|
382727a96d | ||
|
|
0df648c7f3 | ||
|
|
27f361923c | ||
|
|
60b2612e8a | ||
|
|
0171b0ce2f | ||
|
|
311369f81b | ||
|
|
ef2c8c50e8 | ||
|
|
5319d8a5a6 | ||
|
|
37247e319c | ||
|
|
766af2b347 | ||
|
|
5dcd30010e | ||
|
|
d589df6993 | ||
|
|
8a19f0f311 | ||
|
|
887885cd98 | ||
|
|
7a0e33efd8 | ||
|
|
b9557961e5 | ||
|
|
53c8e47981 | ||
|
|
adc9292cd8 | ||
|
|
b95d6f6752 | ||
|
|
c9f667a065 | ||
|
|
01d9c6ea05 | ||
|
|
d383d2db91 | ||
|
|
7a8d01debe | ||
|
|
3be15d3b15 | ||
|
|
fea9cf81d8 | ||
|
|
7a10d431ab | ||
|
|
b7343eb235 | ||
|
|
3bcc392f38 | ||
|
|
5241de44af | ||
|
|
1a75662120 | ||
|
|
7ca3f92467 | ||
|
|
6b044f3bc5 | ||
|
|
4e8f9cc5fa | ||
|
|
b6659ef66e | ||
|
|
21d7396c80 | ||
|
|
3a768bc8ba | ||
|
|
8a03a50132 | ||
|
|
246bb14a00 | ||
|
|
86bcffec66 | ||
|
|
81e7151876 | ||
|
|
0bb664884a | ||
|
|
f02958df1d | ||
|
|
c4c52c02ab | ||
|
|
c9239c4f3c | ||
|
|
7128cc0ae7 | ||
|
|
467f71493d | ||
|
|
0cec10ede3 | ||
|
|
a6fba5f953 | ||
|
|
18893bfe02 | ||
|
|
7fdf9e2876 | ||
|
|
7d0781b035 | ||
|
|
b9b843b9db | ||
|
|
01215b3124 | ||
|
|
d70223e7b3 | ||
|
|
6ea064e1bd | ||
|
|
9c0669a152 | ||
|
|
b2d4b29cb5 | ||
|
|
1df2ba787d | ||
|
|
e5d9b66cca | ||
|
|
37edb1b76d | ||
|
|
6288f5f8d4 | ||
|
|
57ac326c2a | ||
|
|
dccc18b1c1 | ||
|
|
0cb01a1d4c | ||
|
|
51652da4f8 | ||
|
|
7a74f9405e | ||
|
|
94bf93194f | ||
|
|
d55173e8c1 | ||
|
|
4a73088c09 | ||
|
|
eaa20448a8 | ||
|
|
367d78d43d | ||
|
|
2fc6d11d78 | ||
|
|
0f5c735be0 | ||
|
|
4bea6330bf | ||
|
|
8389752172 | ||
|
|
19b5aa00ee | ||
|
|
863ccc0fd2 | ||
|
|
29d99cbedb | ||
|
|
dbeb98bbe4 | ||
|
|
c0436dc2ac | ||
|
|
e1e76fadc0 | ||
|
|
9b2c15ef7d | ||
|
|
fbe3fceb9f | ||
|
|
39f3cd8b20 | ||
|
|
791fec7751 | ||
|
|
114e2b4011 | ||
|
|
f15a003cef | ||
|
|
7f07a9cbf6 | ||
|
|
5fa234884e | ||
|
|
b453b432ed | ||
|
|
7f05d2a681 | ||
|
|
b14f487824 | ||
|
|
5b03824a72 | ||
|
|
74dda549f5 | ||
|
|
137b63f256 | ||
|
|
f747405264 | ||
|
|
cbc17c8513 | ||
|
|
c41fafc319 | ||
|
|
0bc3f06b77 | ||
|
|
2f68bcf039 | ||
|
|
41dcd5cec9 | ||
|
|
6391f07eb6 | ||
|
|
ae9dd364c4 | ||
|
|
e005add233 | ||
|
|
6d81ff8b53 | ||
|
|
5d84ae928a | ||
|
|
ba05985725 | ||
|
|
3e80d5995b | ||
|
|
68daae7926 | ||
|
|
9413c470a8 | ||
|
|
ad1b0aa979 | ||
|
|
4d9a1c0a35 | ||
|
|
5635705c56 | ||
|
|
4c97ed569d | ||
|
|
22a88de993 | ||
|
|
9456aa98bc | ||
|
|
21c6a8d9d0 | ||
|
|
c29ffa9a12 | ||
|
|
8875de843b | ||
|
|
679ea752bb | ||
|
|
1161e97d9e | ||
|
|
55d7dedd9a | ||
|
|
ad2752b7b0 | ||
|
|
58db357cde | ||
|
|
99a9ff5512 | ||
|
|
5bcf4f69d3 | ||
|
|
95099c1a94 | ||
|
|
94912f7edc | ||
|
|
bf6adfa3f1 | ||
|
|
e4b9dd4254 | ||
|
|
f1907c8697 | ||
|
|
805bcb863d | ||
|
|
11b4f8940f | ||
|
|
fba9686fdb | ||
|
|
9b8ac9f71f | ||
|
|
fa41c78a39 | ||
|
|
5f7bfb98da | ||
|
|
9ecafdb15f | ||
|
|
e8cc673e52 | ||
|
|
3bd8117b65 | ||
|
|
a7268d8f05 | ||
|
|
1f9098879e | ||
|
|
7a3bff52ac | ||
|
|
dfb4126b12 | ||
|
|
ffead579fa | ||
|
|
aa85cf8b29 | ||
|
|
9a7ae0fa3d | ||
|
|
98fe6a0f4f | ||
|
|
d10eae13e5 | ||
|
|
43697b4fd2 | ||
|
|
27e3ba5f0d | ||
|
|
31485eec8f |
27
.env.example
@@ -3,10 +3,10 @@
|
||||
# ============================================
|
||||
|
||||
# === Database ===
|
||||
# PostgreSQL local (Docker): use host "db"
|
||||
# PostgreSQL local (sem Docker): use host "localhost"
|
||||
# Desenvolvimento local (pnpm dev): use host "localhost" (padrão abaixo)
|
||||
# Docker Compose completo: o compose.yml define DATABASE_URL automaticamente com host "db"
|
||||
# PostgreSQL remoto: use URL completa do provider
|
||||
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@db:5432/openmonetis_db
|
||||
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
|
||||
|
||||
# Credenciais do PostgreSQL (apenas para Docker local) - Alterar
|
||||
POSTGRES_USER=openmonetis
|
||||
@@ -17,6 +17,11 @@ POSTGRES_DB=openmonetis_db
|
||||
# Gere com: openssl rand -base64 32
|
||||
BETTER_AUTH_SECRET=your-secret-key-here-change-this
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
# Defina como true para bloquear novos cadastros
|
||||
DISABLE_SIGNUP=false
|
||||
# Duração de sessões persistentes quando "Manter conectado" estiver marcado
|
||||
AUTH_SESSION_EXPIRES_IN_DAYS=30
|
||||
AUTH_SESSION_UPDATE_AGE_HOURS=24
|
||||
|
||||
# === Portas ===
|
||||
APP_PORT=3000
|
||||
@@ -44,8 +49,22 @@ GOOGLE_CLIENT_SECRET=
|
||||
# Se não definido, todas as rotas ficam acessíveis.
|
||||
# PUBLIC_DOMAIN=openmonetis.com
|
||||
|
||||
# === Analytics (Opcional) ===
|
||||
# Umami: https://umami.is — self-hosted ou cloud
|
||||
UMAMI_URL=
|
||||
UMAMI_WEBSITE_ID=
|
||||
UMAMI_DOMAINS=
|
||||
|
||||
# === AI Providers (Opcional) ===
|
||||
ANTHROPIC_API_KEY=
|
||||
OPENAI_API_KEY=
|
||||
GOOGLE_GENERATIVE_AI_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
MINIMAX_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
OLLAMA_BASE_URL=http://127.0.0.1:11434/v1
|
||||
OLLAMA_API_KEY=
|
||||
|
||||
# === Logo.dev (Opcional) ===
|
||||
# Logos automáticos de estabelecimentos. Cadastre em https://www.logo.dev
|
||||
LOGO_DEV_TOKEN=
|
||||
LOGO_DEV_SECRET_KEY=
|
||||
|
||||
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Força LF para arquivos que precisam de line endings Unix no container
|
||||
*.sh text eol=lf
|
||||
docker-entrypoint.sh text eol=lf
|
||||
Dockerfile text eol=lf
|
||||
9
.github/workflows/docker-publish.yml
vendored
@@ -13,22 +13,19 @@ on:
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE_NAME: openmonetis
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.33.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
@@ -48,7 +45,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
5
.github/workflows/release.yml
vendored
@@ -5,9 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,7 +13,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
1
.gitignore
vendored
@@ -106,6 +106,7 @@ docker-compose.override.yml
|
||||
.cursor/
|
||||
QWEN.md
|
||||
AGENTS.md
|
||||
.codex
|
||||
# === Backups locais ===
|
||||
/backup/
|
||||
|
||||
|
||||
32
.vscode/settings.json
vendored
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/node_modules": true,
|
||||
"node_modules": true,
|
||||
"**/.vscode": true,
|
||||
".vscode": true,
|
||||
"**/.next": true,
|
||||
".next": true
|
||||
},
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "never",
|
||||
"source.organizeImports.biome": "always",
|
||||
"source.fixAll": "never",
|
||||
"source.fixAll.biome": "always",
|
||||
"source.fixAll.eslint": "never"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"prettier.enable": false,
|
||||
"editor.fontSize": 15,
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
}
|
||||
}
|
||||
602
CHANGELOG.md
@@ -5,10 +5,538 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo.
|
||||
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
|
||||
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
|
||||
|
||||
## [Unreleased]
|
||||
## [2.7.1] - 2026-05-30
|
||||
|
||||
Esta versão melhora a clareza dos fluxos de lançamento e a experiência do dashboard. Boletos de receita agora diferenciam pagamentos de recebimentos, a navegação mensal ficou mais direta e o painel ganhou atalhos mais úteis com personalização simplificada.
|
||||
|
||||
### Adicionado
|
||||
- Preferências: nova opção para exibir ou ocultar o card `Resumo da operação` no modal de lançamento.
|
||||
- Navegação mensal: ao passar o mouse, focar ou clicar no período selecionado, agora é possível abrir um seletor e ir diretamente para outro mês.
|
||||
|
||||
### Alterado
|
||||
- Documentação: o guia visual foi reescrito com os tokens, temas, componentes e práticas de acessibilidade atuais; o README agora apresenta a identidade visual e as preferências disponíveis.
|
||||
- Dashboard: os cards de receitas e despesas agora oferecem um atalho discreto para abrir os lançamentos da pessoa principal filtrados pelo tipo e período.
|
||||
- Dashboard: a configuração e a reordenação de widgets agora partem de uma única ação `Personalizar`, com controle de visibilidade durante a edição.
|
||||
- Dashboard: em telas pequenas, os atalhos para receita, despesa e anotação foram agrupados no menu `Adicionar`.
|
||||
- Dashboard: os títulos dos widgets agora usam sentence case para reduzir ruído visual.
|
||||
- Dashboard: os widgets receberam uma revisão ampla de UX, com hierarquia visual mais clara, listas compactas, textos mais diretos, estados acessíveis e navegação interna consistente.
|
||||
- Dashboard: o widget `Comportamento de pagamento` foi renomeado para `Distribuição de despesas`.
|
||||
- Dashboard: limites de orçamento agora aparecem apenas no widget de despesas por categoria.
|
||||
- Dashboard: o widget `Panorama de gastos` agora exibe todos os lançamentos sem filtro adicional por cartão.
|
||||
- Navegação: o menu de finanças agora oferece submenus para abrir diretamente as faturas dos cartões e os extratos das contas ativas.
|
||||
- Lançamentos: ao passar o mouse sobre `Filtros`, os filtros ativos agora aparecem em um painel compacto com remoção individual e ação para limpar todos de uma vez.
|
||||
|
||||
### Corrigido
|
||||
- Dashboard: o saldo consolidado do widget `Minhas contas` não inclui mais contas inativas.
|
||||
- Boletos: lançamentos de receita agora exibem ações e status como `Receber`, `Recebido` e `Recebido em`, enquanto despesas continuam usando `Pagar`, `Pago` e `Pago em`.
|
||||
- Dashboard: o modal de baixa de boleto agora usa textos de recebimento e conta de destino para receitas.
|
||||
- Calendário e pessoas: os detalhes de boletos de receita agora preservam a nomenclatura de recebimento.
|
||||
|
||||
## [2.7.0] - 2026-05-28
|
||||
|
||||
Esta versão amplia o OpenMonetis para quem usa o app todos os dias e para quem prefere mais controle sobre os próprios dados. Os Insights ganham novas opções de IA, incluindo modelos locais via Ollama, enquanto a autenticação fica mais confortável em dispositivos pessoais. Também entram melhorias práticas em contas, lançamentos compartilhados, filtros, relatórios e dashboard, deixando os fluxos financeiros mais completos e fáceis de revisar.
|
||||
|
||||
### Adicionado
|
||||
- Autenticação: a tela de login agora tem a opção "Manter conectado neste dispositivo", usando a persistência nativa do Better Auth para evitar novo login ao reabrir o navegador ou PWA.
|
||||
- Autenticação: novas variáveis `AUTH_SESSION_EXPIRES_IN_DAYS` e `AUTH_SESSION_UPDATE_AGE_HOURS` para configurar, em ambientes self-hosted, a duração e a renovação de sessões persistentes.
|
||||
- Contas: o extrato de uma conta agora tem um atalho "Adicionar rendimento" ao lado de "Ajustar saldo", abrindo um modal simples com valor e data para criar uma receita paga na conta atual, com categoria `Rendimentos`, forma de pagamento `Transferência bancária` e pessoa admin.
|
||||
- Insights: adicionado suporte ao provider MiniMax via `vercel-minimax-ai-provider`, incluindo os modelos M2.7, M2.7 Highspeed, M2.5, M2.5 Highspeed, M2.1, M2.1 Highspeed e M2.
|
||||
- Insights: adicionado suporte ao provider Ollama via endpoint OpenAI-compatible, com modelos sugeridos `llama3.2`, `llama3.1`, `qwen2.5` e `mistral`, além de input para qualquer modelo instalado localmente.
|
||||
- Configuração: adicionadas as variáveis `MINIMAX_API_KEY`, `OLLAMA_BASE_URL` e `OLLAMA_API_KEY` aos exemplos de ambiente, ao assistente de setup e à documentação.
|
||||
- Dependências: adicionada `@ai-sdk/openai-compatible` para integrar provedores compatíveis com a API da OpenAI, incluindo Ollama.
|
||||
- Lançamentos: o campo "Dividir com" agora permite selecionar múltiplas pessoas e exibe um campo de valor para cada participante escolhido.
|
||||
- Lançamentos: o modal de criação e edição agora exibe um card compacto de resumo da operação abaixo dos anexos, incluindo forma de pagamento, destino, categoria, pessoas, valores divididos e quantidade de lançamentos que serão criados.
|
||||
|
||||
### Alterado
|
||||
- Contas: o modal "Adicionar rendimento" usa o mesmo seletor de data do modal de lançamentos e os botões de rendimento e ajuste de saldo agora exibem tooltip.
|
||||
- Categorias: o header de `/categories/[categoryId]` agora usa três blocos de métrica alinhados para total do mês selecionado, total do mês anterior e variação.
|
||||
- Dashboard: o botão expansível dos widgets passou de "Ver tudo" para "Expandir", com visual secundário e gradiente inferior mais compacto para diferenciar melhor a ação de abrir o modal dos links que navegam para páginas completas.
|
||||
- Insights: a resolução de modelos foi centralizada em `model-provider.ts`, reduzindo ramificações na action de geração e preservando OpenAI, Anthropic, Google, MiniMax e OpenRouter.
|
||||
- Insights: o aviso de privacidade agora diferencia providers externos de providers locais como Ollama.
|
||||
- Lançamentos: o filtro de categorias agora separa as opções em grupos de `Despesas` e `Receitas`, preservando ícones e busca dentro do seletor.
|
||||
- Lançamentos: a configuração de divisão foi movida para um modal dedicado e minimalista, com seleção direta de participantes, divisão igual e conferência do total distribuído.
|
||||
- Lançamentos: a validação de divisão agora aceita uma lista de participações, exige pessoas distintas e confere se a soma dos valores bate com o total do lançamento.
|
||||
- Lançamentos: os textos de edição de lançamentos divididos foram ajustados para tratar divisões com mais de duas pessoas.
|
||||
- Lançamentos: o card "Dividir lançamento" agora mostra avatares discretos antes dos nomes das pessoas selecionadas e remove as vírgulas entre os nomes no resumo.
|
||||
- Relatórios: em `/reports/category-trends`, o seletor de categorias não exibe mais a ação `Todas`; quando há seleção ativa, mostra apenas `Limpar seleção` e resume múltiplas escolhas pela contagem.
|
||||
- Relatórios: em `/reports/category-trends`, as tabelas agora usam os cabeçalhos `Categoria Despesa` e `Categoria Receita` e não exibem mais o ponto colorido antes do nome da categoria.
|
||||
|
||||
### Corrigido
|
||||
- Categorias: em `/categories/[categoryId]`, o percentual de variação do header agora aparece sem `+` quando já há uma etiqueta indicando aumento, queda ou estabilidade.
|
||||
- Dashboard: os modais "Ver tudo" dos widgets agora reservam espaço para a barra de rolagem, evitando que ela fique sobreposta aos valores alinhados à direita.
|
||||
- Insights: o seletor de modelo do OpenRouter mantém o provider selecionado enquanto o usuário digita um modelo customizado sem `/`, evitando voltar automaticamente para o provider padrão.
|
||||
- Relatórios: em `/reports/category-trends`, a busca do seletor de categorias agora pesquisa pelo nome da categoria, e não apenas pelo ID interno, incluindo correspondência sem acentos.
|
||||
|
||||
## [2.6.4] - 2026-05-23
|
||||
|
||||
Esta versão reúne o polimento final antes da próxima publicação: melhora o fluxo de antecipação de parcelas, deixa os dialogs de lançamentos mais seguros e consistentes, e incorpora as contribuições vindas dos PRs abertos para nomes de logos e ajustes no cadastro de transações.
|
||||
|
||||
### Adicionado
|
||||
- Logos: adicionado um dicionário de nomes de exibição para logos, com busca normalizada sem acentos e fallback para o comportamento anterior quando não houver mapeamento específico (PR #69).
|
||||
- Lançamentos: o dialog de adicionar múltiplos lançamentos agora pede confirmação antes de descartar alterações não salvas ao fechar ou cancelar (PR #70).
|
||||
|
||||
### Alterado
|
||||
- Lançamentos: o modal "Histórico de Antecipações" agora segue o padrão do modal de detalhes, com `Fechar` e `Desfazer Antecipação` no rodapé, contagem dentro do conteúdo e cards de antecipação reorganizados em blocos mais escaneáveis.
|
||||
- Lançamentos: a antecipação de parcelas agora só permite selecionar parcelas futuras ao período escolhido, evitando antecipar a parcela do próprio mês sem bloquear parcelas seguintes da mesma compra.
|
||||
- Lançamentos: ao criar uma antecipação, o cache do histórico da série agora é invalidado e o modal refaz a busca ao abrir.
|
||||
- Lançamentos: ao adicionar uma nova linha no dialog de múltiplos lançamentos, a data passa a seguir a última transação informada em vez de voltar para a data atual (PR #72).
|
||||
|
||||
### Corrigido
|
||||
- Lançamentos: ajustado o espaçamento horizontal da área rolável do dialog de adicionar transação para preservar o alinhamento dos campos e botões (PR #71).
|
||||
|
||||
## [2.6.3] - 2026-05-22
|
||||
|
||||
Esta versão concentra os ajustes feitos depois da `2.6.2` em um único ciclo público. O foco está em dar mais precisão aos filtros de lançamentos por período real de compra e em polir a análise de parcelas para priorizar parcelamentos mais próximos da quitação sem causar saltos visuais nos cards.
|
||||
|
||||
### Adicionado
|
||||
- Lançamentos: o drawer de filtros agora permite informar data inicial e data final para filtrar a tabela por `data_compra`.
|
||||
|
||||
### Alterado
|
||||
- Lançamentos: quando um intervalo de datas está ativo, a consulta server-side deixa de limitar os dados a um único mês e usa o intervalo real de compra, mantendo paginação e exportação alinhadas ao que aparece na tabela.
|
||||
- Relatórios: os cards de `/reports/installment-analysis` agora são ordenados pelo percentual pago em ordem decrescente, mantendo a data da compra como critério de desempate.
|
||||
- Relatórios: em `/reports/installment-analysis`, o contador de parcelas selecionadas agora aparece discretamente no botão "detalhes", sem criar uma área extra no corpo do card.
|
||||
|
||||
### Corrigido
|
||||
- Relatórios: selecionar parcelas em um card de `/reports/installment-analysis` não força mais os outros cards da mesma linha a reservarem espaço vazio para o resumo de seleção.
|
||||
|
||||
## [2.6.2] - 2026-05-21
|
||||
|
||||
Esta versão corrige o build da imagem Docker depois da atualização para `pnpm@11.1.3`. A etapa de dependências dentro do Docker não recebia a configuração do workspace, então o install congelado falhava ao comparar os `overrides` e as políticas de build com o lockfile.
|
||||
|
||||
### Corrigido
|
||||
- Docker: o `Dockerfile` agora usa `pnpm@11.1.3` em todos os estágios e copia `pnpm-workspace.yaml` antes do `pnpm install --frozen-lockfile`, garantindo que `overrides` e `allowBuilds` sejam aplicados também no build da imagem.
|
||||
|
||||
## [2.6.1] - 2026-05-21
|
||||
|
||||
Esta versão corrige o pipeline de publicação após o salto para a `2.6.0`. O build do GitHub Actions falhava antes mesmo de instalar as dependências porque o workflow ainda fixava uma versão antiga do `pnpm`, enquanto o projeto já declarava `pnpm@11.1.3` no `packageManager`.
|
||||
|
||||
### Corrigido
|
||||
- CI: o workflow de build deixou de fixar uma versão diferente do `pnpm`, usando a versão declarada em `packageManager` para evitar conflito no `pnpm/action-setup`.
|
||||
- CI: a política de builds do `pnpm` foi migrada para `allowBuilds`, permitindo explicitamente os scripts necessários de `core-js`, `esbuild`, `sharp` e `unrs-resolver` durante o install no GitHub Actions.
|
||||
|
||||
## [2.6.0] - 2026-05-21
|
||||
|
||||
Esta versão implementa melhorias pensadas para o uso real do dia a dia. O OpenMonetis passa a lidar melhor com parcelamentos que já começaram, recupera atalhos importantes no extrato, deixa a revisão de importações mais precisa e dá mais controle para quem mantém uma instância self-hosted. Também entram correções de consistência no dashboard, em cartões, no tratamento da categoria `Pagamentos` e nas datas vindas de planilhas.
|
||||
|
||||
### Adicionado
|
||||
- Autenticação: nova variável `DISABLE_SIGNUP=true` para bloquear novos cadastros. Quando ativa, a tela de cadastro deixa de aparecer na navegação, `/signup` redireciona para login/dashboard e a API de signup responde `403`.
|
||||
- Lançamentos: compras parceladas agora podem começar em uma parcela intermediária, como `5 de 10`. O sistema gera apenas as parcelas restantes e preserva o cálculo do valor unitário com base no total original.
|
||||
- Logos: adicionado o logo da Bipa à biblioteca local de marcas.
|
||||
- Relatórios: a análise de parcelas agora separa parcelas acompanhadas daquelas que ficaram fora do acompanhamento quando o parcelamento começa no meio da série.
|
||||
|
||||
### Alterado
|
||||
- Contas: a página de extrato em `/accounts/[accountId]` voltou a exibir os botões "Nova Receita" e "Nova Despesa", alinhando o fluxo com as demais telas de lançamentos.
|
||||
- Cartões: os cards de `/cards` agora mostram o valor da fatura do mês atual junto dos indicadores de limite. O limite utilizado passa a considerar faturas em aberto, não apenas o status interno do lançamento.
|
||||
- Lançamentos: ao criar um lançamento a partir do extrato de uma conta, o diálogo já abre com essa conta selecionada como destino padrão.
|
||||
- Importação: os controles globais da revisão de extrato foram realinhados à esquerda, com espaçamento mais compacto e larguras mais consistentes.
|
||||
|
||||
### Corrigido
|
||||
- Dashboard: o widget "Status de Pagamento" voltou a mostrar corretamente os valores em "A Pagar", somando despesas pelo valor absoluto e mantendo reembolsos como abatimento.
|
||||
- Importação: datas vindas de planilhas agora preservam o dia informado no Excel, evitando que `20/05/2026` apareça como `19/05/2026` em fusos como `America/Sao_Paulo`.
|
||||
- Importação: o seletor de categoria por linha agora mostra apenas categorias compatíveis com o tipo detectado do lançamento, separando receitas e despesas durante a revisão do extrato.
|
||||
- Importação: cada linha da revisão de extrato agora permite escolher uma pessoa específica, enquanto o campo global continua servindo como atalho para aplicar a pessoa nos lançamentos selecionados.
|
||||
- Lançamentos: despesas comuns na categoria `Pagamentos` voltaram a poder ser editadas, removidas, copiadas e importadas. A proteção continua valendo apenas para pagamentos automáticos de fatura com nota técnica `AUTO_FATURA:`.
|
||||
|
||||
### Dependências
|
||||
- Stack core: `pnpm` 10.33.0 → 11.1.3.
|
||||
- Auth: `better-auth` e `@better-auth/passkey` 1.6.10 → 1.6.11.
|
||||
- AI SDKs: `@ai-sdk/anthropic` 3.0.76 → 3.0.78, `@ai-sdk/google` 3.0.71 → 3.0.75, `@ai-sdk/openai` 3.0.63 → 3.0.64 e `ai` 6.0.177 → 6.0.185.
|
||||
- AWS: `@aws-sdk/client-s3` e `@aws-sdk/s3-request-presigner` 3.1045.0 → 3.1050.0.
|
||||
- UI e dados: `@tanstack/react-query` 5.100.9 → 5.100.11, `date-fns` 4.1.0 → 4.2.1, `jspdf-autotable` 5.0.7 → 5.0.8, `pg` 8.20.0 → 8.21.0 e `react-day-picker` 10.0.0 → 10.0.1.
|
||||
- Dev tooling: `@types/node` 25.6.2 → 25.9.1, `@types/react` 19.2.14 → 19.2.15, `knip` 6.12.2 → 6.14.1, `tsx` 4.21.0 → 4.22.3 e novo `babel-plugin-react-compiler` 1.0.0.
|
||||
|
||||
## [2.5.7] - 2026-05-14
|
||||
|
||||
Esta versão faz um polimento visual no relatório de análise de parcelas, deixando o estabelecimento como referência principal do card e mantendo o cartão visível de forma mais discreta no contexto da compra.
|
||||
|
||||
### Alterado
|
||||
- Relatórios: em `/reports/installment-analysis`, os cards de parcelas passam a usar o logo do estabelecimento como avatar principal; o logo do cartão agora aparece menor ao lado do nome do cartão, tanto no card quanto no modal de detalhes.
|
||||
- Relatórios: a página de análise de parcelas pré-carrega os mapeamentos de logos de estabelecimentos para evitar troca visual após o primeiro render.
|
||||
- Lançamentos: o campo de anexos no modal agora aceita arquivos colados com `Ctrl+V`, mantendo o botão para buscar arquivos normalmente.
|
||||
- Lançamentos: o modal agora usa uma única área interna de rolagem, com cabeçalho e rodapé estáveis, reduzindo travadas ao rolar e ao abrir "Condições, anotações e anexos".
|
||||
- Anotações: tarefas agora podem ser editadas inline no modal "Atualizar anotação"; clicar no texto abre o input e o botão de remover vira botão de salvar naquela linha.
|
||||
|
||||
### Corrigido
|
||||
- Relatórios: o join com cartões na análise de parcelas agora também valida `cards.userId`, mantendo o filtro de ownership explícito na consulta.
|
||||
|
||||
## [2.5.6] - 2026-05-07
|
||||
|
||||
Esta versão entrega um conjunto de melhorias em torno do fluxo de lançamentos: filtros mais úteis, divisão por porcentagem, indicador de orçamento dentro do modal e correção de um bug em totais por pessoa que considerava contas excluídas do saldo. Também inclui ajustes de robustez no display da calculadora (sem mais overflow do modal com valores longos) e o fix do cache de RSC nos filtros multi-seleção.
|
||||
|
||||
### Adicionado
|
||||
- Lançamentos: filtro por faixa de valor (mín/máx) com debounce e persistência via query string (`amountMin`/`amountMax`).
|
||||
- Lançamentos: botão "Limpar" discreto ao lado do botão "Filtros", visível apenas quando há filtros ativos.
|
||||
- Modal de lançamento: toggle compacto R$/% no card "Dividir lançamento", permitindo distribuir o valor por porcentagem entre as pessoas. Cada input em modo % exibe o valor convertido em R$ logo abaixo, no mesmo padrão visual do `InlinePeriodPicker`.
|
||||
- Modal de lançamento: indicador de orçamento ao lado do nome da categoria selecionada, mostrando `R$ gasto de R$ orçado (%)` com cores semânticas (verde / âmbar / vermelho) conforme o consumo. Suprimido quando o input divide a linha com o tipo de transação (caso pré-lançamentos). Implementado via `getCategoryBudgetSummaryAction` e `fetchCategoryBudgetSummary` em `features/budgets`.
|
||||
|
||||
### Alterado
|
||||
- Calculadora: display com tamanho de fonte adaptativo (de `text-3xl` a `text-sm`) conforme o comprimento da expressão, mais `truncate` funcional via `min-w-0` nos containers flex. Resolve o overflow do modal com valores muito longos (ex: `9.999.999.999 × 9.999.999.999`).
|
||||
|
||||
### Corrigido
|
||||
- Pessoas: "Totais do mês" em `/payers/[id]` deixa de somar lançamentos vinculados a contas marcadas como `excludeFromBalance` (ex: "Ajuste de saldo"). Adicionado `excludeTransactionsFromExcludedAccounts()` em 6 queries de `src/shared/lib/payers/details.ts`.
|
||||
- Orçamentos: `fetchBudgetsForUser` e `fetchCategoryBudgetSummary` agora respeitam o filtro de contas excluídas do saldo, alinhando o gasto exibido na tela de Orçamentos com o badge de orçamento dentro do modal de lançamento.
|
||||
- Lançamentos: tabela de resultados agora reflete corretamente a remoção de um valor em filtros multi-seleção (Pessoa, Conta/Cartão, Categoria, Condição, Forma de Pagamento). Adicionado `router.refresh()` em `handleMultiFilterChange` para invalidar o cache de segmento do router (issue #54).
|
||||
|
||||
### Dependências
|
||||
- Stack core: `next` 16.2.4 → 16.2.6, `react`/`react-dom` 19.2.5 → 19.2.6.
|
||||
- UI: `react-day-picker` 9 → 10 (major), `tailwindcss` / `@tailwindcss/postcss` 4.2.4 → 4.3.0, `tailwind-merge` 3.5.0 → 3.6.0.
|
||||
- Auth: `better-auth` 1.6.9 → 1.6.10 e `@better-auth/passkey` 1.6.9 → 1.6.10.
|
||||
- AI SDKs: `@ai-sdk/anthropic` 3.0.74 → 3.0.76, `@ai-sdk/google` 3.0.67 → 3.0.71, `@ai-sdk/openai` 3.0.60 → 3.0.63, `ai` 6.0.175 → 6.0.177.
|
||||
- AWS: `@aws-sdk/client-s3` e `@aws-sdk/s3-request-presigner` 3.1042.0 → 3.1045.0.
|
||||
- E-mail: `resend` 6.12.2 → 6.12.3.
|
||||
- Dev tooling: `@biomejs/biome` 2.4.14 → 2.4.15, `knip` 6.11.0 → 6.12.2, `@types/node` 25.6.0 → 25.6.2.
|
||||
|
||||
## [2.5.5] - 2026-05-06
|
||||
|
||||
Esta versão melhora a navegação por históricos e lançamentos. O changelog ganhou uma linha do tempo mais leve, colapsável e fácil de escanear; os filtros de lançamentos passam a aceitar múltiplas pessoas, categorias, formas de pagamento, condições e contas/cartões na mesma busca; e os diálogos adotam as animações compartilhadas do design system. Também há pequenos polimentos de texto e layout para deixar a interface mais consistente.
|
||||
|
||||
### Adicionado
|
||||
- Lançamentos: filtros multi-seleção para condição, forma de pagamento, pessoa, categoria e conta/cartão, permitindo combinar vários valores no mesmo filtro (query string passa a aceitar múltiplos valores por chave).
|
||||
- Changelog: parser passou a inferir o tipo de bump (major/minor/patch) a partir da numeração e a extrair o parágrafo de resumo abaixo do cabeçalho de versão; novo arquivo `src/features/settings/lib/changelog-types.ts` consolidando os tipos compartilhados.
|
||||
- UI: dependência `tw-animate-css` para usar as mesmas animações utilitárias já presentes nos componentes shadcn/ui.
|
||||
|
||||
### Alterado
|
||||
- Changelog: visual da página reformulado para linha do tempo com resumo sempre visível, detalhes colapsáveis por versão, agrupamento por mês e marcadores visuais por tipo de bump; componente migrado para `"use client"` com `Collapsible` e abertura via âncora (`#vX-Y-Z`).
|
||||
- Lançamentos: botões "Nova Receita" e "Nova Despesa" agora usam os próprios triggers do `TransactionDialog` (via prop `createSlot`), reduzindo estado manual na página e eliminando o fluxo `setCreateOpen` + `transactionTypeForCreate`.
|
||||
- Diálogos: animações customizadas em CSS (`@keyframes dialog-in/out` e `overlay-in/out`) substituídas pelas classes utilitárias compartilhadas em `Dialog`/`DialogOverlay` (`data-[state=open]:animate-in`, `zoom-in-95`, `fade-in-0`).
|
||||
- BulkActionDialog: label do escopo "Todas as pessoas" passa a indicar a parcela atual (`Todas as pessoas desta parcela (N/Total)`) com descrição mais clara sobre o efeito da ação.
|
||||
- Checkbox: `RiCheckLine`/`RiSubtractLine` agora herdam `text-current` para alinhar com a cor do indicator nativo.
|
||||
- Landing page: remoção de fundos alternados (`bg-muted/40`) nas seções "Funcionalidades", "Stack" e "Para quem é" para uma leitura visual mais limpa.
|
||||
- Navbar: aviso de atualização passa a usar o texto "Versão X disponível".
|
||||
|
||||
## [2.5.4] - 2026-05-06
|
||||
|
||||
Esta versão é uma faxina arquitetural de larga escala sem nenhuma mudança visível ao usuário. Removido código morto, padronizamos identificadores em inglês conforme a convenção do projeto, simplificamos o barrel de Server Actions e consolidamos os arquivos de helpers/queries soltos nas raízes das features dentro de pastas `lib/`. O resultado é uma estrutura previsível e consistente entre features (`actions.ts`, `queries.ts`, `actions/`, `components/`, `hooks/`, `lib/`) e um saldo líquido de −428 linhas de código com zero impacto em comportamento, performance ou banco de dados.
|
||||
|
||||
### Alterado
|
||||
- Padronização da estrutura de `transactions/`: 14 helpers soltos na raiz movidos para `lib/`; barrel `actions.ts` reduzido de 76 linhas de wrappers redundantes para 14 linhas de re-exports puros; `anticipation-actions.ts` movido para `actions/anticipation.ts`.
|
||||
- Reorganização de `dashboard/`: 8 helpers soltos consolidados em `dashboard/lib/`; orquestradores (`fetch-dashboard-data.ts`, `page-data-queries.ts`) permanecem na raiz como entry points.
|
||||
- Reorganização de `reports/`: 5 query files na raiz consolidados em `reports/lib/`.
|
||||
- Reorganização de `payers/`: god file `detail-actions.ts` (21KB) e `detail-queries.ts` movidos para `payers/lib/`.
|
||||
- `shared/components/`: 9 dos 16 componentes soltos agrupados em 3 novas subpastas temáticas (`brand/`, `widgets/`, `feedback/`).
|
||||
- `shared/lib/fetch-json.ts` movido para `shared/utils/fetch-json.ts` (categorização correta — utilitário genérico de transporte HTTP).
|
||||
- Padronização EN dos identificadores remanescentes: 4 constantes globais (`LANCAMENTOS_*` → `TRANSACTIONS_*`), 12 tipos/interfaces (`Lancamento*`/`Pagador*`/`Estabelecimento*` → equivalentes em EN), 13 funções/components exportados (`fetchPagador*`, `EstabelecimentoInput`, `PagadorInfoCard`, etc.), 5 props cross-file (`preLancamentosCount` → `inboxPendingCount`, etc.).
|
||||
- Server Actions de `insights/` simplificadas: barrel reduzido para re-exports puros.
|
||||
- Mantidas intencionalmente em PT-BR conforme exceção do `CLAUDE.md`: variáveis locais (`pagador`, `categoria`, `lancamento`), accessor key `pagadorName` (persistida em preferências do usuário), strings de UI.
|
||||
|
||||
### Removido
|
||||
- 14 funções/constantes mortas verificadas via `grep` em todo o repo: `validateCategoriaOwnership`, `getInstallmentAnticipationsAction`, `getAnticipationDetailsAction`, `formatDecimalForDb`, `currencyFormatterNoCents`, `optionalDecimalSchema`, `formatMonthLabel`, `getGoalProgressStatusColorClass`, `MONTH_PERIOD_PARAM`, `calculateRemainingInstallments`, e 5 funções `fetch*` não usadas em `inbox/queries.ts`.
|
||||
- 1 tipo morto: `ImportRow` em `transactions/actions/import-action.ts`.
|
||||
- 2 tipos órfãos consequentes: `InstallmentAnticipationWithRelations`, `GoalProgressStatus` (este último convertido em interno).
|
||||
- ~30 `export` keywords desnecessários (símbolos usados apenas no próprio arquivo) — visibilidade reduzida sem mudar comportamento.
|
||||
- Re-exports mortos em barrels: `EstablishmentLogoPicker` em `entity-avatar/index.ts`, `CategoryReportSkeleton` e `WidgetSkeleton` em `skeletons/index.ts`, `toNameKey` em `establishment-logo-queries.ts`.
|
||||
- Arquivo `features/reports/types.ts` (barrel inteiro era órfão — todos os 5 tipos eram importados direto de `@/shared/lib/types/reports`).
|
||||
|
||||
## [2.5.3] - 2026-05-05
|
||||
|
||||
Esta versão foca em polimento do diálogo de detalhes do lançamento, refresh visual da linha do tempo de parcelas e limpeza terminológica em torno de contas/cartões inativos. O diálogo de detalhes ganhou logo da conta/cartão, ícone colorido por categoria e avatar do responsável; a barra de progresso de parcelas foi redesenhada num layout horizontal compacto; e o widget "Minhas Contas" do dashboard passou a ocultar automaticamente contas marcadas como inativas. Internamente, o termo "arquivadas" foi padronizado como "inativas" nas tabs de contas e cartões, surgiram constantes compartilhadas para formas de pagamento liquidáveis e um helper `isAccountInactive`, e o seed de mock data ganhou cobertura mais realista (novas pessoas, contas, cartões e assinaturas recorrentes).
|
||||
|
||||
### Adicionado
|
||||
- Logo da conta/cartão, ícone colorido por categoria e avatar do responsável no diálogo de detalhes do lançamento.
|
||||
- Constantes `SETTLEABLE_PAYMENT_METHODS` e `CREDIT_CARD_PAYMENT_METHOD` em `features/transactions/constants.ts`.
|
||||
- Helper `isAccountInactive(status)` em `shared/lib/accounts/constants.ts`, reaproveitado em `account-card.tsx` e `my-accounts-widget.tsx`.
|
||||
|
||||
### Alterado
|
||||
- Widget "Minhas Contas" do dashboard agora oculta contas inativas (filtra antes de aplicar a regra de "não consideradas") e ajusta o empty state quando o usuário só tem contas inativas.
|
||||
- Linha do tempo de parcelas (`InstallmentTimeline`) redesenhada: layout horizontal com barra de progresso, datas de compra e quitação alinhadas nas pontas e contador "N restante(s)" / "Última parcela" abaixo.
|
||||
- Diálogo de detalhes do lançamento: badge de status "Pendente" virou "Em aberto" com variante `info`, "Resumo" virou "Total" e ID do lançamento passou a exibir o UUID completo em fonte monoespaçada (sem truncar).
|
||||
- Tabs em contas e cartões: "Arquivadas/Arquivados" renomeadas para "Inativas/Inativos".
|
||||
- Legenda do calendário envolvida em `Card` para destacar visualmente do conteúdo da página.
|
||||
- Páginas `cards`, `categories`, `inbox`, `notes`, `payers` perderam `items-start` no `<main>` (alinhamento natural à largura total); `calendar` ajustou gap de 3 para 4.
|
||||
- Tabela de lançamentos: extraído IIFE de payment-method dos botões de liquidação com as novas constantes compartilhadas; bloco logo+label da coluna Conta/Cartão deduplicado via reuso de variável JSX; removido `capitalize` redundante do label "Venc.".
|
||||
- Mock data renovado em `scripts/mock-data.ts`: novas pessoas (Mario), novas contas (Itaú Personnalité, Banco Inter), novo cartão Inter Black, e cobertura mais ampla de assinaturas recorrentes (Vivo, Sabesp, Disney+, HBO Max, Amazon Prime, OpenAI, Apple iCloud, Notion, YouTube Premium).
|
||||
|
||||
### Removido
|
||||
- Comentário narrativo `{/* Opções de Antecipação */}` em `transactions-columns.tsx`.
|
||||
- Helper local `shortTransactionId` em `transaction-details-dialog.tsx` (substituído pela exibição do UUID completo).
|
||||
|
||||
## [2.5.2] - 2026-05-04
|
||||
|
||||
Esta versão traz melhorias visuais e de usabilidade em contas, lançamentos, orçamentos, cartões e anotações: novos tipos de conta, ícones no seletor, feedback visual de limite excedido nas progress bars e refinamentos nos ícones de tarefas em anotações.
|
||||
|
||||
### Adicionado
|
||||
- Novos tipos de conta `"Dinheiro"` e `"Outros"` na lista padrão do diálogo de contas (issue #50).
|
||||
- Ícones por tipo de conta no seletor (Conta Corrente, Poupança, Carteira Digital, Investimento, Pré-Pago, Dinheiro, Outros).
|
||||
- Filtro automático: ao selecionar `"Dinheiro"` como forma de pagamento em lançamentos, o select de conta exibe apenas contas do tipo `"Dinheiro"`.
|
||||
- Sinal `+` no valor de transferências recebidas na tabela de lançamentos (mantém cor azul).
|
||||
|
||||
### Alterado
|
||||
- Forma de pagamento de novas transferências entre contas alterada de `"Pix"` para `"Transferência bancária"`.
|
||||
- Progress bar de orçamentos excedidos agora exibe indicador e fundo na cor `destructive`.
|
||||
- Progress bar de cartões com 100% do limite utilizado agora exibe indicador e fundo na cor `destructive`.
|
||||
- Ícone de tarefa não concluída no card e no modal de detalhes de anotações substituído por `RiSubtractLine` (locais sem interação de marcação).
|
||||
|
||||
## [2.5.1] - 2026-05-04
|
||||
|
||||
Versão de correção pontual focada na exibição do indicador de anexo nas tabelas de lançamentos da fatura do cartão. Em `/cards/[cardId]/invoice`, lançamentos com anexos não mostravam o ícone porque o fetcher dedicado da fatura não calculava o flag `hasAttachments`. A primeira tentativa de adicionar o EXISTS via `extras` na query relacional gerou SQL inválido (Drizzle re-aliasava `transactionAttachments.transactionId` para o alias da tabela externa). A correção definitiva troca o fetcher pela função compartilhada `fetchTransactionsWithRelations` de `features/transactions`, que já implementa o EXISTS corretamente via `select`.
|
||||
|
||||
### Corrigido
|
||||
- Ícone de anexo voltou a aparecer na tabela de lançamentos da fatura do cartão (`/cards/[cardId]/invoice`). `fetchCardTransactions` em `features/invoices/queries.ts` agora delega para `fetchTransactionsWithRelations`, garantindo que o flag `hasAttachments` seja preenchido com a mesma EXISTS subquery usada no restante do app.
|
||||
|
||||
## [2.5.0] - 2026-05-01
|
||||
|
||||
Esta versão melhora o fechamento de faturas, a correção de lançamentos já registrados e a conferência de saldos contra o extrato do banco. O novo **ajuste de fatura** fecha a conta entre o total calculado pelo sistema e o valor real cobrado pelo banco, sem exigir que o usuário reabra lançamentos individuais. A mesma ideia foi estendida para **contas correntes**: na página do extrato, ao lado de "Saldo ao final do período", o usuário informa o saldo real e o sistema cria (ou atualiza) um lançamento de ajuste no período visualizado. Também entra o fluxo de **reembolso** para despesas à vista: pelo menu de ações do lançamento, o usuário informa a data do reembolso e o sistema cria uma receita espelhada no extrato ou na fatura correta. O widget de boletos do dashboard ganhou paridade com o widget de faturas — confirmação de pagamento agora pede conta de origem e data antes de quitar o boleto. Por fim, o **limite do cartão** passou a ser obrigatório e o sistema bloqueia despesas em cartão que ultrapassem o limite disponível, retornando uma mensagem com o valor exato disponível. As operações mantêm rastro no lançamento gerado e respeitam a proteção de faturas já pagas.
|
||||
|
||||
### Adicionado
|
||||
- Nome do boleto no widget de Boletos agora é um link para `/transactions?q=<nome>`, incluindo `?periodo=<mes-ano>` automaticamente quando o período selecionado não é o atual. Ícone `RiExternalLinkLine` ao lado do nome, igual ao padrão do widget de Faturas.
|
||||
- Botão "Ajustar fatura" ao lado do valor na página da fatura.
|
||||
- Dialog `AdjustInvoiceDialog` com input de valor correto e preview da diferença.
|
||||
- Action `adjustInvoiceAction` que faz upsert/delete idempotente do lançamento de ajuste.
|
||||
- Botão "Ajustar saldo" ao lado do valor na página do extrato da conta.
|
||||
- Dialog `AdjustBalanceDialog` com input do saldo correto e preview da diferença que será lançada (receita ou despesa).
|
||||
- Action `adjustAccountBalanceAction` que faz upsert/delete idempotente do lançamento de ajuste por `(accountId, period)`.
|
||||
- Opção "Reembolso" no dropdown de ações de despesas à vista, posicionada após "Copiar" e antes de "Remover".
|
||||
- Dialog `RefundTransactionDialog` com seleção da data do reembolso e indicação do período de destino.
|
||||
- Action `refundTransactionAction` que cria uma receita de reembolso vinculada ao lançamento original.
|
||||
- Constantes compartilhadas `INVOICE_ADJUSTMENT_NAME`, `ACCOUNT_BALANCE_ADJUSTMENT_NAME`, `REFUND_NOTE_PREFIX` e `buildRefundNote()` em `shared/lib/accounts/constants.ts`.
|
||||
- Validação de limite de cartão: `validateCardLimit()` em `transactions/actions/core.ts` calcula o uso atual do cartão (somando lançamentos não quitados, com a mesma regra usada em `cards/queries.ts` para recorrentes) e bloqueia criação ou edição de despesa em cartão que ultrapasse o disponível, retornando "Lançamento de R$ X excede o limite disponível do cartão (R$ Y)."
|
||||
- Schema reutilizável `requiredDecimalSchema(fieldName)` em `shared/lib/schemas/common.ts` — número/string positiva (`> 0`) com mensagens parametrizáveis.
|
||||
|
||||
### Alterado
|
||||
- **Limite do cartão é obrigatório**: campo `limite` em `cartoes` ganhou `NOT NULL DEFAULT 0` no schema, validação Zod com `requiredDecimalSchema("limite")`, atributo `required` no input do formulário e checagem client-side antes do submit. Tipos `Card.limit` e `Card.limitAvailable` deixam de ser nullable; branch "Ainda não há limite registrado" foi removido de `card-item.tsx` e a derivação defensiva em `cards/[cardId]/invoice` foi simplificada.
|
||||
- Migration `0029_friendly_spitfire`: preenche com `0` registros legados antes do `SET NOT NULL` para não quebrar bancos com cartões sem limite.
|
||||
- Métricas principais passam a tratar reembolsos como abatimento de despesa, não como receita comum.
|
||||
- Cards de receitas/despesas, série histórica do dashboard e resumo do extrato agora preservam o efeito líquido do reembolso no balanço sem inflar entradas e saídas.
|
||||
- Pagamento de fatura agora abre confirmação com conta de origem selecionável; por padrão vem a conta vinculada ao cartão, mas o usuário pode escolher outra conta antes de confirmar.
|
||||
- Widget de faturas no dashboard ganhou a mesma confirmação: o modal "Confirmar pagamento" agora pede conta de origem e data antes de marcar a fatura como paga, alinhando o comportamento ao da página de fatura.
|
||||
- Widget de boletos no dashboard ganhou a mesma paridade: o modal "Confirmar pagamento" passou a oferecer seleção de **conta de pagamento** e **data do pagamento**, com mesma estrutura de cards de detalhes, métricas, separator e formulário condicional do widget de faturas.
|
||||
- `toggleTransactionSettlementAction` agora aceita `paymentAccountId` e `paymentDate` opcionais para boletos — quando informados, atualiza a `accountId` do lançamento e usa a data escolhida em `boletoPaymentDate` (em vez da data atual).
|
||||
- `DashboardBill` passa a expor `accountId` para que o dialog inicialize a conta com o valor já vinculado ao boleto.
|
||||
- Widget "Lançamentos por Categorias" agora ignora a categoria "Transferência interna" — transferências entre contas próprias deixam de poluir o ranking de categorias.
|
||||
|
||||
### Corrigido
|
||||
- Erro de hidratação no widget de Anotações: `Intl.DateTimeFormat` sem `timeZone` usava o fuso do servidor (UTC) no SSR e o fuso do browser (BRT) no cliente, resultando em datas divergentes. Ambos os formatters passam a usar `timeZone: "America/Sao_Paulo"` explicitamente.
|
||||
- Extrato da conta agora contabiliza transferências internas nos cards de **Entradas** e **Saídas**: transferência recebida soma em Entradas, transferência enviada soma em Saídas. Antes o saldo final refletia o movimento mas os cards permaneciam zerados, gerando inconsistência visível na tela (issue #47).
|
||||
|
||||
### Removido
|
||||
- Seção "Veja o que você pode fazer" (galeria de screenshots com abas) da landing page, junto com o componente `ScreenshotTabs`, as 14 imagens `preview-*.webp`, o bloco `screenshots` em `images.ts`, o link `#telas` do nav e o export `pwaCompatList` sem uso.
|
||||
- Exports mortos `dateFormatter` e `monthFormatter` de `features/transactions/formatting-helpers.ts`.
|
||||
|
||||
## [2.4.4] - 2026-04-27
|
||||
|
||||
Esta versão remove a dependência da extensão `pgcrypto` do PostgreSQL para a geração do `share_code` em pagadores. O default a nível de banco (`gen_random_bytes`) foi removido — agora a aplicação gera o código sempre via `crypto.randomBytes` do Node.js, num utilitário compartilhado. A consequência prática é que o setup inicial fica mais simples: não há mais script de habilitação de extensão, nem etapa extra no primeiro `db:push`, e bancos restaurados de dumps externos não precisam ter `pgcrypto` instalada. O script de backup também foi enxugado para gerar dumps focados nos schemas relevantes (`public` e `drizzle`), descartando os schemas internos do Supabase e eliminando os ~148 erros de restore em PostgreSQL padrão. Por fim, os logos da marca (ícone laranja e wordmark) foram vetorizados: as PNGs antigas foram substituídas por SVGs inline em componentes próprios e por arquivos `.svg` no `public/`, escalando perfeitamente em qualquer tamanho — inclusive nos PDFs exportados, que agora rasterizam o SVG em alta resolução.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Schema: coluna `share_code` em `pagadores` perdeu o default `substr(encode(gen_random_bytes(24), 'base64'), 1, 24)` — campo continua `NOT NULL` e a aplicação passa a fornecer o valor explicitamente em todas as inserções
|
||||
- Pagadores: nova função utilitária `generateShareCode()` em `src/shared/lib/payers/share-code.ts` (server-only) — usa `crypto.randomBytes(18).toString("base64url").slice(0, 24)`
|
||||
- Pagadores: `createPayerAction`, `ensureDefaultPagadorForUser`, `resetUserAppData` (settings) e `mock-data.ts` agora chamam `generateShareCode()` ao inserir um pagador
|
||||
- Backup: `scripts/backup.sh` agora dumpa apenas os schemas `public` e `drizzle` — schemas internos do Supabase (`auth`, `realtime`, `storage`, `vault`, `graphql`, `graphql_public`, `extensions`, `pgbouncer`) e suas extensions/roles deixam de poluir os dumps. Restaurações em PostgreSQL padrão passam a executar sem os ~148 erros de `role/extension does not exist`
|
||||
- Logo: `Logo` foi quebrado em três arquivos — `src/shared/components/logo.tsx` (orquestrador), `logo-icon.tsx` (ícone laranja em SVG inline, viewBox `0 0 200 200`) e `logo-text.tsx` (wordmark em SVG inline, viewBox `0 0 574.201 89.6`). API pública (`variant`, `invertTextOnDark`, `colorIcon`, `iconClassName`, `textClassName`) preservada
|
||||
- Assets: `public/images/logo_small.png` e `logo_text.png` substituídos por `logo_small.svg` e `logo_text.svg` (com `width`/`height` explícitos para compatibilidade com `<img>` em canvas)
|
||||
- Exports: `loadExportLogoDataUrl` agora carrega SVG e rasteriza no canvas a 4× a resolução natural antes de gerar o data URL — mantém nitidez quando o PDF amplia a imagem
|
||||
|
||||
### Removido
|
||||
|
||||
- Pasta `scripts/postgres/` (continha `init.sql` e `enable-extensions.ts`)
|
||||
- Script `pnpm db:extensions` no `package.json`
|
||||
- Referências ao `pnpm db:extensions` no README
|
||||
- `public/images/logo_small.png` e `public/images/logo_text.png` (substituídos pelos `.svg`)
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Migrations: conflito de numeração resolvido — `0027_fancy_reaper` renomeado para `0028_fancy_reaper` (o número 0027 já estava ocupado pelo arquivo órfão `0027_glorious_mindworm`); journal e snapshot atualizados
|
||||
- TS: removido `baseUrl` do `tsconfig.json` para evitar erro `TS5101` (deprecação no TS 7) — `moduleResolution: bundler` resolve os `paths` relativos ao próprio `tsconfig`, dispensando `baseUrl`
|
||||
|
||||
### Documentação
|
||||
|
||||
- README: seção Backup atualizada — arquivos gerados agora especificam que apenas os schemas `public` e `drizzle` são dumpados
|
||||
- README: seção Restore reescrita com o fluxo correto para banco Docker (`DROP SCHEMA public CASCADE` + `pg_restore --clean --if-exists --disable-triggers`)
|
||||
- README: comando rápido de Docker Compose de backup/restore substituído por `pnpm backup`
|
||||
- README: header passa a apontar para `logo_small.svg`
|
||||
|
||||
## [2.4.3] - 2026-04-25
|
||||
|
||||
Esta versão amplia o trabalho com lançamentos divididos: anexos passam a ser visíveis para pessoas com acesso compartilhado, a importação para conta própria copia os arquivos de forma independente e a edição ganha a opção de aplicar a alteração nos dois lados do par. Três caminhos de deleção foram corrigidos para não deixar arquivos órfãos no storage. Também traz refresh visual nos badges de tipo e radio buttons, prefetch server-side de logos para reduzir chamadas de API no dashboard, e ajustes pontuais no healthcheck do container e em rótulos da UI.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Schema: coluna `split_group_id` (uuid, nullable) em `lancamentos` com índice `(user_id, split_group_id)` — liga as shares do mesmo evento de divisão
|
||||
- Split: `buildLancamentoRecords` atribui um `splitGroupId` único por cycle (parcelado, recorrente ou único) para ambas as shares
|
||||
- Split: edição cooperativa via `updateTransactionSplitPairAction` — ao editar um lançamento dividido, novo dialog `SplitPairDialog` permite escolher entre aplicar somente neste lado ou nos dois lados (nome, data, categoria e demais campos compartilhados; valor e payer permanecem por share)
|
||||
- Importação: "Importar para Minha Conta" agora copia os anexos do lançamento-fonte para a conta de quem está importando (novo arquivo, novo `userId`, novo `fileKey` — cópia independente via S3 CopyObject). `createSchema` ganhou campo opcional `importFromTransactionId`; helper `copyAttachmentsForImport` valida acesso à fonte via ownership direto ou `payerShares`
|
||||
- Importação: dialog "Importar para Minha Conta" exibe seção read-only "Anexos que serão copiados" listando os anexos do lançamento-fonte antes da confirmação
|
||||
- Filtros: nova chave `isDivided` na tabela de lançamentos — toggle "Somente divididos" no drawer de filtros mantém o estado na URL
|
||||
- Performance: prefetch server-side de mapeamentos Logo.dev no `/dashboard`, `/transactions` e `/payers/[payerId]` — uma única query SQL em batch (`fetchEstablishmentLogoMap`) semeia o cache do React Query antes do primeiro render, eliminando os N requests para `/api/logo/mapping`
|
||||
|
||||
### Alterado
|
||||
|
||||
- Anexos: `fetchTransactionAttachments` e `fetchTransactionAttachmentsAction` passam a autorizar leitura por acesso à transação (direto ou via `payerShares`), permitindo que pessoas com pagador compartilhado visualizem anexos de lançamentos divididos
|
||||
- Anexos: upload (`confirmAttachmentUploadAction`) e detach em massa (`detachAttachmentBulkAction`) agora expandem `transactionIds` para incluir shares irmãs via `splitGroupId` — o vínculo em `transaction_attachments` é replicado para manter simetria
|
||||
- Anexos: delete/detach continuam restritos ao criador (sem alteração de escrita); dashboard (`fetchAttachmentsForPeriod`) permanece listando apenas os anexos do próprio usuário
|
||||
- Migração: lançamentos divididos criados antes desta versão ficam com `split_group_id` NULL e mantêm o comportamento antigo (anexos não visíveis para a contraparte); apenas splits novos são afetados
|
||||
- Storage: `deleteS3Object` passa a ignorar `NoSuchKey` silenciosamente — providers S3-compatíveis (ex.: Cloudflare R2) lançam esse erro ao deletar objeto inexistente, ao contrário do comportamento idempotente do S3 padrão
|
||||
- UI/Badges: `TransactionTypeBadge` redesenhado — substitui o `StatusDot` por ícones direcionais (`RiArrowRightDownLine` receita, `RiArrowRightUpLine` despesa, `RiArrowLeftRightLine` transferência), com borda visível, shadow sutil e variantes dark mode dessaturadas; rótulo "Transferência" abreviado para "Transf."
|
||||
- UI/Forms: indicador do `RadioGroup` trocado de círculo (`RiCircleLine`) por check (`RiCheckLine`) com fundo sólido `primary` no estado selecionado
|
||||
- UI/Antecipação: tabela de seleção de parcelas reduzida de quatro para três colunas (estabelecimento + fatura + valor) — informações de parcela e vencimento absorvidas pela coluna do estabelecimento
|
||||
- Tipografia: fonte Inter agora carrega explicitamente os pesos 500, 600 e 700 (antes derivava de 400)
|
||||
- Deps: better-auth 1.6.5 → 1.6.9, @aws-sdk/client-s3 3.1032 → 3.1037, @tanstack/react-query 5.99.2 → 5.100.3, @biomejs/biome 2.4.12 → 2.4.13, tailwindcss 4.2.2 → 4.2.4, resend 6.12.0 → 6.12.2
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Anexos: deleção em massa por série (`deleteTransactionBulkAction`) não chamava cleanup de storage — arquivos ficavam órfãos no S3 após apagar "este e futuros" ou "todos" de uma série parcelada/recorrente com anexo
|
||||
- Anexos: deleção múltipla por seleção (`deleteMultipleTransactionsAction`) não chamava cleanup de storage — mesmo problema ao selecionar vários lançamentos com anexo e deletar em lote
|
||||
- Anexos: reset de conta em Ajustes (`resetUserAppData`) não limpava o storage — todos os arquivos do usuário ficavam órfãos no S3 após a operação de zeragem
|
||||
- Página da pessoa (`/payers/[payerId]`): `fetchPagadorLancamentos` agora calcula `hasAttachments` via `EXISTS`, fazendo o ícone de clipe aparecer na tabela de lançamentos (antes só aparecia em `/transactions`)
|
||||
- Categorias: mensagem de sucesso ao atualizar exibia "Category atualizada com sucesso." — corrigido para "Categoria atualizada com sucesso."
|
||||
- Antecipação: rótulos "Category" e "Período" no dialog corrigidos para "Categoria" e "Fatura"
|
||||
- Docker: healthcheck do container `app` agora usa `127.0.0.1:3000` em vez de `localhost:3000`, evitando connection timeout em hosts com IPv6 (resolvendo [#44](https://github.com/felipegcoutinho/openmonetis/issues/44))
|
||||
|
||||
## [2.4.2] - 2026-04-20
|
||||
|
||||
Esta versão é quase toda sobre organização e polimento. O código interno do Dashboard foi reestruturado — módulos espalhados pela raiz da feature foram agrupados em subdiretórios coesos e a arquitetura de widgets foi renovada com um novo `widget-registry`. A sidebar lateral foi aposentada em favor de uma navegação concentrada na navbar. A interface passou por um refinamento visual amplo: cards redesenhados, dark mode mais consistente e efeitos decorativos removidos para uma composição mais limpa. As imagens de preview da landing page foram atualizadas. Por fim, a integração com Logo.dev ganhou uma arquitetura mais segura — o token agora é lido apenas no servidor e nunca chega ao cliente. O conceito de "Pagador" foi renomeado para "Pessoa" em toda a interface.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Dashboard: nova arquitetura de widgets com `widget-registry` — módulos reorganizados em subdiretórios (`bills/`, `invoices/`, `notes/`, `notifications/`, `overview/`, `payments/`, `goals-progress/`, `categories/`)
|
||||
- Dashboard: novos componentes `category-breakdown-chart`, `category-breakdown-list`, `goals-progress-item` e `percentage-change-indicator`
|
||||
- Logo.dev: `server.ts` com `isLogoDevEnabled()` e `buildLogoDevUrl()` server-side; `LogoDevProvider` propaga flag `enabled` para Client Components
|
||||
- Scripts: `mockup` adicionado ao `package.json` (`tsx scripts/mock-data.ts`)
|
||||
|
||||
### Alterado
|
||||
|
||||
- Nav: sidebar lateral removida — navegação unificada na navbar
|
||||
- UI/Tema: raio de borda global 0.625rem → 0.7rem; ajustes finos em `--card` e `--border` (light e dark)
|
||||
- UI: `DotPattern` removido do layout dashboard, tela de autenticação e landing page
|
||||
- UI: account-card redesenhado com cores de saldo (success/destructive) e tooltip para flags de exclusão
|
||||
- UI: budget-card, card-item e componentes do calendário (day-cell, event-modal) com layout revisado
|
||||
- UI: auth-card-shell simplificado (removido glassmorphism e blob animado)
|
||||
- Landing: imagens de preview atualizadas; `mainFeatures` + `extraFeatures` unificados em grid único; dark mode nos botões de CTA
|
||||
- Navbar: dark mode corrigido no navbar-shell (`dark:bg-card`, `dark:border-b-border`)
|
||||
- Logo.dev: `NEXT_PUBLIC_LOGO_DEV_TOKEN` renomeado para `LOGO_DEV_TOKEN` (agora lido em runtime server-side apenas)
|
||||
- UI: conceito "Pagador/Pagadores" renomeado para **"Pessoa/Pessoas"** em toda a interface — labels, títulos, toasts, mensagens de erro, cabeçalhos de tabela e exportações. Código, rotas (`/payers`) e schema do banco (`pagadores`) permanecem inalterados; a divergência entre UI e código é intencional
|
||||
- Deps: next 16.2.3 → 16.2.4, better-auth 1.6.2 → 1.6.5, ai 6.0.159 → 6.0.168 e outros patches menores
|
||||
- Notas/Tarefas: ícone de tarefa concluída em visualização (card e detalhes) simplificado para `RiCheckLine` verde sem caixa; checkbox no modal de edição usa fundo e borda `success` com ícone `success-foreground` (claro no light, escuro no dark)
|
||||
- Notas/Detalhes: botões do footer reordenados ("Cancelar" à esquerda, "Alterar" primário à direita)
|
||||
|
||||
### Removido
|
||||
|
||||
- Nav: componentes sidebar (`app-sidebar`, `nav-main`, `nav-secondary`, `nav-user`, `nav-link`), `sidebar.tsx` e `use-mobile.ts`
|
||||
- Dashboard: ~25 widgets monolíticos obsoletos (`inbox-widget`, `bills-widget`, `notes-widget`, `payers-widget`, `my-accounts-widget` etc.)
|
||||
- Dashboard: arquivos dispersos na raiz da feature movidos para subdiretórios (arquivos antigos removidos)
|
||||
- CSS: variáveis `--data-7` a `--data-10` removidas do tema
|
||||
- CI: build arg `NEXT_PUBLIC_LOGO_DEV_TOKEN` removido do `Dockerfile` e do workflow `docker-publish.yml` — basta configurar `LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY` como variáveis de runtime no host (Coolify, Railway, etc.)
|
||||
|
||||
## [2.4.1] - 2026-04-16
|
||||
|
||||
Versão pequena com refresh visual nas telas de autenticação (efeito blob com três círculos coloridos em movimento e card com glassmorphism), capitalização dos labels da navbar para melhor legibilidade e otimização do banco com 17 índices novos em foreign keys — evitando sequential scans em deletes em tabelas grandes como `lancamentos`. Corrigida regressão no `postgres:18-alpine` que recusava iniciar em instalações existentes; adicionada variável `PGDATA` no compose para preservar dados de quem já tinha o volume populado.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- UI/Auth: layout animado nas páginas de login e signup com efeito blob (3 círculos coloridos em movimento) e card com glassmorphism; layout compartilhado extraído para `app/(auth)/layout.tsx` eliminando duplicação (PR #42)
|
||||
- DB: 17 índices em foreign keys — evita sequential scans em deletes nas tabelas pai. Impacto maior nas FKs de `lancamentos` (conta_id, categoria_id, antecipacao_id), onde deletes em `categorias` antes provocavam full scan na tabela de lançamentos
|
||||
|
||||
### Alterado
|
||||
|
||||
- UI/Navbar: labels capitalizados (Lançamentos, Categorias, Contas) em vez de caixa baixa — melhora legibilidade (PR #42)
|
||||
|
||||
### Removido
|
||||
|
||||
- DB: 7 índices sem uso — `tokens_api_user_id_idx`, `cartoes_user_id_status_idx`, `contas_user_id_status_idx`, `pagadores_user_id_status_idx`, `pagadores_user_id_role_idx`, `dashboard_notification_states_user_id_archived_idx`, `antecipacoes_parcelas_series_id_idx` (0 scans em 187 dias de estatísticas)
|
||||
- UI/Settings: tab de Integrações órfã removida (não tinha `TabsContent` correspondente)
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Docker: container do PostgreSQL falhava ao iniciar em instalações existentes após atualização da imagem `postgres:18-alpine` — entrypoint passou a recusar dados no caminho legado `/var/lib/postgresql/data`. Adicionada variável `PGDATA` no `docker-compose.yml` para fixar o caminho e preservar dados de quem já tinha o volume populado (resolve #41)
|
||||
|
||||
## [2.4.0] - 2026-04-13
|
||||
|
||||
Esta versão integra o serviço Logo.dev para exibir automaticamente logos de marcas na coluna de estabelecimentos dos lançamentos, com picker manual para fixar o domínio quando a sugestão automática não acerta. As consultas vão por novas rotas de API (`/api/logo/search` e `/api/logo/mapping`) que servem como proxy seguro — a secret key fica server-side. Inclui também tabela própria `establishment_logos` com PK composta `(user_id, name_key)` para persistir as preferências por usuário.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Estabelecimentos: integração com Logo.dev — logos automáticos de marcas exibidos na coluna de estabelecimentos nos lançamentos
|
||||
- Estabelecimentos: picker de logo por estabelecimento — clique no avatar para buscar e fixar um domínio Logo.dev específico (salvo por usuário no banco)
|
||||
- API: rotas `/api/logo/search` e `/api/logo/mapping` — proxy seguro para Logo.dev Brand Search API (secret key server-side) e consulta de mapeamentos salvos
|
||||
- Schema: tabela `establishment_logos` com PK composta `(user_id, name_key)` para persistir preferências de logo por usuário
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Dev: `.env.example` usava host `db` no `DATABASE_URL`, causando erro `EAI_AGAIN` ao rodar `pnpm dev` localmente — corrigido para `localhost`
|
||||
|
||||
### Documentação
|
||||
|
||||
- README: tabela comparativa entre Perfil 1 (Usar) e Perfil 2 (Desenvolver) com diferenças de setup, `DATABASE_URL` e instruções de atualização
|
||||
- README: seção "Variáveis de Ambiente" esclarecida — distingue contexto Docker (Perfil 1) de desenvolvimento local (Perfil 2)
|
||||
- Logo.dev: crie uma conta em logo.dev para obter as chaves `NEXT_PUBLIC_LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY` — plano gratuito inclui 500.000 requisições/mês
|
||||
|
||||
## [2.3.8] - 2026-04-12
|
||||
|
||||
Refatoração do `docker-compose.yml` para virar standalone — agora basta um `curl` + `docker compose up -d`, sem dependências de arquivos externos ou profiles complexos. README reescrito em dois perfis claros (Usar com Docker e Desenvolver com hot-reload) e scripts npm reduzidos de 10 para 5.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Docker: `docker-compose.yml` refatorado — removidos profiles, build e dependência de arquivo externo; compose agora é standalone (basta `curl` + `docker compose up -d`)
|
||||
- Docker: `docker-entrypoint.sh` simplificado — extensão `pgcrypto` criada via Node.js antes das migrations; loop de retry reescrito; removido hack `@localhost → @db`
|
||||
- Docker: scripts reduzidos de 10 para 5 — `docker:up`, `docker:db`, `docker:down`, `docker:logs`, `docker:update`
|
||||
- Docs: README reestruturado em dois perfis claros — **Usar** (só Docker) e **Desenvolver** (hot-reload)
|
||||
|
||||
## [2.3.7] - 2026-04-11
|
||||
|
||||
Esta versão amplia significativamente o dashboard com três novos widgets configuráveis (Anexos, Inbox, Tendências de Categoria), adiciona filtros úteis na tabela de lançamentos (por status de pagamento e por presença de anexo) e moderniza a tipografia substituindo a fonte local por Inter (Google Fonts, self-hosted pelo Next.js) — eliminando arquivos `.woff2` do repositório. Pesos tipográficos foram padronizados para `font-semibold` em títulos, rótulos e valores monetários, e o card de grupo de parcelas foi redesenhado expandindo num dialog de detalhes com parcelas pagas/pendentes separadas. No backend, a CSP foi expandida para permitir preview de anexos PDF via S3, e o setup ganhou script `install-deps.sh` pra preparar servidores Ubuntu 24.04 limpos.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Dashboard: novos widgets configuráveis — Anexos (resumo de arquivos do período), Inbox (snapshot de pré-lançamentos pendentes) e Tendências de Categoria
|
||||
- Lançamentos: filtro por status de pagamento (somente pagos / somente não pagos) e filtro por presença de anexo
|
||||
- Lançamentos: indicador visual no status de liquidação para lançamentos de cartão de crédito com fatura paga — exibe ícone verde com tooltip explicativo
|
||||
- Scripts: `scripts/install-deps.sh` — script de preparação para servidores Ubuntu 24.04 limpos (instala Docker, Node.js 22, pnpm via Homebrew)
|
||||
- Docker: variáveis `PUBLIC_DOMAIN`, `UMAMI_URL`, `UMAMI_WEBSITE_ID` e `UMAMI_DOMAINS` passadas ao container da aplicação no `docker-compose.yml`
|
||||
|
||||
### Alterado
|
||||
|
||||
- Fonte: substituída fonte local `America` por `Inter` (Google Fonts, self-hosted pelo Next.js) — elimina arquivos `.woff2` do repositório
|
||||
- Tipografia: peso tipográfico padronizado de `font-medium` para `font-semibold` em títulos, rótulos e valores monetários em toda a interface
|
||||
- Parcelas: redesenho do card de grupo de parcelas — expandindo para dialog de detalhes com parcelas pagas/pendentes separadas
|
||||
- Inbox: redesenho do card de pré-lançamento — logo maior, hierarquia tipográfica melhorada
|
||||
- Lançamentos: filtros de tipo, condição e forma de pagamento agora usam slugs em URL (ex: `receita` em vez do valor literal com acentos)
|
||||
- Estabelecimento: popover de autocomplete agora respeita a largura do input ao abrir
|
||||
- CSP: adicionado `frame-src` para permitir preview de anexos PDF via S3
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Docker: corrigido crash loop no container com mensagem `exec /app/docker-entrypoint.sh: no such file or directory` causado por CRLF no `docker-entrypoint.sh` em ambientes Windows/WSL2 — adicionado `sed -i 's/\r$//'` no Dockerfile e `.gitattributes` com `eol=lf` para scripts shell
|
||||
- S3: corrigido `Error: Region is missing` ao usar o app sem S3 configurado — `S3_REGION` vazio (string vazia) não era tratado pelo operador `??`; substituído por `||` em todo o `s3-client.ts`
|
||||
- i18n: corrigidas mensagens de erro que exibiam "Payer" em inglês em vez de "Pagador"
|
||||
- Logos: corrigido modal seletor de logos de cartões e contas para renderizar miniaturas sem avisos de proporção
|
||||
- Scripts: `install-deps.sh` — spinner travava o script por `wait` retornar código não-zero com `set -e` ativo; corrigido com `|| true`
|
||||
- Scripts: `install-deps.sh` — prompt interativo do corepack suprimido com `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`
|
||||
- Scripts: `install-deps.sh` — PATH do Homebrew não estava configurado na seção de resumo
|
||||
|
||||
### Removido
|
||||
|
||||
- Scripts: removidos arquivos órfãos `scripts/dev.ts` e `scripts/setup-env.sh` (substituídos pelo `setup.mjs`)
|
||||
- Docker: `docker-compose.yml` agora funciona sem arquivo `.env` — `DATABASE_URL` tem valor padrão com credenciais de desenvolvimento
|
||||
- Docker: `docker-entrypoint.sh` converte automaticamente `@localhost:` para `@db:` na `DATABASE_URL` ao iniciar o container, eliminando a necessidade de usar hosts diferentes no `.env` para desenvolvimento local e Docker
|
||||
|
||||
## [2.3.6] - 2026-04-09
|
||||
|
||||
Correção pontual no Docker — adicionado `NODE_PATH=/app/migrate/node_modules` no entrypoint para o `drizzle-kit` resolver corretamente o `drizzle-orm` ao executar as migrations no container.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Docker: adicionado `NODE_PATH=/app/migrate/node_modules` no entrypoint para que o `drizzle-kit` consiga resolver `drizzle-orm` ao executar as migrations no container
|
||||
|
||||
## [2.3.5] - 2026-04-07
|
||||
|
||||
Correção crítica na CSP: regra movida do `next.config.ts` (build time) para `proxy.ts` (runtime), desbloqueando uploads de anexos quando o `S3_ENDPOINT` ainda não estava disponível durante o build da imagem Docker.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- CSP: movido `Content-Security-Policy` do `next.config.ts` (build time) para `proxy.ts` (runtime), corrigindo bloqueio de upload de anexos quando `S3_ENDPOINT` não estava disponível durante o build do Docker
|
||||
|
||||
## [2.3.4] - 2026-04-05
|
||||
|
||||
Correção pontual no upload de anexos — a CSP `connect-src` bloqueava o fetch para o storage, gerando `NetworkError` na hora de subir o arquivo.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Anexos: corrigido upload que falhava com `NetworkError` — CSP `connect-src` bloqueava fetch para o Storage
|
||||
|
||||
## [2.3.3] - 2026-04-05
|
||||
|
||||
Correção do fluxo de tokens da API: `/api/auth/device/verify` voltou a aceitar tokens criados pela tela de Settings (revertido de JWT para hash lookup). O prefixo dos tokens também foi renomeado de `os_` para `opm_` (OpenMonetis) e rotas JWT não utilizadas foram removidas — usuários precisam recriar os tokens existentes.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Tokens: corrigido `/api/auth/device/verify` que rejeitava tokens criados via Settings (revertido de JWT para hash lookup)
|
||||
@@ -21,6 +549,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.3.2] - 2026-04-04
|
||||
|
||||
Esta versão concentra hardening de segurança. Tokens da API ganharam expiração obrigatória de 1 ano (sem mais tokens eternos) e o refresh foi corrigido para validar JWT por assinatura. A CSP foi expandida com `default-src`, `script-src`, `style-src`, `img-src`, `font-src` e `connect-src` (no lugar de uma regra única ampla), e foi adicionada mitigação para CVE-2024-44294 desabilitando parsing de fórmulas em `xlsx`. Inclui ainda novos headers (`Referrer-Policy`, `X-Permitted-Cross-Domain-Policies`), respostas `401 JSON` em vez de redirect 302 em rotas autenticadas, `security.txt` (RFC 9116) e correção de URL com protocolo duplicado no sitemap.
|
||||
|
||||
### Segurança
|
||||
|
||||
- Tokens: removido aceite de tokens sem expiração (`expiresAt NULL`); tokens criados via settings agora expiram em 1 ano
|
||||
@@ -36,12 +566,16 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.3.1] - 2026-04-03
|
||||
|
||||
Correção pontual de infraestrutura — dependências do `drizzle-kit` passaram a ser instaladas em `/app/migrate/` separadamente do `node_modules` do build standalone, corrigindo o erro `Cannot find module 'next'` no startup do container.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Infraestrutura: deps do drizzle-kit agora são instaladas em `/app/migrate/` separado do `node_modules` do standalone, corrigindo erro `Cannot find module 'next'` no startup do container
|
||||
|
||||
## [2.3.0] - 2026-04-03
|
||||
|
||||
Esta versão introduz `@tanstack/react-query` no projeto, padronizando cache, deduplicação e invalidação de leituras client-side. Várias features (anexos, insights, antecipação de parcelas) passaram a usar React Query no lugar de `useEffect` manual sobre rotas GET dedicadas. O dashboard ganhou ajuda contextual em cada métrica e configuração persistida pra ocultar contas marcadas como não consideradas no saldo total; o menu do usuário na navbar passou a avisar quando há release nova publicada no GitHub; e o Docker passou a rodar migrations automaticamente no startup via `docker-entrypoint.sh`. Internamente, o `knip` foi adicionado pra auditar arquivos/exports/tipos sem uso, várias rotas e actions ganharam validações extras (filtros por `userId` em joins, rate limits explícitos no Better Auth, headers `Cache-Control: private, no-store` em rotas privadas) e o projeto foi atualizado para Next.js 16.2.2 e Biome 2.4.10.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Dependências: adiciona `@tanstack/react-query` e um provider global para padronizar cache, deduplicação e invalidação de leituras client-side
|
||||
@@ -77,12 +611,16 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.2.1] - 2026-04-01
|
||||
|
||||
Correção pontual no build da imagem Docker — removido `chown -R /app` do stage final (que travava o build/push da GitHub Action por lentidão excessiva); permissões agora definidas via `COPY --chown` direto.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Docker: imagem de produção deixa de executar `chown -R /app` no stage final; as permissões passam a ser definidas nos `COPY --chown`, reduzindo o risco de travamento e lentidão excessiva no build/push da GitHub Action
|
||||
|
||||
## [2.2.0] - 2026-04-01
|
||||
|
||||
Esta versão entrega uma nova página dedicada de galeria de anexos em `/attachments` com miniaturas, visualização inline (incluindo PDF via `pdfjs-dist`), download direto e acesso a partir do lançamento. As páginas de login e cadastro foram redesenhadas com sidebar mockup de faturas, três blocos de funcionalidade e gradiente decorativo. O dashboard passou a notificar boletos e faturas com vencimento dentro de 5 dias, e o cache do dashboard migrou de `unstable_cache` para a diretiva `use cache` (com `cacheTag` e `cacheLife`), com `cacheComponents: true` no `next.config.ts` e `connection()` em todas as páginas para forçar render dinâmico. A tipografia ganhou peso 500 (Medium) padronizado em títulos, valores e rótulos.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Anexos: nova página de galeria em `/attachments` com miniaturas, visualização inline de imagem e PDF, download direto e acesso a partir do lançamento
|
||||
@@ -103,6 +641,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.1.2] - 2026-03-30
|
||||
|
||||
Pequena versão de polimento: novo escopo `"period"` na ação em lote de lançamentos (aplica alteração a todos os lançamentos do período sem sobrescrever o pagador individual de cada um), preferência de tamanho máximo por arquivo de anexo (5/10/25/50/100 MB) persistida no banco e respeitada em todos os pontos de upload, e redesign visual da página de Configurações com separadores entre seções e títulos maiores.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Preferências: nova configuração de tamanho máximo por arquivo de anexo (5, 10, 25, 50 ou 100 MB), persistida no banco e respeitada em todos os pontos de upload
|
||||
@@ -119,6 +659,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.1.1] - 2026-03-29
|
||||
|
||||
Esta versão extrai a navbar pra um componente `NavbarShell` compartilhado entre app e landing page e cria uma variante `navbar` no Button pra centralizar os estilos antes duplicados em `nav-styles.ts`. A integração com `@vercel/analytics`/`@vercel/speed-insights` foi substituída por Umami self-hosted via script tag no layout raiz.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Navbar: novo componente `NavbarShell` que unifica a estrutura da barra de navegação entre o app e a landing page
|
||||
@@ -140,6 +682,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.1.0] - 2026-03-28
|
||||
|
||||
Esta versão adiciona suporte a anexos em transações, com upload direto para storage compatível com S3, persistência em tabelas dedicadas (`anexos` e `lancamento_anexos`) e ações de visualizar/remover no detalhe do lançamento. O upload exige token assinado por arquivo, valida ownership da transação na leitura/remoção e confere tamanho/tipo do objeto no storage antes de persistir o vínculo no banco. Inclui também novo workflow `release.yml` que cria tag e GitHub Release automaticamente a partir da versão do `package.json` e da entrada correspondente no `CHANGELOG.md`.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Lançamentos: suporte a anexos em transações com upload direto para storage compatível com S3, persistência em tabelas dedicadas (`anexos` e `lancamento_anexos`) e ações de visualizar/remover no detalhe do lançamento
|
||||
@@ -155,12 +699,16 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.0.3] - 2026-03-26
|
||||
|
||||
Correção pontual em `/transactions` — removida dependência de `crypto.randomUUID()` no carregamento inicial, que falhava em ambientes self-hosted sem HTTPS (a API só está disponível em contextos seguros).
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Lançamentos: `/transactions` deixa de depender de `crypto.randomUUID()` no carregamento inicial, corrigindo a falha em ambientes self-hosted sem HTTPS ao abrir a página
|
||||
|
||||
## [2.0.2] - 2026-03-25
|
||||
|
||||
Versão focada nas notificações da navbar: novo estado persistido permite marcar alertas de fatura, boleto e orçamento como lidos ou arquivados por usuário; o snapshot global passa a usar o período corrente do negócio (não mais o `periodo` da URL), itens lidos saem do badge e arquivados somem da lista padrão do sino. O filtro foi refinado para um seletor explícito entre `Ativas` e `Arquivadas`. Inclui ajustes pontuais no detalhamento por categoria do dashboard (oculta categorias sem movimentação no período), na arte decorativa do cabeçalho de boas-vindas e na edição em lote de lançamentos em série (que agora propaga também o status de pagamento para transações fora do cartão).
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Scripts: novo comando `mockup` no `package.json` para executar `scripts/mock-data.ts`
|
||||
@@ -189,6 +737,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.0.1] - 2026-03-21
|
||||
|
||||
Versão de correções na inbox de pré-lançamentos: filtro por app passa a montar a lista completa a partir de todos os itens do status atual (sem depender da página carregada), notificações de cartões/apps sem logo cadastrado passam a usar `default_icon.png` como fallback, e o select de apps exibe os logos. Inclui também correção de divergência entre a versão exibida no UI e a reportada pelo `/api/health` (que agora reporta a versão atual do `package.json`).
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Inbox: filtro por app em `/inbox` agora monta a lista completa de apps da aba a partir de todos os itens do status atual, sem depender apenas da página carregada, e o SSR deixa de quebrar quando `sourceApps` vier inconsistente
|
||||
@@ -199,6 +749,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [2.0.0] - 2026-03-21
|
||||
|
||||
Marco importante do projeto. Esta versão consolida ganhos de performance, segurança e organização interna. No backend, paginação server-side real foi implementada em transações, extrato e inbox; o dashboard reduziu de 19 fetchers para 7 blocos com agregações compartilhadas; exportações de PDF/Excel passaram a carregar libs sob demanda apenas no clique; e o cache de dashboard/insights ganhou invalidação segmentada por `userId` (sem fallback global). Internamente, identificadores foram migrados de PT-BR para inglês (`lancamento` → `transaction`, `pagador` → `payer`, `conta` → `account`, etc.) e helpers foram consolidados em módulos de domínio. Visualmente, a navbar e os cards de auth ganharam dot pattern + brilho em primary, faturas tiveram refinamento na hierarquia visual, e a tipografia foi unificada na família America. Inclui ainda script `scripts/backup.sh` para backup automático do PostgreSQL, importação de extratos OFX e XLS/XLSX com tela de revisão e dedup por FITID, e nova opção de zerar dados financeiros sem excluir o usuário.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Infraestrutura: script `scripts/backup.sh` para backup automático do banco PostgreSQL; configuração de destino (rclone, cron, retenção) feita separadamente; passa a gerar também `*.data.sql.gz` com dados puros de todas as tabelas públicas (`--data-only --schema=public`)
|
||||
@@ -255,6 +807,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.7] - 2026-03-05
|
||||
|
||||
Versão de organização interna sem mudanças visíveis grandes. Períodos e navegação mensal passaram a usar os helpers centrais de período (`YYYY-MM`), hooks locais (calculadora, month-picker, logo picker) foram movidos pra perto das respectivas features e `components/navbar`/`sidebar` foram consolidados em `components/navigation/*`. Análise de parcelas migrou para `/relatorios/analise-parcelas`, exportações em PDF/CSV/Excel ganharam melhor branding e apresentação, e a calculadora teve ajustes de estabilidade no arrasto.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Períodos e navegação mensal: `useMonthPeriod` passou a usar os helpers centrais de período (`YYYY-MM`), o month-picker foi simplificado e o rótulo visual agora segue o formato `Março 2026`.
|
||||
@@ -269,6 +823,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.6] - 2026-03-02
|
||||
|
||||
Esta versão adiciona suporte completo a Passkeys (WebAuthn) via `@better-auth/passkey`: nova aba em `/ajustes` permite listar, adicionar, renomear e remover credenciais, e a tela de login ganhou ação dedicada para passkey. O dashboard ganhou widget de Anotações e atalhos rápidos na toolbar de widgets pra criar Receita, Despesa ou Anotação direto. Top Estabelecimentos foi unificado num único widget com abas, e o widget "Lançamentos recentes" foi substituído por "Progresso de metas" com lista de orçamentos do período (gasto, limite e percentual de uso por categoria).
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Suporte completo a Passkeys (WebAuthn) com plugin `@better-auth/passkey` no servidor e `passkeyClient` no cliente de autenticação
|
||||
@@ -303,6 +859,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.5] - 2026-02-28
|
||||
|
||||
Versão pequena de polimento: ações para excluir item individual (processado/descartado) e limpar itens em lote por status na inbox de pré-lançamentos, redesign dos cards e diálogos dos widgets de boletos e faturas com indicação "Atrasado / Pagar" quando vencidos e não pagos, e migração da página de categorias de cards pra layout em tabela com link direto para detalhe e ações inline.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Inbox de pré-lançamentos: ações para excluir item individual (processado/descartado) e limpar itens em lote por status
|
||||
@@ -321,6 +879,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.4] - 2026-02-28
|
||||
|
||||
Versão de polimento de responsividade no mobile: 26 componentes ajustados (navbar, filtros, skeletons, widgets, dialogs), card de análise de parcelas empilhado verticalmente em telas pequenas e cards do top estabelecimentos reorganizados em coluna única no mobile. Inclui também regra mais inteligente em "Remover selecionados" — quando todos os itens pertencem à mesma série, abre dialog de escopo com 3 opções; e ajuste no consumo de limite por despesa recorrente no cartão (só consome quando a data já passou).
|
||||
|
||||
### Alterado
|
||||
|
||||
- Card de análise de parcelas (`/dashboard/analise-parcelas`): layout empilhado no mobile — nome/cartão e valores Total/Pendente em linhas separadas ao invés de lado-a-lado, evitando truncamento
|
||||
@@ -332,6 +892,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.3] - 2026-02-27
|
||||
|
||||
Versão pequena com nova prop `compact` no DatePicker (formato abreviado "28 fev", sem "de" e sem ano) e modal de múltiplos lançamentos reformulado: selects de conta e cartão separados por forma de pagamento, InlinePeriodPicker ao escolher cartão de crédito e DatePicker compacto.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Prop `compact` no DatePicker para formato abreviado "28 fev" (sem "de" e sem ano)
|
||||
@@ -343,6 +905,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.2] - 2026-02-26
|
||||
|
||||
Versão de polimento dos diálogos: padding maior (p-10), largura padronizada em `max-w-xl` e botões do footer com largura igual; o lançamento dialog ganhou seção colapsável "Condições e anotações" e cálculo automático do período da fatura via `deriveCreditCardPeriod()`. Inclui também uma faxina de tipos (non-null assertions removidas, `any` substituído por tipos explícitos em 15+ arquivos) e remoção de 6 componentes e 20+ funções/tipos sem uso.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Dialogs padronizados: padding maior (p-10), largura max-w-xl, botões do footer com largura igual (flex-1)
|
||||
@@ -366,6 +930,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.7.1] - 2026-02-24
|
||||
|
||||
Esta versão substitui o header lateral por uma topbar de navegação com backdrop blur e links agrupados em 5 seções (Dashboard, Lançamentos, Cartões, Relatórios, Ferramentas), expande o sino de notificações pra exibir orçamentos estourados e pré-lançamentos pendentes em seções separadas, e cria página dedicada de changelog em `/changelog` (acessível pelo menu do usuário com a versão atual exibida ao lado).
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Topbar de navegação substituindo o header fixo: backdrop blur, links agrupados em 5 seções (Dashboard, Lançamentos, Cartões, Relatórios, Ferramentas)
|
||||
@@ -389,6 +955,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.6.3] - 2026-02-19
|
||||
|
||||
Correção pontual: variável `RESEND_FROM_EMAIL` não era lida corretamente do `.env` quando o valor continha espaços (precisa estar entre aspas). Leitura centralizada em `lib/email/resend.ts` com `getResendFromEmail()` e carregamento explícito do `.env` no contexto de Server Actions.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- E-mail Resend: variável `RESEND_FROM_EMAIL` não era lida do `.env` (valores com espaço precisam estar entre aspas). Leitura centralizada em `lib/email/resend.ts` com `getResendFromEmail()` e carregamento explícito do `.env` no contexto de Server Actions
|
||||
@@ -400,12 +968,16 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.6.2] - 2026-02-19
|
||||
|
||||
Correção pontual no mobile: ao selecionar um logo no diálogo de criação de conta/cartão, o diálogo principal fechava inesperadamente. Adicionado `stopPropagation` nos eventos de click/touch dos botões e delay com `requestAnimationFrame` antes de fechar o seletor.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Bug no mobile onde, ao selecionar um logo no diálogo de criação de conta/cartão, o diálogo principal fechava inesperadamente: adicionado `stopPropagation` nos eventos de click/touch dos botões de logo e delay com `requestAnimationFrame` antes de fechar o seletor de logo
|
||||
|
||||
## [1.6.1] - 2026-02-18
|
||||
|
||||
Versão pequena: nome do estabelecimento padronizado para transferências entre contas ("Saída - Transf. entre contas" e "Entrada - Transf. entre contas") com anotação no formato "de {origem} -> {destino}", e correção de avisos `width(-1) and height(-1)` do `ChartContainer` no console.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Transferências entre contas: nome do estabelecimento passa a ser "Saída - Transf. entre contas" na saída e "Entrada - Transf. entre contas" na entrada e adicionando em anotação no formato "de {conta origem} -> {conta destino}"
|
||||
@@ -413,6 +985,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.6.0] - 2026-02-18
|
||||
|
||||
Versão de personalização da tabela de lançamentos. Duas novas preferências em Ajustes > Extrato e lançamentos: "Anotações em coluna" (controla se a anotação aparece como coluna ou tooltip no ícone) e "Ordem das colunas" (lista ordenável por arrasto pra reordenar Estabelecimento, Transação, Valor etc.). Inclui ajustes mobile no header do dashboard (fixo só no mobile) e na rolagem horizontal de tabs e botões de ação.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Preferência "Anotações em coluna" em Ajustes > Extrato e lançamentos: quando ativa, a anotação dos lançamentos aparece em coluna na tabela; quando inativa, permanece no balão (tooltip) no ícone
|
||||
@@ -433,6 +1007,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.5.3] - 2026-02-21
|
||||
|
||||
Versão focada no painel do pagador (novo card "Status de Pagamento" com totais pagos/pendentes e listagem individual de boletos com data de vencimento, data de pagamento e status), além de SEO completo na landing page (Open Graph, Twitter Card, JSON-LD Schema.org, sitemap.xml e robots.txt) e layout específico com metadados ricos. Imagens da landing convertidas de PNG para WebP para melhor performance.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Painel do pagador: card "Status de Pagamento" com totais pagos/pendentes e listagem individual de boletos com data de vencimento, data de pagamento e status
|
||||
@@ -454,6 +1030,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.5.2] - 2026-02-16
|
||||
|
||||
Reforma visual da landing page: hero com gradient sutil e tipografia responsiva, dashboard preview sem bordas pra visual mais limpo, seção "Funcionalidades" reorganizada em 6 cards principais + 6 extras compactos, seção "Como usar" com tabs Docker (Recomendado) vs Manual e footer simplificado em 3 colunas. Inclui menu hamburger mobile com Sheet drawer, animações fade-in via Intersection Observer e seção dedicada ao OpenMonetis Companion com screenshots e fluxo de captura.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Landing page reformulada: visual modernizado, melhor experiência mobile e novas seções
|
||||
@@ -476,6 +1054,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.5.1] - 2026-02-16
|
||||
|
||||
Esta versão renomeia o projeto de **OpenSheets** para **OpenMonetis** em todo o codebase (~40 arquivos: package.json, manifests, layouts, componentes, server actions, emails, Docker, docs e landing page). URLs do repositório atualizados de `opensheets-app` para `openmonetis`, image Docker renomeada para `felipegcoutinho/openmonetis` e logo textual atualizado. Inclui também suporte a multi-domínio via `PUBLIC_DOMAIN` (domínio público serve apenas a landing page, com middleware bloqueando rotas do app).
|
||||
|
||||
### Alterado
|
||||
|
||||
- Projeto renomeado de **OpenSheets** para **OpenMonetis** em todo o codebase (~40 arquivos): package.json, manifests, layouts, componentes, server actions, emails, Docker, docs e landing page
|
||||
@@ -490,6 +1070,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.5.0] - 2026-02-15
|
||||
|
||||
Versão de personalização tipográfica: 13 fontes disponíveis (incluindo SF Pro Display, SF Pro Rounded, Inter, Geist Sans, Roboto, Reddit Sans, JetBrains Mono e outras) configuráveis por usuário tanto pra interface quanto pros valores monetários, com FontProvider que aplica a troca instantaneamente via CSS variables sem necessidade de reload. Fontes Apple SF Pro carregadas localmente com 4 pesos (Regular, Medium, Semibold, Bold) e novas colunas `system_font` e `money_font` na tabela `preferencias_usuario`.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Customização de fontes nas preferências — fonte da interface e fonte de valores monetários configuráveis por usuário
|
||||
@@ -509,6 +1091,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.4.1] - 2026-02-15
|
||||
|
||||
Versão focada na inbox de pré-lançamentos: novas abas "Pendentes", "Processados" e "Descartados" (antes só pendentes), logo do cartão/conta exibido automaticamente nos cards via matching por nome do app, pre-fill automático do cartão de crédito ao processar e badges de status com data nos itens já processados/descartados em modo readonly. Cor `--warning` ajustada para melhor contraste (mais alaranjada).
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Abas "Pendentes", "Processados" e "Descartados" na página de pré-lançamentos (antes exibia apenas pendentes)
|
||||
@@ -530,6 +1114,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.4.0] - 2026-02-07
|
||||
|
||||
Reforma do design system: ~60+ componentes migrados de cores hardcoded do Tailwind (`green-500`, `red-600`, `amber-500`, `blue-500` etc.) pra tokens semânticos (`success`, `destructive`, `warning`, `info`); adicionados novos tokens `--success`, `--warning`, `--info` (com foregrounds) tanto em light quanto dark mode, novas variantes `success` e `info` no Badge, e cores de chart estendidas de 6 para 10. Inclui também correção do bug de invalidação de cache do dashboard que impedia widgets de boleto/fatura de atualizar após pagamento, e fix de scroll em listas Popover+Command (estabelecimento, categorias, filtros) com a prop `modal`.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Widgets de boleto/fatura não atualizavam após pagamento: actions de fatura (`updateInvoicePaymentStatusAction`, `updatePaymentDateAction`) e antecipação de parcelas não invalidavam o cache do dashboard
|
||||
@@ -560,6 +1146,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.3.1] - 2026-02-06
|
||||
|
||||
Versão pequena: calculadora arrastável via drag handle no header do dialog, callback `onSelectValue` pra inserir valor diretamente no campo de lançamento, e nova aba "Changelog" em Ajustes com histórico parseado do `CHANGELOG.md`. As páginas de itens ativos e arquivados em Cartões, Contas e Anotações foram unificadas com sistema de tabs (mesmo padrão de Categorias), eliminando rotas separadas e nomenclatura inconsistente.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Calculadora arrastável via drag handle no header do dialog
|
||||
@@ -575,6 +1163,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.3.0] - 2026-02-06
|
||||
|
||||
Versão de performance no dashboard: indexes compostos em `lancamentos`, cache cross-request via `unstable_cache` com tag `"dashboard"` e TTL de 120s, e invalidação automática em mutations financeiras via `revalidateTag`. Eliminados ~20 JOINs com a tabela `pagadores` (substituídos por filtro direto via `pagadorId`) e queries consolidadas (income-expense-balance: 12→1 com GROUP BY; payment-status: 2→1; expenses/income por categoria: 4→2). Auth session deduplicada por request via `React.cache()` e scan de métricas limitado a 24 meses.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Indexes compostos em `lancamentos`: `(userId, period, transactionType)` e `(pagadorId, period)`
|
||||
@@ -595,6 +1185,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.2.6] - 2025-02-04
|
||||
|
||||
Versão de adaptação ao React 19 compiler: removidos ~60 `useCallback`/`useMemo` desnecessários, wrappers `React.memo` redundantes e simplificação de padrões de hidratação com `useSyncExternalStore`. Sem mudanças visíveis ao usuário — só faxina interna alinhada às novas otimizações automáticas do compilador.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Refatoração para otimização do React 19 compiler
|
||||
@@ -623,6 +1215,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.2.5] - 2025-02-01
|
||||
|
||||
Versão pequena: novo widget de pagadores no dashboard com avatares atualizados.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Widget de pagadores no dashboard
|
||||
@@ -630,6 +1224,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.2.4] - 2025-01-22
|
||||
|
||||
Correção pontual: preservação de formatação nas anotações e ajuste no layout do card de anotações.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- Preservar formatação nas anotações
|
||||
@@ -637,6 +1233,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.2.3] - 2025-01-22
|
||||
|
||||
Versão pequena: versão do app passa a aparecer na sidebar e atualização da documentação.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Versão exibida na sidebar
|
||||
@@ -644,6 +1242,8 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
||||
|
||||
## [1.2.2] - 2025-01-22
|
||||
|
||||
Versão de manutenção: atualização de dependências e formatação aplicada em todo o código.
|
||||
|
||||
### Alterado
|
||||
|
||||
- Atualização de dependências
|
||||
|
||||
99
CLAUDE.md
@@ -16,9 +16,10 @@
|
||||
3. **Periods** usam formato `YYYY-MM` (ex: `"2025-11"`). Utils em `src/shared/utils/period/`.
|
||||
4. **Moeda**: R$ com 2 decimais. DB: `numeric(12, 2)`. Utils em `src/shared/utils/currency.ts`.
|
||||
5. **Revalidation**: usar `revalidateForEntity("entity")` de `src/shared/lib/actions/helpers.ts` apos mutations.
|
||||
6. **Versionamento**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também altere o `package.json`.
|
||||
6. **Versionamento**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também altere o `package.json` e `readme.md` (Badges do README.md). Cada versão deve ter um parágrafo introdutório em linguagem humana logo abaixo do cabeçalho `## [x.y.z]`, antes das seções `### Adicionado/Alterado/Removido` — descrevendo em prosa o que a versão representa (ex: "Esta versão foca em polimento visual e reorganização interna...").
|
||||
7. **Comunicacao**: responder em portugues clara e direta com o time.
|
||||
8. **Commit messages**: agrupar por natureza. em pt-br. seguindo o padrao do sistema.
|
||||
9. **README.md**: sempre que fizer alteracoes significativas, atualize o README.md.
|
||||
|
||||
---
|
||||
|
||||
@@ -84,6 +85,7 @@ src/
|
||||
│ │ ├── insights/
|
||||
│ │ ├── calendar/
|
||||
│ │ ├── inbox/
|
||||
│ │ ├── attachments/
|
||||
│ │ ├── changelog/
|
||||
│ │ ├── reports/
|
||||
│ │ │ ├── category-trends/
|
||||
@@ -95,7 +97,7 @@ src/
|
||||
│ ├── api/
|
||||
│ ├── globals.css
|
||||
│ └── layout.tsx
|
||||
├── features/
|
||||
├── features/ # cada feature segue: actions.ts, queries.ts, actions/, components/, hooks/, lib/
|
||||
│ ├── auth/
|
||||
│ ├── landing/
|
||||
│ ├── dashboard/
|
||||
@@ -110,13 +112,17 @@ src/
|
||||
│ ├── insights/
|
||||
│ ├── calendar/
|
||||
│ ├── inbox/
|
||||
│ ├── attachments/
|
||||
│ ├── reports/
|
||||
│ └── settings/
|
||||
├── shared/
|
||||
│ ├── components/
|
||||
│ │ ├── ui/
|
||||
│ │ ├── navigation/
|
||||
│ │ ├── providers/
|
||||
│ │ ├── ui/ # shadcn/ui primitives
|
||||
│ │ ├── navigation/ # navbar, sidebar, breadcrumbs
|
||||
│ │ ├── providers/ # React context providers
|
||||
│ │ ├── brand/ # logos do app (logo, logo-icon, logo-text)
|
||||
│ │ ├── widgets/ # widget-card, widget-empty-state, expandable-widget-card
|
||||
│ │ ├── feedback/ # empty-state, status-dot, payment-success
|
||||
│ │ ├── month-picker/
|
||||
│ │ ├── logo-picker/
|
||||
│ │ ├── calculator/
|
||||
@@ -131,34 +137,56 @@ src/
|
||||
│ │ ├── calculator/
|
||||
│ │ ├── categories/
|
||||
│ │ ├── email/
|
||||
│ │ ├── import/
|
||||
│ │ ├── installments/
|
||||
│ │ ├── invoices/
|
||||
│ │ ├── logo/
|
||||
│ │ ├── notifications/
|
||||
│ │ ├── payers/
|
||||
│ │ ├── schemas/
|
||||
│ │ ├── storage/
|
||||
│ │ ├── transfers/
|
||||
│ │ ├── types/
|
||||
│ │ ├── version/
|
||||
│ │ └── db.ts
|
||||
│ └── utils/
|
||||
│ ├── period/
|
||||
│ ├── calculator.ts
|
||||
│ ├── calendar.ts
|
||||
│ ├── category-colors.ts
|
||||
│ ├── currency.ts
|
||||
│ ├── date.ts
|
||||
│ ├── export-branding.ts
|
||||
│ ├── fetch-json.ts
|
||||
│ ├── financial-dates.ts
|
||||
│ ├── percentage.ts
|
||||
│ ├── category-colors.ts
|
||||
│ ├── calendar.ts
|
||||
│ ├── icons.tsx
|
||||
│ ├── id.ts
|
||||
│ ├── initials.ts
|
||||
│ ├── math.ts
|
||||
│ ├── number.ts
|
||||
│ ├── percentage.ts
|
||||
│ ├── string.ts
|
||||
│ ├── initials.ts
|
||||
│ ├── icons.tsx
|
||||
│ ├── export-branding.ts
|
||||
│ ├── ui.ts
|
||||
│ └── calculator.ts
|
||||
│ └── ui.ts
|
||||
└── db/
|
||||
└── schema.ts
|
||||
```
|
||||
|
||||
### Estrutura interna padrão de uma feature
|
||||
|
||||
Toda feature em `src/features/<nome>/` segue:
|
||||
|
||||
```text
|
||||
<feature>/
|
||||
├── actions.ts # entry point de Server Actions (barrel quando há actions/)
|
||||
├── queries.ts # entry point de leitura do banco
|
||||
├── actions/ # (opcional) Server Actions divididas por domínio quando o volume cresce
|
||||
├── components/ # componentes de UI da feature
|
||||
├── hooks/ # React hooks específicos da feature
|
||||
└── lib/ # helpers, types, sub-queries e constantes internas
|
||||
```
|
||||
|
||||
`actions.ts` e `queries.ts` são as portas de entrada da feature. Tudo que é helper interno fica em `lib/`. Componentes e hooks ficam nas pastas com nome óbvio.
|
||||
|
||||
---
|
||||
|
||||
## Import Patterns
|
||||
@@ -214,7 +242,9 @@ Layouts, `loading.tsx` e metadata continuam em `src/app/`.
|
||||
| `contas` | `accounts` |
|
||||
| `categorias` | `categories` |
|
||||
| `orcamentos` | `budgets` |
|
||||
| `pagadores` | `payers` |
|
||||
| `pessoas` | `payers` |
|
||||
|
||||
> **Nota:** o conceito de "pagador" foi renomeado para **"pessoa"** na UI (labels, toasts, textos visíveis ao usuário). O código, rotas e schema continuam usando o termo original em inglês (`payer`, `payerId`, `adminPayerId`) e em português interno (`pagador` como variável). Não renomear esses identificadores — a divergência entre UI e código é intencional e documentada.
|
||||
| `anotacoes` | `notes` |
|
||||
| `calendario` | `calendar` |
|
||||
| `ajustes` | `settings` |
|
||||
@@ -294,9 +324,11 @@ export async function fetchData(userId: string, period: string) {
|
||||
2. Criar a feature em `src/features/<feature>/`
|
||||
3. Separar:
|
||||
- `components/`
|
||||
- `queries.ts`
|
||||
- `actions.ts`
|
||||
- `types.ts` ou `schemas.ts` quando fizer sentido
|
||||
- `queries.ts` (entry point de leitura)
|
||||
- `actions.ts` (entry point de Server Actions; vira barrel quando crescer e migrar para `actions/`)
|
||||
- `lib/` para helpers internos, sub-queries por tópico, types e constantes da feature
|
||||
- `types.ts` ou `schemas.ts` quando fizer sentido (alternativa a `lib/`)
|
||||
- `hooks/` quando houver hooks específicos da feature
|
||||
4. Extrair para `src/shared/` tudo que for reutilizavel
|
||||
5. Atualizar navegacao e `revalidateForEntity()` se a feature tiver CRUD
|
||||
6. Rodar:
|
||||
@@ -306,18 +338,29 @@ export async function fetchData(userId: string, period: string) {
|
||||
|
||||
---
|
||||
|
||||
## Response Style
|
||||
## Security Rules
|
||||
|
||||
Quando o time pedir avaliacao de plano ou feature:
|
||||
Regras aplicadas automaticamente ao gerar codigo.
|
||||
|
||||
1. Responder em portugues simples.
|
||||
2. Listar 3-5 problemas principais.
|
||||
3. Fechar com decisao pratica:
|
||||
- aprova agora
|
||||
- nao aprova agora
|
||||
- o que ajustar antes de comecar codigo
|
||||
### Secrets
|
||||
Nunca colocar API keys, credenciais de banco ou tokens em codigo frontend. Evitar variaveis prefixadas com `NEXT_PUBLIC_` para dados sensiveis — estas sao bundladas no cliente. Usar variaveis server-side apenas. `.env` deve estar no `.gitignore` antes do primeiro commit. `.env.example` deve ter apenas placeholders.
|
||||
|
||||
Exemplo:
|
||||
### Autenticacao & Autorizacao
|
||||
Toda rota protegida em `src/app/api/` requer `getUser()` ou `getOptionalUserSession()` antes de qualquer logica, retornando 401 para nao autenticados. Rotas com IDs de recursos devem verificar ownership: `eq(table.userId, userId)`. Rotas admin devem checar role e retornar 403 para nao-admins. Session cookies em Better Auth ja tem `httpOnly`, `secure` e `sameSite` configurados — nao alterar.
|
||||
|
||||
- "Nao aprovaria para comecar codigo imediatamente."
|
||||
- "Primeiro ajustaria o doc com estes 5 pontos."
|
||||
### Input & Output
|
||||
Usar Drizzle ORM (parametrizado por padrao) — nunca concatenar input de usuario em SQL. Validar todo input com Zod antes de usar. Upload de arquivos: usar whitelist de MIME types (`ALLOWED_MIME_TYPES`), presigned URLs para S3, token de upload assinado com verificacao pos-upload. Nunca usar `dangerouslySetInnerHTML` com conteudo de usuario.
|
||||
|
||||
### Headers & CSP
|
||||
CSP definida em `src/proxy.ts` via middleware — alterar la, nao em `next.config.ts`. Headers de seguranca (HSTS, X-Frame-Options, etc.) definidos em `next.config.ts`. Nao remover nem enfraquecer essas configuracoes.
|
||||
|
||||
### Rate Limiting
|
||||
Login: 5 tentativas/min. Signup: 3 tentativas/min. API tokens: 100 req/min (inbox), 20 req/min (batch). Configurado em `src/shared/lib/auth/config.ts` e nas rotas de inbox. Nao remover.
|
||||
|
||||
### Tratamento de Erros
|
||||
Erros nao devem expor stack traces, paths ou nomes de bibliotecas ao cliente. Usar mensagens genericas: `"Algo deu errado"`. Logar detalhes apenas no servidor com `console.error()`.
|
||||
|
||||
### Dependencias
|
||||
Verificar pacotes novos sugeridos pela IA em npmjs.com antes de instalar. Red flags: menos de 1.000 downloads/semana, publicado nos ultimos 30 dias, nome muito parecido com pacote popular. Rodar `pnpm audit` periodicamente.
|
||||
|
||||
---
|
||||
|
||||
178
DESIGN.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Design System do OpenMonetis
|
||||
|
||||
Este documento descreve a identidade visual implementada no OpenMonetis. Ele deve
|
||||
ser usado como referência ao criar telas, revisar componentes e manter a
|
||||
experiência consistente entre dashboard, relatórios, formulários e landing page.
|
||||
|
||||
## 1. Direção visual
|
||||
|
||||
O OpenMonetis busca tornar a gestão financeira clara e acolhedora. A interface
|
||||
usa superfícies quentes, poucos elementos decorativos e uma cor laranja de
|
||||
destaque para orientar o olhar sem transformar toda ação em urgência.
|
||||
|
||||
Princípios:
|
||||
|
||||
- priorizar legibilidade e hierarquia em telas com muitos dados;
|
||||
- usar laranja para ações principais, seleção e foco;
|
||||
- manter superfícies leves no tema claro e contraste confortável no tema escuro;
|
||||
- aplicar cores semânticas para comunicar estado, não como decoração;
|
||||
- preservar espaço suficiente entre blocos para evitar ruído visual;
|
||||
- favorecer componentes responsivos e navegação acessível por teclado.
|
||||
|
||||
## 2. Fonte de verdade
|
||||
|
||||
Os tokens globais estão definidos em
|
||||
[`src/app/globals.css`](./src/app/globals.css). Componentes reutilizáveis ficam
|
||||
em [`src/shared/components/ui/`](./src/shared/components/ui/) e seguem o padrão
|
||||
do shadcn/ui com Radix UI e Tailwind CSS 4.
|
||||
|
||||
Ao implementar uma tela:
|
||||
|
||||
1. use classes semânticas como `bg-background`, `bg-card`, `text-foreground`,
|
||||
`text-muted-foreground`, `border-border` e `ring-ring`;
|
||||
2. reutilize os componentes em `src/shared/components/ui/`;
|
||||
3. evite cores hexadecimais e valores arbitrários quando já existir um token;
|
||||
4. valide os dois temas antes de concluir a alteração.
|
||||
|
||||
## 3. Cores
|
||||
|
||||
A paleta é definida em OKLCH para manter uma percepção de contraste mais
|
||||
consistente. Não copie os valores para componentes: use os tokens semânticos.
|
||||
|
||||
| Token | Papel |
|
||||
|---|---|
|
||||
| `background` | Fundo geral da aplicação |
|
||||
| `foreground` | Texto principal |
|
||||
| `card` / `card-foreground` | Cards e conteúdo em destaque |
|
||||
| `popover` / `popover-foreground` | Menus, popovers e overlays |
|
||||
| `primary` / `primary-foreground` | Ações principais, foco e seleção |
|
||||
| `secondary` / `secondary-foreground` | Ações secundárias e superfícies discretas |
|
||||
| `muted` / `muted-foreground` | Apoio visual, descrições e metadados |
|
||||
| `accent` / `accent-foreground` | Hover e seleção leve |
|
||||
| `success` | Confirmações, recebimentos e estados positivos |
|
||||
| `warning` | Atenção, vencimentos e estados intermediários |
|
||||
| `info` | Informações auxiliares |
|
||||
| `destructive` | Erros e ações destrutivas |
|
||||
| `border`, `input`, `ring` | Bordas, campos e foco |
|
||||
|
||||
### Gráficos
|
||||
|
||||
Gráficos usam `chart-1` a `chart-10`. Visualizações que precisam de uma escala
|
||||
sequencial quente podem usar `data-1` a `data-6`. A cor nunca deve ser o único
|
||||
meio de distinguir uma série: inclua legenda, rótulo ou tooltip.
|
||||
|
||||
### Tema escuro
|
||||
|
||||
O tema escuro redefine a mesma camada semântica dentro de `.dark`. Não crie uma
|
||||
segunda árvore de componentes para suportá-lo. Prefira tokens e, somente quando
|
||||
necessário, variantes Tailwind `dark:`.
|
||||
|
||||
## 4. Tipografia
|
||||
|
||||
A família principal é **Bricolage Grotesque**, carregada com `next/font` em
|
||||
[`public/fonts/font_index.ts`](./public/fonts/font_index.ts). Os pesos
|
||||
disponíveis são `500`, `600` e `700`, com fallback para Arial e fontes sans-serif
|
||||
do sistema.
|
||||
|
||||
Diretrizes:
|
||||
|
||||
- corpo e controles: `text-sm` ou `text-base`;
|
||||
- descrições e metadados: `text-sm text-muted-foreground`;
|
||||
- títulos de card: `text-base font-medium`;
|
||||
- títulos de modal: `text-lg font-semibold`;
|
||||
- títulos de página: hierarquia responsiva conforme a densidade da tela;
|
||||
- números financeiros: destaque por peso e alinhamento, sem depender apenas da
|
||||
cor.
|
||||
|
||||
## 5. Espaçamento, raio e elevação
|
||||
|
||||
A escala base é de `0.25rem` (`4px`). Prefira a escala padrão do Tailwind para
|
||||
padding, gap e margens. O raio base é `0.7rem`, exposto pelas classes
|
||||
`rounded-sm`, `rounded-md`, `rounded-lg` e `rounded-xl`.
|
||||
|
||||
Sombras também são tokens. Cards comuns usam `shadow-xs`; menus, tooltips e
|
||||
modais podem subir de nível conforme a necessidade. Evite adicionar sombra forte
|
||||
a cada bloco: bordas e diferença de superfície devem resolver a maior parte da
|
||||
hierarquia.
|
||||
|
||||
## 6. Componentes
|
||||
|
||||
### Botões
|
||||
|
||||
Use [`Button`](./src/shared/components/ui/button.tsx) e suas variantes:
|
||||
|
||||
| Variante | Uso |
|
||||
|---|---|
|
||||
| `default` | Ação principal da tela ou do fluxo |
|
||||
| `secondary` | Ação complementar |
|
||||
| `outline` | Ação neutra com contorno |
|
||||
| `ghost` | Ação discreta em barras e grupos |
|
||||
| `link` | Ação textual |
|
||||
| `destructive` | Exclusão ou operação irreversível |
|
||||
| `navbar` | Ferramentas da navegação superior |
|
||||
|
||||
Não coloque duas ações `default` competindo na mesma região. Para ícones sem
|
||||
rótulo visível, inclua `aria-label` ou texto apenas para leitores de tela.
|
||||
|
||||
### Cards
|
||||
|
||||
Use [`Card`](./src/shared/components/ui/card.tsx) para agrupar informações
|
||||
relacionadas. O componente já define fundo, borda, sombra leve, raio e destaque
|
||||
de hover. Não transforme todo conteúdo em card: listas densas e tabelas podem
|
||||
usar uma única superfície.
|
||||
|
||||
### Formulários
|
||||
|
||||
Campos devem usar os componentes compartilhados, como `Input`, `Select`,
|
||||
`Checkbox`, `Switch` e `DatePicker`. Eles já aplicam foco com `ring`, estados
|
||||
desabilitados e integração visual com os temas. Sempre associe controles a
|
||||
`Label` e apresente erros próximos ao campo correspondente.
|
||||
|
||||
### Diálogos
|
||||
|
||||
Use [`Dialog`](./src/shared/components/ui/dialog.tsx) para tarefas focadas. Em
|
||||
mobile, o conteúdo respeita a largura disponível; em telas maiores, o modal pode
|
||||
ganhar mais espaço. Botões do rodapé devem preservar a ordem e a hierarquia da
|
||||
ação principal.
|
||||
|
||||
### Feedback
|
||||
|
||||
Use toast para retorno breve, `Alert` para contexto persistente e componentes em
|
||||
[`src/shared/components/feedback/`](./src/shared/components/feedback/) para
|
||||
estados vazios, status e confirmações. Textos visíveis ao usuário devem estar em
|
||||
português claro.
|
||||
|
||||
## 7. Layout e navegação
|
||||
|
||||
As páginas protegidas usam uma navbar fixa e um contêiner central com largura
|
||||
máxima `max-w-8xl`, padding lateral responsivo e espaçamento vertical enxuto. A
|
||||
navegação principal fica em
|
||||
[`src/shared/components/navigation/navbar/`](./src/shared/components/navigation/navbar/).
|
||||
|
||||
Padrões:
|
||||
|
||||
- telas do App Router devem continuar finas;
|
||||
- conteúdo principal começa abaixo da navbar fixa (`pt-16`);
|
||||
- use uma coluna em telas pequenas e expanda grids progressivamente;
|
||||
- tabelas e gráficos devem preservar leitura em viewport estreita;
|
||||
- ações essenciais precisam continuar alcançáveis por toque e teclado.
|
||||
|
||||
## 8. Acessibilidade
|
||||
|
||||
- mantenha foco visível com os tokens `ring`;
|
||||
- use HTML semântico antes de adicionar ARIA;
|
||||
- não comunique estado apenas por cor;
|
||||
- associe labels a inputs;
|
||||
- forneça nome acessível para botões de ícone;
|
||||
- confira contraste e navegação por teclado nos temas claro e escuro;
|
||||
- mantenha áreas de toque confortáveis em mobile.
|
||||
|
||||
## 9. Checklist de revisão visual
|
||||
|
||||
- O componente compartilhado existente foi reutilizado?
|
||||
- As cores usam tokens semânticos?
|
||||
- A tela funciona em tema claro e escuro?
|
||||
- O layout continua legível em mobile?
|
||||
- Foco, labels e nomes acessíveis estão presentes?
|
||||
- Estados vazio, carregando, erro e sucesso foram considerados?
|
||||
- Valores financeiros continuam fáceis de comparar?
|
||||
21
Dockerfile
@@ -5,12 +5,13 @@
|
||||
# ============================================
|
||||
FROM node:22-alpine AS deps
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate
|
||||
ARG PNPM_VERSION=11.1.3
|
||||
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copiar apenas arquivos de dependências para aproveitar cache
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
|
||||
|
||||
# Criar pasta public para o postinstall do pdfjs-dist
|
||||
RUN mkdir -p public
|
||||
@@ -23,7 +24,8 @@ RUN pnpm install --frozen-lockfile
|
||||
# ============================================
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate
|
||||
ARG PNPM_VERSION=11.1.3
|
||||
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -40,6 +42,10 @@ COPY --from=deps /app/public/pdf.worker.min.mjs ./public/pdf.worker.min.mjs
|
||||
ENV NEXT_TELEMETRY_DISABLED=1 \
|
||||
NODE_ENV=production
|
||||
|
||||
# Nota: a integração Logo.dev não precisa mais de build args. O token
|
||||
# `LOGO_DEV_TOKEN` é lido em runtime no servidor — basta configurá-lo no
|
||||
# host (Coolify, Railway, etc.) junto com `LOGO_DEV_SECRET_KEY`.
|
||||
|
||||
# Build da aplicação Next.js
|
||||
RUN pnpm build
|
||||
|
||||
@@ -48,7 +54,8 @@ RUN pnpm build
|
||||
# ============================================
|
||||
FROM node:22-alpine AS runner
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate
|
||||
ARG PNPM_VERSION=11.1.3
|
||||
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -87,7 +94,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/src/db ./src/db
|
||||
|
||||
# Copiar entrypoint de migrations
|
||||
COPY docker-entrypoint.sh ./
|
||||
RUN chmod +x /app/docker-entrypoint.sh && chown nextjs:nodejs /app/docker-entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /app/docker-entrypoint.sh && \
|
||||
chmod +x /app/docker-entrypoint.sh && \
|
||||
chown nextjs:nodejs /app/docker-entrypoint.sh
|
||||
|
||||
# Definir variáveis de ambiente de produção
|
||||
ENV NODE_ENV=production \
|
||||
@@ -103,7 +112,7 @@ USER nextjs
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD wget --quiet --tries=1 --spider http://localhost:3000/api/health || exit 1
|
||||
CMD wget --quiet --tries=1 --spider http://127.0.0.1:3000/api/health || exit 1
|
||||
|
||||
# Entrypoint: roda migrations e depois executa o CMD
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
|
||||
420
README.md
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="./public/images/logo_small.png" alt="OpenMonetis Logo" height="80" />
|
||||
<img src="./public/images/logo_small.svg" alt="OpenMonetis Logo" height="80" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor.
|
||||
|
||||
[](CHANGELOG.md)
|
||||
[](CHANGELOG.md)
|
||||
[](https://nextjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://www.postgresql.org/)
|
||||
@@ -20,7 +20,7 @@
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="./public/images/dashboard-preview-light.webp" alt="Dashboard Preview" width="800" />
|
||||
<img src="./public/images/dashboard-preview-light.png" alt="Dashboard Preview" width="800" />
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -28,15 +28,19 @@
|
||||
## 📖 Índice
|
||||
|
||||
- [Sobre o Projeto](#-sobre-o-projeto)
|
||||
- [Instalação via Script](#-instalação-via-script)
|
||||
- [Início Rápido (manual)](#-início-rápido)
|
||||
- [Como rodar o OpenMonetis](#-como-rodar-o-openmonetis)
|
||||
- [Perfil 1 — Usar](#perfil-1--usar-self-hosting)
|
||||
- [Perfil 2 — Desenvolver](#perfil-2--desenvolver)
|
||||
- [Scripts Disponíveis](#-scripts-disponíveis)
|
||||
- [Docker](#-docker)
|
||||
- [Backup](#-backup)
|
||||
- [Storage S3 Compatível](#-storage-s3-compatível)
|
||||
- [Variáveis de Ambiente](#-variáveis-de-ambiente)
|
||||
- [Design System](#-design-system)
|
||||
- [Arquitetura](#-arquitetura)
|
||||
- [Contribuindo](#-contribuindo)
|
||||
- [Apoie o Projeto](#-apoie-o-projeto)
|
||||
- [Star History](#-star-history)
|
||||
- [Licença](#-licença)
|
||||
|
||||
---
|
||||
@@ -53,15 +57,15 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
|
||||
**1. Não há versão hospedada online** — Este projeto é self-hosted. Você precisa rodar no seu próprio computador ou servidor.
|
||||
|
||||
**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente ou importar extratos nos formatos OFX e XLS/XLSX.
|
||||
**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente, usar o app companion para capturar notificações bancárias ou importar extratos nos formatos OFX e XLS/XLSX.
|
||||
|
||||
**3. Requer disciplina** — O OpenMonetis funciona melhor para quem tem disciplina de registrar os gastos regularmente, quer controle total sobre seus dados e gosta de entender exatamente onde o dinheiro está indo.
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
💰 **Contas e transações** — Contas bancárias, cartões, dinheiro. Receitas, despesas e transferências. Categorização, extratos detalhados e importação de extratos OFX e XLS/XLSX com detecção automática de categoria.
|
||||
💰 **Contas e transações** — Contas bancárias, cartões, dinheiro. Receitas, despesas, rendimentos e transferências. Categorização, divisão de lançamentos entre várias pessoas, filtros combináveis com intervalo de datas, extratos detalhados e importação de extratos OFX e XLS/XLSX com detecção automática de categoria.
|
||||
|
||||
📊 **Dashboard e relatórios** — Widgets interativos de métricas, gráficos de evolução, comparativos por categoria, tendências, uso de cartões, top estabelecimentos. Exportação em PDF e Excel.
|
||||
📊 **Dashboard e relatórios** — Widgets personalizáveis, métricas com atalhos para lançamentos, gráficos de evolução, comparativos por categoria, tendências, uso de cartões, top estabelecimentos e navegação direta entre meses pelo seletor de período. Exportação em PDF e Excel.
|
||||
|
||||
💳 **Faturas de cartão** — Acompanhe faturas por período, controle limites e vencimentos.
|
||||
|
||||
@@ -69,7 +73,7 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
|
||||
💸 **Parcelamentos avançados** — Séries de parcelas, antecipação com cálculo de desconto, análise consolidada.
|
||||
|
||||
🤖 **Insights com IA** — Análises geradas por Claude, GPT, Gemini ou OpenRouter. Insights personalizados e histórico salvo.
|
||||
🤖 **Insights com IA** — Análises geradas por Claude, GPT, Gemini, MiniMax, OpenRouter ou modelos locais via Ollama. Insights personalizados e histórico salvo.
|
||||
|
||||
👥 **Gestão colaborativa** — Pagadores com permissões (admin/viewer), notificações automáticas por e-mail, códigos de compartilhamento.
|
||||
|
||||
@@ -77,9 +81,13 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
|
||||
📅 **Calendário financeiro** — Visualize todos os lançamentos em um calendário mensal.
|
||||
|
||||
📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia como pré-lançamentos para revisão. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion).
|
||||
📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia automaticamente como pré-lançamentos para revisão — sem digitar nada. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion).
|
||||
|
||||
⚙️ **Personalização** — Tema dark/light e modo privacidade.
|
||||
<p align="center">
|
||||
<img src="./public/images/companion-preview-light.webp" alt="OpenMonetis Companion" width="300" height="600" />
|
||||
</p>
|
||||
|
||||
⚙️ **Personalização** — Tema dark/light, modo privacidade, ordem das colunas, exibição de anotações, tamanho máximo de anexos, resumo opcional no modal de lançamento e changelog visual para acompanhar as novidades do app.
|
||||
|
||||
### Stack técnica
|
||||
|
||||
@@ -87,88 +95,126 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
- **PostgreSQL** + **Drizzle ORM**
|
||||
- **Better Auth** (email/senha, OAuth, Passkeys/WebAuthn)
|
||||
- **shadcn/ui** (Radix UI) + **Tailwind CSS**
|
||||
- **Bricolage Grotesque** via `next/font`
|
||||
- **Docker** (multi-stage build)
|
||||
- **Biome** (linting + formatting)
|
||||
- **Vercel AI SDK** (Claude, GPT, Gemini, OpenRouter)
|
||||
- **Vercel AI SDK** (Claude, GPT, Gemini, MiniMax, OpenRouter, Ollama)
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Instalação via Script
|
||||
## 🚀 Como rodar o OpenMonetis
|
||||
|
||||
A forma mais rápida de instalar. O script verifica dependências, configura o `.env` interativamente e sobe o banco automaticamente.
|
||||
Escolha o perfil que corresponde ao seu objetivo:
|
||||
|
||||
**Pré-requisito:** Node.js 22+
|
||||
| | Perfil 1 — Usar | Perfil 2 — Desenvolver |
|
||||
|---|---|---|
|
||||
| **Objetivo** | Rodar o app pronto | Modificar o código |
|
||||
| **Clonar repositório** | Não | Sim |
|
||||
| **Node.js / pnpm** | Não | Sim (Node 22+) |
|
||||
| **Docker** | Sim | Sim |
|
||||
| **Como iniciar** | `docker compose up -d` | `pnpm docker:db` + `pnpm dev` |
|
||||
| **App roda em** | Container Docker | Host local (hot-reload) |
|
||||
| **Banco roda em** | Container Docker | Container Docker |
|
||||
| **`DATABASE_URL` (host)** | `db` (automático pelo compose) | `localhost` |
|
||||
| **Banco remoto (Supabase, Neon...)** | Sim (`docker compose up -d app`) | Sim (ajustar `DATABASE_URL`) |
|
||||
| **Como atualizar** | `pnpm docker:update` | `git pull` + `pnpm install` + `pnpm db:push` |
|
||||
| **Indicado para** | Self-hosting, VPS, servidor | Contribuidores, customizações |
|
||||
|
||||
---
|
||||
|
||||
### Perfil 1 — Usar (self-hosting)
|
||||
|
||||
Só quer rodar o OpenMonetis. **Não precisa clonar o repositório nem instalar Node.js** — apenas Docker.
|
||||
|
||||
```bash
|
||||
# Mac / Linux / WSL
|
||||
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/setup.mjs -o setup.mjs && node setup.mjs
|
||||
# 1. Baixe o compose
|
||||
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml
|
||||
|
||||
# Windows (PowerShell)
|
||||
curl -o setup.mjs https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/setup.mjs ; node setup.mjs
|
||||
# 2. Crie um .env na mesma pasta.
|
||||
# .env mínimo recomendado para produção
|
||||
BETTER_AUTH_SECRET=gere-um-valor-com-openssl-rand-base64-32
|
||||
BETTER_AUTH_URL=http://seu-dominio.com
|
||||
DISABLE_SIGNUP=false # opcional: true bloqueia novos cadastros
|
||||
|
||||
# 3. Suba tudo
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
O script irá:
|
||||
- Verificar Node, pnpm, Git e Docker
|
||||
- Perguntar se quer banco local (Docker) ou remoto (Supabase, Neon, etc.)
|
||||
- Gerar o `BETTER_AUTH_SECRET` automaticamente
|
||||
- Configurar opcionais: Google OAuth, e-mail, IA, domínio público
|
||||
- Clonar o repositório, instalar dependências e aplicar o schema
|
||||
Acesse em: `http://localhost:3000`
|
||||
|
||||
O banco sobe com credenciais padrão. Para personalizar (senha, Google OAuth, e-mail, IA...), crie um `.env` na mesma pasta **antes** de subir.
|
||||
|
||||
Mais sobre .env em [Variáveis de Ambiente](#-variáveis-de-ambiente).
|
||||
|
||||
**Banco remoto (Supabase, Neon, Railway...):** defina `DATABASE_URL` no `.env` e suba só o app:
|
||||
|
||||
```bash
|
||||
docker compose up -d app
|
||||
```
|
||||
|
||||
**Não tem Docker instalado?** Em servidores Ubuntu 24.04 limpos, use o script de instalação:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh
|
||||
sudo sh install-deps.sh
|
||||
```
|
||||
|
||||
> Ao final, faça **logout e login** para as permissões do grupo `docker` terem efeito.
|
||||
|
||||
#### Atualizando (Perfil 1)
|
||||
|
||||
```bash
|
||||
pnpm docker:update
|
||||
# ou equivalente:
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
O schema do banco é aplicado automaticamente no startup — nenhum passo extra necessário.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Início Rápido (manual)
|
||||
### Perfil 2 — Desenvolver
|
||||
|
||||
### Pré-requisitos
|
||||
Quer modificar o código com hot-reload. O banco roda no Docker, o app roda direto no seu servidor.
|
||||
|
||||
- Node.js 22+ e pnpm
|
||||
- Docker e Docker Compose
|
||||
**Requisitos:** Docker + Node.js 22+ + pnpm
|
||||
|
||||
### Passo a Passo
|
||||
```bash
|
||||
# 1. Clone o repositório
|
||||
git clone https://github.com/felipegcoutinho/openmonetis.git
|
||||
cd openmonetis
|
||||
|
||||
1. **Clone e instale**
|
||||
# 2. Instale as dependências
|
||||
pnpm install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/felipegcoutinho/openmonetis.git
|
||||
cd openmonetis
|
||||
pnpm install
|
||||
```
|
||||
# 3. Configure o ambiente
|
||||
cp .env.example .env
|
||||
# O DATABASE_URL já vem com host "localhost" (correto para dev local).
|
||||
# Edite o .env com suas configurações (BETTER_AUTH_SECRET, etc.)
|
||||
|
||||
2. **Configure o `.env`**
|
||||
# 4. Suba o banco
|
||||
pnpm docker:db
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
# 5. Aplique o schema no banco (apenas no primeiro setup)
|
||||
pnpm db:push
|
||||
|
||||
Edite o `.env` com suas credenciais. O principal é o `DATABASE_URL` e o `BETTER_AUTH_SECRET`:
|
||||
# 6. Inicie o app com hot-reload
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```env
|
||||
# Banco local (Docker): use host "localhost"
|
||||
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
|
||||
Acesse em: `http://localhost:3000`
|
||||
|
||||
# Banco remoto (Supabase, Neon, etc): use a URL completa do provider
|
||||
# DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
|
||||
Toda vez que salvar um arquivo, o app atualiza automaticamente sem precisar reiniciar.
|
||||
|
||||
BETTER_AUTH_SECRET=seu-secret-aqui # gere com: openssl rand -base64 32
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
```
|
||||
#### Atualizando (Perfil 2)
|
||||
|
||||
3. **Suba o banco de dados** (pule se estiver usando banco remoto)
|
||||
```bash
|
||||
git pull
|
||||
pnpm install # instala dependências novas, se houver
|
||||
pnpm db:push # aplica mudanças de schema, se houver
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up db -d
|
||||
pnpm db:extensions
|
||||
```
|
||||
|
||||
4. **Execute as migrations e inicie**
|
||||
|
||||
```bash
|
||||
pnpm db:push
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
5. Acesse `http://localhost:3000`
|
||||
|
||||
> **Docker completo** (app + banco em containers): use `pnpm docker:up` ao invés dos passos 3-4.
|
||||
O `pnpm dev` já em execução detecta as mudanças de código automaticamente — não precisa reiniciar.
|
||||
|
||||
---
|
||||
|
||||
@@ -196,41 +242,54 @@ pnpm db:studio # Drizzle Studio (UI visual)
|
||||
### Utilitários
|
||||
|
||||
```bash
|
||||
pnpm backup # Backup do banco (requer scripts/backup.sh configurado)
|
||||
pnpm backup # Backup completo do banco (ver seção Backup)
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
pnpm docker:up # Subir app + banco
|
||||
pnpm docker:up:d # Subir em background
|
||||
pnpm docker:up:db # Subir apenas o banco
|
||||
pnpm docker:down # Parar containers
|
||||
pnpm docker:down:volumes # Parar e remover volumes (⚠️ apaga dados!)
|
||||
pnpm docker:logs # Logs em tempo real
|
||||
pnpm docker:restart # Reiniciar
|
||||
pnpm docker:rebuild # Rebuild completo
|
||||
pnpm docker:up # Sobe app (Docker Hub) + banco em background
|
||||
pnpm docker:db # Sobe apenas o banco em background (usar com pnpm dev)
|
||||
pnpm docker:down # Para e remove os containers
|
||||
pnpm docker:logs # Logs em tempo real (todos os containers)
|
||||
pnpm docker:update # Atualiza para a imagem mais recente do Hub e reinicia
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
O `Dockerfile` usa multi-stage build (deps → builder → runner) com imagem final ~200MB rodando como usuário não-root.
|
||||
O `Dockerfile` usa multi-stage build (deps → builder → runner) com imagem final ~200MB rodando como usuário não-root. Health checks configurados para ambos os serviços (PostgreSQL via `pg_isready`, app via `GET /api/health`).
|
||||
|
||||
Health checks configurados para ambos os serviços (PostgreSQL via `pg_isready`, app via `GET /api/health`).
|
||||
### Self-hosting (recomendado)
|
||||
|
||||
Baixe apenas o `docker-compose.yml` e suba tudo — sem clonar o repositório, sem instalar dependências:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
As credenciais padrão do banco já estão configuradas. Para personalizar (senhas, opcionais), crie um `.env` na mesma pasta antes de subir — veja [Variáveis de Ambiente](#-variáveis-de-ambiente).
|
||||
|
||||
### Banco remoto (Supabase, Neon, Railway...)
|
||||
|
||||
Suba apenas o app e aponte para o banco externo via `DATABASE_URL` no `.env`:
|
||||
|
||||
```bash
|
||||
docker compose up -d app
|
||||
```
|
||||
|
||||
### Comandos úteis
|
||||
|
||||
```bash
|
||||
docker compose exec app sh # Shell da aplicação
|
||||
docker compose exec app sh # Shell da aplicação
|
||||
docker compose exec db psql -U openmonetis -d openmonetis_db # Shell do banco
|
||||
docker compose ps # Status
|
||||
docker compose exec db pg_dump -U openmonetis openmonetis_db > backup.sql # Backup
|
||||
docker compose exec -T db psql -U openmonetis -d openmonetis_db < backup.sql # Restore
|
||||
pnpm backup # Backup (ver seção Backup)
|
||||
```
|
||||
|
||||
### Customizando Portas
|
||||
### Customizando portas
|
||||
|
||||
```env
|
||||
APP_PORT=3001 # Padrão: 3000
|
||||
@@ -239,6 +298,71 @@ DB_PORT=5433 # Padrão: 5432
|
||||
|
||||
---
|
||||
|
||||
## 💾 Backup
|
||||
|
||||
O backup é uma rotina de infraestrutura — não é uma tela no app. Ele opera diretamente sobre o banco PostgreSQL e é executado via linha de comando.
|
||||
|
||||
```bash
|
||||
pnpm backup
|
||||
```
|
||||
|
||||
### O que é salvo
|
||||
|
||||
Cada execução gera **3 arquivos** em `backup/`:
|
||||
|
||||
| Arquivo | Conteúdo | Uso |
|
||||
|---|---|---|
|
||||
| `openmonetis_YYYY-MM-DD_HH-MM.dump` | Dump custom dos schemas `public` + `drizzle` | Restore completo via `pg_restore` |
|
||||
| `openmonetis_YYYY-MM-DD_HH-MM.sql.gz` | Dump SQL compactado dos schemas `public` + `drizzle` | Inspeção manual, portabilidade |
|
||||
| `openmonetis_YYYY-MM-DD_HH-MM.data.sql.gz` | Apenas os dados do schema `public` (sem DDL) | Migração parcial, seed de outro ambiente |
|
||||
|
||||
### Modos de conexão
|
||||
|
||||
Configure `DB_MODE` no topo de `scripts/backup.sh`:
|
||||
|
||||
| Modo | Quando usar | Fonte de dados |
|
||||
|---|---|---|
|
||||
| `remote` (padrão) | Banco em Supabase, Neon, Railway, etc. | `DATABASE_URL` do `.env` |
|
||||
| `docker` | Banco no container local | Container `openmonetis_postgres` |
|
||||
|
||||
### Upload para Google Drive (opcional)
|
||||
|
||||
Se o [rclone](https://rclone.org/) estiver instalado e configurado com um remote chamado `gdrive`, os arquivos são enviados automaticamente para `gdrive:BACKUP OPENMONETIS`. Sem o rclone, o backup funciona normalmente e fica apenas local.
|
||||
|
||||
**Retenção:**
|
||||
- Local: 7 dias
|
||||
- Google Drive: 30 dias
|
||||
|
||||
### Automatizar com cron
|
||||
|
||||
Para rodar o backup automaticamente todo dia às 3h:
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```cron
|
||||
0 3 * * * cd /caminho/para/openmonetis && pnpm backup >> /var/log/openmonetis-backup.log 2>&1
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# 1. Zerar o banco
|
||||
docker exec <container-db> psql -U openmonetis -d openmonetis_db \
|
||||
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
|
||||
|
||||
# 2. Restaurar schema + dados (um comando)
|
||||
docker exec -i <container-db> pg_restore \
|
||||
-U openmonetis -d openmonetis_db \
|
||||
--clean --if-exists --disable-triggers --no-owner --no-privileges \
|
||||
< backup/openmonetis_YYYY-MM-DD_HH-MM.dump
|
||||
```
|
||||
|
||||
> `--disable-triggers` é necessário para evitar erros de FK durante o restore (os dados são inseridos fora de ordem). O usuário `openmonetis` tem permissão para isso.
|
||||
|
||||
---
|
||||
|
||||
## ☁️ Storage S3 Compatível
|
||||
|
||||
O suporte a anexos de lançamentos usa upload direto com URL pré-assinada. Essa configuração é opcional, mas passa a ser necessária se você quiser habilitar anexos no app.
|
||||
@@ -256,20 +380,60 @@ S3_BUCKET=
|
||||
### Compatibilidade
|
||||
|
||||
- O código atual espera um provider com API compatível com S3 e suporte a `PutObject`, `GetObject`, `HeadObject`, `DeleteObject` e URLs pré-assinadas.
|
||||
- A implementação usa `endpoint` customizado e `forcePathStyle: true` em [`src/shared/lib/storage/s3-client.ts`](/home/ubuntu/github/openmonetis/src/shared/lib/storage/s3-client.ts).
|
||||
- A implementação usa `endpoint` customizado e `forcePathStyle: true` em [`src/shared/lib/storage/s3-client.ts`](./src/shared/lib/storage/s3-client.ts).
|
||||
- Em geral isso cobre MinIO, Cloudflare R2, Backblaze B2 S3-Compatible, DigitalOcean Spaces e AWS S3. Mas foi testado apenas no Supabase Storage.
|
||||
- Se o seu provider exigir `virtual-hosted-style` em vez de `path-style`, você vai precisar ajustar essa configuração antes de usar anexos.
|
||||
- Se as variáveis de S3 não forem configuradas, mantenha os anexos desabilitados no seu fluxo de uso.
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Logos de Estabelecimentos (Logo.dev)
|
||||
|
||||
O app exibe logos automáticos de marcas na coluna de estabelecimentos nos lançamentos. A integração usa a [Logo.dev](https://www.logo.dev) e é opcional — sem ela, o app exibe as iniciais coloridas normalmente.
|
||||
|
||||
### Variáveis
|
||||
|
||||
```env
|
||||
LOGO_DEV_TOKEN=pk_... # token público (obrigatório para exibir logos)
|
||||
LOGO_DEV_SECRET_KEY=sk_... # chave secreta (obrigatório para o picker de busca)
|
||||
```
|
||||
|
||||
> **Atualizando da v2.4.1 ou anterior:** a variável foi renomeada de `NEXT_PUBLIC_LOGO_DEV_TOKEN` para `LOGO_DEV_TOKEN`. Renomeie no seu `.env` (ou nas variáveis do Coolify/host) e remova o secret homônimo do GitHub Actions — ele não é mais usado. Não há outra etapa de migração.
|
||||
|
||||
### Como configurar
|
||||
|
||||
Ambas as variáveis são lidas em **runtime** pelo servidor Next.js. Não há mais nenhuma etapa no CI nem `--build-arg` no Docker.
|
||||
|
||||
**Self-hosted via Docker Hub (Coolify, Railway, etc.):**
|
||||
|
||||
1. Adicione `LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY` nas variáveis de ambiente do host
|
||||
2. Reinicie o container — pronto
|
||||
|
||||
**Desenvolvimento local:**
|
||||
|
||||
Adicione as duas no `.env` e rode `pnpm dev`.
|
||||
|
||||
### Como usar
|
||||
|
||||
Após configurado, passe o mouse sobre o avatar de qualquer estabelecimento nos lançamentos — um ícone de lápis aparece. Clique para abrir o picker, busque pelo nome da marca e selecione o logo desejado. O mapeamento fica salvo por usuário no banco.
|
||||
|
||||
### Arquitetura
|
||||
|
||||
O token **nunca chega ao cliente**. O servidor constrói a URL `https://img.logo.dev/{domain}?token=...` nos endpoints `/api/logo/mapping` e `/api/logo/search`, e o cliente apenas consome a URL pronta. Um Context Provider (`LogoDevProvider`) propaga a flag `enabled` para os componentes que decidem se renderizam o picker.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Variáveis de Ambiente
|
||||
|
||||
Copie `.env.example` para `.env` e configure:
|
||||
**Perfil 2 (dev):** copie `.env.example` para `.env` — o `DATABASE_URL` já vem com `localhost`, pronto para uso com `pnpm dev`.
|
||||
|
||||
**Perfil 1 (Docker):** não precisa definir `DATABASE_URL` — o compose já configura automaticamente com host `db`. Só defina se usar banco remoto (Supabase, Neon, etc.).
|
||||
|
||||
### Obrigatórias
|
||||
|
||||
```env
|
||||
# Perfil 2 (dev): host "localhost" — o banco roda em container, o app no host
|
||||
# Perfil 1 (Docker): não precisa definir — o compose usa "db" automaticamente
|
||||
DATABASE_URL=postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db
|
||||
BETTER_AUTH_SECRET=seu-secret-aqui # openssl rand -base64 32
|
||||
BETTER_AUTH_URL=http://localhost:3000
|
||||
@@ -283,6 +447,11 @@ POSTGRES_USER=openmonetis
|
||||
POSTGRES_PASSWORD=openmonetis_dev_password
|
||||
POSTGRES_DB=openmonetis_db
|
||||
|
||||
# Autenticação
|
||||
DISABLE_SIGNUP=false # true bloqueia novos cadastros
|
||||
AUTH_SESSION_EXPIRES_IN_DAYS=30 # duração de sessões persistentes
|
||||
AUTH_SESSION_UPDATE_AGE_HOURS=24 # frequência de renovação da sessão
|
||||
|
||||
# S3 Server (opcional, necessario para anexos)
|
||||
S3_ENDPOINT=
|
||||
S3_REGION=
|
||||
@@ -305,9 +474,49 @@ RESEND_FROM_EMAIL=
|
||||
ANTHROPIC_API_KEY=
|
||||
OPENAI_API_KEY=
|
||||
GOOGLE_GENERATIVE_AI_API_KEY=
|
||||
MINIMAX_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
OLLAMA_BASE_URL=http://localhost:11434/v1
|
||||
OLLAMA_API_KEY=
|
||||
|
||||
# Logo.dev (opcional, necessário para logos automáticos de estabelecimentos)
|
||||
# Ambas as variáveis são runtime — basta definir no host; nenhum build arg necessário.
|
||||
LOGO_DEV_TOKEN=
|
||||
LOGO_DEV_SECRET_KEY=
|
||||
```
|
||||
|
||||
### IA local com Ollama
|
||||
|
||||
O provider Ollama permite gerar insights usando modelos locais. Instale e suba o Ollama no host onde o modelo ficará disponível:
|
||||
|
||||
```bash
|
||||
ollama pull llama3.2
|
||||
ollama serve
|
||||
```
|
||||
|
||||
Configure a URL OpenAI-compatible no `.env`:
|
||||
|
||||
```env
|
||||
OLLAMA_BASE_URL=http://localhost:11434/v1
|
||||
# Opcional; normalmente o Ollama local não exige chave.
|
||||
OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
Se o OpenMonetis estiver rodando dentro de um container Docker e o Ollama estiver no host, `localhost` aponta para o próprio container. Nesse caso, use uma URL acessível a partir do container, como `http://host.docker.internal:11434/v1` quando disponível, ou o endereço da rede Docker/host configurado no seu ambiente.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Design System
|
||||
|
||||
O OpenMonetis usa uma identidade visual própria com superfícies quentes, laranja
|
||||
como cor de destaque, temas claro e escuro e tipografia Bricolage Grotesque. A
|
||||
interface é construída com tokens semânticos em OKLCH, Tailwind CSS 4 e
|
||||
componentes compartilhados baseados em shadcn/ui e Radix UI.
|
||||
|
||||
As regras de cores, tipografia, componentes, responsividade e acessibilidade
|
||||
estão documentadas no [`DESIGN.md`](DESIGN.md). Use esse guia como referência ao
|
||||
criar telas ou alterar componentes visuais.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Arquitetura
|
||||
@@ -342,7 +551,18 @@ openmonetis/
|
||||
│ │ └── auth/ # Formulários de autenticação
|
||||
│ │
|
||||
│ ├── shared/ # Código reutilizado entre features
|
||||
│ │ ├── components/ # UI compartilhada (shadcn/ui, navigation, skeletons...)
|
||||
│ │ ├── components/ # UI compartilhada
|
||||
│ │ │ ├── ui/ # shadcn/ui primitives
|
||||
│ │ │ ├── navigation/ # navbar, sidebar, breadcrumbs
|
||||
│ │ │ ├── brand/ # logos do app
|
||||
│ │ │ ├── widgets/ # widget-card e variantes
|
||||
│ │ │ ├── feedback/ # empty-state, status-dot, payment-success
|
||||
│ │ │ ├── entity-avatar/ # avatares de categoria/estabelecimento
|
||||
│ │ │ ├── month-picker/ # seletor de período
|
||||
│ │ │ ├── logo-picker/ # seletor de logos
|
||||
│ │ │ ├── calculator/ # calculadora de cálculos rápidos
|
||||
│ │ │ ├── skeletons/ # loading skeletons
|
||||
│ │ │ └── providers/ # React context providers
|
||||
│ │ ├── hooks/ # React hooks globais
|
||||
│ │ ├── lib/ # Helpers de domínio (auth, db, payers, schemas, email...)
|
||||
│ │ └── utils/ # Utilitários (currency, date, period, math, string...)
|
||||
@@ -358,6 +578,22 @@ openmonetis/
|
||||
└── proxy.ts # Middleware (auth + multi-domínio)
|
||||
```
|
||||
|
||||
### Estrutura interna de uma feature
|
||||
|
||||
Toda feature em `src/features/<nome>/` segue o mesmo padrão:
|
||||
|
||||
```
|
||||
<feature>/
|
||||
├── actions.ts # Server Actions (entry point — barrel re-export quando há actions/)
|
||||
├── queries.ts # Funções de leitura do banco (entry point)
|
||||
├── actions/ # (opcional) Server Actions divididas por domínio quando o volume cresce
|
||||
├── components/ # Componentes de UI da feature
|
||||
├── hooks/ # React hooks específicos da feature
|
||||
└── lib/ # Helpers, types, sub-queries e constantes
|
||||
```
|
||||
|
||||
A regra é: `actions.ts` e `queries.ts` são as portas de entrada da feature. Tudo que é helper interno fica em `lib/`. Componentes e hooks ficam nas pastas com nome óbvio.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contribuindo
|
||||
@@ -383,6 +619,18 @@ Outras formas de contribuir: ⭐ estrela no repo, reportar bugs, melhorar docs,
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
<a href="https://www.star-history.com/?repos=felipegcoutinho%2Fopenmonetis&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&theme=dark&legend=top-left" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&legend=top-left" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=felipegcoutinho/openmonetis&type=date&legend=top-left" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licença
|
||||
|
||||
**Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International** (CC BY-NC-SA 4.0).
|
||||
@@ -397,12 +645,6 @@ Para o texto legal completo, consulte o arquivo [LICENSE](LICENSE) ou visite [cr
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Agradecimentos
|
||||
|
||||
[Next.js](https://nextjs.org/) · [Better Auth](https://better-auth.com/) · [Drizzle ORM](https://orm.drizzle.team/) · [shadcn/ui](https://ui.shadcn.com/) · [Biome](https://biomejs.dev/) · [Vercel](https://vercel.com/)
|
||||
|
||||
---
|
||||
|
||||
**Desenvolvido por:** Felipe Coutinho — [@felipegcoutinho](https://github.com/felipegcoutinho)
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
||||
@@ -1,138 +1,52 @@
|
||||
# Docker Compose para Next.js + PostgreSQL
|
||||
name: openmonetis
|
||||
|
||||
# MODOS DE USO:
|
||||
# 1. Banco LOCAL (PostgreSQL em container):
|
||||
# - Configure DATABASE_URL com host "db" no .env
|
||||
# - Execute: docker compose --profile local up
|
||||
#
|
||||
# 2. Banco REMOTO (ex: Supabase, Neon, etc):
|
||||
# - Configure DATABASE_URL com a URL do banco remoto no .env
|
||||
# - Execute: docker compose up
|
||||
#
|
||||
# 3. Build local (desenvolvimento):
|
||||
# - Execute: docker compose --profile local up --build
|
||||
#
|
||||
# 4. Para parar todos os serviços:
|
||||
# - Execute: docker compose down
|
||||
#
|
||||
# 5. Para remover volumes (CUIDADO: apaga dados do banco local):
|
||||
# - Execute: docker compose down -v
|
||||
|
||||
services:
|
||||
# ============================================
|
||||
# Serviço: PostgreSQL (Banco de dados local)
|
||||
# Ativado apenas com: --profile local
|
||||
# ============================================
|
||||
db:
|
||||
profiles: ["local"]
|
||||
image: postgres:18-alpine
|
||||
container_name: openmonetis_postgres
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-openmonetis}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-openmonetis_dev_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-openmonetis_db}
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
|
||||
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
ports:
|
||||
- "${DB_PORT:-5432}:5432"
|
||||
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
# Cria extensão pgcrypto inline (necessária para gen_random_bytes no schema)
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
echo 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' > /docker-entrypoint-initdb.d/init.sql
|
||||
exec docker-entrypoint.sh postgres
|
||||
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${POSTGRES_USER:-openmonetis} -d ${POSTGRES_DB:-openmonetis_db}",
|
||||
]
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-openmonetis} -d ${POSTGRES_DB:-openmonetis_db}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
# Para ativar logs de queries (debug), adicione ao command acima:
|
||||
# exec docker-entrypoint.sh postgres -c log_statement=all
|
||||
|
||||
# ============================================
|
||||
# Serviço: Aplicação Next.js
|
||||
# ============================================
|
||||
app:
|
||||
build: .
|
||||
image: felipegcoutinho/openmonetis:latest
|
||||
|
||||
container_name: openmonetis_app
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "${APP_PORT:-3000}:3000"
|
||||
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
|
||||
# Banco local: use host "db" | Banco remoto: URL completa do provider
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
|
||||
DATABASE_URL: ${DATABASE_URL:-postgresql://openmonetis:openmonetis_dev_password@db:5432/openmonetis_db}
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
|
||||
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
|
||||
|
||||
# S3 (opcional)
|
||||
S3_ENDPOINT: ${S3_ENDPOINT:-}
|
||||
S3_REGION: ${S3_REGION:-}
|
||||
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-}
|
||||
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-}
|
||||
S3_BUCKET: ${S3_BUCKET:-}
|
||||
|
||||
# Email (opcional)
|
||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-}
|
||||
|
||||
# OAuth (opcional)
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
|
||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
|
||||
|
||||
# AI providers (opcional)
|
||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY:-}
|
||||
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
|
||||
|
||||
# required: false permite subir sem banco local (banco remoto via DATABASE_URL)
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
required: false
|
||||
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--quiet",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:3000/api/health",
|
||||
]
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# ============================================
|
||||
# Volumes
|
||||
# ============================================
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Rodando migrations..."
|
||||
RETRIES=5
|
||||
until /app/migrate/node_modules/.bin/drizzle-kit push || [ "$RETRIES" -eq 0 ]; do
|
||||
RETRIES=$((RETRIES - 1))
|
||||
echo "Migration falhou, aguardando banco... ($RETRIES tentativas restantes)"
|
||||
MIGRATED=0
|
||||
for i in 1 2 3 4 5; do
|
||||
if NODE_PATH=/app/migrate/node_modules /app/migrate/node_modules/.bin/drizzle-kit push; then
|
||||
MIGRATED=1
|
||||
break
|
||||
fi
|
||||
echo "Tentativa $i/5 falhou. Aguardando 5s..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ "$RETRIES" -eq 0 ]; then
|
||||
echo "Aviso: migrations nao foram aplicadas"
|
||||
fi
|
||||
[ "$MIGRATED" -eq 0 ] && echo "Aviso: migrations não foram aplicadas."
|
||||
|
||||
exec "$@"
|
||||
|
||||
24
drizzle/0025_burly_colonel_america.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
DROP INDEX "tokens_api_user_id_idx";--> statement-breakpoint
|
||||
DROP INDEX "cartoes_user_id_status_idx";--> statement-breakpoint
|
||||
DROP INDEX "dashboard_notification_states_user_id_archived_idx";--> statement-breakpoint
|
||||
DROP INDEX "contas_user_id_status_idx";--> statement-breakpoint
|
||||
DROP INDEX "antecipacoes_parcelas_series_id_idx";--> statement-breakpoint
|
||||
DROP INDEX "pagadores_user_id_status_idx";--> statement-breakpoint
|
||||
DROP INDEX "pagadores_user_id_role_idx";--> statement-breakpoint
|
||||
CREATE INDEX "account_user_id_idx" ON "account" USING btree ("userId");--> statement-breakpoint
|
||||
CREATE INDEX "anexos_user_id_idx" ON "anexos" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "orcamentos_categoria_id_idx" ON "orcamentos" USING btree ("categoria_id");--> statement-breakpoint
|
||||
CREATE INDEX "cartoes_conta_id_idx" ON "cartoes" USING btree ("conta_id");--> statement-breakpoint
|
||||
CREATE INDEX "import_category_mappings_category_id_idx" ON "import_category_mappings" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE INDEX "pre_lancamentos_lancamento_id_idx" ON "pre_lancamentos" USING btree ("lancamento_id");--> statement-breakpoint
|
||||
CREATE INDEX "antecipacoes_parcelas_lancamento_id_idx" ON "antecipacoes_parcelas" USING btree ("lancamento_id");--> statement-breakpoint
|
||||
CREATE INDEX "antecipacoes_parcelas_pagador_id_idx" ON "antecipacoes_parcelas" USING btree ("pagador_id");--> statement-breakpoint
|
||||
CREATE INDEX "antecipacoes_parcelas_categoria_id_idx" ON "antecipacoes_parcelas" USING btree ("categoria_id");--> statement-breakpoint
|
||||
CREATE INDEX "anotacoes_user_id_idx" ON "anotacoes" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "passkey_user_id_idx" ON "passkey" USING btree ("userId");--> statement-breakpoint
|
||||
CREATE INDEX "compartilhamentos_pagador_shared_with_user_id_idx" ON "compartilhamentos_pagador" USING btree ("shared_with_user_id");--> statement-breakpoint
|
||||
CREATE INDEX "compartilhamentos_pagador_created_by_user_id_idx" ON "compartilhamentos_pagador" USING btree ("created_by_user_id");--> statement-breakpoint
|
||||
CREATE INDEX "session_user_id_idx" ON "session" USING btree ("userId");--> statement-breakpoint
|
||||
CREATE INDEX "lancamentos_conta_id_idx" ON "lancamentos" USING btree ("conta_id");--> statement-breakpoint
|
||||
CREATE INDEX "lancamentos_categoria_id_idx" ON "lancamentos" USING btree ("categoria_id");--> statement-breakpoint
|
||||
CREATE INDEX "lancamentos_antecipacao_id_idx" ON "lancamentos" USING btree ("antecipacao_id");
|
||||
2
drizzle/0026_bored_eternity.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "lancamentos" ADD COLUMN "split_group_id" uuid;--> statement-breakpoint
|
||||
CREATE INDEX "lancamentos_user_id_split_group_id_idx" ON "lancamentos" USING btree ("user_id","split_group_id");
|
||||
1
drizzle/0028_fancy_reaper.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "pagadores" ALTER COLUMN "share_code" DROP DEFAULT;
|
||||
3
drizzle/0029_friendly_spitfire.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "cartoes" ALTER COLUMN "limite" SET DEFAULT '0';--> statement-breakpoint
|
||||
UPDATE "cartoes" SET "limite" = '0' WHERE "limite" IS NULL;--> statement-breakpoint
|
||||
ALTER TABLE "cartoes" ALTER COLUMN "limite" SET NOT NULL;
|
||||
1
drizzle/0030_complete_umar.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "preferencias_usuario" ADD COLUMN "mostrar_resumo_lancamento" boolean DEFAULT true NOT NULL;
|
||||
2889
drizzle/meta/0025_snapshot.json
Normal file
2916
drizzle/meta/0026_snapshot.json
Normal file
2915
drizzle/meta/0028_snapshot.json
Normal file
2916
drizzle/meta/0029_snapshot.json
Normal file
2923
drizzle/meta/0030_snapshot.json
Normal file
@@ -176,6 +176,41 @@
|
||||
"when": 1774891206703,
|
||||
"tag": "0024_petite_lucky_pierre",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "7",
|
||||
"when": 1776351838548,
|
||||
"tag": "0025_burly_colonel_america",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 26,
|
||||
"version": "7",
|
||||
"when": 1777042423451,
|
||||
"tag": "0026_bored_eternity",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "7",
|
||||
"when": 1777153372633,
|
||||
"tag": "0028_fancy_reaper",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 29,
|
||||
"version": "7",
|
||||
"when": 1777648189399,
|
||||
"tag": "0029_friendly_spitfire",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 30,
|
||||
"version": "7",
|
||||
"when": 1780150535055,
|
||||
"tag": "0030_complete_umar",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
],
|
||||
// PostCSS is inferred from the config file, but the project only depends on
|
||||
// the Tailwind PostCSS plugin directly.
|
||||
// `server-only` is provided implicitly by Next.js — no install needed.
|
||||
"ignoreDependencies": [
|
||||
"postcss"
|
||||
"postcss",
|
||||
"server-only"
|
||||
],
|
||||
"next": true,
|
||||
"postcss": true,
|
||||
|
||||
@@ -4,15 +4,16 @@ import type { NextConfig } from "next";
|
||||
// Carregar variáveis de ambiente explicitamente
|
||||
dotenv.config();
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
cacheComponents: true,
|
||||
reactCompiler: true,
|
||||
|
||||
images: {
|
||||
remotePatterns: [new URL("https://lh3.googleusercontent.com/**")],
|
||||
remotePatterns: [
|
||||
new URL("https://lh3.googleusercontent.com/**"),
|
||||
{ protocol: "https", hostname: "**" },
|
||||
{ protocol: "http", hostname: "**" },
|
||||
],
|
||||
},
|
||||
devIndicators: {
|
||||
position: "bottom-right",
|
||||
@@ -20,6 +21,7 @@ const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
prefetchInlining: true,
|
||||
turbopackFileSystemCacheForDev: true,
|
||||
optimizePackageImports: ["@remixicon/react"],
|
||||
},
|
||||
|
||||
// Headers for Safari compatibility
|
||||
@@ -44,18 +46,6 @@ const nextConfig: NextConfig = {
|
||||
key: "X-Frame-Options",
|
||||
value: "DENY",
|
||||
},
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: [
|
||||
"default-src 'self'",
|
||||
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://umami.felipecoutinho.com`,
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' https://lh3.googleusercontent.com data: blob:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self' https://umami.felipecoutinho.com",
|
||||
"frame-ancestors 'none'",
|
||||
].join("; "),
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
|
||||
95
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "openmonetis",
|
||||
"version": "2.3.3",
|
||||
"version": "2.7.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"packageManager": "pnpm@11.1.3",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"db:seed": "tsx scripts/mock-data.ts",
|
||||
@@ -11,36 +11,37 @@
|
||||
"lint": "biome check .",
|
||||
"lint:deadcode": "knip --reporter compact",
|
||||
"lint:fix": "biome check --write .",
|
||||
"env:setup": "bash scripts/setup-env.sh",
|
||||
"env:setup": "node setup.mjs",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:extensions": "tsx scripts/postgres/enable-extensions.ts",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"docker:up": "docker compose up --build",
|
||||
"postinstall": "cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs",
|
||||
"docker:up:db": "docker compose up -d db",
|
||||
"docker:up:d": "docker compose up --build -d",
|
||||
"docker:up": "docker compose up -d",
|
||||
"//docker:up": "Sobe app (Docker Hub) + banco PostgreSQL em background",
|
||||
"docker:db": "docker compose up -d db",
|
||||
"//docker:db": "Sobe apenas o banco em background (para usar com pnpm dev)",
|
||||
"docker:down": "docker compose down",
|
||||
"docker:down:volumes": "docker compose down -v",
|
||||
"//docker:down": "Para e remove os containers",
|
||||
"docker:logs": "docker compose logs -f",
|
||||
"docker:logs:app": "docker compose logs -f app",
|
||||
"docker:logs:db": "docker compose logs -f db",
|
||||
"docker:restart": "docker compose restart",
|
||||
"docker:rebuild": "docker compose up --build --force-recreate",
|
||||
"backup": "bash scripts/backup.sh"
|
||||
"//docker:logs": "Acompanha logs de todos os containers em tempo real",
|
||||
"docker:update": "docker compose pull && docker compose up -d",
|
||||
"//docker:update": "Atualiza para a imagem mais recente do Docker Hub e reinicia",
|
||||
"backup": "bash scripts/backup.sh",
|
||||
"mockup": "tsx scripts/mock-data.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.65",
|
||||
"@ai-sdk/google": "^3.0.55",
|
||||
"@ai-sdk/openai": "^3.0.49",
|
||||
"@aws-sdk/client-s3": "^3.1022.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1022.0",
|
||||
"@better-auth/passkey": "^1.5.6",
|
||||
"@ai-sdk/anthropic": "^3.0.79",
|
||||
"@ai-sdk/google": "^3.0.79",
|
||||
"@ai-sdk/openai": "^3.0.65",
|
||||
"@ai-sdk/openai-compatible": "^2.0.48",
|
||||
"@aws-sdk/client-s3": "^3.1050.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1050.0",
|
||||
"@better-auth/passkey": "^1.6.11",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@openrouter/ai-sdk-provider": "^2.3.3",
|
||||
"@openrouter/ai-sdk-provider": "^2.9.0",
|
||||
"@radix-ui/react-alert-dialog": "1.1.15",
|
||||
"@radix-ui/react-avatar": "1.1.11",
|
||||
"@radix-ui/react-checkbox": "1.3.3",
|
||||
@@ -49,11 +50,13 @@
|
||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
"@radix-ui/react-label": "2.1.8",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-progress": "1.1.8",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "2.2.6",
|
||||
"@radix-ui/react-separator": "1.1.8",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@radix-ui/react-slot": "1.2.4",
|
||||
"@radix-ui/react-switch": "1.2.6",
|
||||
"@radix-ui/react-tabs": "1.1.13",
|
||||
@@ -61,48 +64,50 @@
|
||||
"@radix-ui/react-toggle-group": "1.1.11",
|
||||
"@radix-ui/react-tooltip": "1.2.8",
|
||||
"@remixicon/react": "4.9.0",
|
||||
"@tanstack/react-query": "^5.96.2",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.23",
|
||||
"ai": "^6.0.143",
|
||||
"better-auth": "1.5.6",
|
||||
"@tanstack/react-virtual": "^3.13.26",
|
||||
"ai": "^6.0.191",
|
||||
"better-auth": "1.6.11",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns": "^4.3.0",
|
||||
"drizzle-orm": "0.45.2",
|
||||
"exceljs": "^4.4.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jspdf-autotable": "^5.0.7",
|
||||
"next": "16.2.2",
|
||||
"jspdf-autotable": "^5.0.8",
|
||||
"next": "16.2.6",
|
||||
"next-themes": "0.4.6",
|
||||
"pdfjs-dist": "^5.6.205",
|
||||
"pg": "8.20.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.2.4",
|
||||
"react-day-picker": "^9.14.0",
|
||||
"react-dom": "19.2.4",
|
||||
"pdfjs-dist": "^5.7.284",
|
||||
"pg": "8.21.0",
|
||||
"react": "19.2.6",
|
||||
"react-day-picker": "^10.0.1",
|
||||
"react-dom": "19.2.6",
|
||||
"recharts": "3.8.1",
|
||||
"resend": "^6.10.0",
|
||||
"resend": "^6.12.4",
|
||||
"sonner": "2.0.7",
|
||||
"tailwind-merge": "3.5.0",
|
||||
"tailwind-merge": "3.6.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vaul": "1.1.2",
|
||||
"zod": "4.3.6"
|
||||
"vercel-minimax-ai-provider": "^0.0.2",
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.10",
|
||||
"@tailwindcss/postcss": "4.2.2",
|
||||
"@biomejs/biome": "2.4.15",
|
||||
"@tailwindcss/postcss": "4.3.0",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@types/node": "25.5.0",
|
||||
"@types/node": "25.9.1",
|
||||
"@types/pg": "^8.20.0",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react": "19.2.15",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"dotenv": "^17.4.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-kit": "0.31.10",
|
||||
"knip": "^6.3.0",
|
||||
"tailwindcss": "4.2.2",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "6.0.2"
|
||||
"knip": "^6.14.2",
|
||||
"tailwindcss": "4.3.0",
|
||||
"tsx": "4.22.3",
|
||||
"typescript": "6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
5960
pnpm-lock.yaml
generated
@@ -1,5 +1,35 @@
|
||||
onlyBuiltDependencies:
|
||||
- core-js
|
||||
- esbuild
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
allowBuilds:
|
||||
core-js: true
|
||||
esbuild: true
|
||||
sharp: true
|
||||
unrs-resolver: true
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- '@aws-sdk/client-s3@3.1050.0'
|
||||
- '@aws-sdk/s3-request-presigner@3.1050.0'
|
||||
- '@types/node@25.9.1'
|
||||
- '@types/react@19.2.15'
|
||||
- '@aws-sdk/client-s3@3.1054.0'
|
||||
- '@aws-sdk/core@3.974.14'
|
||||
- '@aws-sdk/credential-provider-env@3.972.40'
|
||||
- '@aws-sdk/credential-provider-http@3.972.42'
|
||||
- '@aws-sdk/credential-provider-ini@3.972.44'
|
||||
- '@aws-sdk/credential-provider-login@3.972.44'
|
||||
- '@aws-sdk/credential-provider-node@3.972.45'
|
||||
- '@aws-sdk/credential-provider-process@3.972.40'
|
||||
- '@aws-sdk/credential-provider-sso@3.972.44'
|
||||
- '@aws-sdk/credential-provider-web-identity@3.972.44'
|
||||
- '@aws-sdk/middleware-bucket-endpoint@3.972.16'
|
||||
- '@aws-sdk/middleware-flexible-checksums@3.974.22'
|
||||
- '@aws-sdk/middleware-sdk-s3@3.972.43'
|
||||
- '@aws-sdk/nested-clients@3.997.12'
|
||||
- '@aws-sdk/s3-request-presigner@3.1054.0'
|
||||
- '@aws-sdk/signature-v4-multi-region@3.996.29'
|
||||
- '@aws-sdk/token-providers@3.1054.0'
|
||||
- '@aws-sdk/xml-builder@3.972.26'
|
||||
|
||||
overrides:
|
||||
defu: 6.1.7
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import localFont from "next/font/local";
|
||||
import { Bricolage_Grotesque } from "next/font/google";
|
||||
|
||||
export const america = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "./america-regular.woff2",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "./america-medium.woff2",
|
||||
weight: "500",
|
||||
style: "normal",
|
||||
},
|
||||
],
|
||||
display: "fallback",
|
||||
variable: "--font-america",
|
||||
export const bricolage = Bricolage_Grotesque({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-bricolage",
|
||||
fallback: ["arial", "ui-sans-serif", "system-ui"],
|
||||
weight: ["500", "600", "700"],
|
||||
preload: true,
|
||||
});
|
||||
|
||||
BIN
public/images/dashboard-preview-dark.png
Normal file
|
After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 165 KiB |
BIN
public/images/dashboard-preview-light.png
Normal file
|
After Width: | Height: | Size: 562 KiB |
|
Before Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
3
public/images/logo_small.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200" role="img" aria-label="OpenMonetis">
|
||||
<path fill="#ff7733" d="M 77.66,165.64 L 37.77,141.54 L 63.30,108.72 L 27.13,97.44 L 46.81,50.77 L 77.66,63.08 L 81.91,30.26 L 126.40,29.23 L 126.06,33.85 L 122.87,67.69 L 158.51,50.77 L 178.19,90.26 L 140.96,104.62 L 162.23,127.18 L 132.98,162.56 L 103.19,131.79 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 21 KiB |
3
public/images/logo_text.svg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 92 KiB |
BIN
public/logos/bipa.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/logos/dinheiro.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
1
public/providers/minimax.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>MiniMax</title><defs><linearGradient id="lobe-icons-minimax-gradient" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"/><stop offset="100%" stop-color="#FE603C"/></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-gradient)" fill-rule="nonzero"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
public/providers/ollama_dark.svg
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
1
public/providers/ollama_light.svg
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
@@ -58,21 +58,26 @@ DATA_FILE="$BACKUP_DIR/openmonetis_${TIMESTAMP}.data.sql.gz"
|
||||
|
||||
log "Iniciando backup (modo: $DB_MODE)..."
|
||||
|
||||
# Schemas relevantes do OpenMonetis (descarta lixo Supabase: auth, realtime, storage, vault, graphql, etc.)
|
||||
SCHEMA_FLAGS=(--schema=public --schema=drizzle)
|
||||
|
||||
# --- Dump ---
|
||||
if [[ "$DB_MODE" == "remote" ]]; then
|
||||
# --no-owner --no-privileges: necessário no Supabase (roles gerenciados internamente)
|
||||
pg_dump --format=custom --no-owner --no-privileges \
|
||||
"${SCHEMA_FLAGS[@]}" \
|
||||
"$REMOTE_DB_URL" > "$DUMP_FILE"
|
||||
|
||||
pg_dump --no-owner --no-privileges \
|
||||
"${SCHEMA_FLAGS[@]}" \
|
||||
"$REMOTE_DB_URL" | gzip > "$SQL_FILE"
|
||||
|
||||
elif [[ "$DB_MODE" == "docker" ]]; then
|
||||
docker exec "$DOCKER_CONTAINER" pg_dump \
|
||||
-U "$DOCKER_DB_USER" -Fc "$DOCKER_DB_NAME" > "$DUMP_FILE"
|
||||
-U "$DOCKER_DB_USER" -Fc "${SCHEMA_FLAGS[@]}" "$DOCKER_DB_NAME" > "$DUMP_FILE"
|
||||
|
||||
docker exec "$DOCKER_CONTAINER" pg_dump \
|
||||
-U "$DOCKER_DB_USER" "$DOCKER_DB_NAME" | gzip > "$SQL_FILE"
|
||||
-U "$DOCKER_DB_USER" "${SCHEMA_FLAGS[@]}" "$DOCKER_DB_NAME" | gzip > "$SQL_FILE"
|
||||
|
||||
else
|
||||
log "ERRO: DB_MODE inválido ('$DB_MODE'). Use 'remote' ou 'docker'."
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { config } from "dotenv";
|
||||
|
||||
// Carregar variáveis de ambiente
|
||||
config();
|
||||
|
||||
const port = process.env.PORT || "3000";
|
||||
|
||||
console.log(`Starting Next.js development server on port ${port}...`);
|
||||
|
||||
// Executar next dev com a porta especificada
|
||||
execSync(`npx next dev --turbopack --port ${port}`, {
|
||||
stdio: "inherit",
|
||||
env: { ...process.env, PORT: port },
|
||||
});
|
||||
245
scripts/install-deps.sh
Executable file
@@ -0,0 +1,245 @@
|
||||
#!/bin/sh
|
||||
# install-deps.sh — Instala pré-requisitos do OpenMonetis
|
||||
# Testado apenas em Ubuntu Server 24.04 LTS
|
||||
# Uso: curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh
|
||||
# sudo sh install-deps.sh
|
||||
|
||||
set -e
|
||||
|
||||
LOG_FILE="/tmp/openmonetis-install.log"
|
||||
> "$LOG_FILE"
|
||||
|
||||
# Suprimir prompt interativo do corepack ao chamar pnpm/node versioning
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
# ── Cores ──────────────────────────────────────────────────────────────────────
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
|
||||
ok() { printf "${GREEN}✔${RESET} %s\n" "$1"; }
|
||||
warn() { printf "${YELLOW}!${RESET} %s\n" "$1"; }
|
||||
info() { printf "${CYAN}→${RESET} %s\n" "$1"; }
|
||||
fail() { printf "${RED}✗${RESET} %s\n" "$1"; exit 1; }
|
||||
|
||||
# ── Contador de etapas ─────────────────────────────────────────────────────────
|
||||
_STEP=0
|
||||
_TOTAL=5
|
||||
|
||||
section() {
|
||||
_STEP=$((_STEP + 1))
|
||||
printf "\n${BOLD}[%d/%d] %s${RESET}\n" "$_STEP" "$_TOTAL" "$1"
|
||||
}
|
||||
|
||||
# ── Spinner ────────────────────────────────────────────────────────────────────
|
||||
_spin_pid=""
|
||||
|
||||
spinner_start() {
|
||||
_spin_label="$1"
|
||||
( i=0
|
||||
while true; do
|
||||
case $((i % 4)) in
|
||||
0) d=" " ;; 1) d=". " ;; 2) d=".. " ;; *) d="..." ;;
|
||||
esac
|
||||
printf "\r${CYAN}→${RESET} %s%s" "$_spin_label" "$d"
|
||||
i=$((i + 1))
|
||||
sleep 0.4
|
||||
done
|
||||
) &
|
||||
_spin_pid=$!
|
||||
}
|
||||
|
||||
spinner_stop() {
|
||||
if [ -n "$_spin_pid" ]; then
|
||||
kill "$_spin_pid" 2>/dev/null || true
|
||||
wait "$_spin_pid" 2>/dev/null || true
|
||||
_spin_pid=""
|
||||
printf "\r\033[2K"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Executores silenciosos com spinner ─────────────────────────────────────────
|
||||
|
||||
# run_quiet "label" cmd [args...] — roda comando com spinner, falha mostra log
|
||||
run_quiet() {
|
||||
_rq_label="$1"; shift
|
||||
spinner_start "$_rq_label"
|
||||
if ! "$@" >> "$LOG_FILE" 2>&1; then
|
||||
spinner_stop
|
||||
printf "${RED}✗ Falha em: %s${RESET}\n" "$_rq_label"
|
||||
printf " Log completo: %s\n\n" "$LOG_FILE"
|
||||
tail -20 "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
spinner_stop
|
||||
}
|
||||
|
||||
# run_as_user "label" "comando_shell" — roda comando como $CURRENT_USER com spinner
|
||||
run_as_user() {
|
||||
_ru_label="$1"; shift
|
||||
spinner_start "$_ru_label"
|
||||
if ! su - "$CURRENT_USER" -c "$*" >> "$LOG_FILE" 2>&1; then
|
||||
spinner_stop
|
||||
printf "${RED}✗ Falha em: %s${RESET}\n" "$_ru_label"
|
||||
printf " Log completo: %s\n\n" "$LOG_FILE"
|
||||
tail -20 "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
spinner_stop
|
||||
}
|
||||
|
||||
# ── Cleanup no Ctrl+C ──────────────────────────────────────────────────────────
|
||||
cleanup() {
|
||||
spinner_stop
|
||||
printf "\n${YELLOW}Instalação interrompida.${RESET} Log em: %s\n" "$LOG_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap cleanup INT TERM
|
||||
|
||||
# ── Tempo total ────────────────────────────────────────────────────────────────
|
||||
_START=$(date +%s)
|
||||
elapsed() {
|
||||
_secs=$(( $(date +%s) - _START ))
|
||||
printf "%dm%ds" $((_secs / 60)) $((_secs % 60))
|
||||
}
|
||||
|
||||
# ── Root check ─────────────────────────────────────────────────────────────────
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
fail "Execute como root ou com sudo: sudo sh install-deps.sh"
|
||||
fi
|
||||
|
||||
CURRENT_USER="${SUDO_USER:-$(whoami)}"
|
||||
|
||||
printf "\n${BOLD}OpenMonetis — Instalação de Dependências${RESET}\n"
|
||||
printf "Usuário: ${CYAN}%s${RESET} | Log: %s\n" "$CURRENT_USER" "$LOG_FILE"
|
||||
|
||||
# ── [1/5] Dependências base ────────────────────────────────────────────────────
|
||||
section "Dependências base"
|
||||
run_quiet "Atualizando lista de pacotes" apt-get update -qq
|
||||
run_quiet "Instalando git, curl, ca-certificates" apt-get install -y -qq ca-certificates curl git
|
||||
ok "git $(git --version | cut -d' ' -f3) · curl · ca-certificates"
|
||||
|
||||
# ── [2/5] Docker ───────────────────────────────────────────────────────────────
|
||||
section "Docker"
|
||||
|
||||
if command -v docker > /dev/null 2>&1; then
|
||||
ok "Docker já instalado: $(docker --version | cut -d',' -f1)"
|
||||
else
|
||||
info "Adicionando repositório oficial do Docker..."
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
run_quiet "Baixando chave GPG do Docker" \
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
. /etc/os-release
|
||||
mkdir -p /etc/apt/sources.list.d
|
||||
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu %s stable\n' \
|
||||
"$(dpkg --print-architecture)" "$VERSION_CODENAME" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
|
||||
run_quiet "Atualizando lista de pacotes" apt-get update -qq
|
||||
run_quiet "Instalando Docker Engine (pode levar alguns minutos)" \
|
||||
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
systemctl enable docker > /dev/null 2>&1 || true
|
||||
systemctl start docker > /dev/null 2>&1 || true
|
||||
ok "Docker $(docker --version | cut -d',' -f1 | cut -d' ' -f3) instalado"
|
||||
fi
|
||||
|
||||
if docker compose version > /dev/null 2>&1; then
|
||||
ok "Docker Compose $(docker compose version | cut -d' ' -f4)"
|
||||
else
|
||||
run_quiet "Instalando Docker Compose plugin" \
|
||||
sh -c 'mkdir -p /usr/local/lib/docker/cli-plugins && curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/lib/docker/cli-plugins/docker-compose && chmod +x /usr/local/lib/docker/cli-plugins/docker-compose'
|
||||
ok "Docker Compose $(docker compose version | cut -d' ' -f4) instalado"
|
||||
fi
|
||||
|
||||
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
|
||||
if ! groups "$CURRENT_USER" | grep -q docker; then
|
||||
usermod -aG docker "$CURRENT_USER"
|
||||
warn "Usuário '$CURRENT_USER' adicionado ao grupo docker — faça logout/login para aplicar"
|
||||
else
|
||||
ok "Usuário '$CURRENT_USER' já está no grupo docker"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── [3/5] Homebrew ─────────────────────────────────────────────────────────────
|
||||
section "Homebrew"
|
||||
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
ok "Homebrew já instalado: $(brew --version | head -1)"
|
||||
else
|
||||
warn "Esta etapa pode levar de 5 a 10 minutos."
|
||||
run_quiet "Instalando dependências de compilação" \
|
||||
apt-get install -y -qq build-essential procps file
|
||||
|
||||
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
|
||||
run_as_user "Instalando Homebrew" \
|
||||
'NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||||
|
||||
BREW_PROFILE="/home/$CURRENT_USER/.bashrc"
|
||||
BREW_EVAL='eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"'
|
||||
grep -qxF "$BREW_EVAL" "$BREW_PROFILE" 2>/dev/null || echo "$BREW_EVAL" >> "$BREW_PROFILE"
|
||||
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
|
||||
else
|
||||
fail "Homebrew não pode ser instalado como root. Use sudo com um usuário normal."
|
||||
fi
|
||||
|
||||
ok "Homebrew instalado"
|
||||
fi
|
||||
|
||||
# ── [4/5] Node.js 22 ───────────────────────────────────────────────────────────
|
||||
section "Node.js 22"
|
||||
|
||||
NODE_MAJOR=0
|
||||
if command -v node > /dev/null 2>&1; then
|
||||
NODE_MAJOR=$(node -e "process.stdout.write(String(parseInt(process.versions.node)))")
|
||||
fi
|
||||
|
||||
if [ "$NODE_MAJOR" -ge 22 ] 2>/dev/null; then
|
||||
ok "Node.js já instalado: $(node --version)"
|
||||
else
|
||||
warn "Node.js via Homebrew pode levar alguns minutos."
|
||||
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
|
||||
run_as_user "Instalando Node.js 22" \
|
||||
'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew install node@22 && brew link node@22 --force --overwrite'
|
||||
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
|
||||
else
|
||||
fail "Node.js via Homebrew não pode ser instalado como root."
|
||||
fi
|
||||
ok "Node.js $(node --version) instalado"
|
||||
fi
|
||||
|
||||
# ── [5/5] pnpm ─────────────────────────────────────────────────────────────────
|
||||
section "pnpm"
|
||||
|
||||
if command -v pnpm > /dev/null 2>&1; then
|
||||
ok "pnpm já instalado: $(pnpm --version)"
|
||||
else
|
||||
if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then
|
||||
run_as_user "Instalando pnpm via corepack" \
|
||||
'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@latest --activate'
|
||||
else
|
||||
run_quiet "Instalando pnpm via corepack" \
|
||||
sh -c 'corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@latest --activate'
|
||||
fi
|
||||
ok "pnpm instalado"
|
||||
fi
|
||||
|
||||
# ── Resumo ─────────────────────────────────────────────────────────────────────
|
||||
# Garantir que node/pnpm do brew estejam no PATH para o resumo
|
||||
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true
|
||||
|
||||
printf "\n${BOLD}Concluído em $(elapsed)${RESET}\n"
|
||||
|
||||
ok "git: $(git --version | cut -d' ' -f3)"
|
||||
ok "docker: $(docker --version | cut -d',' -f1 | cut -d' ' -f3)"
|
||||
ok "docker compose: $(docker compose version | cut -d' ' -f4)"
|
||||
ok "node: $(node --version)"
|
||||
ok "pnpm: $(pnpm --version)"
|
||||
1092
scripts/mock-data.ts
@@ -1,45 +0,0 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { config } from "dotenv";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
|
||||
// Load environment variables from .env
|
||||
config();
|
||||
|
||||
async function initDatabase() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
console.error("DATABASE_URL environment variable is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = new Pool({ connectionString: databaseUrl });
|
||||
const db = drizzle(pool);
|
||||
|
||||
try {
|
||||
console.log("🔧 Initializing database extensions...");
|
||||
|
||||
// Read and execute init.sql as a single query
|
||||
const initSqlPath = path.join(
|
||||
process.cwd(),
|
||||
"scripts",
|
||||
"postgres",
|
||||
"init.sql",
|
||||
);
|
||||
const initSql = fs.readFileSync(initSqlPath, "utf-8");
|
||||
|
||||
console.log("Executing init.sql...");
|
||||
await db.execute(initSql);
|
||||
|
||||
console.log("✅ Database initialization completed");
|
||||
} catch (error) {
|
||||
console.error("❌ Database initialization failed:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
initDatabase();
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Script de inicialização do PostgreSQL para Docker
|
||||
-- Este script é executado automaticamente quando o banco é criado pela primeira vez
|
||||
|
||||
-- Habilitar extensão pgcrypto (necessária para gen_random_bytes usado pelo Drizzle)
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
-- Log de sucesso
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '✅ Extensão pgcrypto habilitada com sucesso';
|
||||
END $$;
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script para configurar ambiente de forma segura
|
||||
# Cria backup do .env atual antes de sobrescrever
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔧 Configurando ambiente..."
|
||||
|
||||
# Se .env já existe, criar backup
|
||||
if [ -f .env ]; then
|
||||
BACKUP_FILE=".env.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
echo "⚠️ Arquivo .env existente detectado!"
|
||||
echo "📦 Criando backup em: $BACKUP_FILE"
|
||||
cp .env "$BACKUP_FILE"
|
||||
echo "✅ Backup criado com sucesso!"
|
||||
echo ""
|
||||
read -p "Deseja sobrescrever o .env atual com .env.example? (s/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Ss]$ ]]; then
|
||||
echo "❌ Operação cancelada. Seu .env não foi modificado."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copiar .env.example para .env
|
||||
if [ -f .env.example ]; then
|
||||
cp .env.example .env
|
||||
echo "✅ Arquivo .env criado a partir de .env.example"
|
||||
else
|
||||
echo "❌ Erro: .env.example não encontrado!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gerar BETTER_AUTH_SECRET automaticamente
|
||||
if command -v openssl &> /dev/null; then
|
||||
SECRET=$(openssl rand -base64 32)
|
||||
sed -i.bak "s|BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$SECRET|" .env && rm -f .env.bak
|
||||
echo "✅ BETTER_AUTH_SECRET gerado automaticamente"
|
||||
else
|
||||
echo "⚠️ openssl não encontrado — configure BETTER_AUTH_SECRET manualmente:"
|
||||
echo " openssl rand -base64 32"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "⚠️ IMPORTANTE: Edite o arquivo .env e configure:"
|
||||
echo " - DATABASE_URL"
|
||||
echo " - BETTER_AUTH_URL"
|
||||
echo " - Demais variáveis opcionais (OAuth, e-mail, IA)"
|
||||
16
setup.mjs
@@ -229,14 +229,22 @@ if (await askYesNo(" E-mail via Resend (notificações e convites)?")) {
|
||||
let anthropicKey = "";
|
||||
let openaiKey = "";
|
||||
let googleAiKey = "";
|
||||
let minimaxKey = "";
|
||||
let openrouterKey = "";
|
||||
if (await askYesNo(" Insights com IA (Claude, GPT, Gemini, OpenRouter)?")) {
|
||||
let ollamaBaseUrl = "";
|
||||
let ollamaApiKey = "";
|
||||
if (await askYesNo(" Insights com IA (Claude, GPT, Gemini, MiniMax, OpenRouter)?")) {
|
||||
console.log(` ${c.dim}Deixe em branco o que não for usar${c.reset}`);
|
||||
anthropicKey = await ask(" ANTHROPIC_API_KEY: ");
|
||||
openaiKey = await ask(" OPENAI_API_KEY: ");
|
||||
googleAiKey = await ask(" GOOGLE_GENERATIVE_AI_API_KEY: ");
|
||||
minimaxKey = await ask(" MINIMAX_API_KEY: ");
|
||||
openrouterKey = await ask(" OPENROUTER_API_KEY: ");
|
||||
}
|
||||
if (await askYesNo(" Insights locais com Ollama?")) {
|
||||
ollamaBaseUrl = await askDefault(" OLLAMA_BASE_URL", "http://localhost:11434/v1");
|
||||
ollamaApiKey = await ask(" OLLAMA_API_KEY (opcional): ");
|
||||
}
|
||||
|
||||
// Domínio público
|
||||
let publicDomain = "";
|
||||
@@ -285,6 +293,9 @@ const envContent = [
|
||||
"# === Better Auth ===",
|
||||
`BETTER_AUTH_SECRET=${authSecret}`,
|
||||
`BETTER_AUTH_URL=${betterAuthUrl}`,
|
||||
"DISABLE_SIGNUP=false",
|
||||
"AUTH_SESSION_EXPIRES_IN_DAYS=30",
|
||||
"AUTH_SESSION_UPDATE_AGE_HOURS=24",
|
||||
"",
|
||||
"# === Portas ===",
|
||||
"APP_PORT=3000",
|
||||
@@ -310,7 +321,10 @@ const envContent = [
|
||||
opt("ANTHROPIC_API_KEY", anthropicKey),
|
||||
opt("OPENAI_API_KEY", openaiKey),
|
||||
opt("GOOGLE_GENERATIVE_AI_API_KEY", googleAiKey),
|
||||
opt("MINIMAX_API_KEY", minimaxKey),
|
||||
opt("OPENROUTER_API_KEY", openrouterKey),
|
||||
opt("OLLAMA_BASE_URL", ollamaBaseUrl),
|
||||
opt("OLLAMA_API_KEY", ollamaApiKey),
|
||||
].join("\n");
|
||||
|
||||
writeFileSync(join(targetDir, ".env"), envContent);
|
||||
|
||||
23
src/app/(auth)/layout.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Logo } from "@/shared/components/brand/logo";
|
||||
|
||||
export default function AuthLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative flex min-h-svh flex-col items-center justify-center overflow-hidden bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden flex items-center justify-center">
|
||||
<div className="absolute -right-32 top-0 h-96 w-96 rounded-full bg-primary/10 blur-3xl animate-blob mix-blend-multiply" />
|
||||
<div className="absolute -left-32 bottom-0 h-96 w-96 rounded-full bg-primary/7 blur-3xl animate-blob animation-delay-2000 mix-blend-multiply" />
|
||||
<div className="absolute -bottom-32 left-1/2 h-80 w-80 rounded-full bg-secondary/30 blur-3xl animate-blob animation-delay-4000 mix-blend-multiply" />
|
||||
</div>
|
||||
|
||||
<div className="relative mb-6 flex md:hidden z-20">
|
||||
<Logo variant="compact" colorIcon />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-sm md:max-w-5xl">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,6 @@
|
||||
import { LoginForm } from "@/features/auth/components/login-form";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { isSignupDisabled } from "@/shared/lib/auth/signup";
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="relative flex min-h-svh flex-col items-center justify-center overflow-hidden bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute -right-32 -top-32 h-72 w-72 rounded-full bg-primary/10 blur-3xl" />
|
||||
<div className="absolute -bottom-32 -left-32 h-72 w-72 rounded-full bg-primary/7 blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="relative mb-6 flex md:hidden">
|
||||
<Logo variant="compact" colorIcon />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-sm md:max-w-5xl">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <LoginForm signupDisabled={isSignupDisabled()} />;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { SignupForm } from "@/features/auth/components/signup-form";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { isSignupDisabled } from "@/shared/lib/auth/signup";
|
||||
|
||||
export default function SignupPage() {
|
||||
return (
|
||||
<div className="relative flex min-h-svh flex-col items-center justify-center overflow-hidden bg-linear-to-b from-background via-background to-muted/20 px-5 py-8 md:px-8 md:py-10">
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute -right-32 -top-32 h-72 w-72 rounded-full bg-primary/10 blur-3xl" />
|
||||
<div className="absolute -bottom-32 -left-32 h-72 w-72 rounded-full bg-primary/7 blur-3xl" />
|
||||
</div>
|
||||
if (isSignupDisabled()) {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
<div className="relative mb-6 flex md:hidden">
|
||||
<Logo variant="compact" colorIcon />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-sm md:max-w-5xl">
|
||||
<SignupForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <SignupForm />;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { notFound } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
import { AccountDialog } from "@/features/accounts/components/account-dialog";
|
||||
import { AccountStatementCard } from "@/features/accounts/components/account-statement-card";
|
||||
import { AddYieldDialog } from "@/features/accounts/components/add-yield-dialog";
|
||||
import { AdjustBalanceDialog } from "@/features/accounts/components/adjust-balance-dialog";
|
||||
import type { Account } from "@/features/accounts/components/types";
|
||||
import {
|
||||
fetchAccountData,
|
||||
fetchAccountLancamentosPage,
|
||||
fetchAccountSummary,
|
||||
fetchAccountTransactionsPage,
|
||||
} from "@/features/accounts/statement-queries";
|
||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
@@ -21,7 +23,7 @@ import {
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
resolveTransactionPagination,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
@@ -30,6 +32,7 @@ import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
import { loadLogoOptions } from "@/shared/lib/logo/options";
|
||||
import { getBusinessDateString } from "@/shared/utils/date";
|
||||
import { parsePeriodParam } from "@/shared/utils/period";
|
||||
|
||||
type PageSearchParams = Promise<ResolvedSearchParams>;
|
||||
@@ -42,6 +45,26 @@ type PageProps = {
|
||||
const capitalize = (value: string) =>
|
||||
value.length > 0 ? value[0]?.toUpperCase().concat(value.slice(1)) : value;
|
||||
|
||||
const resolveDefaultPaymentMethod = (
|
||||
accountType: string | null | undefined,
|
||||
) => {
|
||||
if (accountType === "Dinheiro") return "Dinheiro";
|
||||
if (accountType === "Pré-Pago | VR/VA") return "Pré-Pago | VR/VA";
|
||||
|
||||
return "Pix";
|
||||
};
|
||||
|
||||
const resolveDefaultYieldDate = (period: string) => {
|
||||
const today = getBusinessDateString();
|
||||
if (today.startsWith(period)) return today;
|
||||
|
||||
const [year, month] = period.split("-").map((part) => Number(part));
|
||||
if (!year || !month) return today;
|
||||
|
||||
const lastDay = new Date(year, month, 0).getDate();
|
||||
return `${period}-${String(lastDay).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
await connection();
|
||||
const { accountId } = await params;
|
||||
@@ -88,7 +111,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
accountId: account.id,
|
||||
});
|
||||
|
||||
const transactionsPage = await fetchAccountLancamentosPage(
|
||||
const transactionsPage = await fetchAccountTransactionsPage(
|
||||
filters,
|
||||
pagination,
|
||||
);
|
||||
@@ -99,6 +122,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
accountSummary;
|
||||
|
||||
const periodLabel = `${capitalize(monthName)} de ${year}`;
|
||||
const defaultYieldDate = resolveDefaultYieldDate(selectedPeriod);
|
||||
|
||||
const accountDialogData: Account = {
|
||||
id: account.id,
|
||||
@@ -141,6 +165,19 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
totalIncomes={totalIncomes}
|
||||
totalExpenses={totalExpenses}
|
||||
logo={account.logo}
|
||||
balanceAdjustment={
|
||||
<>
|
||||
<AddYieldDialog
|
||||
accountId={account.id}
|
||||
defaultDate={defaultYieldDate}
|
||||
/>
|
||||
<AdjustBalanceDialog
|
||||
accountId={account.id}
|
||||
period={selectedPeriod}
|
||||
currentBalance={currentBalance}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<AccountDialog
|
||||
mode="update"
|
||||
@@ -189,7 +226,11 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
accountId: account.id,
|
||||
settledOnly: true,
|
||||
}}
|
||||
allowCreate={false}
|
||||
allowCreate
|
||||
defaultAccountId={account.id}
|
||||
defaultPaymentMethod={resolveDefaultPaymentMethod(
|
||||
account.accountType,
|
||||
)}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
|
||||
|
||||
@@ -11,13 +11,12 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiBankLine />}
|
||||
title="Contas"
|
||||
subtitle="Acompanhe todas as contas do mês selecionado incluindo receitas,
|
||||
despesas e transações previstas. Use o seletor abaixo para navegar pelos
|
||||
meses e visualizar as movimentações correspondentes."
|
||||
despesas e transações previstas."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
|
||||
25
src/app/(dashboard)/attachments/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { RiAttachmentLine } from "@remixicon/react";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import PageDescription from "@/shared/components/page-description";
|
||||
|
||||
export const metadata = {
|
||||
title: "Anexos",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiAttachmentLine />}
|
||||
title="Anexos"
|
||||
subtitle="Gerencie os anexos das suas transações"
|
||||
/>
|
||||
<MonthNavigation />
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiBarChart2Line />}
|
||||
title="Orçamentos"
|
||||
|
||||
@@ -20,23 +20,12 @@ const getSingleParam = (
|
||||
return Array.isArray(value) ? (value[0] ?? null) : value;
|
||||
};
|
||||
|
||||
const capitalize = (value: string) =>
|
||||
value.length === 0 ? value : value[0]?.toUpperCase() + value.slice(1);
|
||||
|
||||
export default async function Page({ searchParams }: PageProps) {
|
||||
await connection();
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
const periodoParam = getSingleParam(resolvedSearchParams, "periodo");
|
||||
|
||||
const {
|
||||
period: selectedPeriod,
|
||||
monthName: rawMonthName,
|
||||
year,
|
||||
} = parsePeriodParam(periodoParam);
|
||||
|
||||
const periodLabel = `${capitalize(rawMonthName)} ${year}`;
|
||||
|
||||
const { period: selectedPeriod } = parsePeriodParam(periodoParam);
|
||||
const { budgets, categoriesOptions } = await fetchBudgetsForUser(
|
||||
userId,
|
||||
selectedPeriod,
|
||||
@@ -49,7 +38,6 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
budgets={budgets}
|
||||
categories={categoriesOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
periodLabel={periodLabel}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiCalendarEventLine />}
|
||||
title="Calendário"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fetchCalendarData } from "@/features/calendar/queries";
|
||||
import {
|
||||
getSingleParam,
|
||||
type ResolvedSearchParams,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
import type { CalendarPeriod } from "@/shared/lib/types/calendar";
|
||||
@@ -36,7 +36,7 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-3">
|
||||
<main className="flex flex-col gap-4">
|
||||
<MonthNavigation />
|
||||
<MonthlyCalendar
|
||||
period={calendarPeriod}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
getSingleParam,
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
@@ -118,6 +118,8 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
financialAccount.id === card.accountId,
|
||||
)?.name ?? "Conta";
|
||||
|
||||
const limitAmount = Number(card.limit);
|
||||
|
||||
const cardDialogData: Card = {
|
||||
id: card.id,
|
||||
name: card.name,
|
||||
@@ -127,19 +129,16 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
dueDay: card.dueDay,
|
||||
note: card.note ?? null,
|
||||
logo: card.logo,
|
||||
limit:
|
||||
card.limit !== null && card.limit !== undefined
|
||||
? Number(card.limit)
|
||||
: null,
|
||||
limit: limitAmount,
|
||||
accountId: card.accountId,
|
||||
accountName,
|
||||
limitInUse: 0,
|
||||
limitAvailable: null,
|
||||
limitAvailable: limitAmount,
|
||||
currentInvoiceAmount: 0,
|
||||
currentInvoiceLabel: "",
|
||||
};
|
||||
|
||||
const { totalAmount, invoiceStatus, paymentDate } = invoiceData;
|
||||
const limitAmount =
|
||||
card.limit !== null && card.limit !== undefined ? Number(card.limit) : null;
|
||||
|
||||
const periodLabel = `${monthName.charAt(0).toUpperCase()}${monthName.slice(
|
||||
1,
|
||||
@@ -163,6 +162,12 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
limitAmount={limitAmount}
|
||||
invoiceStatus={invoiceStatus}
|
||||
paymentDate={paymentDate}
|
||||
defaultPaymentAccountId={card.accountId}
|
||||
paymentAccountOptions={accountOptions.map((option) => ({
|
||||
value: option.value,
|
||||
label: option.label,
|
||||
logo: option.logo ?? null,
|
||||
}))}
|
||||
logo={card.logo}
|
||||
actions={
|
||||
<CardDialog
|
||||
|
||||
@@ -11,13 +11,12 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiBankCard2Line />}
|
||||
title="Cartões"
|
||||
subtitle="Acompanhe todas os cartões do mês selecionado incluindo faturas, limites
|
||||
e transações previstas. Use o seletor abaixo para navegar pelos meses e
|
||||
visualizar as movimentações correspondentes."
|
||||
e transações previstas."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function Page() {
|
||||
await fetchAllCardsForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<main className="flex flex-col gap-6">
|
||||
<CardsPage
|
||||
cards={activeCards}
|
||||
archivedCards={archivedCards}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { TransactionsPage } from "@/features/transactions/components/page/transa
|
||||
import {
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { connection } from "next/server";
|
||||
import { fetchCategoryHistory } from "@/features/dashboard/categories/category-history-queries";
|
||||
import { CategoryHistoryWidget } from "@/features/dashboard/components/category-history-widget";
|
||||
import { CategoryHistoryWidget } from "@/features/dashboard/components/widgets/category-history-widget";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { getCurrentPeriod } from "@/shared/utils/period";
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiPriceTag3Line />}
|
||||
title="Categorias"
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function Page() {
|
||||
const categories = await fetchCategoriesForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<main className="flex flex-col gap-6">
|
||||
<CategoriesPage categories={categories} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiHistoryLine />}
|
||||
title="Changelog"
|
||||
|
||||
@@ -2,10 +2,13 @@ import { connection } from "next/server";
|
||||
import { DashboardGridEditable } from "@/features/dashboard/components/dashboard-grid-editable";
|
||||
import { DashboardMetricsCards } from "@/features/dashboard/components/dashboard-metrics-cards";
|
||||
import { DashboardWelcome } from "@/features/dashboard/components/dashboard-welcome";
|
||||
import { extractDashboardLogoNames } from "@/features/dashboard/lib/extract-logo-names";
|
||||
import { fetchDashboardPageData } from "@/features/dashboard/page-data-queries";
|
||||
import { getSingleParam } from "@/features/transactions/page-helpers";
|
||||
import { getSingleParam } from "@/features/transactions/lib/page-helpers";
|
||||
import { LogoPrefetchProvider } from "@/shared/components/entity-avatar";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
import { prefetchLogoMappings } from "@/shared/lib/logo/prefetch-server";
|
||||
import { parsePeriodParam } from "@/shared/utils/period";
|
||||
|
||||
type PageSearchParams = Promise<Record<string, string | string[] | undefined>>;
|
||||
@@ -24,18 +27,33 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
const { dashboardData, preferences, quickActionOptions } =
|
||||
await fetchDashboardPageData(user.id, selectedPeriod);
|
||||
const { dashboardWidgets } = preferences;
|
||||
const adminPayerSlug =
|
||||
quickActionOptions.payerOptions.find(
|
||||
(option) => option.value === quickActionOptions.defaultPayerId,
|
||||
)?.slug ?? null;
|
||||
|
||||
const logoMappings = await prefetchLogoMappings(
|
||||
user.id,
|
||||
extractDashboardLogoNames(dashboardData),
|
||||
);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-4">
|
||||
<DashboardWelcome name={user.name} />
|
||||
<MonthNavigation />
|
||||
<DashboardMetricsCards metrics={dashboardData.metrics} />
|
||||
<DashboardGridEditable
|
||||
data={dashboardData}
|
||||
<DashboardMetricsCards
|
||||
metrics={dashboardData.metrics}
|
||||
period={selectedPeriod}
|
||||
initialPreferences={dashboardWidgets}
|
||||
quickActionOptions={quickActionOptions}
|
||||
adminPayerSlug={adminPayerSlug}
|
||||
/>
|
||||
<LogoPrefetchProvider mappings={logoMappings}>
|
||||
<DashboardGridEditable
|
||||
data={dashboardData}
|
||||
period={selectedPeriod}
|
||||
initialPreferences={dashboardWidgets}
|
||||
quickActionOptions={quickActionOptions}
|
||||
/>
|
||||
</LogoPrefetchProvider>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiAtLine />}
|
||||
title="Pré-Lançamentos"
|
||||
|
||||
@@ -56,7 +56,7 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
const normalizedSourceApps = Array.isArray(sourceApps) ? sourceApps : [];
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<main className="flex flex-col gap-6">
|
||||
<InboxPage
|
||||
activeStatus={activeStatus}
|
||||
activeApp={activeApp}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiSparklingLine />}
|
||||
title="Insights"
|
||||
|
||||
@@ -1,39 +1,125 @@
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import { Skeleton } from "@/shared/components/ui/skeleton";
|
||||
|
||||
/**
|
||||
* Loading state para a página de insights com IA
|
||||
*/
|
||||
const providers = [
|
||||
"openai",
|
||||
"anthropic",
|
||||
"google",
|
||||
"minimax",
|
||||
"openrouter",
|
||||
"ollama",
|
||||
];
|
||||
|
||||
const summaryRows = ["period", "data-source"];
|
||||
|
||||
export default function InsightsLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<div className="space-y-6 pt-4">
|
||||
{/* Header */}
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-10 w-64 rounded-md bg-foreground/10" />
|
||||
<Skeleton className="h-6 w-96 rounded-md bg-foreground/10" />
|
||||
<Card className="flex w-full flex-row items-center justify-between gap-2 px-3 py-3 sm:px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="size-8 bg-foreground/10" />
|
||||
<Skeleton className="h-8 w-40 bg-foreground/10" />
|
||||
<Skeleton className="size-8 bg-foreground/10" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Grid de insights */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="rounded-md border p-6 space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-2 flex-1">
|
||||
<Skeleton className="h-6 w-48 rounded-md bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-full rounded-md bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-3/4 rounded-md bg-foreground/10" />
|
||||
<section className="space-y-4">
|
||||
<div className="grid items-stretch gap-4 xl:grid-cols-[minmax(0,1fr)_340px]">
|
||||
<div className="space-y-4">
|
||||
<Card className="border-border/70 bg-card/95 shadow-sm">
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-64 bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-full max-w-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-3/4 max-w-xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-28 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-80 max-w-full bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{providers.map((provider) => (
|
||||
<div
|
||||
className="flex min-h-24 items-start gap-3 rounded-2xl border p-4"
|
||||
key={provider}
|
||||
>
|
||||
<Skeleton className="mt-1 size-4 shrink-0 rounded-full bg-foreground/10" />
|
||||
<Skeleton className="size-8 shrink-0 rounded-full bg-foreground/10" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-20 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-full bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-3/4 bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-border/70 bg-card/95 shadow-sm">
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-32 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-72 max-w-full bg-foreground/10" />
|
||||
</div>
|
||||
<Skeleton className="h-9 w-full max-w-72 bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Skeleton className="h-9 w-24 bg-foreground/10" />
|
||||
<Skeleton className="h-9 w-32 bg-foreground/10" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="border-border/70 bg-card/95 shadow-sm">
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="size-9 rounded-xl bg-foreground/10" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-32 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-24 bg-foreground/10" />
|
||||
</div>
|
||||
<Skeleton className="size-8 rounded-full bg-foreground/10" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-3 w-full rounded-md bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-full rounded-md bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-2/3 rounded-md bg-foreground/10" />
|
||||
|
||||
<Skeleton className="h-9 w-full bg-foreground/10" />
|
||||
<Skeleton className="h-8 w-full bg-foreground/10" />
|
||||
|
||||
<div className="space-y-4">
|
||||
{summaryRows.map((row) => (
|
||||
<div className="flex gap-3" key={row}>
|
||||
<Skeleton className="size-4 shrink-0 bg-foreground/10" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-3 w-24 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-full bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-3 w-32 bg-foreground/10" />
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="size-8 rounded-full bg-foreground/10" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-20 bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-32 bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Skeleton className="h-20 w-full rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-20 w-full rounded-2xl bg-foreground/10" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { connection } from "next/server";
|
||||
import { fetchDashboardNavbarData } from "@/features/dashboard/navbar-queries";
|
||||
import { fetchDashboardNavbarData } from "@/features/dashboard/lib/navbar-queries";
|
||||
import { AppNavbar } from "@/shared/components/navigation/navbar/app-navbar";
|
||||
import { AppPreferencesProvider } from "@/shared/components/providers/app-preferences-provider";
|
||||
import { LogoDevProvider } from "@/shared/components/providers/logo-dev-provider";
|
||||
import { PrivacyProvider } from "@/shared/components/providers/privacy-provider";
|
||||
import { DotPattern } from "@/shared/components/ui/dot-pattern";
|
||||
import { getUserSession } from "@/shared/lib/auth/server";
|
||||
import { isLogoDevEnabled } from "@/shared/lib/logo/server";
|
||||
import { fetchAppPreferences } from "@/shared/lib/preferences/queries";
|
||||
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
@@ -12,34 +15,32 @@ export default async function DashboardLayout({
|
||||
}>) {
|
||||
await connection();
|
||||
const session = await getUserSession();
|
||||
const navbarData = await fetchDashboardNavbarData(session.user.id);
|
||||
const [navbarData, appPreferences] = await Promise.all([
|
||||
fetchDashboardNavbarData(session.user.id),
|
||||
fetchAppPreferences(session.user.id),
|
||||
]);
|
||||
const logoDevEnabled = isLogoDevEnabled();
|
||||
|
||||
return (
|
||||
<PrivacyProvider>
|
||||
<AppNavbar
|
||||
user={{ ...session.user, image: session.user.image ?? null }}
|
||||
pagadorAvatarUrl={navbarData.pagadorAvatarUrl}
|
||||
preLancamentosCount={navbarData.preLancamentosCount}
|
||||
notificationsSnapshot={navbarData.notificationsSnapshot}
|
||||
/>
|
||||
<div className="relative flex flex-1 flex-col pt-16">
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 overflow-hidden md:h-36">
|
||||
<DotPattern
|
||||
width={20}
|
||||
height={20}
|
||||
cx={1.25}
|
||||
cy={1.25}
|
||||
cr={1.25}
|
||||
className="text-primary/10 mask-[linear-gradient(to_bottom,black_0%,transparent_100%)]"
|
||||
<LogoDevProvider enabled={logoDevEnabled}>
|
||||
<AppPreferencesProvider {...appPreferences}>
|
||||
<PrivacyProvider>
|
||||
<AppNavbar
|
||||
user={{ ...session.user, image: session.user.image ?? null }}
|
||||
payerAvatarUrl={navbarData.payerAvatarUrl}
|
||||
inboxPendingCount={navbarData.inboxPendingCount}
|
||||
notificationsSnapshot={navbarData.notificationsSnapshot}
|
||||
financeLinks={navbarData.financeLinks}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-linear-to-b from-primary/6 to-transparent" />
|
||||
</div>
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-5 md:gap-6 w-full max-w-8xl mx-auto px-4 ">
|
||||
{children}
|
||||
<div className="relative flex flex-1 flex-col pt-16">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-5 md:gap-6 w-full max-w-8xl mx-auto px-4 ">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PrivacyProvider>
|
||||
</PrivacyProvider>
|
||||
</AppPreferencesProvider>
|
||||
</LogoDevProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 pt-4">
|
||||
<section className="space-y-6">
|
||||
<PageDescription
|
||||
icon={<RiTodoLine />}
|
||||
title="Anotações"
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function Page() {
|
||||
const { activeNotes, archivedNotes } = await fetchAllNotesForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<main className="flex flex-col gap-6">
|
||||
<NotesPage notes={activeNotes} archivedNotes={archivedNotes} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Skeleton } from "@/shared/components/ui/skeleton";
|
||||
* Loading state para a página de detalhes do pagador.
|
||||
* Layout: navegação mensal + tabs com card compartilhado do pagador.
|
||||
*/
|
||||
export default function PagadorDetailsLoading() {
|
||||
export default function PayerDetailsLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<div className="h-[60px] animate-pulse rounded-md bg-foreground/10" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { connection } from "next/server";
|
||||
import { PayerCardUsageCard } from "@/features/payers/components/details/payer-card-usage-card";
|
||||
import { PayerHeaderCard } from "@/features/payers/components/details/payer-header-card";
|
||||
import { PayerHistoryCard } from "@/features/payers/components/details/payer-history-card";
|
||||
import { PagadorInfoCard } from "@/features/payers/components/details/payer-info-card";
|
||||
import { PayerInfoCard } from "@/features/payers/components/details/payer-info-card";
|
||||
import { PayerLeaveShareCard } from "@/features/payers/components/details/payer-leave-share-card";
|
||||
import { PayerMonthlySummaryCard } from "@/features/payers/components/details/payer-monthly-summary-card";
|
||||
import {
|
||||
@@ -16,12 +16,12 @@ import {
|
||||
PayerPaymentStatusCard,
|
||||
} from "@/features/payers/components/details/payer-payment-method-cards";
|
||||
import { PayerSharingCard } from "@/features/payers/components/details/payer-sharing-card";
|
||||
import { buildReadOnlyOptionSets } from "@/features/payers/lib/build-readonly-option-sets";
|
||||
import {
|
||||
fetchCurrentUserShare,
|
||||
fetchPagadorLancamentos,
|
||||
fetchPayerShares,
|
||||
} from "@/features/payers/detail-queries";
|
||||
import { buildReadOnlyOptionSets } from "@/features/payers/lib/build-readonly-option-sets";
|
||||
fetchPayerTransactions,
|
||||
} from "@/features/payers/lib/detail-queries";
|
||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||
import {
|
||||
@@ -36,12 +36,12 @@ import {
|
||||
type SluggedFilters,
|
||||
type SlugMaps,
|
||||
type TransactionSearchFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
} from "@/features/transactions/queries";
|
||||
import { ExpandableWidgetCard } from "@/shared/components/expandable-widget-card";
|
||||
import { LogoPrefetchProvider } from "@/shared/components/entity-avatar";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import {
|
||||
Tabs,
|
||||
@@ -49,15 +49,17 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/shared/components/ui/tabs";
|
||||
import { ExpandableWidgetCard } from "@/shared/components/widgets/expandable-widget-card";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
import { prefetchLogoMappings } from "@/shared/lib/logo/prefetch-server";
|
||||
import { getPayerAccess } from "@/shared/lib/payers/access";
|
||||
import {
|
||||
fetchPagadorBoletoItems,
|
||||
fetchPagadorBoletoStats,
|
||||
fetchPagadorCardUsage,
|
||||
fetchPagadorPaymentStatus,
|
||||
fetchPayerBoletoItems,
|
||||
fetchPayerBoletoStats,
|
||||
fetchPayerCardUsage,
|
||||
fetchPayerHistory,
|
||||
fetchPayerMonthlyBreakdown,
|
||||
fetchPayerPaymentStatus,
|
||||
type PayerCardUsageItem,
|
||||
} from "@/shared/lib/payers/details";
|
||||
import { parsePeriodParam } from "@/shared/utils/period";
|
||||
@@ -74,12 +76,19 @@ const capitalize = (value: string) =>
|
||||
|
||||
const EMPTY_FILTERS: TransactionSearchFilters = {
|
||||
transactionFilter: null,
|
||||
conditionFilter: null,
|
||||
paymentFilter: null,
|
||||
payerFilter: null,
|
||||
categoryFilter: null,
|
||||
accountCardFilter: null,
|
||||
conditionFilters: [],
|
||||
paymentFilters: [],
|
||||
payerFilters: [],
|
||||
categoryFilters: [],
|
||||
accountCardFilters: [],
|
||||
searchFilter: null,
|
||||
settledFilter: null,
|
||||
attachmentFilter: null,
|
||||
dividedFilter: null,
|
||||
amountMinFilter: null,
|
||||
amountMaxFilter: null,
|
||||
dateStartFilter: null,
|
||||
dateEndFilter: null,
|
||||
};
|
||||
|
||||
const createEmptySlugMaps = (): SlugMaps => ({
|
||||
@@ -177,7 +186,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
estabelecimentos,
|
||||
userPreferences,
|
||||
] = await Promise.all([
|
||||
fetchPagadorLancamentos(filters),
|
||||
fetchPayerTransactions(filters),
|
||||
fetchPayerMonthlyBreakdown({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
@@ -188,22 +197,22 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
payerId: pagador.id,
|
||||
period: selectedPeriod,
|
||||
}),
|
||||
fetchPagadorCardUsage({
|
||||
fetchPayerCardUsage({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
period: selectedPeriod,
|
||||
}),
|
||||
fetchPagadorBoletoStats({
|
||||
fetchPayerBoletoStats({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
period: selectedPeriod,
|
||||
}),
|
||||
fetchPagadorBoletoItems({
|
||||
fetchPayerBoletoItems({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
period: selectedPeriod,
|
||||
}),
|
||||
fetchPagadorPaymentStatus({
|
||||
fetchPayerPaymentStatus({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
period: selectedPeriod,
|
||||
@@ -305,104 +314,113 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
lancamentoCount: transactionData.length,
|
||||
};
|
||||
|
||||
const logoMappings = await prefetchLogoMappings(dataOwnerId, [
|
||||
...transactionData.map((t) => t.name),
|
||||
...boletoItems.map((b) => b.name),
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<MonthNavigation />
|
||||
|
||||
<Tabs defaultValue="profile" className="w-full">
|
||||
<TabsList className="mb-2">
|
||||
<TabsTrigger value="profile">Perfil</TabsTrigger>
|
||||
<TabsTrigger value="painel">Painel</TabsTrigger>
|
||||
<TabsTrigger value="lancamentos">Lançamentos</TabsTrigger>
|
||||
</TabsList>
|
||||
<PayerHeaderCard
|
||||
payer={payerData}
|
||||
selectedPeriod={selectedPeriod}
|
||||
summary={summaryPreview}
|
||||
/>
|
||||
<LogoPrefetchProvider mappings={logoMappings}>
|
||||
<Tabs defaultValue="profile" className="w-full">
|
||||
<TabsList className="mb-2">
|
||||
<TabsTrigger value="profile">Perfil</TabsTrigger>
|
||||
<TabsTrigger value="painel">Painel</TabsTrigger>
|
||||
<TabsTrigger value="lancamentos">Lançamentos</TabsTrigger>
|
||||
</TabsList>
|
||||
<PayerHeaderCard
|
||||
payer={payerData}
|
||||
selectedPeriod={selectedPeriod}
|
||||
summary={summaryPreview}
|
||||
/>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<PagadorInfoCard payer={payerData} />
|
||||
{canEdit && payerData.shareCode ? (
|
||||
<PayerSharingCard
|
||||
payerId={pagador.id}
|
||||
shareCode={payerData.shareCode}
|
||||
shares={payerSharesData}
|
||||
/>
|
||||
) : null}
|
||||
{!canEdit && currentUserShare ? (
|
||||
<PayerLeaveShareCard
|
||||
shareId={currentUserShare.id}
|
||||
pagadorName={payerData.name}
|
||||
createdAt={currentUserShare.createdAt}
|
||||
/>
|
||||
) : null}
|
||||
</TabsContent>
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<PayerInfoCard payer={payerData} />
|
||||
{canEdit && payerData.shareCode ? (
|
||||
<PayerSharingCard
|
||||
payerId={pagador.id}
|
||||
shareCode={payerData.shareCode}
|
||||
shares={payerSharesData}
|
||||
/>
|
||||
) : null}
|
||||
{!canEdit && currentUserShare ? (
|
||||
<PayerLeaveShareCard
|
||||
shareId={currentUserShare.id}
|
||||
pagadorName={payerData.name}
|
||||
createdAt={currentUserShare.createdAt}
|
||||
/>
|
||||
) : null}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="painel" className="space-y-4">
|
||||
<section className="grid gap-3 lg:grid-cols-2">
|
||||
<PayerMonthlySummaryCard
|
||||
periodLabel={periodLabel}
|
||||
breakdown={monthlyBreakdown}
|
||||
/>
|
||||
<PayerHistoryCard data={historyData} />
|
||||
</section>
|
||||
<TabsContent value="painel" className="space-y-4">
|
||||
<section className="grid gap-3 lg:grid-cols-2">
|
||||
<PayerMonthlySummaryCard
|
||||
periodLabel={periodLabel}
|
||||
breakdown={monthlyBreakdown}
|
||||
/>
|
||||
<PayerHistoryCard data={historyData} />
|
||||
</section>
|
||||
|
||||
<section className="grid gap-3 lg:grid-cols-3">
|
||||
<ExpandableWidgetCard
|
||||
title="Minhas Faturas"
|
||||
subtitle="Valores por cartão neste período"
|
||||
icon={<RiBankCard2Line className="size-4" />}
|
||||
>
|
||||
<PayerCardUsageCard items={cardUsage} />
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Boletos"
|
||||
subtitle="Boletos registrados neste período"
|
||||
icon={<RiBarcodeLine className="size-4" />}
|
||||
>
|
||||
<PayerBoletoCard items={boletoItems} />
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Status de Pagamento"
|
||||
subtitle="Situação das despesas no período"
|
||||
icon={<RiWallet3Line className="size-4" />}
|
||||
>
|
||||
<PayerPaymentStatusCard data={paymentStatus} />
|
||||
</ExpandableWidgetCard>
|
||||
</section>
|
||||
</TabsContent>
|
||||
<section className="grid gap-3 lg:grid-cols-3">
|
||||
<ExpandableWidgetCard
|
||||
title="Minhas Faturas"
|
||||
subtitle="Valores por cartão neste período"
|
||||
icon={<RiBankCard2Line className="size-4" />}
|
||||
>
|
||||
<PayerCardUsageCard items={cardUsage} />
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Boletos"
|
||||
subtitle="Boletos registrados neste período"
|
||||
icon={<RiBarcodeLine className="size-4" />}
|
||||
>
|
||||
<PayerBoletoCard items={boletoItems} />
|
||||
</ExpandableWidgetCard>
|
||||
<ExpandableWidgetCard
|
||||
title="Status de Pagamento"
|
||||
subtitle="Situação das despesas no período"
|
||||
icon={<RiWallet3Line className="size-4" />}
|
||||
>
|
||||
<PayerPaymentStatusCard data={paymentStatus} />
|
||||
</ExpandableWidgetCard>
|
||||
</section>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="lancamentos">
|
||||
<section className="flex flex-col gap-4">
|
||||
<LancamentosSection
|
||||
currentUserId={userId}
|
||||
transactions={transactionData}
|
||||
payerOptions={optionSets.payerOptions}
|
||||
splitPayerOptions={optionSets.splitPayerOptions}
|
||||
defaultPayerId={pagador.id}
|
||||
accountOptions={optionSets.accountOptions}
|
||||
cardOptions={optionSets.cardOptions}
|
||||
categoryOptions={optionSets.categoryOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={optionSets.categoryFilterOptions}
|
||||
accountCardFilterOptions={optionSets.accountCardFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
allowCreate={canEdit}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
|
||||
importPayerOptions={loggedUserOptionSets?.payerOptions}
|
||||
importSplitPayerOptions={loggedUserOptionSets?.splitPayerOptions}
|
||||
importDefaultPayerId={loggedUserOptionSets?.defaultPayerId}
|
||||
importAccountOptions={loggedUserOptionSets?.accountOptions}
|
||||
importCardOptions={loggedUserOptionSets?.cardOptions}
|
||||
importCategoryOptions={loggedUserOptionSets?.categoryOptions}
|
||||
/>
|
||||
</section>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<TabsContent value="lancamentos">
|
||||
<section className="flex flex-col gap-4">
|
||||
<LancamentosSection
|
||||
currentUserId={userId}
|
||||
transactions={transactionData}
|
||||
payerOptions={optionSets.payerOptions}
|
||||
splitPayerOptions={optionSets.splitPayerOptions}
|
||||
defaultPayerId={pagador.id}
|
||||
accountOptions={optionSets.accountOptions}
|
||||
cardOptions={optionSets.cardOptions}
|
||||
categoryOptions={optionSets.categoryOptions}
|
||||
payerFilterOptions={payerFilterOptions}
|
||||
categoryFilterOptions={optionSets.categoryFilterOptions}
|
||||
accountCardFilterOptions={optionSets.accountCardFilterOptions}
|
||||
selectedPeriod={selectedPeriod}
|
||||
estabelecimentos={estabelecimentos}
|
||||
allowCreate={canEdit}
|
||||
noteAsColumn={userPreferences?.statementNoteAsColumn ?? false}
|
||||
columnOrder={userPreferences?.transactionsColumnOrder ?? null}
|
||||
attachmentMaxSizeMb={userPreferences?.attachmentMaxSizeMb ?? 50}
|
||||
importPayerOptions={loggedUserOptionSets?.payerOptions}
|
||||
importSplitPayerOptions={
|
||||
loggedUserOptionSets?.splitPayerOptions
|
||||
}
|
||||
importDefaultPayerId={loggedUserOptionSets?.defaultPayerId}
|
||||
importAccountOptions={loggedUserOptionSets?.accountOptions}
|
||||
importCardOptions={loggedUserOptionSets?.cardOptions}
|
||||
importCategoryOptions={loggedUserOptionSets?.categoryOptions}
|
||||
/>
|
||||
</section>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</LogoPrefetchProvider>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||