6 Commits

Author SHA1 Message Date
Felipe Coutinho
60a52b9873 fix(inbox): alinhar horario da tooltip do card 2026-03-21 19:42:55 +00:00
Felipe Coutinho
c9205f2be9 style(drizzle): normalizar snapshots gerados 2026-03-21 19:32:49 +00:00
Felipe Coutinho
1d36b12109 style: normalizar formatacao de importacao e suporte 2026-03-21 19:32:38 +00:00
Felipe Coutinho
19a1b1e943 chore(release): preparar versao 2.0.1 2026-03-21 19:31:53 +00:00
Felipe Coutinho
d3fc81db73 fix(inbox): melhorar filtros e identidade visual 2026-03-21 19:31:38 +00:00
Felipe Coutinho
80de9501f6 fix: move proxy.ts para src/ e atualiza dependências
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:52:20 +00:00
27 changed files with 5643 additions and 5581 deletions

View File

@@ -5,6 +5,16 @@ 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/), 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/). e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
## [2.0.1] - 2026-03-21
### 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
- Inbox: notificações de cartões/apps sem logo cadastrado agora exibem `default_icon.png` como fallback visual nos cards
- Inbox: select de apps em `/inbox` agora exibe os logos dos apps/cartões, com fallback para `default_icon.png` quando não houver logo mapeado
- Inbox: cabeçalhos de data entre grupos de cards agora exibem ícone e tipografia um pouco maior para melhorar a leitura
- Versionamento: `/api/health` passa a reportar a versão atual do `package.json`, evitando divergência entre healthcheck, UI e release publicada
## [2.0.0] - 2026-03-21 ## [2.0.0] - 2026-03-21
### Adicionado ### Adicionado

View File

@@ -93,12 +93,8 @@
"name": "account_userId_user_id_fk", "name": "account_userId_user_id_fk",
"tableFrom": "account", "tableFrom": "account",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -213,12 +209,8 @@
"name": "tokens_api_user_id_user_id_fk", "name": "tokens_api_user_id_user_id_fk",
"tableFrom": "tokens_api", "tableFrom": "tokens_api",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -327,12 +319,8 @@
"name": "orcamentos_user_id_user_id_fk", "name": "orcamentos_user_id_user_id_fk",
"tableFrom": "orcamentos", "tableFrom": "orcamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -340,12 +328,8 @@
"name": "orcamentos_categoria_id_categorias_id_fk", "name": "orcamentos_categoria_id_categorias_id_fk",
"tableFrom": "orcamentos", "tableFrom": "orcamentos",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -463,12 +447,8 @@
"name": "cartoes_user_id_user_id_fk", "name": "cartoes_user_id_user_id_fk",
"tableFrom": "cartoes", "tableFrom": "cartoes",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -476,12 +456,8 @@
"name": "cartoes_conta_id_contas_id_fk", "name": "cartoes_conta_id_contas_id_fk",
"tableFrom": "cartoes", "tableFrom": "cartoes",
"tableTo": "contas", "tableTo": "contas",
"columnsFrom": [ "columnsFrom": ["conta_id"],
"conta_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -563,12 +539,8 @@
"name": "categorias_user_id_user_id_fk", "name": "categorias_user_id_user_id_fk",
"tableFrom": "categorias", "tableFrom": "categorias",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -683,12 +655,8 @@
"name": "contas_user_id_user_id_fk", "name": "contas_user_id_user_id_fk",
"tableFrom": "contas", "tableFrom": "contas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -847,12 +815,8 @@
"name": "pre_lancamentos_user_id_user_id_fk", "name": "pre_lancamentos_user_id_user_id_fk",
"tableFrom": "pre_lancamentos", "tableFrom": "pre_lancamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -860,12 +824,8 @@
"name": "pre_lancamentos_lancamento_id_lancamentos_id_fk", "name": "pre_lancamentos_lancamento_id_lancamentos_id_fk",
"tableFrom": "pre_lancamentos", "tableFrom": "pre_lancamentos",
"tableTo": "lancamentos", "tableTo": "lancamentos",
"columnsFrom": [ "columnsFrom": ["lancamento_id"],
"lancamento_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1005,12 +965,8 @@
"name": "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk", "name": "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "lancamentos", "tableTo": "lancamentos",
"columnsFrom": [ "columnsFrom": ["lancamento_id"],
"lancamento_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1018,12 +974,8 @@
"name": "antecipacoes_parcelas_pagador_id_pagadores_id_fk", "name": "antecipacoes_parcelas_pagador_id_pagadores_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1031,12 +983,8 @@
"name": "antecipacoes_parcelas_categoria_id_categorias_id_fk", "name": "antecipacoes_parcelas_categoria_id_categorias_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1044,12 +992,8 @@
"name": "antecipacoes_parcelas_user_id_user_id_fk", "name": "antecipacoes_parcelas_user_id_user_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1179,12 +1123,8 @@
"name": "faturas_user_id_user_id_fk", "name": "faturas_user_id_user_id_fk",
"tableFrom": "faturas", "tableFrom": "faturas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1192,12 +1132,8 @@
"name": "faturas_cartao_id_cartoes_id_fk", "name": "faturas_cartao_id_cartoes_id_fk",
"tableFrom": "faturas", "tableFrom": "faturas",
"tableTo": "cartoes", "tableTo": "cartoes",
"columnsFrom": [ "columnsFrom": ["cartao_id"],
"cartao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -1271,12 +1207,8 @@
"name": "anotacoes_user_id_user_id_fk", "name": "anotacoes_user_id_user_id_fk",
"tableFrom": "anotacoes", "tableFrom": "anotacoes",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1364,12 +1296,8 @@
"name": "passkey_userId_user_id_fk", "name": "passkey_userId_user_id_fk",
"tableFrom": "passkey", "tableFrom": "passkey",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1452,12 +1380,8 @@
"name": "compartilhamentos_pagador_pagador_id_pagadores_id_fk", "name": "compartilhamentos_pagador_pagador_id_pagadores_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1465,12 +1389,8 @@
"name": "compartilhamentos_pagador_shared_with_user_id_user_id_fk", "name": "compartilhamentos_pagador_shared_with_user_id_user_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["shared_with_user_id"],
"shared_with_user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1478,12 +1398,8 @@
"name": "compartilhamentos_pagador_created_by_user_id_user_id_fk", "name": "compartilhamentos_pagador_created_by_user_id_user_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["created_by_user_id"],
"created_by_user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1639,12 +1555,8 @@
"name": "pagadores_user_id_user_id_fk", "name": "pagadores_user_id_user_id_fk",
"tableFrom": "pagadores", "tableFrom": "pagadores",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1733,12 +1645,8 @@
"name": "insights_salvos_user_id_user_id_fk", "name": "insights_salvos_user_id_user_id_fk",
"tableFrom": "insights_salvos", "tableFrom": "insights_salvos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1808,12 +1716,8 @@
"name": "session_userId_user_id_fk", "name": "session_userId_user_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1823,9 +1727,7 @@
"session_token_unique": { "session_token_unique": {
"name": "session_token_unique", "name": "session_token_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["token"]
"token"
]
} }
}, },
"policies": {}, "policies": {},
@@ -2228,12 +2130,8 @@
"name": "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk", "name": "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "antecipacoes_parcelas", "tableTo": "antecipacoes_parcelas",
"columnsFrom": [ "columnsFrom": ["antecipacao_id"],
"antecipacao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -2241,12 +2139,8 @@
"name": "lancamentos_user_id_user_id_fk", "name": "lancamentos_user_id_user_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -2254,12 +2148,8 @@
"name": "lancamentos_cartao_id_cartoes_id_fk", "name": "lancamentos_cartao_id_cartoes_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "cartoes", "tableTo": "cartoes",
"columnsFrom": [ "columnsFrom": ["cartao_id"],
"cartao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2267,12 +2157,8 @@
"name": "lancamentos_conta_id_contas_id_fk", "name": "lancamentos_conta_id_contas_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "contas", "tableTo": "contas",
"columnsFrom": [ "columnsFrom": ["conta_id"],
"conta_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2280,12 +2166,8 @@
"name": "lancamentos_categoria_id_categorias_id_fk", "name": "lancamentos_categoria_id_categorias_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2293,12 +2175,8 @@
"name": "lancamentos_pagador_id_pagadores_id_fk", "name": "lancamentos_pagador_id_pagadores_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -2363,9 +2241,7 @@
"user_email_unique": { "user_email_unique": {
"name": "user_email_unique", "name": "user_email_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["email"]
"email"
]
} }
}, },
"policies": {}, "policies": {},
@@ -2443,12 +2319,8 @@
"name": "preferencias_usuario_user_id_user_id_fk", "name": "preferencias_usuario_user_id_user_id_fk",
"tableFrom": "preferencias_usuario", "tableFrom": "preferencias_usuario",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -2458,9 +2330,7 @@
"preferencias_usuario_user_id_unique": { "preferencias_usuario_user_id_unique": {
"name": "preferencias_usuario_user_id_unique", "name": "preferencias_usuario_user_id_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["user_id"]
"user_id"
]
} }
}, },
"policies": {}, "policies": {},

View File

@@ -93,12 +93,8 @@
"name": "account_userId_user_id_fk", "name": "account_userId_user_id_fk",
"tableFrom": "account", "tableFrom": "account",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -213,12 +209,8 @@
"name": "tokens_api_user_id_user_id_fk", "name": "tokens_api_user_id_user_id_fk",
"tableFrom": "tokens_api", "tableFrom": "tokens_api",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -327,12 +319,8 @@
"name": "orcamentos_user_id_user_id_fk", "name": "orcamentos_user_id_user_id_fk",
"tableFrom": "orcamentos", "tableFrom": "orcamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -340,12 +328,8 @@
"name": "orcamentos_categoria_id_categorias_id_fk", "name": "orcamentos_categoria_id_categorias_id_fk",
"tableFrom": "orcamentos", "tableFrom": "orcamentos",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -463,12 +447,8 @@
"name": "cartoes_user_id_user_id_fk", "name": "cartoes_user_id_user_id_fk",
"tableFrom": "cartoes", "tableFrom": "cartoes",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -476,12 +456,8 @@
"name": "cartoes_conta_id_contas_id_fk", "name": "cartoes_conta_id_contas_id_fk",
"tableFrom": "cartoes", "tableFrom": "cartoes",
"tableTo": "contas", "tableTo": "contas",
"columnsFrom": [ "columnsFrom": ["conta_id"],
"conta_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -563,12 +539,8 @@
"name": "categorias_user_id_user_id_fk", "name": "categorias_user_id_user_id_fk",
"tableFrom": "categorias", "tableFrom": "categorias",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -683,12 +655,8 @@
"name": "contas_user_id_user_id_fk", "name": "contas_user_id_user_id_fk",
"tableFrom": "contas", "tableFrom": "contas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -847,12 +815,8 @@
"name": "pre_lancamentos_user_id_user_id_fk", "name": "pre_lancamentos_user_id_user_id_fk",
"tableFrom": "pre_lancamentos", "tableFrom": "pre_lancamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -860,12 +824,8 @@
"name": "pre_lancamentos_lancamento_id_lancamentos_id_fk", "name": "pre_lancamentos_lancamento_id_lancamentos_id_fk",
"tableFrom": "pre_lancamentos", "tableFrom": "pre_lancamentos",
"tableTo": "lancamentos", "tableTo": "lancamentos",
"columnsFrom": [ "columnsFrom": ["lancamento_id"],
"lancamento_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1005,12 +965,8 @@
"name": "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk", "name": "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "lancamentos", "tableTo": "lancamentos",
"columnsFrom": [ "columnsFrom": ["lancamento_id"],
"lancamento_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1018,12 +974,8 @@
"name": "antecipacoes_parcelas_pagador_id_pagadores_id_fk", "name": "antecipacoes_parcelas_pagador_id_pagadores_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1031,12 +983,8 @@
"name": "antecipacoes_parcelas_categoria_id_categorias_id_fk", "name": "antecipacoes_parcelas_categoria_id_categorias_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1044,12 +992,8 @@
"name": "antecipacoes_parcelas_user_id_user_id_fk", "name": "antecipacoes_parcelas_user_id_user_id_fk",
"tableFrom": "antecipacoes_parcelas", "tableFrom": "antecipacoes_parcelas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1179,12 +1123,8 @@
"name": "faturas_user_id_user_id_fk", "name": "faturas_user_id_user_id_fk",
"tableFrom": "faturas", "tableFrom": "faturas",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1192,12 +1132,8 @@
"name": "faturas_cartao_id_cartoes_id_fk", "name": "faturas_cartao_id_cartoes_id_fk",
"tableFrom": "faturas", "tableFrom": "faturas",
"tableTo": "cartoes", "tableTo": "cartoes",
"columnsFrom": [ "columnsFrom": ["cartao_id"],
"cartao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -1271,12 +1207,8 @@
"name": "anotacoes_user_id_user_id_fk", "name": "anotacoes_user_id_user_id_fk",
"tableFrom": "anotacoes", "tableFrom": "anotacoes",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1364,12 +1296,8 @@
"name": "passkey_userId_user_id_fk", "name": "passkey_userId_user_id_fk",
"tableFrom": "passkey", "tableFrom": "passkey",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1452,12 +1380,8 @@
"name": "compartilhamentos_pagador_pagador_id_pagadores_id_fk", "name": "compartilhamentos_pagador_pagador_id_pagadores_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1465,12 +1389,8 @@
"name": "compartilhamentos_pagador_shared_with_user_id_user_id_fk", "name": "compartilhamentos_pagador_shared_with_user_id_user_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["shared_with_user_id"],
"shared_with_user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -1478,12 +1398,8 @@
"name": "compartilhamentos_pagador_created_by_user_id_user_id_fk", "name": "compartilhamentos_pagador_created_by_user_id_user_id_fk",
"tableFrom": "compartilhamentos_pagador", "tableFrom": "compartilhamentos_pagador",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["created_by_user_id"],
"created_by_user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1639,12 +1555,8 @@
"name": "pagadores_user_id_user_id_fk", "name": "pagadores_user_id_user_id_fk",
"tableFrom": "pagadores", "tableFrom": "pagadores",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1733,12 +1645,8 @@
"name": "insights_salvos_user_id_user_id_fk", "name": "insights_salvos_user_id_user_id_fk",
"tableFrom": "insights_salvos", "tableFrom": "insights_salvos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1808,12 +1716,8 @@
"name": "session_userId_user_id_fk", "name": "session_userId_user_id_fk",
"tableFrom": "session", "tableFrom": "session",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["userId"],
"userId" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -1823,9 +1727,7 @@
"session_token_unique": { "session_token_unique": {
"name": "session_token_unique", "name": "session_token_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["token"]
"token"
]
} }
}, },
"policies": {}, "policies": {},
@@ -2228,12 +2130,8 @@
"name": "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk", "name": "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "antecipacoes_parcelas", "tableTo": "antecipacoes_parcelas",
"columnsFrom": [ "columnsFrom": ["antecipacao_id"],
"antecipacao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -2241,12 +2139,8 @@
"name": "lancamentos_user_id_user_id_fk", "name": "lancamentos_user_id_user_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -2254,12 +2148,8 @@
"name": "lancamentos_cartao_id_cartoes_id_fk", "name": "lancamentos_cartao_id_cartoes_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "cartoes", "tableTo": "cartoes",
"columnsFrom": [ "columnsFrom": ["cartao_id"],
"cartao_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2267,12 +2157,8 @@
"name": "lancamentos_conta_id_contas_id_fk", "name": "lancamentos_conta_id_contas_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "contas", "tableTo": "contas",
"columnsFrom": [ "columnsFrom": ["conta_id"],
"conta_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2280,12 +2166,8 @@
"name": "lancamentos_categoria_id_categorias_id_fk", "name": "lancamentos_categoria_id_categorias_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["categoria_id"],
"categoria_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
}, },
@@ -2293,12 +2175,8 @@
"name": "lancamentos_pagador_id_pagadores_id_fk", "name": "lancamentos_pagador_id_pagadores_id_fk",
"tableFrom": "lancamentos", "tableFrom": "lancamentos",
"tableTo": "pagadores", "tableTo": "pagadores",
"columnsFrom": [ "columnsFrom": ["pagador_id"],
"pagador_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "cascade" "onUpdate": "cascade"
} }
@@ -2363,9 +2241,7 @@
"user_email_unique": { "user_email_unique": {
"name": "user_email_unique", "name": "user_email_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["email"]
"email"
]
} }
}, },
"policies": {}, "policies": {},
@@ -2443,12 +2319,8 @@
"name": "preferencias_usuario_user_id_user_id_fk", "name": "preferencias_usuario_user_id_user_id_fk",
"tableFrom": "preferencias_usuario", "tableFrom": "preferencias_usuario",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -2458,9 +2330,7 @@
"preferencias_usuario_user_id_unique": { "preferencias_usuario_user_id_unique": {
"name": "preferencias_usuario_user_id_unique", "name": "preferencias_usuario_user_id_unique",
"nullsNotDistinct": false, "nullsNotDistinct": false,
"columns": [ "columns": ["user_id"]
"user_id"
]
} }
}, },
"policies": {}, "policies": {},
@@ -2552,12 +2422,8 @@
"name": "import_category_mappings_user_id_user_id_fk", "name": "import_category_mappings_user_id_user_id_fk",
"tableFrom": "import_category_mappings", "tableFrom": "import_category_mappings",
"tableTo": "user", "tableTo": "user",
"columnsFrom": [ "columnsFrom": ["user_id"],
"user_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
}, },
@@ -2565,12 +2431,8 @@
"name": "import_category_mappings_category_id_categorias_id_fk", "name": "import_category_mappings_category_id_categorias_id_fk",
"tableFrom": "import_category_mappings", "tableFrom": "import_category_mappings",
"tableTo": "categorias", "tableTo": "categorias",
"columnsFrom": [ "columnsFrom": ["category_id"],
"category_id" "columnsTo": ["id"],
],
"columnsTo": [
"id"
],
"onDelete": "cascade", "onDelete": "cascade",
"onUpdate": "no action" "onUpdate": "no action"
} }
@@ -2578,10 +2440,7 @@
"compositePrimaryKeys": { "compositePrimaryKeys": {
"import_category_mappings_user_id_description_key_pk": { "import_category_mappings_user_id_description_key_pk": {
"name": "import_category_mappings_user_id_description_key_pk", "name": "import_category_mappings_user_id_description_key_pk",
"columns": [ "columns": ["user_id", "description_key"]
"user_id",
"description_key"
]
} }
}, },
"uniqueConstraints": {}, "uniqueConstraints": {},

View File

@@ -1,6 +1,6 @@
{ {
"name": "openmonetis", "name": "openmonetis",
"version": "2.0.0", "version": "2.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
@@ -28,9 +28,9 @@
"backup": "bash scripts/backup.sh" "backup": "bash scripts/backup.sh"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^3.0.62", "@ai-sdk/anthropic": "^3.0.63",
"@ai-sdk/google": "^3.0.51", "@ai-sdk/google": "^3.0.52",
"@ai-sdk/openai": "^3.0.46", "@ai-sdk/openai": "^3.0.47",
"@better-auth/passkey": "^1.5.5", "@better-auth/passkey": "^1.5.5",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
@@ -60,7 +60,7 @@
"@tanstack/react-virtual": "^3.13.23", "@tanstack/react-virtual": "^3.13.23",
"@vercel/analytics": "^2.0.1", "@vercel/analytics": "^2.0.1",
"@vercel/speed-insights": "^2.0.0", "@vercel/speed-insights": "^2.0.0",
"ai": "^6.0.127", "ai": "^6.0.134",
"better-auth": "1.5.5", "better-auth": "1.5.5",
"canvas-confetti": "^1.9.4", "canvas-confetti": "^1.9.4",
"class-variance-authority": "0.7.1", "class-variance-authority": "0.7.1",
@@ -90,12 +90,12 @@
"@tailwindcss/postcss": "4.2.2", "@tailwindcss/postcss": "4.2.2",
"@types/canvas-confetti": "^1.9.0", "@types/canvas-confetti": "^1.9.0",
"@types/node": "25.5.0", "@types/node": "25.5.0",
"@types/pg": "^8.18.0", "@types/pg": "^8.20.0",
"@types/react": "19.2.14", "@types/react": "19.2.14",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.2.3",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"drizzle-kit": "0.31.10", "drizzle-kit": "0.31.10",
"tailwindcss": "4.2.1", "tailwindcss": "4.2.2",
"tsx": "4.21.0", "tsx": "4.21.0",
"typescript": "5.9.3" "typescript": "5.9.3"
} }

113
pnpm-lock.yaml generated
View File

@@ -9,17 +9,17 @@ importers:
.: .:
dependencies: dependencies:
'@ai-sdk/anthropic': '@ai-sdk/anthropic':
specifier: ^3.0.62 specifier: ^3.0.63
version: 3.0.62(zod@4.3.6) version: 3.0.63(zod@4.3.6)
'@ai-sdk/google': '@ai-sdk/google':
specifier: ^3.0.51 specifier: ^3.0.52
version: 3.0.51(zod@4.3.6) version: 3.0.52(zod@4.3.6)
'@ai-sdk/openai': '@ai-sdk/openai':
specifier: ^3.0.46 specifier: ^3.0.47
version: 3.0.46(zod@4.3.6) version: 3.0.47(zod@4.3.6)
'@better-auth/passkey': '@better-auth/passkey':
specifier: ^1.5.5 specifier: ^1.5.5
version: 1.5.5(933ec2e58dee4f4ee115209e94e4bc6e) version: 1.5.5(67982ffe784494dfa72893bff94217f8)
'@dnd-kit/core': '@dnd-kit/core':
specifier: ^6.3.1 specifier: ^6.3.1
version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -31,7 +31,7 @@ importers:
version: 3.2.2(react@19.2.4) version: 3.2.2(react@19.2.4)
'@openrouter/ai-sdk-provider': '@openrouter/ai-sdk-provider':
specifier: ^2.3.3 specifier: ^2.3.3
version: 2.3.3(ai@6.0.129(zod@4.3.6))(zod@4.3.6) version: 2.3.3(ai@6.0.134(zod@4.3.6))(zod@4.3.6)
'@radix-ui/react-alert-dialog': '@radix-ui/react-alert-dialog':
specifier: 1.1.15 specifier: 1.1.15
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -105,11 +105,11 @@ importers:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) version: 2.0.0(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
ai: ai:
specifier: ^6.0.127 specifier: ^6.0.134
version: 6.0.129(zod@4.3.6) version: 6.0.134(zod@4.3.6)
better-auth: better-auth:
specifier: 1.5.5 specifier: 1.5.5
version: 1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
canvas-confetti: canvas-confetti:
specifier: ^1.9.4 specifier: ^1.9.4
version: 1.9.4 version: 1.9.4
@@ -127,7 +127,7 @@ importers:
version: 4.1.0 version: 4.1.0
drizzle-orm: drizzle-orm:
specifier: 0.45.1 specifier: 0.45.1
version: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) version: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))
jspdf: jspdf:
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1 version: 4.2.1
@@ -190,8 +190,8 @@ importers:
specifier: 25.5.0 specifier: 25.5.0
version: 25.5.0 version: 25.5.0
'@types/pg': '@types/pg':
specifier: ^8.18.0 specifier: ^8.20.0
version: 8.18.0 version: 8.20.0
'@types/react': '@types/react':
specifier: 19.2.14 specifier: 19.2.14
version: 19.2.14 version: 19.2.14
@@ -205,8 +205,8 @@ importers:
specifier: 0.31.10 specifier: 0.31.10
version: 0.31.10 version: 0.31.10
tailwindcss: tailwindcss:
specifier: 4.2.1 specifier: 4.2.2
version: 4.2.1 version: 4.2.2
tsx: tsx:
specifier: 4.21.0 specifier: 4.21.0
version: 4.21.0 version: 4.21.0
@@ -216,32 +216,32 @@ importers:
packages: packages:
'@ai-sdk/anthropic@3.0.62': '@ai-sdk/anthropic@3.0.63':
resolution: {integrity: sha512-CkShXR8tmNO7QQnvpKbSMe2Vr1zUUcpqlp69iR+DYrbHm+tDJO9u6zZsjEHjcoRU9/e9z++p0W6NiuLC3aZ4Bg==} resolution: {integrity: sha512-SiLosFr0FfKfrNpAAj8mD/i3S5YBB/z5orb1DH3pN1yATuBNjjPMLnRE4P3Dn7Y5cQsro0uzw5g5117hkShWoQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
'@ai-sdk/gateway@3.0.75': '@ai-sdk/gateway@3.0.77':
resolution: {integrity: sha512-7Hwa0VdH+l85NFS7zqZhRRaiwZMStDxEwUoTPxPNEH6V0Vgw9wi9OGopIsYdywmfSOPfSAsPL8XXPAuaSLGchw==} resolution: {integrity: sha512-UdwIG2H2YMuntJQ5L+EmED5XiwnlvDT3HOmKfVFxR4Nq/RSLFA/HcchhwfNXHZ5UJjyuL2VO0huLbWSZ9ijemQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
'@ai-sdk/google@3.0.51': '@ai-sdk/google@3.0.52':
resolution: {integrity: sha512-S5pG/iRt+E12a4TSnquBFnkHkbS+rcAJ2lRzds59vdnVqTsZGGIncaLefpGmq/MZNfbSo6JIO60duoZIpZXOqg==} resolution: {integrity: sha512-HiFB4VlHnv55k9xIbgQW9tHw5OsLXzbAghnDUqrnk/S94QpQuyrDwLSDsk/tUkxJeT00B+wvhL1y6/SARdLeXw==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
'@ai-sdk/openai@3.0.46': '@ai-sdk/openai@3.0.47':
resolution: {integrity: sha512-8grBb4sAMU0MAC6uOOD/wP/+SyX/3MMS/Lf+ToGgeUzoF9oWU9dWBLkvgjXpn7Ro81bPDeycW2GCCT63V/Vnvg==} resolution: {integrity: sha512-bRsb2sDN5u+pKO3Kdr0flpxtL+cPwQ2uCo/pVyzIbj2I4AkKAokJHhw5JWLVOeEwdlYzWfmv+hzaiGarzUcTFQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
'@ai-sdk/provider-utils@4.0.20': '@ai-sdk/provider-utils@4.0.21':
resolution: {integrity: sha512-gpUIj9uDhIGbuo9afKEgQ074BWmhvK4THJAAeBjRnroTy2yQYo6rbtGD7pQDMZM8ouXPYmT/SCdkWVJ0KcpX8A==} resolution: {integrity: sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
@@ -2240,8 +2240,8 @@ packages:
'@types/pako@2.0.4': '@types/pako@2.0.4':
resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
'@types/pg@8.18.0': '@types/pg@8.20.0':
resolution: {integrity: sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==} resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
'@types/raf@3.4.3': '@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
@@ -2329,8 +2329,8 @@ packages:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
ai@6.0.129: ai@6.0.134:
resolution: {integrity: sha512-5nGckqbzwUBZD7wV9jsA8qaoYRwGpU9LVMtXD+ZrxSi2H6QNjpbrhsuuEBKS9xcMYevCviVNoFzpmSUWzn45Hw==} resolution: {integrity: sha512-YalNEaavld/kE444gOcsMKXdVVRGEe0SK77fAFcWYcqLg+a7xKnEet8bdfrEAJTfnMjj01rhgrIL10903w1a5Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
zod: ^3.25.76 || ^4.1.8 zod: ^3.25.76 || ^4.1.8
@@ -3380,9 +3380,6 @@ packages:
tailwind-merge@3.5.0: tailwind-merge@3.5.0:
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
tailwindcss@4.2.1:
resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==}
tailwindcss@4.2.2: tailwindcss@4.2.2:
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
@@ -3514,32 +3511,32 @@ packages:
snapshots: snapshots:
'@ai-sdk/anthropic@3.0.62(zod@4.3.6)': '@ai-sdk/anthropic@3.0.63(zod@4.3.6)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.20(zod@4.3.6) '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6)
zod: 4.3.6 zod: 4.3.6
'@ai-sdk/gateway@3.0.75(zod@4.3.6)': '@ai-sdk/gateway@3.0.77(zod@4.3.6)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.20(zod@4.3.6) '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6)
'@vercel/oidc': 3.1.0 '@vercel/oidc': 3.1.0
zod: 4.3.6 zod: 4.3.6
'@ai-sdk/google@3.0.51(zod@4.3.6)': '@ai-sdk/google@3.0.52(zod@4.3.6)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.20(zod@4.3.6) '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6)
zod: 4.3.6 zod: 4.3.6
'@ai-sdk/openai@3.0.46(zod@4.3.6)': '@ai-sdk/openai@3.0.47(zod@4.3.6)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.20(zod@4.3.6) '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6)
zod: 4.3.6 zod: 4.3.6
'@ai-sdk/provider-utils@4.0.20(zod@4.3.6)': '@ai-sdk/provider-utils@4.0.21(zod@4.3.6)':
dependencies: dependencies:
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
@@ -3577,12 +3574,12 @@ snapshots:
nanostores: 1.1.1 nanostores: 1.1.1
zod: 4.3.6 zod: 4.3.6
'@better-auth/drizzle-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))': '@better-auth/drizzle-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))':
dependencies: dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1 '@better-auth/utils': 0.3.1
optionalDependencies: optionalDependencies:
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))
'@better-auth/kysely-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)': '@better-auth/kysely-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)':
dependencies: dependencies:
@@ -3601,14 +3598,14 @@ snapshots:
'@better-auth/utils': 0.3.1 '@better-auth/utils': 0.3.1
mongodb: 7.1.0 mongodb: 7.1.0
'@better-auth/passkey@1.5.5(933ec2e58dee4f4ee115209e94e4bc6e)': '@better-auth/passkey@1.5.5(67982ffe784494dfa72893bff94217f8)':
dependencies: dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1 '@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21 '@better-fetch/fetch': 1.1.21
'@simplewebauthn/browser': 13.2.2 '@simplewebauthn/browser': 13.2.2
'@simplewebauthn/server': 13.2.3 '@simplewebauthn/server': 13.2.3
better-auth: 1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) better-auth: 1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
better-call: 1.3.2(zod@4.3.6) better-call: 1.3.2(zod@4.3.6)
nanostores: 1.1.1 nanostores: 1.1.1
zod: 4.3.6 zod: 4.3.6
@@ -4146,9 +4143,9 @@ snapshots:
'@noble/hashes@2.0.1': {} '@noble/hashes@2.0.1': {}
'@openrouter/ai-sdk-provider@2.3.3(ai@6.0.129(zod@4.3.6))(zod@4.3.6)': '@openrouter/ai-sdk-provider@2.3.3(ai@6.0.134(zod@4.3.6))(zod@4.3.6)':
dependencies: dependencies:
ai: 6.0.129(zod@4.3.6) ai: 6.0.134(zod@4.3.6)
zod: 4.3.6 zod: 4.3.6
'@opentelemetry/api@1.9.0': {} '@opentelemetry/api@1.9.0': {}
@@ -5305,7 +5302,7 @@ snapshots:
'@types/pako@2.0.4': {} '@types/pako@2.0.4': {}
'@types/pg@8.18.0': '@types/pg@8.20.0':
dependencies: dependencies:
'@types/node': 25.5.0 '@types/node': 25.5.0
pg-protocol: 1.13.0 pg-protocol: 1.13.0
@@ -5347,11 +5344,11 @@ snapshots:
adler-32@1.3.1: {} adler-32@1.3.1: {}
ai@6.0.129(zod@4.3.6): ai@6.0.134(zod@4.3.6):
dependencies: dependencies:
'@ai-sdk/gateway': 3.0.75(zod@4.3.6) '@ai-sdk/gateway': 3.0.77(zod@4.3.6)
'@ai-sdk/provider': 3.0.8 '@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.20(zod@4.3.6) '@ai-sdk/provider-utils': 4.0.21(zod@4.3.6)
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
zod: 4.3.6 zod: 4.3.6
@@ -5378,10 +5375,10 @@ snapshots:
baseline-browser-mapping@2.10.0: {} baseline-browser-mapping@2.10.0: {}
better-auth@1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): better-auth@1.5.5(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.1.0)(mysql2@3.15.3)(next@16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.20.0)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1) '@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))) '@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))
'@better-auth/kysely-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11) '@better-auth/kysely-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
'@better-auth/memory-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1) '@better-auth/memory-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
'@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0) '@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)
@@ -5400,7 +5397,7 @@ snapshots:
optionalDependencies: optionalDependencies:
'@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) '@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)
drizzle-kit: 0.31.10 drizzle-kit: 0.31.10
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))
mongodb: 7.1.0 mongodb: 7.1.0
mysql2: 3.15.3 mysql2: 3.15.3
next: 16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next: 16.1.7(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -5607,12 +5604,12 @@ snapshots:
esbuild: 0.25.12 esbuild: 0.25.12
tsx: 4.21.0 tsx: 4.21.0
drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.18.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)): drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.20.0)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)):
optionalDependencies: optionalDependencies:
'@electric-sql/pglite': 0.3.15 '@electric-sql/pglite': 0.3.15
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) '@prisma/client': 7.4.2(prisma@7.4.2(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)
'@types/pg': 8.18.0 '@types/pg': 8.20.0
kysely: 0.28.11 kysely: 0.28.11
mysql2: 3.15.3 mysql2: 3.15.3
pg: 8.20.0 pg: 8.20.0
@@ -6397,8 +6394,6 @@ snapshots:
tailwind-merge@3.5.0: {} tailwind-merge@3.5.0: {}
tailwindcss@4.2.1: {}
tailwindcss@4.2.2: {} tailwindcss@4.2.2: {}
tapable@2.3.0: {} tapable@2.3.0: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -1,6 +1,7 @@
import { InboxPage } from "@/features/inbox/components/inbox-page"; import { InboxPage } from "@/features/inbox/components/inbox-page";
import { import {
type ResolvedInboxSearchParams, type ResolvedInboxSearchParams,
resolveInboxApp,
resolveInboxPagination, resolveInboxPagination,
resolveInboxStatus, resolveInboxStatus,
} from "@/features/inbox/page-helpers"; } from "@/features/inbox/page-helpers";
@@ -8,6 +9,7 @@ import {
fetchAppLogoMap, fetchAppLogoMap,
fetchInboxDialogData, fetchInboxDialogData,
fetchInboxItemsPage, fetchInboxItemsPage,
fetchInboxSourceApps,
fetchInboxStatusCounts, fetchInboxStatusCounts,
} from "@/features/inbox/queries"; } from "@/features/inbox/queries";
import { getUserId } from "@/shared/lib/auth/server"; import { getUserId } from "@/shared/lib/auth/server";
@@ -32,21 +34,31 @@ export default async function Page({ searchParams }: PageProps) {
const userId = await getUserId(); const userId = await getUserId();
const resolvedSearchParams = searchParams ? await searchParams : undefined; const resolvedSearchParams = searchParams ? await searchParams : undefined;
const activeStatus = resolveInboxStatus(resolvedSearchParams); const activeStatus = resolveInboxStatus(resolvedSearchParams);
const activeApp = resolveInboxApp(resolvedSearchParams);
const paginationInput = resolveInboxPagination(resolvedSearchParams); const paginationInput = resolveInboxPagination(resolvedSearchParams);
const [itemsPage, counts, dialogData, appLogoMap] = await Promise.all([ const [itemsPage, counts, sourceApps, dialogData, appLogoMap] =
fetchInboxItemsPage(userId, activeStatus, paginationInput), await Promise.all([
fetchInboxItemsPage(userId, activeStatus, {
...paginationInput,
sourceApp: activeApp,
}),
fetchInboxStatusCounts(userId), fetchInboxStatusCounts(userId),
fetchInboxSourceApps(userId, activeStatus).catch(() => [] as string[]),
activeStatus === "pending" activeStatus === "pending"
? fetchInboxDialogData(userId) ? fetchInboxDialogData(userId)
: Promise.resolve(EMPTY_DIALOG_DATA), : Promise.resolve(EMPTY_DIALOG_DATA),
fetchAppLogoMap(userId), fetchAppLogoMap(userId),
]); ]);
const normalizedSourceApps = Array.isArray(sourceApps) ? sourceApps : [];
return ( return (
<main className="flex flex-col items-start gap-6"> <main className="flex flex-col items-start gap-6">
<InboxPage <InboxPage
activeStatus={activeStatus} activeStatus={activeStatus}
activeApp={activeApp}
sourceApps={normalizedSourceApps}
items={itemsPage.items} items={itemsPage.items}
counts={counts} counts={counts}
pagination={itemsPage.pagination} pagination={itemsPage.pagination}

View File

@@ -1,14 +1,25 @@
import { ImportPage } from "@/features/transactions/components/import/import-page"; import { ImportPage } from "@/features/transactions/components/import/import-page";
import {
buildOptionSets,
buildSluggedFilters,
} from "@/features/transactions/page-helpers";
import { fetchTransactionFilterSources } from "@/features/transactions/queries"; import { fetchTransactionFilterSources } from "@/features/transactions/queries";
import { buildOptionSets, buildSluggedFilters } from "@/features/transactions/page-helpers";
import { getUserId } from "@/shared/lib/auth/server"; import { getUserId } from "@/shared/lib/auth/server";
export default async function Page() { export default async function Page() {
const userId = await getUserId(); const userId = await getUserId();
const filterSources = await fetchTransactionFilterSources(userId); const filterSources = await fetchTransactionFilterSources(userId);
const sluggedFilters = buildSluggedFilters(filterSources); const sluggedFilters = buildSluggedFilters(filterSources);
const { payerOptions, accountOptions, cardOptions, categoryOptions, defaultPayerId } = const {
buildOptionSets({ ...sluggedFilters, payerRows: filterSources.payerRows }); payerOptions,
accountOptions,
cardOptions,
categoryOptions,
defaultPayerId,
} = buildOptionSets({
...sluggedFilters,
payerRows: filterSources.payerRows,
});
return ( return (
<main className="flex flex-col gap-6"> <main className="flex flex-col gap-6">

View File

@@ -1,8 +1,7 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { version as APP_VERSION } from "@/package.json";
import { db } from "@/shared/lib/db"; import { db } from "@/shared/lib/db";
const APP_VERSION = "1.0.0";
/** /**
* Health check endpoint para Docker, monitoring e OpenMonetis Companion * Health check endpoint para Docker, monitoring e OpenMonetis Companion
* GET /api/health * GET /api/health

View File

@@ -48,8 +48,7 @@ const accountBaseSchema = z.object({
.string({ message: "Selecione um logo." }) .string({ message: "Selecione um logo." })
.trim() .trim()
.min(1, "Selecione um logo."), .min(1, "Selecione um logo."),
initialBalance: z initialBalance: z.union([
.union([
z.number(), z.number(),
z z
.string() .string()

View File

@@ -20,16 +20,15 @@ import {
CardTitle, CardTitle,
} from "@/shared/components/ui/card"; } from "@/shared/components/ui/card";
import { Checkbox } from "@/shared/components/ui/checkbox"; import { Checkbox } from "@/shared/components/ui/checkbox";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/shared/components/ui/tooltip";
import { resolveLogoSrc } from "@/shared/lib/logo"; import { resolveLogoSrc } from "@/shared/lib/logo";
import type { InboxItem } from "./types"; import type { InboxItem } from "./types";
// O timestamp vem do app Android em horário local mas salvo como UTC. const DEFAULT_INBOX_APP_LOGO = "/avatars/default_icon.png";
// Adicionamos o offset de Brasília para corrigir o cálculo de "há X tempo".
const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000;
function adjustToBrasilia(date: Date): Date {
return new Date(date.getTime() + BRASILIA_OFFSET_MS);
}
function findMatchingLogo( function findMatchingLogo(
sourceAppName: string | null, sourceAppName: string | null,
@@ -78,17 +77,19 @@ export function InboxCard({
const matchedLogo = appLogoMap const matchedLogo = appLogoMap
? findMatchingLogo(item.sourceAppName, appLogoMap) ? findMatchingLogo(item.sourceAppName, appLogoMap)
: null; : null;
const displayLogo = matchedLogo ?? DEFAULT_INBOX_APP_LOGO;
const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null; const amount = item.parsedAmount ? parseFloat(item.parsedAmount) : null;
const rawDate = new Date(item.notificationTimestamp); const createdAtDate = new Date(item.createdAt);
const notificationDate = adjustToBrasilia(rawDate);
const timeAgo = formatDistanceToNow(notificationDate, { const timeAgo = formatDistanceToNow(createdAtDate, {
addSuffix: true, addSuffix: true,
locale: ptBR, locale: ptBR,
}); });
const fullDate = format(createdAtDate, "PPpp", { locale: ptBR });
const statusDate = const statusDate =
item.status === "processed" item.status === "processed"
? item.processedAt ? item.processedAt
@@ -107,21 +108,32 @@ export function InboxCard({
<CardHeader className="pt-4"> <CardHeader className="pt-4">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<CardTitle className="flex min-w-0 items-center gap-1.5 text-sm"> <CardTitle className="flex min-w-0 items-center gap-1.5 text-sm">
{matchedLogo && ( {onSelectToggle && (
<Image <Checkbox
src={matchedLogo} checked={!!selected}
alt="" onCheckedChange={() => onSelectToggle(item.id)}
width={24} aria-label="Selecionar item"
height={24} className="shrink-0"
className="shrink-0 rounded-full"
/> />
)} )}
<Image
src={displayLogo}
alt=""
width={32}
height={32}
className="shrink-0 rounded-full"
/>
<span className="truncate"> <span className="truncate">
{item.sourceAppName || item.sourceApp} {item.sourceAppName || item.sourceApp}
</span> </span>
<span className="shrink-0 text-xs font-normal text-muted-foreground"> <Tooltip>
<TooltipTrigger asChild>
<span className="shrink-0 cursor-default text-xs font-normal text-muted-foreground underline decoration-dotted underline-offset-2">
{timeAgo} {timeAgo}
</span> </span>
</TooltipTrigger>
<TooltipContent>{fullDate}</TooltipContent>
</Tooltip>
</CardTitle> </CardTitle>
{amount !== null && ( {amount !== null && (
<MoneyValues amount={amount} className="shrink-0 text-sm" /> <MoneyValues amount={amount} className="shrink-0 text-sm" />
@@ -174,13 +186,6 @@ export function InboxCard({
<RiDeleteBinLine className="size-4" /> <RiDeleteBinLine className="size-4" />
</Button> </Button>
)} )}
{onSelectToggle && (
<Checkbox
checked={!!selected}
onCheckedChange={() => onSelectToggle(item.id)}
aria-label="Selecionar item"
/>
)}
</div> </div>
</CardFooter> </CardFooter>
) : ( ) : (
@@ -213,13 +218,6 @@ export function InboxCard({
> >
<RiDeleteBinLine className="size-4" /> <RiDeleteBinLine className="size-4" />
</Button> </Button>
{onSelectToggle && (
<Checkbox
checked={!!selected}
onCheckedChange={() => onSelectToggle(item.id)}
aria-label="Selecionar item"
/>
)}
</CardFooter> </CardFooter>
)} )}
</Card> </Card>

View File

@@ -52,7 +52,14 @@ export function InboxDetailsDialog({
<div className="grid gap-2 text-sm"> <div className="grid gap-2 text-sm">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-muted-foreground">App</span> <span className="text-muted-foreground">App</span>
<div className="flex flex-col items-end gap-0.5">
<span>{item.sourceAppName || item.sourceApp}</span> <span>{item.sourceAppName || item.sourceApp}</span>
{item.sourceAppName && (
<span className="font-mono text-xs text-muted-foreground">
{item.sourceApp}
</span>
)}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -109,6 +116,11 @@ export function InboxDetailsDialog({
</div> </div>
<DialogFooter> <DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Fechar
</Button>
</DialogClose>
{isPending && onProcess && ( {isPending && onProcess && (
<Button <Button
type="button" type="button"
@@ -120,11 +132,6 @@ export function InboxDetailsDialog({
Processar Processar
</Button> </Button>
)} )}
<DialogClose asChild>
<Button type="button" variant="outline">
Fechar
</Button>
</DialogClose>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -6,8 +6,12 @@ import {
RiArrowRightDoubleLine, RiArrowRightDoubleLine,
RiArrowRightSLine, RiArrowRightSLine,
RiAtLine, RiAtLine,
RiCalendarEventLine,
RiDeleteBinLine, RiDeleteBinLine,
} from "@remixicon/react"; } from "@remixicon/react";
import { format } from "date-fns";
import { ptBR } from "date-fns/locale";
import Image from "next/image";
import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState, useTransition } from "react"; import { useEffect, useMemo, useState, useTransition } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -42,6 +46,7 @@ import {
TabsList, TabsList,
TabsTrigger, TabsTrigger,
} from "@/shared/components/ui/tabs"; } from "@/shared/components/ui/tabs";
import { resolveLogoSrc } from "@/shared/lib/logo";
import { InboxCard } from "./inbox-card"; import { InboxCard } from "./inbox-card";
import { InboxDetailsDialog } from "./inbox-details-dialog"; import { InboxDetailsDialog } from "./inbox-details-dialog";
import type { import type {
@@ -52,8 +57,71 @@ import type {
SelectOption, SelectOption,
} from "./types"; } from "./types";
const BRASILIA_OFFSET_MS = 3 * 60 * 60 * 1000;
const DEFAULT_INBOX_APP_LOGO = "/avatars/default_icon.png";
function getDateKey(date: Date): string {
const adjusted = new Date(date.getTime() + BRASILIA_OFFSET_MS);
return adjusted.toISOString().slice(0, 10);
}
function getGroupLabel(dateKey: string): string {
const now = new Date();
const todayKey = getDateKey(now);
const yesterdayKey = getDateKey(
new Date(now.getTime() - 24 * 60 * 60 * 1000),
);
if (dateKey === todayKey) return "Hoje";
if (dateKey === yesterdayKey) return "Ontem";
const [year, month, day] = dateKey.split("-").map(Number);
return format(new Date(year, month - 1, day), "d 'de' MMMM", {
locale: ptBR,
});
}
function groupItemsByDay(
items: InboxItem[],
): { label: string; items: InboxItem[] }[] {
const groups = new Map<string, InboxItem[]>();
for (const item of items) {
const key = getDateKey(new Date(item.notificationTimestamp));
const group = groups.get(key);
if (group) {
group.push(item);
} else {
groups.set(key, [item]);
}
}
const sortedKeys = [...groups.keys()].sort((a, b) => b.localeCompare(a));
return sortedKeys.map((key) => ({
label: getGroupLabel(key),
items: groups.get(key) ?? [],
}));
}
function findMatchingLogo(
sourceAppName: string | null,
appLogoMap: Record<string, string>,
): string | null {
if (!sourceAppName) return null;
const appName = sourceAppName.toLowerCase();
if (appLogoMap[appName]) return resolveLogoSrc(appLogoMap[appName]);
for (const [name, logo] of Object.entries(appLogoMap)) {
if (name.includes(appName) || appName.includes(name)) {
return resolveLogoSrc(logo);
}
}
return null;
}
interface InboxPageProps { interface InboxPageProps {
activeStatus: InboxStatus; activeStatus: InboxStatus;
activeApp: string | null;
sourceApps: string[];
items: InboxItem[]; items: InboxItem[];
counts: InboxStatusCounts; counts: InboxStatusCounts;
pagination: InboxPaginationState; pagination: InboxPaginationState;
@@ -69,6 +137,8 @@ interface InboxPageProps {
export function InboxPage({ export function InboxPage({
activeStatus, activeStatus,
activeApp,
sourceApps = [],
items, items,
counts, counts,
pagination, pagination,
@@ -111,6 +181,38 @@ export function InboxPage({
const [selectionBulkStatus, setSelectionBulkStatus] = const [selectionBulkStatus, setSelectionBulkStatus] =
useState<InboxStatus>("pending"); useState<InboxStatus>("pending");
const normalizedSourceApps = useMemo(() => {
if (!Array.isArray(sourceApps)) {
return [];
}
const uniqueApps = new Set<string>();
for (const app of sourceApps) {
if (typeof app !== "string") {
continue;
}
const trimmedApp = app.trim();
if (!trimmedApp) {
continue;
}
uniqueApps.add(trimmedApp);
}
return [...uniqueApps].sort((left, right) =>
left.localeCompare(right, "pt-BR"),
);
}, [sourceApps]);
const appFilterOptions =
activeApp && !normalizedSourceApps.includes(activeApp)
? [activeApp, ...normalizedSourceApps]
: normalizedSourceApps;
const getAppLogo = (appName: string | null) =>
findMatchingLogo(appName, appLogoMap) ?? DEFAULT_INBOX_APP_LOGO;
const handleProcessOpenChange = (open: boolean) => { const handleProcessOpenChange = (open: boolean) => {
setProcessOpen(open); setProcessOpen(open);
if (!open) { if (!open) {
@@ -239,7 +341,6 @@ export function InboxPage({
setSelectedIds([]); setSelectedIds([]);
return; return;
} }
setSelectedIds(items.map((item) => item.id)); setSelectedIds(items.map((item) => item.id));
}; };
@@ -276,8 +377,42 @@ export function InboxPage({
}); });
}; };
const handleAppChange = (nextApp: string) => {
const nextParams = new URLSearchParams(searchParams.toString());
if (nextApp === "all") {
nextParams.delete("app");
} else {
nextParams.set("app", nextApp);
}
nextParams.delete("page");
startTransition(() => {
const target = nextParams.toString()
? `${pathname}?${nextParams.toString()}`
: pathname;
router.replace(target, { scroll: false });
});
};
const handleTabChange = (nextStatus: string) => { const handleTabChange = (nextStatus: string) => {
updateUrl(nextStatus as InboxStatus, 1, pagination.pageSize); const nextParams = new URLSearchParams(searchParams.toString());
nextParams.delete("app");
if (nextStatus === "pending") {
nextParams.delete("status");
} else {
nextParams.set("status", nextStatus);
}
nextParams.delete("page");
if (pagination.pageSize === INBOX_DEFAULT_PAGE_SIZE) {
nextParams.delete("pageSize");
} else {
nextParams.set("pageSize", pagination.pageSize.toString());
}
startTransition(() => {
const target = nextParams.toString()
? `${pathname}?${nextParams.toString()}`
: pathname;
router.replace(target, { scroll: false });
});
}; };
const handleSelectionBulkRequest = (status: InboxStatus) => { const handleSelectionBulkRequest = (status: InboxStatus) => {
@@ -401,16 +536,30 @@ export function InboxPage({
</Card> </Card>
); );
const renderGrid = (list: InboxItem[], readonly?: boolean) => const renderGroupedGrid = (list: InboxItem[], readonly?: boolean) => {
list.length === 0 ? ( if (list.length === 0) {
renderEmptyState( if (activeApp) {
return renderEmptyState("Nenhuma notificação deste app");
}
return renderEmptyState(
readonly readonly
? "Nenhuma notificação nesta aba" ? "Nenhuma notificação nesta aba"
: "Nenhum pré-lançamento pendente", : "Nenhum pré-lançamento pendente",
) );
) : ( }
const groups = groupItemsByDay(list);
return (
<div className="space-y-6">
{groups.map((group) => (
<div key={group.label}>
<div className="mb-3 flex items-center gap-1 text-muted-foreground">
<RiCalendarEventLine className="size-3.5 shrink-0" />
<p className="text-sm font-medium">{group.label}</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{list.map((item) => ( {group.items.map((item) => (
<InboxCard <InboxCard
key={item.id} key={item.id}
item={item} item={item}
@@ -420,13 +569,72 @@ export function InboxPage({
onDiscard={readonly ? undefined : handleDiscardRequest} onDiscard={readonly ? undefined : handleDiscardRequest}
onViewDetails={readonly ? undefined : handleDetailsRequest} onViewDetails={readonly ? undefined : handleDetailsRequest}
onDelete={readonly ? handleDeleteRequest : undefined} onDelete={readonly ? handleDeleteRequest : undefined}
onRestoreToPending={readonly ? handleRestoreRequest : undefined} onRestoreToPending={
readonly ? handleRestoreRequest : undefined
}
selected={selectedIds.includes(item.id)} selected={selectedIds.includes(item.id)}
onSelectToggle={toggleSelection} onSelectToggle={toggleSelection}
/> />
))} ))}
</div> </div>
</div>
))}
</div>
); );
};
const renderAppFilter = () => {
if (appFilterOptions.length === 0) {
return null;
}
return (
<Select value={activeApp ?? "all"} onValueChange={handleAppChange}>
<SelectTrigger className="w-[190px]">
<SelectValue>
<span className="flex min-w-0 items-center gap-2">
<Image
src={activeApp ? getAppLogo(activeApp) : DEFAULT_INBOX_APP_LOGO}
alt=""
width={20}
height={20}
className="shrink-0 rounded-full"
/>
<span className="truncate">{activeApp ?? "Todos"}</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
<span className="flex items-center gap-2">
<Image
src={DEFAULT_INBOX_APP_LOGO}
alt=""
width={20}
height={20}
className="shrink-0 rounded-full"
/>
<span>Todos</span>
</span>
</SelectItem>
{appFilterOptions.map((app) => (
<SelectItem key={app} value={app}>
<span className="flex min-w-0 items-center gap-2">
<Image
src={getAppLogo(app)}
alt=""
width={20}
height={20}
className="shrink-0 rounded-full"
/>
<span className="truncate">{app}</span>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
);
};
return ( return (
<> <>
@@ -463,9 +671,17 @@ export function InboxPage({
</TabsList> </TabsList>
<TabsContent value="pending" className="mt-4"> <TabsContent value="pending" className="mt-4">
{activeStatus === "pending" && items.length > 0 && ( {activeStatus === "pending" &&
<div className="mb-4 flex items-center justify-end gap-2"> (appFilterOptions.length > 0 || items.length > 0) && (
<Button variant="outline" size="sm" onClick={toggleSelectAll}> <div className="mb-4 flex flex-wrap items-center gap-2">
{renderAppFilter()}
{items.length > 0 ? (
<div className="ml-auto flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={toggleSelectAll}
>
{allSelected ? "Cancelar seleção" : "Selecionar página"} {allSelected ? "Cancelar seleção" : "Selecionar página"}
</Button> </Button>
{selectedIds.length > 0 && ( {selectedIds.length > 0 && (
@@ -479,13 +695,23 @@ export function InboxPage({
</Button> </Button>
)} )}
</div> </div>
) : null}
</div>
)} )}
{activeStatus === "pending" ? renderGrid(items, false) : null} {activeStatus === "pending" ? renderGroupedGrid(items, false) : null}
</TabsContent> </TabsContent>
<TabsContent value="processed" className="mt-4"> <TabsContent value="processed" className="mt-4">
{activeStatus === "processed" && items.length > 0 && ( {activeStatus === "processed" &&
<div className="mb-4 flex items-center justify-end gap-2"> (appFilterOptions.length > 0 || items.length > 0) && (
<Button variant="outline" size="sm" onClick={toggleSelectAll}> <div className="mb-4 flex flex-wrap items-center gap-2">
{renderAppFilter()}
{items.length > 0 ? (
<div className="ml-auto flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={toggleSelectAll}
>
{allSelected ? "Cancelar seleção" : "Selecionar página"} {allSelected ? "Cancelar seleção" : "Selecionar página"}
</Button> </Button>
{selectedIds.length > 0 && ( {selectedIds.length > 0 && (
@@ -507,13 +733,23 @@ export function InboxPage({
Limpar processados Limpar processados
</Button> </Button>
</div> </div>
) : null}
</div>
)} )}
{activeStatus === "processed" ? renderGrid(items, true) : null} {activeStatus === "processed" ? renderGroupedGrid(items, true) : null}
</TabsContent> </TabsContent>
<TabsContent value="discarded" className="mt-4"> <TabsContent value="discarded" className="mt-4">
{activeStatus === "discarded" && items.length > 0 && ( {activeStatus === "discarded" &&
<div className="mb-4 flex items-center justify-end gap-2"> (appFilterOptions.length > 0 || items.length > 0) && (
<Button variant="outline" size="sm" onClick={toggleSelectAll}> <div className="mb-4 flex flex-wrap items-center gap-2">
{renderAppFilter()}
{items.length > 0 ? (
<div className="ml-auto flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={toggleSelectAll}
>
{allSelected ? "Cancelar seleção" : "Selecionar página"} {allSelected ? "Cancelar seleção" : "Selecionar página"}
</Button> </Button>
{selectedIds.length > 0 && ( {selectedIds.length > 0 && (
@@ -535,8 +771,10 @@ export function InboxPage({
Limpar descartados Limpar descartados
</Button> </Button>
</div> </div>
) : null}
</div>
)} )}
{activeStatus === "discarded" ? renderGrid(items, true) : null} {activeStatus === "discarded" ? renderGroupedGrid(items, true) : null}
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@@ -31,6 +31,10 @@ export const resolveInboxStatus = (
: "pending"; : "pending";
}; };
export const resolveInboxApp = (
params: ResolvedInboxSearchParams,
): string | null => getSingleParam(params, "app");
export const resolveInboxPagination = ( export const resolveInboxPagination = (
params: ResolvedInboxSearchParams, params: ResolvedInboxSearchParams,
): Pick<InboxPaginationState, "page" | "pageSize"> => { ): Pick<InboxPaginationState, "page" | "pageSize"> => {

View File

@@ -39,18 +39,26 @@ export async function fetchInboxItemsPage(
{ {
page, page,
pageSize, pageSize,
sourceApp,
}: { }: {
page: number; page: number;
pageSize: number; pageSize: number;
sourceApp?: string | null;
}, },
): Promise<{ ): Promise<{
items: InboxItem[]; items: InboxItem[];
pagination: InboxPaginationState; pagination: InboxPaginationState;
}> { }> {
const where = and(
eq(inboxItems.userId, userId),
eq(inboxItems.status, status),
sourceApp ? eq(inboxItems.sourceAppName, sourceApp) : undefined,
);
const [countRow] = await db const [countRow] = await db
.select({ total: count() }) .select({ total: count() })
.from(inboxItems) .from(inboxItems)
.where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status))); .where(where);
const totalItems = Number(countRow?.total ?? 0); const totalItems = Number(countRow?.total ?? 0);
const totalPages = Math.max(Math.ceil(totalItems / pageSize), 1); const totalPages = Math.max(Math.ceil(totalItems / pageSize), 1);
@@ -60,7 +68,7 @@ export async function fetchInboxItemsPage(
const items = await db const items = await db
.select() .select()
.from(inboxItems) .from(inboxItems)
.where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status))) .where(where)
.orderBy(desc(inboxItems.notificationTimestamp), desc(inboxItems.createdAt)) .orderBy(desc(inboxItems.notificationTimestamp), desc(inboxItems.createdAt))
.limit(pageSize) .limit(pageSize)
.offset(offset); .offset(offset);
@@ -76,6 +84,22 @@ export async function fetchInboxItemsPage(
}; };
} }
export async function fetchInboxSourceApps(
userId: string,
status: InboxStatus,
): Promise<string[]> {
const rows = await db
.select({ name: inboxItems.sourceAppName })
.from(inboxItems)
.where(and(eq(inboxItems.userId, userId), eq(inboxItems.status, status)));
const seen = new Set<string>();
for (const row of rows) {
if (row.name) seen.add(row.name);
}
return [...seen].sort();
}
export async function fetchInboxStatusCounts( export async function fetchInboxStatusCounts(
userId: string, userId: string,
): Promise<InboxStatusCounts> { ): Promise<InboxStatusCounts> {

View File

@@ -6,7 +6,6 @@ import { normalizeDescriptionKey } from "@/features/transactions/lib/import-util
import { getUserId } from "@/shared/lib/auth/server"; import { getUserId } from "@/shared/lib/auth/server";
import { db } from "@/shared/lib/db"; import { db } from "@/shared/lib/db";
// Retorna um map de descriptionKey → categoryId para as descrições fornecidas // Retorna um map de descriptionKey → categoryId para as descrições fornecidas
export async function fetchCategoryMappings( export async function fetchCategoryMappings(
descriptions: string[], descriptions: string[],
@@ -53,7 +52,10 @@ export async function saveCategoryMappings(
.insert(importCategoryMappings) .insert(importCategoryMappings)
.values(toUpsert) .values(toUpsert)
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [importCategoryMappings.userId, importCategoryMappings.descriptionKey], target: [
importCategoryMappings.userId,
importCategoryMappings.descriptionKey,
],
set: { set: {
categoryId: sql`excluded.category_id`, categoryId: sql`excluded.category_id`,
updatedAt: sql`excluded.updated_at`, updatedAt: sql`excluded.updated_at`,

View File

@@ -29,7 +29,11 @@ const importSchema = z.object({
accountId: uuidSchema("FinancialAccount").nullable().optional(), accountId: uuidSchema("FinancialAccount").nullable().optional(),
cardId: uuidSchema("Cartão").nullable().optional(), cardId: uuidSchema("Cartão").nullable().optional(),
paymentMethod: z.string().min(1), paymentMethod: z.string().min(1),
invoicePeriod: z.string().regex(/^\d{4}-\d{2}$/, "Período inválido.").nullable().optional(), invoicePeriod: z
.string()
.regex(/^\d{4}-\d{2}$/, "Período inválido.")
.nullable()
.optional(),
}); });
export type ImportRow = z.infer<typeof importRowSchema>; export type ImportRow = z.infer<typeof importRowSchema>;
@@ -51,10 +55,7 @@ export async function checkDuplicateFitIds(
.select({ ofxFitId: transactions.ofxFitId }) .select({ ofxFitId: transactions.ofxFitId })
.from(transactions) .from(transactions)
.where( .where(
and( and(eq(transactions.userId, userId), inArray(transactions.ofxFitId, ids)),
eq(transactions.userId, userId),
inArray(transactions.ofxFitId, ids),
),
); );
return rows.map((r) => r.ofxFitId).filter((id): id is string => id !== null); return rows.map((r) => r.ofxFitId).filter((id): id is string => id !== null);
@@ -67,10 +68,14 @@ export async function importTransactionsAction(
const parsed = importSchema.safeParse(input); const parsed = importSchema.safeParse(input);
if (!parsed.success) { if (!parsed.success) {
return { success: false, error: parsed.error.issues[0]?.message ?? "Dados inválidos." }; return {
success: false,
error: parsed.error.issues[0]?.message ?? "Dados inválidos.",
};
} }
const { rows, payerId, accountId, cardId, paymentMethod, invoicePeriod } = parsed.data; const { rows, payerId, accountId, cardId, paymentMethod, invoicePeriod } =
parsed.data;
// Valida ownership // Valida ownership
const [payerOk, accountOk, cardOk] = await Promise.all([ const [payerOk, accountOk, cardOk] = await Promise.all([
@@ -94,14 +99,19 @@ export async function importTransactionsAction(
const records = rows.map((row) => { const records = rows.map((row) => {
const purchaseDate = parseLocalDateString(row.date); const purchaseDate = parseLocalDateString(row.date);
const period = invoicePeriod ?? `${purchaseDate.getFullYear()}-${String(purchaseDate.getMonth() + 1).padStart(2, "0")}`; const period =
invoicePeriod ??
`${purchaseDate.getFullYear()}-${String(purchaseDate.getMonth() + 1).padStart(2, "0")}`;
return { return {
name: row.description, name: row.description,
transactionType: row.transactionType === "income" ? "Receita" : "Despesa", transactionType: row.transactionType === "income" ? "Receita" : "Despesa",
condition: "À vista" as const, condition: "À vista" as const,
paymentMethod, paymentMethod,
amount: (row.transactionType === "expense" ? -row.amount : row.amount).toFixed(2), amount: (row.transactionType === "expense"
? -row.amount
: row.amount
).toFixed(2),
purchaseDate, purchaseDate,
period, period,
isSettled, isSettled,
@@ -143,10 +153,7 @@ export async function deleteTransactionByFitId(
await db await db
.delete(transactions) .delete(transactions)
.where( .where(
and( and(eq(transactions.userId, userId), eq(transactions.ofxFitId, fitId)),
eq(transactions.userId, userId),
eq(transactions.ofxFitId, fitId),
),
); );
await revalidateForEntity("transactions", userId); await revalidateForEntity("transactions", userId);

View File

@@ -33,7 +33,8 @@ export function decodeAccountCard(value: string): {
id: string; id: string;
} | null { } | null {
if (value.startsWith("card:")) return { type: "card", id: value.slice(5) }; if (value.startsWith("card:")) return { type: "card", id: value.slice(5) };
if (value.startsWith("account:")) return { type: "account", id: value.slice(8) }; if (value.startsWith("account:"))
return { type: "account", id: value.slice(8) };
return null; return null;
} }
@@ -65,7 +66,9 @@ export function GlobalFields({
onBulkCategoryChange, onBulkCategoryChange,
}: GlobalFieldsProps) { }: GlobalFieldsProps) {
const isCard = accountCardValue?.startsWith("card:") ?? false; const isCard = accountCardValue?.startsWith("card:") ?? false;
const expenseCategories = categoryOptions.filter((o) => o.group === "despesa"); const expenseCategories = categoryOptions.filter(
(o) => o.group === "despesa",
);
const incomeCategories = categoryOptions.filter((o) => o.group === "receita"); const incomeCategories = categoryOptions.filter((o) => o.group === "receita");
return ( return (
@@ -131,7 +134,10 @@ export function GlobalFields({
<SelectContent> <SelectContent>
{payerOptions.map((opt) => ( {payerOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}> <SelectItem key={opt.value} value={opt.value}>
<PayerSelectContent label={opt.label} avatarUrl={opt.avatarUrl} /> <PayerSelectContent
label={opt.label}
avatarUrl={opt.avatarUrl}
/>
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
@@ -150,7 +156,10 @@ export function GlobalFields({
<SelectLabel>Despesa</SelectLabel> <SelectLabel>Despesa</SelectLabel>
{expenseCategories.map((opt) => ( {expenseCategories.map((opt) => (
<SelectItem key={opt.value} value={opt.value}> <SelectItem key={opt.value} value={opt.value}>
<CategorySelectContent label={opt.label} icon={opt.icon} /> <CategorySelectContent
label={opt.label}
icon={opt.icon}
/>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>
@@ -163,7 +172,10 @@ export function GlobalFields({
<SelectLabel>Receita</SelectLabel> <SelectLabel>Receita</SelectLabel>
{incomeCategories.map((opt) => ( {incomeCategories.map((opt) => (
<SelectItem key={opt.value} value={opt.value}> <SelectItem key={opt.value} value={opt.value}>
<CategorySelectContent label={opt.label} icon={opt.icon} /> <CategorySelectContent
label={opt.label}
icon={opt.icon}
/>
</SelectItem> </SelectItem>
))} ))}
</SelectGroup> </SelectGroup>

View File

@@ -1,13 +1,18 @@
"use client"; "use client";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState, useTransition } from "react"; import {
useCallback,
useEffect,
useMemo,
useState,
useTransition,
} from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
fetchCategoryMappings, fetchCategoryMappings,
saveCategoryMappings, saveCategoryMappings,
} from "@/features/transactions/actions/category-memory-action"; } from "@/features/transactions/actions/category-memory-action";
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
import { import {
checkDuplicateFitIds, checkDuplicateFitIds,
deleteTransactionByFitId, deleteTransactionByFitId,
@@ -27,6 +32,7 @@ import {
} from "@/features/transactions/components/import/review-table"; } from "@/features/transactions/components/import/review-table";
import { UploadZone } from "@/features/transactions/components/import/upload-zone"; import { UploadZone } from "@/features/transactions/components/import/upload-zone";
import type { SelectOption } from "@/features/transactions/components/types"; import type { SelectOption } from "@/features/transactions/components/types";
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
import { Button } from "@/shared/components/ui/button"; import { Button } from "@/shared/components/ui/button";
import { import {
Card, Card,
@@ -82,7 +88,8 @@ export function ImportPage({
...t, ...t,
isDuplicate: t.externalId ? duplicates.has(t.externalId) : false, isDuplicate: t.externalId ? duplicates.has(t.externalId) : false,
selected: t.externalId ? !duplicates.has(t.externalId) : true, selected: t.externalId ? !duplicates.has(t.externalId) : true,
categoryId: categoryMappings[normalizeDescriptionKey(t.description)] ?? null, categoryId:
categoryMappings[normalizeDescriptionKey(t.description)] ?? null,
})), })),
); );
} finally { } finally {
@@ -167,7 +174,9 @@ export function ImportPage({
const handleImport = () => { const handleImport = () => {
if (!statement || !canImport) return; if (!statement || !canImport) return;
const decoded = decodeAccountCard(accountCardValue!); const decoded = accountCardValue
? decodeAccountCard(accountCardValue)
: null;
const cardId = decoded?.type === "card" ? decoded.id : null; const cardId = decoded?.type === "card" ? decoded.id : null;
const accountId = decoded?.type === "account" ? decoded.id : null; const accountId = decoded?.type === "account" ? decoded.id : null;
const paymentMethod = const paymentMethod =
@@ -197,7 +206,10 @@ export function ImportPage({
// Salva mapeamentos description → category (fire-and-forget) // Salva mapeamentos description → category (fire-and-forget)
saveCategoryMappings( saveCategoryMappings(
selectedRows.map((r) => ({ description: r.description, categoryId: r.categoryId })), selectedRows.map((r) => ({
description: r.description,
categoryId: r.categoryId,
})),
); );
const { importBatchId } = result; const { importBatchId } = result;
@@ -236,7 +248,8 @@ export function ImportPage({
<div> <div>
<CardTitle>Importar extrato</CardTitle> <CardTitle>Importar extrato</CardTitle>
<CardDescription> <CardDescription>
Importe transações a partir de um arquivo .ofx ou planilha .xlsx exportado pelo seu banco. Importe transações a partir de um arquivo .ofx ou planilha .xlsx
exportado pelo seu banco.
</CardDescription> </CardDescription>
</div> </div>
<ImportSteps current={currentStep} /> <ImportSteps current={currentStep} />

View File

@@ -34,7 +34,9 @@ export function ImportSteps({ current }: ImportStepsProps) {
isCompleted && isCompleted &&
"border-primary bg-primary text-primary-foreground", "border-primary bg-primary text-primary-foreground",
isActive && "border-primary text-primary", isActive && "border-primary text-primary",
!isCompleted && !isActive && "border-muted-foreground/30 text-muted-foreground", !isCompleted &&
!isActive &&
"border-muted-foreground/30 text-muted-foreground",
)} )}
> >
{isCompleted ? ( {isCompleted ? (

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual"; import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef } from "react";
import { CategorySelectContent } from "@/features/transactions/components/select-items"; import { CategorySelectContent } from "@/features/transactions/components/select-items";
import type { SelectOption } from "@/features/transactions/components/types"; import type { SelectOption } from "@/features/transactions/components/types";
import MoneyValues from "@/shared/components/money-values"; import MoneyValues from "@/shared/components/money-values";
@@ -91,9 +91,7 @@ export function ReviewTable({
onCheckedChange={(v) => onToggleAll(!!v)} onCheckedChange={(v) => onToggleAll(!!v)}
aria-label="Selecionar todas" aria-label="Selecionar todas"
data-state={ data-state={
!allSelected && someSelected !allSelected && someSelected ? "indeterminate" : undefined
? "indeterminate"
: undefined
} }
/> />
</TableHead> </TableHead>
@@ -114,7 +112,10 @@ export function ReviewTable({
</TableRow> </TableRow>
)} )}
{virtualRows.map((virtualRow) => { {virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]!; const row = rows[virtualRow.index];
if (!row) {
return null;
}
const index = virtualRow.index; const index = virtualRow.index;
return ( return (
<TableRow <TableRow
@@ -199,9 +200,7 @@ export function ReviewTable({
<TableCell> <TableCell>
<TransactionTypeBadge <TransactionTypeBadge
kind={ kind={
row.transactionType === "income" row.transactionType === "income" ? "Receita" : "Despesa"
? "Receita"
: "Despesa"
} }
/> />
</TableCell> </TableCell>

View File

@@ -37,7 +37,9 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
} }
onParsed(statement); onParsed(statement);
} catch { } catch {
setError("Não foi possível ler o arquivo. Verifique se é um OFX válido."); setError(
"Não foi possível ler o arquivo. Verifique se é um OFX válido.",
);
} }
}; };
reader.readAsText(file, "windows-1252"); reader.readAsText(file, "windows-1252");
@@ -119,11 +121,7 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
/> />
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{error ? ( {error ? <p className="text-destructive text-sm">{error}</p> : <span />}
<p className="text-destructive text-sm">{error}</p>
) : (
<span />
)}
<button <button
type="button" type="button"
onClick={handleDownloadTemplate} onClick={handleDownloadTemplate}

View File

@@ -13,9 +13,9 @@ import {
RiCheckLine, RiCheckLine,
RiDeleteBin5Line, RiDeleteBin5Line,
RiFileCopyLine, RiFileCopyLine,
RiFileExcel2Line,
RiFileList2Line, RiFileList2Line,
RiFlashlightFill, RiFlashlightFill,
RiFileExcel2Line,
RiGroupLine, RiGroupLine,
RiHistoryLine, RiHistoryLine,
RiMoreFill, RiMoreFill,

View File

@@ -1,4 +1,4 @@
import type { ImportStatement, ImportedTransaction } from "./types"; import type { ImportedTransaction, ImportStatement } from "./types";
// Extrai o valor de uma tag leaf do OFX SGML: <TAG>valor // Extrai o valor de uma tag leaf do OFX SGML: <TAG>valor
function getField(block: string, tag: string): string | null { function getField(block: string, tag: string): string | null {

View File

@@ -1,5 +1,8 @@
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import type { ImportStatement, ImportedTransaction } from "@/shared/lib/import/types"; import type {
ImportedTransaction,
ImportStatement,
} from "@/shared/lib/import/types";
function parseDateValue(value: unknown): string | null { function parseDateValue(value: unknown): string | null {
if (value == null || value === "") return null; if (value == null || value === "") return null;