mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
Compare commits
6 Commits
7dd480284e
...
60a52b9873
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60a52b9873 | ||
|
|
c9205f2be9 | ||
|
|
1d36b12109 | ||
|
|
19a1b1e943 | ||
|
|
d3fc81db73 | ||
|
|
80de9501f6 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,167 +1,167 @@
|
|||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1762993507299,
|
"when": 1762993507299,
|
||||||
"tag": "0000_flashy_manta",
|
"tag": "0000_flashy_manta",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1765199006435,
|
"when": 1765199006435,
|
||||||
"tag": "0001_young_mister_fear",
|
"tag": "0001_young_mister_fear",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1765200545692,
|
"when": 1765200545692,
|
||||||
"tag": "0002_slimy_flatman",
|
"tag": "0002_slimy_flatman",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767102605526,
|
"when": 1767102605526,
|
||||||
"tag": "0003_green_korg",
|
"tag": "0003_green_korg",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 4,
|
"idx": 4,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767104066872,
|
"when": 1767104066872,
|
||||||
"tag": "0004_acoustic_mach_iv",
|
"tag": "0004_acoustic_mach_iv",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 5,
|
"idx": 5,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767106121811,
|
"when": 1767106121811,
|
||||||
"tag": "0005_adorable_bruce_banner",
|
"tag": "0005_adorable_bruce_banner",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 6,
|
"idx": 6,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767107487318,
|
"when": 1767107487318,
|
||||||
"tag": "0006_youthful_mister_fear",
|
"tag": "0006_youthful_mister_fear",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 7,
|
"idx": 7,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767118780033,
|
"when": 1767118780033,
|
||||||
"tag": "0007_sturdy_kate_bishop",
|
"tag": "0007_sturdy_kate_bishop",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 8,
|
"idx": 8,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1767125796314,
|
"when": 1767125796314,
|
||||||
"tag": "0008_fat_stick",
|
"tag": "0008_fat_stick",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 9,
|
"idx": 9,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1768925100873,
|
"when": 1768925100873,
|
||||||
"tag": "0009_add_dashboard_widgets",
|
"tag": "0009_add_dashboard_widgets",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 10,
|
"idx": 10,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1769369834242,
|
"when": 1769369834242,
|
||||||
"tag": "0010_lame_psynapse",
|
"tag": "0010_lame_psynapse",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 11,
|
"idx": 11,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1769447087678,
|
"when": 1769447087678,
|
||||||
"tag": "0011_remove_unused_inbox_columns",
|
"tag": "0011_remove_unused_inbox_columns",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 12,
|
"idx": 12,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1769533200000,
|
"when": 1769533200000,
|
||||||
"tag": "0012_rename_tables_to_portuguese",
|
"tag": "0012_rename_tables_to_portuguese",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 13,
|
"idx": 13,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1769523352777,
|
"when": 1769523352777,
|
||||||
"tag": "0013_fancy_rick_jones",
|
"tag": "0013_fancy_rick_jones",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 14,
|
"idx": 14,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1769619226903,
|
"when": 1769619226903,
|
||||||
"tag": "0014_yielding_jack_flag",
|
"tag": "0014_yielding_jack_flag",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 15,
|
"idx": 15,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1770332054481,
|
"when": 1770332054481,
|
||||||
"tag": "0015_concerned_kat_farrell",
|
"tag": "0015_concerned_kat_farrell",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 16,
|
"idx": 16,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1771166328908,
|
"when": 1771166328908,
|
||||||
"tag": "0016_complete_randall",
|
"tag": "0016_complete_randall",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 17,
|
"idx": 17,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1772400510326,
|
"when": 1772400510326,
|
||||||
"tag": "0017_previous_warstar",
|
"tag": "0017_previous_warstar",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 18,
|
"idx": 18,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1773020417482,
|
"when": 1773020417482,
|
||||||
"tag": "0018_rainy_epoch",
|
"tag": "0018_rainy_epoch",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 19,
|
"idx": 19,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1773699152928,
|
"when": 1773699152928,
|
||||||
"tag": "0019_ordinary_wild_pack",
|
"tag": "0019_ordinary_wild_pack",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 20,
|
"idx": 20,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1773841892114,
|
"when": 1773841892114,
|
||||||
"tag": "0020_add-budget-invoice-unique-constraints",
|
"tag": "0020_add-budget-invoice-unique-constraints",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 21,
|
"idx": 21,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1774033320053,
|
"when": 1774033320053,
|
||||||
"tag": "0021_careful_malcolm_colcord",
|
"tag": "0021_careful_malcolm_colcord",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 22,
|
"idx": 22,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1748000000000,
|
"when": 1748000000000,
|
||||||
"tag": "0022_import-category-mappings",
|
"tag": "0022_import-category-mappings",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -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
113
pnpm-lock.yaml
generated
@@ -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 |
@@ -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([
|
||||||
fetchInboxStatusCounts(userId),
|
fetchInboxItemsPage(userId, activeStatus, {
|
||||||
activeStatus === "pending"
|
...paginationInput,
|
||||||
? fetchInboxDialogData(userId)
|
sourceApp: activeApp,
|
||||||
: Promise.resolve(EMPTY_DIALOG_DATA),
|
}),
|
||||||
fetchAppLogoMap(userId),
|
fetchInboxStatusCounts(userId),
|
||||||
]);
|
fetchInboxSourceApps(userId, activeStatus).catch(() => [] as string[]),
|
||||||
|
activeStatus === "pending"
|
||||||
|
? fetchInboxDialogData(userId)
|
||||||
|
: Promise.resolve(EMPTY_DIALOG_DATA),
|
||||||
|
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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -48,21 +48,20 @@ 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()
|
.trim()
|
||||||
.trim()
|
.transform((value) =>
|
||||||
.transform((value) =>
|
value.length === 0 ? "0" : value.replace(",", "."),
|
||||||
value.length === 0 ? "0" : value.replace(",", "."),
|
)
|
||||||
)
|
.refine(
|
||||||
.refine(
|
(value) => !Number.isNaN(Number.parseFloat(value)),
|
||||||
(value) => !Number.isNaN(Number.parseFloat(value)),
|
"Informe um saldo inicial válido.",
|
||||||
"Informe um saldo inicial válido.",
|
)
|
||||||
)
|
.transform((value) => Number.parseFloat(value)),
|
||||||
.transform((value) => Number.parseFloat(value)),
|
]),
|
||||||
]),
|
|
||||||
excludeFromBalance: z
|
excludeFromBalance: z
|
||||||
.union([z.boolean(), z.string()])
|
.union([z.boolean(), z.string()])
|
||||||
.transform((value) => value === true || value === "true"),
|
.transform((value) => value === true || value === "true"),
|
||||||
|
|||||||
@@ -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>
|
||||||
{timeAgo}
|
<TooltipTrigger asChild>
|
||||||
</span>
|
<span className="shrink-0 cursor-default text-xs font-normal text-muted-foreground underline decoration-dotted underline-offset-2">
|
||||||
|
{timeAgo}
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
<span>{item.sourceAppName || item.sourceApp}</span>
|
<div className="flex flex-col items-end gap-0.5">
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -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,32 +536,105 @@ 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",
|
||||||
)
|
);
|
||||||
) : (
|
}
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{list.map((item) => (
|
const groups = groupItemsByDay(list);
|
||||||
<InboxCard
|
|
||||||
key={item.id}
|
return (
|
||||||
item={item}
|
<div className="space-y-6">
|
||||||
readonly={readonly}
|
{groups.map((group) => (
|
||||||
appLogoMap={appLogoMap}
|
<div key={group.label}>
|
||||||
onProcess={readonly ? undefined : handleProcessRequest}
|
<div className="mb-3 flex items-center gap-1 text-muted-foreground">
|
||||||
onDiscard={readonly ? undefined : handleDiscardRequest}
|
<RiCalendarEventLine className="size-3.5 shrink-0" />
|
||||||
onViewDetails={readonly ? undefined : handleDetailsRequest}
|
<p className="text-sm font-medium">{group.label}</p>
|
||||||
onDelete={readonly ? handleDeleteRequest : undefined}
|
</div>
|
||||||
onRestoreToPending={readonly ? handleRestoreRequest : undefined}
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
selected={selectedIds.includes(item.id)}
|
{group.items.map((item) => (
|
||||||
onSelectToggle={toggleSelection}
|
<InboxCard
|
||||||
/>
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
readonly={readonly}
|
||||||
|
appLogoMap={appLogoMap}
|
||||||
|
onProcess={readonly ? undefined : handleProcessRequest}
|
||||||
|
onDiscard={readonly ? undefined : handleDiscardRequest}
|
||||||
|
onViewDetails={readonly ? undefined : handleDetailsRequest}
|
||||||
|
onDelete={readonly ? handleDeleteRequest : undefined}
|
||||||
|
onRestoreToPending={
|
||||||
|
readonly ? handleRestoreRequest : undefined
|
||||||
|
}
|
||||||
|
selected={selectedIds.includes(item.id)}
|
||||||
|
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,80 +671,110 @@ 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">
|
||||||
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
{renderAppFilter()}
|
||||||
</Button>
|
{items.length > 0 ? (
|
||||||
{selectedIds.length > 0 && (
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleSelectionBulkRequest("pending")}
|
onClick={toggleSelectAll}
|
||||||
>
|
>
|
||||||
<RiDeleteBinLine className="mr-1.5 size-4" />
|
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
||||||
Descartar selecionados ({selectedIds.length})
|
</Button>
|
||||||
</Button>
|
{selectedIds.length > 0 && (
|
||||||
)}
|
<Button
|
||||||
</div>
|
variant="destructive"
|
||||||
)}
|
size="sm"
|
||||||
{activeStatus === "pending" ? renderGrid(items, false) : null}
|
onClick={() => handleSelectionBulkRequest("pending")}
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className="mr-1.5 size-4" />
|
||||||
|
Descartar selecionados ({selectedIds.length})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{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">
|
||||||
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
{renderAppFilter()}
|
||||||
</Button>
|
{items.length > 0 ? (
|
||||||
{selectedIds.length > 0 && (
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleSelectionBulkRequest("processed")}
|
onClick={toggleSelectAll}
|
||||||
>
|
>
|
||||||
<RiDeleteBinLine className="mr-1.5 size-4" />
|
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
||||||
Excluir selecionados ({selectedIds.length})
|
</Button>
|
||||||
</Button>
|
{selectedIds.length > 0 && (
|
||||||
)}
|
<Button
|
||||||
<Button
|
variant="destructive"
|
||||||
variant="outline"
|
size="sm"
|
||||||
size="sm"
|
onClick={() => handleSelectionBulkRequest("processed")}
|
||||||
onClick={() => handleBulkDeleteRequest("processed")}
|
>
|
||||||
>
|
<RiDeleteBinLine className="mr-1.5 size-4" />
|
||||||
<RiDeleteBinLine className="mr-1.5 size-4" />
|
Excluir selecionados ({selectedIds.length})
|
||||||
Limpar processados
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
</div>
|
<Button
|
||||||
)}
|
variant="outline"
|
||||||
{activeStatus === "processed" ? renderGrid(items, true) : null}
|
size="sm"
|
||||||
|
onClick={() => handleBulkDeleteRequest("processed")}
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className="mr-1.5 size-4" />
|
||||||
|
Limpar processados
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{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">
|
||||||
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
{renderAppFilter()}
|
||||||
</Button>
|
{items.length > 0 ? (
|
||||||
{selectedIds.length > 0 && (
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleSelectionBulkRequest("discarded")}
|
onClick={toggleSelectAll}
|
||||||
>
|
>
|
||||||
<RiDeleteBinLine className="mr-1.5 size-4" />
|
{allSelected ? "Cancelar seleção" : "Selecionar página"}
|
||||||
Excluir selecionados ({selectedIds.length})
|
</Button>
|
||||||
</Button>
|
{selectedIds.length > 0 && (
|
||||||
)}
|
<Button
|
||||||
<Button
|
variant="destructive"
|
||||||
variant="outline"
|
size="sm"
|
||||||
size="sm"
|
onClick={() => handleSelectionBulkRequest("discarded")}
|
||||||
onClick={() => handleBulkDeleteRequest("discarded")}
|
>
|
||||||
>
|
<RiDeleteBinLine className="mr-1.5 size-4" />
|
||||||
<RiDeleteBinLine className="mr-1.5 size-4" />
|
Excluir selecionados ({selectedIds.length})
|
||||||
Limpar descartados
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
</div>
|
<Button
|
||||||
)}
|
variant="outline"
|
||||||
{activeStatus === "discarded" ? renderGrid(items, true) : null}
|
size="sm"
|
||||||
|
onClick={() => handleBulkDeleteRequest("discarded")}
|
||||||
|
>
|
||||||
|
<RiDeleteBinLine className="mr-1.5 size-4" />
|
||||||
|
Limpar descartados
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{activeStatus === "discarded" ? renderGroupedGrid(items, true) : null}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|||||||
@@ -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"> => {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -172,17 +184,17 @@ export function GlobalFields({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCard && (
|
{isCard && (
|
||||||
<div className="flex min-w-44 flex-col gap-1.5">
|
<div className="flex min-w-44 flex-col gap-1.5">
|
||||||
<Label>Fatura</Label>
|
<Label>Fatura</Label>
|
||||||
<PeriodPicker
|
<PeriodPicker
|
||||||
value={invoicePeriod ?? ""}
|
value={invoicePeriod ?? ""}
|
||||||
onChange={(v) => onInvoicePeriodChange(v || null)}
|
onChange={(v) => onInvoicePeriodChange(v || null)}
|
||||||
placeholder="Selecionar fatura…"
|
placeholder="Selecionar fatura…"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiDeleteBin5Line,
|
RiDeleteBin5Line,
|
||||||
RiFileCopyLine,
|
RiFileCopyLine,
|
||||||
|
RiFileExcel2Line,
|
||||||
RiFileList2Line,
|
RiFileList2Line,
|
||||||
RiFlashlightFill,
|
RiFlashlightFill,
|
||||||
RiFileExcel2Line,
|
|
||||||
RiGroupLine,
|
RiGroupLine,
|
||||||
RiHistoryLine,
|
RiHistoryLine,
|
||||||
RiMoreFill,
|
RiMoreFill,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user