mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-10 19:21:46 +00:00
Compare commits
18 Commits
1df2ba787d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e7151876 | ||
|
|
0bb664884a | ||
|
|
f02958df1d | ||
|
|
c4c52c02ab | ||
|
|
c9239c4f3c | ||
|
|
7128cc0ae7 | ||
|
|
467f71493d | ||
|
|
0cec10ede3 | ||
|
|
a6fba5f953 | ||
|
|
18893bfe02 | ||
|
|
7fdf9e2876 | ||
|
|
7d0781b035 | ||
|
|
b9b843b9db | ||
|
|
01215b3124 | ||
|
|
d70223e7b3 | ||
|
|
6ea064e1bd | ||
|
|
9c0669a152 | ||
|
|
b2d4b29cb5 |
179
CHANGELOG.md
179
CHANGELOG.md
@@ -5,6 +5,97 @@ 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/).
|
||||
|
||||
## [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.
|
||||
@@ -174,6 +265,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -194,6 +287,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -213,6 +308,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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`)
|
||||
@@ -222,6 +319,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -258,24 +357,32 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -288,6 +395,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -303,12 +412,16 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -344,12 +457,16 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -370,6 +487,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -386,6 +505,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -407,6 +528,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -422,12 +545,16 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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`
|
||||
@@ -456,6 +583,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -466,6 +595,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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`)
|
||||
@@ -522,6 +653,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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`.
|
||||
@@ -536,6 +669,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -570,6 +705,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -588,6 +725,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -599,6 +738,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -610,6 +751,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -633,6 +776,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -656,6 +801,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -667,12 +814,16 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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}"
|
||||
@@ -680,6 +831,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -700,6 +853,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -721,6 +876,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -743,6 +900,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -757,6 +916,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -776,6 +937,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)
|
||||
@@ -797,6 +960,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -827,6 +992,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -842,6 +1009,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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)`
|
||||
@@ -862,6 +1031,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -890,6 +1061,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [1.2.5] - 2025-02-01
|
||||
|
||||
Versão pequena: novo widget de pagadores no dashboard com avatares atualizados.
|
||||
|
||||
### Adicionado
|
||||
|
||||
- Widget de pagadores no dashboard
|
||||
@@ -897,6 +1070,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -904,6 +1079,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
@@ -911,6 +1088,8 @@ Esta versão é quase toda sobre organização e polimento. O código interno do
|
||||
|
||||
## [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
|
||||
|
||||
57
CLAUDE.md
57
CLAUDE.md
@@ -97,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/
|
||||
@@ -117,9 +117,12 @@ src/
|
||||
│ └── 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/
|
||||
@@ -134,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
|
||||
@@ -299,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:
|
||||
|
||||
58
README.md
58
README.md
@@ -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/)
|
||||
@@ -61,7 +61,7 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
|
||||
### 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 e transferências. Categorização, filtros combináveis, 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.
|
||||
|
||||
@@ -85,7 +85,7 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ
|
||||
<img src="./public/images/companion-preview-light.webp" alt="OpenMonetis Companion" width="300" height="600" />
|
||||
</p>
|
||||
|
||||
⚙️ **Personalização** — Tema dark/light e modo privacidade.
|
||||
⚙️ **Personalização** — Tema dark/light, modo privacidade e changelog visual para acompanhar as novidades do app.
|
||||
|
||||
### Stack técnica
|
||||
|
||||
@@ -127,21 +127,20 @@ Só quer rodar o OpenMonetis. **Não precisa clonar o repositório nem instalar
|
||||
# 1. Baixe o compose
|
||||
curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml
|
||||
|
||||
# 2. Suba tudo
|
||||
# 2. Crie um .en 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
|
||||
|
||||
# 3. Suba tudo
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
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:
|
||||
O banco sobe com credenciais padrão. Para personalizar (senha, Google OAuth, e-mail, IA...), crie um `.env` na mesma pasta **antes** de subir.
|
||||
|
||||
```bash
|
||||
# .env mínimo recomendado para produção
|
||||
BETTER_AUTH_SECRET=gere-um-valor-com-openssl-rand-base64-32
|
||||
BETTER_AUTH_URL=https://seu-dominio.com
|
||||
```
|
||||
|
||||
Veja todas as variáveis disponíveis em [Variáveis de Ambiente](#-variáveis-de-ambiente).
|
||||
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:
|
||||
|
||||
@@ -508,7 +507,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...)
|
||||
@@ -524,6 +534,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
|
||||
@@ -563,12 +589,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.13/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -21,6 +21,7 @@ const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
prefetchInlining: true,
|
||||
turbopackFileSystemCacheForDev: true,
|
||||
optimizePackageImports: ["@remixicon/react"],
|
||||
},
|
||||
|
||||
// Headers for Safari compatibility
|
||||
|
||||
45
package.json
45
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openmonetis",
|
||||
"version": "2.5.2",
|
||||
"version": "2.5.6",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"scripts": {
|
||||
@@ -31,12 +31,12 @@
|
||||
"mockup": "tsx scripts/mock-data.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.74",
|
||||
"@ai-sdk/google": "^3.0.67",
|
||||
"@ai-sdk/openai": "^3.0.57",
|
||||
"@aws-sdk/client-s3": "^3.1040.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1040.0",
|
||||
"@better-auth/passkey": "^1.6.9",
|
||||
"@ai-sdk/anthropic": "^3.0.76",
|
||||
"@ai-sdk/google": "^3.0.71",
|
||||
"@ai-sdk/openai": "^3.0.63",
|
||||
"@aws-sdk/client-s3": "^3.1045.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
||||
"@better-auth/passkey": "^1.6.10",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -63,11 +63,11 @@
|
||||
"@radix-ui/react-toggle-group": "1.1.11",
|
||||
"@radix-ui/react-tooltip": "1.2.8",
|
||||
"@remixicon/react": "4.9.0",
|
||||
"@tanstack/react-query": "^5.100.7",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.24",
|
||||
"ai": "^6.0.173",
|
||||
"better-auth": "1.6.9",
|
||||
"ai": "^6.0.177",
|
||||
"better-auth": "1.6.10",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
@@ -77,19 +77,20 @@
|
||||
"exceljs": "^4.4.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jspdf-autotable": "^5.0.7",
|
||||
"next": "16.2.4",
|
||||
"next": "16.2.6",
|
||||
"next-themes": "0.4.6",
|
||||
"pdfjs-dist": "^5.7.284",
|
||||
"pg": "8.20.0",
|
||||
"react": "19.2.5",
|
||||
"react-day-picker": "^9.14.0",
|
||||
"react-dom": "19.2.5",
|
||||
"react": "19.2.6",
|
||||
"react-day-picker": "^10.0.0",
|
||||
"react-dom": "19.2.6",
|
||||
"recharts": "3.8.1",
|
||||
"resend": "^6.12.2",
|
||||
"resend": "^6.12.3",
|
||||
"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.4.1"
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
@@ -97,17 +98,17 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.4.13",
|
||||
"@tailwindcss/postcss": "4.2.4",
|
||||
"@biomejs/biome": "2.4.15",
|
||||
"@tailwindcss/postcss": "4.3.0",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@types/node": "25.6.0",
|
||||
"@types/node": "25.6.2",
|
||||
"@types/pg": "^8.20.0",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-kit": "0.31.10",
|
||||
"knip": "^6.10.0",
|
||||
"tailwindcss": "4.2.4",
|
||||
"knip": "^6.12.2",
|
||||
"tailwindcss": "4.3.0",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "6.0.3"
|
||||
}
|
||||
|
||||
1823
pnpm-lock.yaml
generated
1823
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1089
scripts/mock-data.ts
1089
scripts/mock-data.ts
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { Logo } from "@/shared/components/brand/logo";
|
||||
|
||||
export default function AuthLayout({
|
||||
children,
|
||||
|
||||
@@ -7,8 +7,8 @@ import { AdjustBalanceDialog } from "@/features/accounts/components/adjust-balan
|
||||
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";
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
resolveTransactionPagination,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
@@ -89,7 +89,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
accountId: account.id,
|
||||
});
|
||||
|
||||
const transactionsPage = await fetchAccountLancamentosPage(
|
||||
const transactionsPage = await fetchAccountTransactionsPage(
|
||||
filters,
|
||||
pagination,
|
||||
);
|
||||
|
||||
@@ -19,7 +19,6 @@ export default function RootLayout({
|
||||
subtitle="Gerencie os anexos das suas transações"
|
||||
/>
|
||||
<MonthNavigation />
|
||||
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -25,9 +25,7 @@ export default async function Page({ searchParams }: PageProps) {
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
const periodoParam = getSingleParam(resolvedSearchParams, "periodo");
|
||||
|
||||
const { period: selectedPeriod } = parsePeriodParam(periodoParam);
|
||||
|
||||
const { budgets, categoriesOptions } = await fetchBudgetsForUser(
|
||||
userId,
|
||||
selectedPeriod,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -2,9 +2,9 @@ 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/extract-logo-names";
|
||||
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";
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { LogoDevProvider } from "@/shared/components/providers/logo-dev-provider";
|
||||
import { PrivacyProvider } from "@/shared/components/providers/privacy-provider";
|
||||
@@ -21,8 +21,8 @@ export default async function DashboardLayout({
|
||||
<PrivacyProvider>
|
||||
<AppNavbar
|
||||
user={{ ...session.user, image: session.user.image ?? null }}
|
||||
pagadorAvatarUrl={navbarData.pagadorAvatarUrl}
|
||||
preLancamentosCount={navbarData.preLancamentosCount}
|
||||
payerAvatarUrl={navbarData.payerAvatarUrl}
|
||||
inboxPendingCount={navbarData.inboxPendingCount}
|
||||
notificationsSnapshot={navbarData.notificationsSnapshot}
|
||||
/>
|
||||
<div className="relative flex flex-1 flex-col pt-16">
|
||||
|
||||
@@ -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,13 +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 { LogoPrefetchProvider } from "@/shared/components/entity-avatar";
|
||||
import { ExpandableWidgetCard } from "@/shared/components/expandable-widget-card";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import {
|
||||
Tabs,
|
||||
@@ -50,16 +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";
|
||||
@@ -76,15 +76,17 @@ 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,
|
||||
};
|
||||
|
||||
const createEmptySlugMaps = (): SlugMaps => ({
|
||||
@@ -182,7 +184,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
estabelecimentos,
|
||||
userPreferences,
|
||||
] = await Promise.all([
|
||||
fetchPagadorLancamentos(filters),
|
||||
fetchPayerTransactions(filters),
|
||||
fetchPayerMonthlyBreakdown({
|
||||
userId: dataOwnerId,
|
||||
payerId: pagador.id,
|
||||
@@ -193,22 +195,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,
|
||||
@@ -333,7 +335,7 @@ export default async function Page({ params, searchParams }: PageProps) {
|
||||
/>
|
||||
|
||||
<TabsContent value="profile" className="space-y-4">
|
||||
<PagadorInfoCard payer={payerData} />
|
||||
<PayerInfoCard payer={payerData} />
|
||||
{canEdit && payerData.shareCode ? (
|
||||
<PayerSharingCard
|
||||
payerId={pagador.id}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Skeleton } from "@/shared/components/ui/skeleton";
|
||||
* Loading state para a página de pessoas
|
||||
* Layout: Header + Input de compartilhamento + Grid de cards
|
||||
*/
|
||||
export default function PagadoresLoading() {
|
||||
export default function PayersLoading() {
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<div className="w-full space-y-6">
|
||||
|
||||
@@ -9,7 +9,7 @@ export default async function Page() {
|
||||
const { payers, avatarOptions } = await fetchPayersForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<main className="flex flex-col gap-6">
|
||||
<PayersPage payers={payers} avatarOptions={avatarOptions} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { RiBankCard2Line } from "@remixicon/react";
|
||||
import { connection } from "next/server";
|
||||
import { fetchCartoesReportData } from "@/features/reports/cards-report-queries";
|
||||
import { CardCategoryBreakdown } from "@/features/reports/components/cards/card-category-breakdown";
|
||||
import { CardInvoiceStatus } from "@/features/reports/components/cards/card-invoice-status";
|
||||
import { CardTopExpenses } from "@/features/reports/components/cards/card-top-expenses";
|
||||
import { CardUsageChart } from "@/features/reports/components/cards/card-usage-chart";
|
||||
import { CardsOverview } from "@/features/reports/components/cards/cards-overview";
|
||||
import { fetchCartoesReportData } from "@/features/reports/lib/cards-report-queries";
|
||||
import MonthNavigation from "@/shared/components/month-picker/month-navigation";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import { getUser } from "@/shared/lib/auth/server";
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
import type { Category } from "@/db/schema";
|
||||
import { fetchCategoryChartData } from "@/features/reports/category-chart-queries";
|
||||
import { fetchCategoryReport } from "@/features/reports/category-report-queries";
|
||||
import { fetchUserCategories } from "@/features/reports/category-trends-queries";
|
||||
import { CategoryReportPage } from "@/features/reports/components/category-report-page";
|
||||
import type {
|
||||
CategoryOption,
|
||||
FilterState,
|
||||
} from "@/features/reports/components/types";
|
||||
import { validateDateRange } from "@/features/reports/utils";
|
||||
import { fetchCategoryChartData } from "@/features/reports/lib/category-chart-queries";
|
||||
import { fetchCategoryReport } from "@/features/reports/lib/category-report-queries";
|
||||
import { fetchUserCategories } from "@/features/reports/lib/category-trends-queries";
|
||||
import { validateDateRange } from "@/features/reports/lib/utils";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
import type { CategoryReportFilters } from "@/shared/lib/types/reports";
|
||||
import { addMonthsToPeriod, getCurrentPeriod } from "@/shared/utils/period";
|
||||
|
||||
@@ -34,7 +34,7 @@ const validatePeriodFilter = (value: string | null): PeriodFilter => {
|
||||
return "6";
|
||||
};
|
||||
|
||||
export default async function TopEstabelecimentosPage({
|
||||
export default async function TopEstablishmentsPage({
|
||||
searchParams,
|
||||
}: PageProps) {
|
||||
await connection();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ImportPage } from "@/features/transactions/components/import/import-pag
|
||||
import {
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import { fetchTransactionFilterSources } from "@/features/transactions/queries";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Skeleton } from "@/shared/components/ui/skeleton";
|
||||
* Loading state para a página de lançamentos
|
||||
* Mantém o mesmo layout da página final
|
||||
*/
|
||||
export default function LancamentosLoading() {
|
||||
export default function TransactionsLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
{/* Month Picker placeholder */}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
mapTransactionsData,
|
||||
type ResolvedSearchParams,
|
||||
resolveTransactionPagination,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import { landingImages } from "@/features/landing/images";
|
||||
import { fetchGitHubStats } from "@/features/landing/queries";
|
||||
import { AnimatedThemeToggler } from "@/shared/components/animated-theme-toggler";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { Logo } from "@/shared/components/brand/logo";
|
||||
import { NavbarShell } from "@/shared/components/navigation/navbar/navbar-shell";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
@@ -208,7 +208,7 @@ export default async function Page() {
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section id="funcionalidades" className="py-12 md:py-24 bg-muted/40">
|
||||
<section id="funcionalidades" className="py-12 md:py-24">
|
||||
<div className="max-w-8xl mx-auto px-4">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<AnimateOnScroll>
|
||||
@@ -447,7 +447,7 @@ export default async function Page() {
|
||||
</section>
|
||||
|
||||
{/* Tech Stack Section */}
|
||||
<section id="stack" className="py-12 md:py-24 bg-muted/40">
|
||||
<section id="stack" className="py-12 md:py-24">
|
||||
<div className="max-w-8xl mx-auto px-4">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<AnimateOnScroll>
|
||||
@@ -535,7 +535,7 @@ export default async function Page() {
|
||||
</section>
|
||||
|
||||
{/* Who is this for Section */}
|
||||
<section id="para-quem-e" className="py-12 md:py-24 bg-muted/40">
|
||||
<section id="para-quem-e" className="py-12 md:py-24">
|
||||
<div className="max-w-8xl mx-auto px-4">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<AnimateOnScroll>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { fetchTransactionAttachments } from "@/features/transactions/attachment-queries";
|
||||
import { fetchTransactionAttachments } from "@/features/transactions/lib/attachment-queries";
|
||||
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||
|
||||
const PRIVATE_RESPONSE_HEADERS = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { fetchInstallmentAnticipations } from "@/features/transactions/anticipation-queries";
|
||||
import { fetchInstallmentAnticipations } from "@/features/transactions/lib/anticipation-queries";
|
||||
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||
|
||||
const PRIVATE_RESPONSE_HEADERS = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@@ -269,54 +270,6 @@
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
@keyframes dialog-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes overlay-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes overlay-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
[data-slot="dialog-overlay"][data-state="open"] {
|
||||
animation: overlay-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
[data-slot="dialog-overlay"][data-state="closed"] {
|
||||
animation: overlay-out 0.15s ease-in;
|
||||
}
|
||||
|
||||
[data-slot="dialog-content"][data-state="open"] {
|
||||
animation: dialog-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
[data-slot="dialog-content"][data-state="closed"] {
|
||||
animation: dialog-out 0.15s ease-in;
|
||||
}
|
||||
|
||||
@keyframes blink-in {
|
||||
0%, 40% { opacity: 1; }
|
||||
50%, 90% { opacity: 0; }
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/shared/components/ui/tooltip";
|
||||
import { isAccountInactive } from "@/shared/lib/accounts/constants";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
interface AccountCardProps {
|
||||
@@ -46,7 +47,7 @@ export function AccountCard({
|
||||
onTransfer,
|
||||
className,
|
||||
}: AccountCardProps) {
|
||||
const isInactive = status?.toLowerCase() === "inativa";
|
||||
const isInactive = isAccountInactive(status);
|
||||
|
||||
const balanceColor =
|
||||
balance > 0
|
||||
@@ -145,6 +146,7 @@ export function AccountCard({
|
||||
<span className="text-xs text-muted-foreground">Saldo</span>
|
||||
<MoneyValues
|
||||
amount={balance}
|
||||
showPositiveSign
|
||||
className={cn("text-2xl font-semibold", balanceColor)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import StatusDot from "@/shared/components/status-dot";
|
||||
import StatusDot from "@/shared/components/feedback/status-dot";
|
||||
import { getAccountTypeIcon } from "@/shared/utils/icons";
|
||||
|
||||
export function AccountTypeSelectContent({ label }: { label: string }) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { toast } from "sonner";
|
||||
import { deleteAccountAction } from "@/features/accounts/actions";
|
||||
import { AccountCard } from "@/features/accounts/components/account-card";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { EmptyState } from "@/shared/components/feedback/empty-state";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import {
|
||||
@@ -186,14 +186,14 @@ export function AccountsPage({
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="ativos">Ativas</TabsTrigger>
|
||||
<TabsTrigger value="arquivados">Arquivadas</TabsTrigger>
|
||||
<TabsTrigger value="inativas">Inativas</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="ativos" className="mt-4">
|
||||
{renderAccountList(orderedAccounts, false)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="arquivados" className="mt-4">
|
||||
<TabsContent value="inativas" className="mt-4">
|
||||
{renderAccountList(orderedArchivedAccounts, true)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -99,13 +99,13 @@ async function fetchAccountsByStatus(
|
||||
return { accounts, logoOptions };
|
||||
}
|
||||
|
||||
export async function fetchAccountsForUser(
|
||||
async function fetchAccountsForUser(
|
||||
userId: string,
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: string[] }> {
|
||||
return fetchAccountsByStatus(userId, false);
|
||||
}
|
||||
|
||||
export async function fetchInactiveForUser(
|
||||
async function fetchInactiveForUser(
|
||||
userId: string,
|
||||
): Promise<{ accounts: AccountData[]; logoOptions: string[] }> {
|
||||
return fetchAccountsByStatus(userId, true);
|
||||
|
||||
@@ -154,7 +154,7 @@ export async function fetchAccountSummary(
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchAccountLancamentos(
|
||||
export async function fetchAccountTransactions(
|
||||
filters: SQL[],
|
||||
settledOnly = true,
|
||||
) {
|
||||
@@ -167,7 +167,7 @@ export async function fetchAccountLancamentos(
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchAccountLancamentosPage(
|
||||
export async function fetchAccountTransactionsPage(
|
||||
filters: SQL[],
|
||||
{
|
||||
page,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { fetchTransactionDialogOptionsAction } from "@/features/transactions/act
|
||||
import { TransactionDetailsDialog } from "@/features/transactions/components/dialogs/transaction-details-dialog";
|
||||
import { TransactionDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog";
|
||||
import type { TransactionItem } from "@/features/transactions/components/types";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { EmptyState } from "@/shared/components/feedback/empty-state";
|
||||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { fetchJson } from "@/shared/lib/fetch-json";
|
||||
import { fetchJson } from "@/shared/utils/fetch-json";
|
||||
|
||||
const ATTACHMENT_URL_STALE_TIME = 4 * 60 * 1000;
|
||||
|
||||
export const attachmentUrlQueryKey = (attachmentId: string) =>
|
||||
const attachmentUrlQueryKey = (attachmentId: string) =>
|
||||
["attachments", "url", attachmentId] as const;
|
||||
|
||||
export function useAttachmentUrlQuery(attachmentId: string, enabled: boolean) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
RiBarChart2Line,
|
||||
RiShieldCheckLine,
|
||||
} from "@remixicon/react";
|
||||
import { Logo } from "@/shared/components/logo";
|
||||
import { Logo } from "@/shared/components/brand/logo";
|
||||
import { DotPattern } from "@/shared/components/ui/dot-pattern";
|
||||
import { AuthSidebarInvoicesMock } from "./auth-sidebar-invoices-mock";
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { budgets, categories } from "@/db/schema";
|
||||
import {
|
||||
type CategoryBudgetSummary,
|
||||
fetchCategoryBudgetSummary,
|
||||
} from "@/features/budgets/queries";
|
||||
import {
|
||||
handleActionError,
|
||||
revalidateForEntity,
|
||||
@@ -204,6 +208,34 @@ export async function deleteBudgetAction(
|
||||
}
|
||||
}
|
||||
|
||||
const getCategoryBudgetSummarySchema = z.object({
|
||||
categoryId: uuidSchema("Category"),
|
||||
period: periodSchema,
|
||||
});
|
||||
|
||||
type GetCategoryBudgetSummaryInput = z.input<
|
||||
typeof getCategoryBudgetSummarySchema
|
||||
>;
|
||||
|
||||
export async function getCategoryBudgetSummaryAction(
|
||||
input: GetCategoryBudgetSummaryInput,
|
||||
): Promise<ActionResult<CategoryBudgetSummary | null>> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = getCategoryBudgetSummarySchema.parse(input);
|
||||
const summary = await fetchCategoryBudgetSummary(
|
||||
user.id,
|
||||
data.categoryId,
|
||||
data.period,
|
||||
);
|
||||
return { success: true, message: "ok", data: summary };
|
||||
} catch (error) {
|
||||
return handleActionError(
|
||||
error,
|
||||
) as ActionResult<CategoryBudgetSummary | null>;
|
||||
}
|
||||
}
|
||||
|
||||
const duplicatePreviousMonthSchema = z.object({
|
||||
period: periodSchema,
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
duplicatePreviousMonthBudgetsAction,
|
||||
} from "@/features/budgets/actions";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { EmptyState } from "@/shared/components/feedback/empty-state";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import { BudgetCard } from "./budget-card";
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
|
||||
import { budgets, categories, transactions } from "@/db/schema";
|
||||
import {
|
||||
budgets,
|
||||
categories,
|
||||
financialAccounts,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||
import { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
|
||||
|
||||
@@ -26,7 +32,7 @@ type BudgetData = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type CategoryOption = {
|
||||
type CategoryOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
@@ -75,6 +81,10 @@ export async function fetchBudgetsForUser(
|
||||
totalAmount: sum(transactions.amount).as("totalAmount"),
|
||||
})
|
||||
.from(transactions)
|
||||
.leftJoin(
|
||||
financialAccounts,
|
||||
eq(transactions.accountId, financialAccounts.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(transactions.userId, userId),
|
||||
@@ -86,6 +96,7 @@ export async function fetchBudgetsForUser(
|
||||
isNull(transactions.note),
|
||||
sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
|
||||
),
|
||||
excludeTransactionsFromExcludedAccounts(),
|
||||
),
|
||||
)
|
||||
.groupBy(transactions.categoryId);
|
||||
@@ -127,3 +138,57 @@ export async function fetchBudgetsForUser(
|
||||
|
||||
return { budgets: budgetList, categoriesOptions };
|
||||
}
|
||||
|
||||
export type CategoryBudgetSummary = {
|
||||
amount: number;
|
||||
spent: number;
|
||||
};
|
||||
|
||||
export async function fetchCategoryBudgetSummary(
|
||||
userId: string,
|
||||
categoryId: string,
|
||||
period: string,
|
||||
): Promise<CategoryBudgetSummary | null> {
|
||||
const [adminPayerId, budget] = await Promise.all([
|
||||
getAdminPayerId(userId),
|
||||
db.query.budgets.findFirst({
|
||||
columns: { amount: true },
|
||||
where: and(
|
||||
eq(budgets.userId, userId),
|
||||
eq(budgets.categoryId, categoryId),
|
||||
eq(budgets.period, period),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!adminPayerId || !budget) return null;
|
||||
|
||||
const totals = await db
|
||||
.select({
|
||||
totalAmount: sum(transactions.amount).as("totalAmount"),
|
||||
})
|
||||
.from(transactions)
|
||||
.leftJoin(
|
||||
financialAccounts,
|
||||
eq(transactions.accountId, financialAccounts.id),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(transactions.userId, userId),
|
||||
eq(transactions.period, period),
|
||||
eq(transactions.transactionType, "Despesa"),
|
||||
eq(transactions.payerId, adminPayerId),
|
||||
eq(transactions.categoryId, categoryId),
|
||||
or(
|
||||
isNull(transactions.note),
|
||||
sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
|
||||
),
|
||||
excludeTransactionsFromExcludedAccounts(),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
amount: toNumber(budget.amount),
|
||||
spent: Math.abs(toNumber(totals[0]?.totalAmount ?? 0)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
const LEGEND_ITEMS = [
|
||||
@@ -12,22 +13,24 @@ const LEGEND_ITEMS = [
|
||||
|
||||
export function CalendarLegend() {
|
||||
return (
|
||||
<ul className="flex items-center justify-start gap-2 px-1">
|
||||
{LEGEND_ITEMS.map((item) => (
|
||||
<li
|
||||
key={item.label}
|
||||
className={cn(
|
||||
"flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium",
|
||||
item.wrapper,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("size-1.5 shrink-0 rounded-full", item.dot)}
|
||||
aria-hidden
|
||||
/>
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Card className="px-4 py-2">
|
||||
<ul className="flex flex-row items-center gap-2">
|
||||
{LEGEND_ITEMS.map((item) => (
|
||||
<li
|
||||
key={item.label}
|
||||
className={cn(
|
||||
"flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium",
|
||||
item.wrapper,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("size-1.5 shrink-0 rounded-full", item.dot)}
|
||||
aria-hidden
|
||||
/>
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
buildOptionSets,
|
||||
buildSluggedFilters,
|
||||
mapTransactionsData,
|
||||
} from "@/features/transactions/page-helpers";
|
||||
} from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
fetchRecentEstablishments,
|
||||
fetchTransactionFilterSources,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { RiBankLine } from "@remixicon/react";
|
||||
import Image from "next/image";
|
||||
import StatusDot from "@/shared/components/status-dot";
|
||||
import StatusDot from "@/shared/components/feedback/status-dot";
|
||||
import { resolveCardBrandLogoSrc } from "@/shared/lib/cards/brand-assets";
|
||||
import { resolveLogoSrc } from "@/shared/lib/logo";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { deleteCardAction } from "@/features/cards/actions";
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import { EmptyState } from "@/shared/components/empty-state";
|
||||
import { EmptyState } from "@/shared/components/feedback/empty-state";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card as UiCard } from "@/shared/components/ui/card";
|
||||
import {
|
||||
@@ -174,14 +174,14 @@ export function CardsPage({
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="ativos">Ativos</TabsTrigger>
|
||||
<TabsTrigger value="arquivados">Arquivados</TabsTrigger>
|
||||
<TabsTrigger value="inativos">Inativos</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="ativos" className="mt-4">
|
||||
{renderCardList(orderedCards, false)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="arquivados" className="mt-4">
|
||||
<TabsContent value="inativos" className="mt-4">
|
||||
{renderCardList(orderedArchivedCards, true)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -19,7 +19,7 @@ type CardData = {
|
||||
accountName: string;
|
||||
};
|
||||
|
||||
export type AccountSimple = {
|
||||
type AccountSimple = {
|
||||
id: string;
|
||||
name: string;
|
||||
logo: string | null;
|
||||
@@ -121,7 +121,7 @@ async function fetchCardsByStatus(
|
||||
return { cards: cardList, accounts, logoOptions };
|
||||
}
|
||||
|
||||
export async function fetchCardsForUser(userId: string): Promise<{
|
||||
async function fetchCardsForUser(userId: string): Promise<{
|
||||
cards: CardData[];
|
||||
accounts: AccountSimple[];
|
||||
logoOptions: string[];
|
||||
@@ -129,7 +129,7 @@ export async function fetchCardsForUser(userId: string): Promise<{
|
||||
return fetchCardsByStatus(userId, false);
|
||||
}
|
||||
|
||||
export async function fetchInactiveForUser(userId: string): Promise<{
|
||||
async function fetchInactiveForUser(userId: string): Promise<{
|
||||
cards: CardData[];
|
||||
accounts: AccountSimple[];
|
||||
logoOptions: string[];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import StatusDot from "@/shared/components/status-dot";
|
||||
import StatusDot from "@/shared/components/feedback/status-dot";
|
||||
|
||||
export function TypeSelectContent({ label }: { label: string }) {
|
||||
const isReceita = label === "Receita";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { type Category, categories } from "@/db/schema";
|
||||
import type { CategoryType } from "@/shared/lib/categories/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
export type CategoryData = {
|
||||
type CategoryData = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: CategoryType;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/shared/utils/financial-dates";
|
||||
|
||||
export type BillDialogState = PaymentDialogState;
|
||||
export type BillStatusDateItem = Pick<
|
||||
type BillStatusDateItem = Pick<
|
||||
DashboardBill,
|
||||
"dueDate" | "boletoPaymentDate" | "isSettled"
|
||||
>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { and, desc, eq, isNull, ne, or, sql } from "drizzle-orm";
|
||||
import { categories, financialAccounts, transactions } from "@/db/schema";
|
||||
import { mapTransactionsData } from "@/features/transactions/page-helpers";
|
||||
import { mapTransactionsData } from "@/features/transactions/lib/page-helpers";
|
||||
import {
|
||||
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
|
||||
INITIAL_BALANCE_NOTE,
|
||||
@@ -17,7 +17,7 @@ import { getPreviousPeriod } from "@/shared/utils/period";
|
||||
|
||||
type MappedLancamentos = ReturnType<typeof mapTransactionsData>;
|
||||
|
||||
export type CategoryDetailData = {
|
||||
type CategoryDetailData = {
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
formatPeriodMonthShort,
|
||||
} from "@/shared/utils/period";
|
||||
|
||||
export type CategoryOption = {
|
||||
type CategoryOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
type: "receita" | "despesa";
|
||||
};
|
||||
|
||||
export type CategoryHistoryItem = {
|
||||
type CategoryHistoryItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
excludeInitialBalanceWhenConfigured,
|
||||
excludeRefundEntries,
|
||||
excludeTransactionsFromExcludedAccounts,
|
||||
} from "@/features/dashboard/transaction-filters";
|
||||
} from "@/features/dashboard/lib/transaction-filters";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
|
||||
import { safeToNumber as toNumber } from "@/shared/utils/number";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export type CategoryOption = {
|
||||
type CategoryOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type CategoryTransaction = {
|
||||
type CategoryTransaction = {
|
||||
id: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
|
||||
@@ -44,7 +44,7 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) {
|
||||
|
||||
return (
|
||||
<li className="flex items-center justify-between transition-all duration-300 py-1.5">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 py-1">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 py-0.5">
|
||||
<EstablishmentLogo name={bill.name} size={37} />
|
||||
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -14,8 +14,8 @@ import type {
|
||||
} from "@/features/dashboard/bills/bills-queries";
|
||||
import { AccountCardSelectContent } from "@/features/transactions/components/select-items";
|
||||
import { EstablishmentLogo } from "@/shared/components/entity-avatar";
|
||||
import { PaymentSuccess } from "@/shared/components/feedback/payment-success";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { PaymentSuccess } from "@/shared/components/payment-success";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiBarcodeFill } from "@remixicon/react";
|
||||
import type { DashboardBill } from "@/features/dashboard/bills/bills-queries";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { BillListItem } from "./bill-list-item";
|
||||
|
||||
type BillsListProps = {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/shared/components/ui/tabs";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { formatPeriodForUrl } from "@/shared/utils/period";
|
||||
import { CategoryBreakdownChart } from "./category-breakdown-chart";
|
||||
import { CategoryBreakdownList } from "./category-breakdown-list";
|
||||
|
||||
@@ -40,8 +40,8 @@ import {
|
||||
} from "@/features/dashboard/widget-registry/widget-config";
|
||||
import { NoteDialog } from "@/features/notes/components/note-dialog";
|
||||
import { TransactionDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog";
|
||||
import { ExpandableWidgetCard } from "@/shared/components/expandable-widget-card";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { ExpandableWidgetCard } from "@/shared/components/widgets/expandable-widget-card";
|
||||
|
||||
type DashboardGridEditableProps = {
|
||||
data: DashboardData;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiFundsLine } from "@remixicon/react";
|
||||
import type { GoalProgressItem } from "@/features/dashboard/goals-progress/goals-progress-queries";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { GoalProgressItem as GoalProgressListItem } from "./goals-progress-item";
|
||||
|
||||
type GoalsProgressListProps = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiNumbersLine } from "@remixicon/react";
|
||||
import type { InstallmentExpense } from "@/features/dashboard/expenses/installment-expenses-queries";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { InstallmentExpenseListItem } from "./installment-expense-list-item";
|
||||
|
||||
type InstallmentExpensesListProps = {
|
||||
|
||||
@@ -14,8 +14,8 @@ import type {
|
||||
InvoicePaymentAccountOption,
|
||||
} from "@/features/dashboard/invoices/invoices-queries";
|
||||
import { AccountCardSelectContent } from "@/features/transactions/components/select-items";
|
||||
import { PaymentSuccess } from "@/shared/components/feedback/payment-success";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { PaymentSuccess } from "@/shared/components/payment-success";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiBillLine } from "@remixicon/react";
|
||||
import type { DashboardInvoice } from "@/features/dashboard/invoices/invoices-queries";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { InvoiceListItem } from "./invoice-list-item";
|
||||
|
||||
type InvoicesListProps = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RiTodoLine } from "@remixicon/react";
|
||||
import type { Note } from "@/features/notes/components/types";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { NoteListItem } from "./note-list-item";
|
||||
|
||||
type NotesListProps = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import {
|
||||
PaymentBreakdownListItem,
|
||||
type PaymentBreakdownListItemData,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import StatusDot from "@/shared/components/feedback/status-dot";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import StatusDot from "@/shared/components/status-dot";
|
||||
import { Progress } from "@/shared/components/ui/progress";
|
||||
|
||||
type PaymentStatusCategorySectionProps = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RiWallet3Line } from "@remixicon/react";
|
||||
import type { PaymentStatusData } from "@/features/dashboard/payments/payment-status-queries";
|
||||
import { CardContent } from "@/shared/components/ui/card";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { PaymentStatusCategorySection } from "./payment-status-category-section";
|
||||
|
||||
type PaymentStatusWidgetViewProps = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { formatPercentage } from "@/shared/utils/percentage";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
export type PercentageChangeTrend = "up" | "down" | "flat";
|
||||
type PercentageChangeTrend = "up" | "down" | "flat";
|
||||
|
||||
type PercentageChangeIndicatorProps = {
|
||||
value?: number | null;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/shared/components/ui/tooltip";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { formatDateOnly } from "@/shared/utils/date";
|
||||
import { formatBytes } from "@/shared/utils/number";
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/shared/components/ui/popover";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { CATEGORY_COLORS } from "@/shared/utils/category-colors";
|
||||
import { formatCurrency, formatCurrencyCompact } from "@/shared/utils/currency";
|
||||
import { getIconComponent } from "@/shared/utils/icons";
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { DashboardCategoryBreakdownItem } from "@/features/dashboard/catego
|
||||
import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator";
|
||||
import { CategoryIconBadge } from "@/shared/components/entity-avatar";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { formatPercentage } from "@/shared/utils/percentage";
|
||||
|
||||
type CategoryTrendsWidgetProps = {
|
||||
|
||||
@@ -9,7 +9,7 @@ import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { DashboardInboxSnapshot } from "@/features/dashboard/inbox-snapshot-queries";
|
||||
import type { DashboardInboxSnapshot } from "@/features/dashboard/lib/inbox-snapshot-queries";
|
||||
import type { DashboardWidgetQuickActionOptions } from "@/features/dashboard/widget-registry/widget-config";
|
||||
import {
|
||||
discardInboxItemAction,
|
||||
@@ -19,7 +19,7 @@ import { TransactionDialog } from "@/features/transactions/components/dialogs/tr
|
||||
import { ConfirmActionDialog } from "@/shared/components/confirm-action-dialog";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { resolveLogoSrc } from "@/shared/lib/logo";
|
||||
|
||||
const DEFAULT_INBOX_APP_LOGO = "/avatars/default_icon.png";
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
} from "@/shared/components/ui/chart";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { formatCurrency } from "@/shared/utils/currency";
|
||||
|
||||
type IncomeExpenseBalanceWidgetProps = {
|
||||
|
||||
@@ -10,7 +10,7 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { DashboardAccount } from "@/features/dashboard/accounts-queries";
|
||||
import type { DashboardAccount } from "@/features/dashboard/lib/accounts-queries";
|
||||
import { updateMyAccountsWidgetPreference } from "@/features/dashboard/widget-registry/widget-actions";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { Badge } from "@/shared/components/ui/badge";
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/shared/components/ui/tooltip";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { isAccountInactive } from "@/shared/lib/accounts/constants";
|
||||
import { resolveLogoSrc } from "@/shared/lib/logo";
|
||||
import { formatPeriodForUrl } from "@/shared/utils/period";
|
||||
|
||||
@@ -42,12 +43,15 @@ export function MyAccountsWidget({
|
||||
}: MyAccountsWidgetProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const excludedAccountsCount = accounts.filter(
|
||||
const activeAccounts = accounts.filter(
|
||||
(account) => !isAccountInactive(account.status),
|
||||
);
|
||||
const excludedAccountsCount = activeAccounts.filter(
|
||||
(account) => account.excludeFromBalance,
|
||||
).length;
|
||||
const visibleAccounts = showExcludedAccounts
|
||||
? accounts
|
||||
: accounts.filter((account) => !account.excludeFromBalance);
|
||||
? activeAccounts
|
||||
: activeAccounts.filter((account) => !account.excludeFromBalance);
|
||||
const displayedAccounts = visibleAccounts.slice(0, 5);
|
||||
const remainingCount = visibleAccounts.length - displayedAccounts.length;
|
||||
const hiddenExcludedAccountsCount = showExcludedAccounts
|
||||
@@ -117,7 +121,7 @@ export function MyAccountsWidget({
|
||||
) : null}
|
||||
|
||||
<div>
|
||||
{accounts.length === 0 ? (
|
||||
{activeAccounts.length === 0 ? (
|
||||
<div className="-mt-10">
|
||||
<WidgetEmptyState
|
||||
icon={
|
||||
|
||||
@@ -7,14 +7,14 @@ import {
|
||||
} from "@remixicon/react";
|
||||
import Link from "next/link";
|
||||
import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator";
|
||||
import type { DashboardPagador } from "@/features/dashboard/payers-queries";
|
||||
import type { DashboardPagador } from "@/features/dashboard/lib/payers-queries";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/shared/components/ui/avatar";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { getAvatarSrc } from "@/shared/lib/payers/utils";
|
||||
import { buildInitials } from "@/shared/utils/initials";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { CATEGORY_TYPE_LABEL } from "@/shared/lib/categories/constants";
|
||||
import { formatTransactionDate } from "@/shared/utils/date";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { RiRefreshLine } from "@remixicon/react";
|
||||
import type { RecurringExpensesData } from "@/features/dashboard/expenses/recurring-expenses-queries";
|
||||
import { EstablishmentLogo } from "@/shared/components/entity-avatar";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
|
||||
type RecurringExpensesWidgetProps = {
|
||||
data: RecurringExpensesData;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { RiArrowUpDoubleLine, RiStore2Line } from "@remixicon/react";
|
||||
import { useState } from "react";
|
||||
import type { TopExpensesData } from "@/features/dashboard/expenses/top-expenses-queries";
|
||||
import type { TopEstablishmentsData } from "@/features/dashboard/top-establishments-queries";
|
||||
import type { TopEstablishmentsData } from "@/features/dashboard/lib/top-establishments-queries";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RiStore2Line } from "@remixicon/react";
|
||||
import type { TopEstablishmentsData } from "@/features/dashboard/top-establishments-queries";
|
||||
import type { TopEstablishmentsData } from "@/features/dashboard/lib/top-establishments-queries";
|
||||
import { EstablishmentLogo } from "@/shared/components/entity-avatar";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
|
||||
type TopEstablishmentsWidgetProps = {
|
||||
data: TopEstablishmentsData;
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
import { EstablishmentLogo } from "@/shared/components/entity-avatar";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
import { Switch } from "@/shared/components/ui/switch";
|
||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||
import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state";
|
||||
import { formatTransactionDate } from "@/shared/utils/date";
|
||||
|
||||
type TopExpensesWidgetProps = {
|
||||
|
||||
@@ -31,7 +31,7 @@ function calculateDueDate(period: string, dueDay: string | null): Date | null {
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallmentDetail = {
|
||||
type InstallmentDetail = {
|
||||
id: string;
|
||||
currentInstallment: number;
|
||||
amount: number;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
formatLastInstallmentDate,
|
||||
} from "@/shared/lib/installments/utils";
|
||||
|
||||
export type InstallmentExpenseDisplay = {
|
||||
type InstallmentExpenseDisplay = {
|
||||
compactLabel: string | null;
|
||||
isLast: boolean;
|
||||
remainingInstallments: number;
|
||||
@@ -13,7 +13,7 @@ export type InstallmentExpenseDisplay = {
|
||||
progress: number;
|
||||
};
|
||||
|
||||
export const buildInstallmentCompactLabel = (
|
||||
const buildInstallmentCompactLabel = (
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
) => {
|
||||
@@ -24,7 +24,7 @@ export const buildInstallmentCompactLabel = (
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isInstallmentLast = (
|
||||
const isInstallmentLast = (
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
) => {
|
||||
@@ -35,7 +35,7 @@ export const isInstallmentLast = (
|
||||
return currentInstallment === installmentCount && installmentCount > 1;
|
||||
};
|
||||
|
||||
export const calculateInstallmentRemainingCount = (
|
||||
const calculateInstallmentRemainingCount = (
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
) => {
|
||||
@@ -46,7 +46,7 @@ export const calculateInstallmentRemainingCount = (
|
||||
return Math.max(0, installmentCount - currentInstallment);
|
||||
};
|
||||
|
||||
export const calculateInstallmentRemainingAmount = (
|
||||
const calculateInstallmentRemainingAmount = (
|
||||
amount: number,
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
@@ -54,7 +54,7 @@ export const calculateInstallmentRemainingAmount = (
|
||||
amount *
|
||||
calculateInstallmentRemainingCount(currentInstallment, installmentCount);
|
||||
|
||||
export const formatInstallmentEndDate = (
|
||||
const formatInstallmentEndDate = (
|
||||
period: string,
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
@@ -72,7 +72,7 @@ export const formatInstallmentEndDate = (
|
||||
return formatLastInstallmentDate(lastDate);
|
||||
};
|
||||
|
||||
export const buildInstallmentProgress = (
|
||||
const buildInstallmentProgress = (
|
||||
currentInstallment: number | null,
|
||||
installmentCount: number | null,
|
||||
) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type RecurringExpense = {
|
||||
type RecurringExpense = {
|
||||
id: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { cacheLife, cacheTag } from "next/cache";
|
||||
import { fetchAttachmentsForPeriod } from "@/features/attachments/queries";
|
||||
import { fetchDashboardAccounts } from "./accounts-queries";
|
||||
import { fetchDashboardCategoryOverview } from "./categories/category-overview-queries";
|
||||
import { fetchDashboardInboxSnapshot } from "./inbox-snapshot-queries";
|
||||
import { fetchDashboardInvoices } from "./invoices/invoices-queries";
|
||||
import { fetchDashboardAccounts } from "./lib/accounts-queries";
|
||||
import { fetchDashboardInboxSnapshot } from "./lib/inbox-snapshot-queries";
|
||||
import { fetchDashboardPayers } from "./lib/payers-queries";
|
||||
import { fetchDashboardNotes } from "./notes/notes-queries";
|
||||
import { fetchDashboardCurrentPeriodOverview } from "./overview/current-period-overview-queries";
|
||||
import { fetchDashboardPeriodOverview } from "./overview/period-overview-queries";
|
||||
import { fetchDashboardPayers } from "./payers-queries";
|
||||
|
||||
async function fetchDashboardDataInternal(userId: string, period: string) {
|
||||
const [
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
import type {
|
||||
GoalProgressCategory,
|
||||
GoalProgressItem,
|
||||
GoalProgressStatus,
|
||||
} from "@/features/dashboard/goals-progress/goals-progress-queries";
|
||||
import { formatPercentage } from "@/shared/utils/percentage";
|
||||
|
||||
@@ -18,9 +17,6 @@ export const formatGoalProgressPercentage = (value: number, withSign = false) =>
|
||||
signDisplay: withSign ? "always" : "auto",
|
||||
});
|
||||
|
||||
export const getGoalProgressStatusColorClass = (status: GoalProgressStatus) =>
|
||||
status === "exceeded" ? "text-destructive" : "";
|
||||
|
||||
export const mapGoalProgressCategoriesToBudgetCategories = (
|
||||
categories: GoalProgressCategory[],
|
||||
): BudgetCategory[] =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type GoalProgressStatus = "on-track" | "critical" | "exceeded";
|
||||
type GoalProgressStatus = "on-track" | "critical" | "exceeded";
|
||||
|
||||
export type GoalProgressItem = {
|
||||
id: string;
|
||||
|
||||
@@ -55,7 +55,7 @@ type RawInvoiceBreakdownRow = {
|
||||
amount: number | string | null;
|
||||
};
|
||||
|
||||
export type InvoicePagadorBreakdown = {
|
||||
type InvoicePagadorBreakdown = {
|
||||
payerId: string | null;
|
||||
pagadorName: string;
|
||||
pagadorAvatar: string | null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DashboardData } from "./fetch-dashboard-data";
|
||||
import type { DashboardData } from "../fetch-dashboard-data";
|
||||
|
||||
/**
|
||||
* Coleta todos os nomes de estabelecimentos exibidos nos widgets do
|
||||
@@ -3,7 +3,7 @@ import { cacheLife, cacheTag } from "next/cache";
|
||||
import { cards, financialAccounts, inboxItems } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
|
||||
export type DashboardInboxItem = {
|
||||
type DashboardInboxItem = {
|
||||
id: string;
|
||||
sourceAppName: string | null;
|
||||
parsedName: string | null;
|
||||
@@ -8,11 +8,11 @@ import { getBusinessDateString } from "@/shared/utils/date";
|
||||
import {
|
||||
type DashboardNotificationsSnapshot,
|
||||
fetchDashboardNotifications,
|
||||
} from "./notifications/notifications-queries";
|
||||
} from "../notifications/notifications-queries";
|
||||
|
||||
type DashboardNavbarData = {
|
||||
pagadorAvatarUrl: string | null;
|
||||
preLancamentosCount: number;
|
||||
payerAvatarUrl: string | null;
|
||||
inboxPendingCount: number;
|
||||
notificationsSnapshot: DashboardNotificationsSnapshot;
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ async function fetchDashboardNavbarDataInternal(
|
||||
userId: string,
|
||||
): Promise<DashboardNavbarData> {
|
||||
const currentPeriod = getBusinessDateString().slice(0, 7);
|
||||
const [pagadorAvatarUrl, notificationsSnapshot, preLancamentosCount] =
|
||||
const [payerAvatarUrl, notificationsSnapshot, inboxPendingCount] =
|
||||
await Promise.all([
|
||||
fetchAdminPayerAvatarUrl(userId),
|
||||
fetchDashboardNotifications(userId, currentPeriod),
|
||||
@@ -47,8 +47,8 @@ async function fetchDashboardNavbarDataInternal(
|
||||
]);
|
||||
|
||||
return {
|
||||
pagadorAvatarUrl,
|
||||
preLancamentosCount,
|
||||
payerAvatarUrl,
|
||||
inboxPendingCount,
|
||||
notificationsSnapshot,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { and, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
||||
import { financialAccounts, payers, transactions } from "@/db/schema";
|
||||
import { excludeTransactionsFromExcludedAccounts } from "@/features/dashboard/transaction-filters";
|
||||
import { excludeTransactionsFromExcludedAccounts } from "@/features/dashboard/lib/transaction-filters";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||
@@ -1,4 +1,4 @@
|
||||
export type TopEstablishment = {
|
||||
type TopEstablishment = {
|
||||
id: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user