@hed-hog/finance 0.0.228 → 0.0.231

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,16 @@
1
1
  'use client';
2
2
 
3
3
  import { Page, PageHeader } from '@/components/entity-list';
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogCancel,
8
+ AlertDialogContent,
9
+ AlertDialogDescription,
10
+ AlertDialogFooter,
11
+ AlertDialogHeader,
12
+ AlertDialogTitle,
13
+ } from '@/components/ui/alert-dialog';
4
14
  import { Badge } from '@/components/ui/badge';
5
15
  import { Button } from '@/components/ui/button';
6
16
  import {
@@ -97,6 +107,19 @@ function NovaContaSheet({
97
107
  }) {
98
108
  const { request, showToastHandler } = useApp();
99
109
 
110
+ const createSuccessMessage = t.has('messages.createSuccess')
111
+ ? t('messages.createSuccess')
112
+ : 'Conta bancária cadastrada com sucesso';
113
+ const createErrorMessage = t.has('messages.createError')
114
+ ? t('messages.createError')
115
+ : 'Erro ao cadastrar conta bancária';
116
+ const updateSuccessMessage = t.has('messages.updateSuccess')
117
+ ? t('messages.updateSuccess')
118
+ : 'Conta bancária atualizada com sucesso';
119
+ const updateErrorMessage = t.has('messages.updateError')
120
+ ? t('messages.updateError')
121
+ : 'Erro ao atualizar conta bancária';
122
+
100
123
  const form = useForm<BankAccountFormValues>({
101
124
  resolver: zodResolver(bankAccountFormSchema),
102
125
  defaultValues: {
@@ -171,16 +194,12 @@ function NovaContaSheet({
171
194
  onEditingAccountChange(null);
172
195
  showToastHandler?.(
173
196
  'success',
174
- editingAccount
175
- ? 'Conta bancária atualizada com sucesso'
176
- : 'Conta bancária cadastrada com sucesso'
197
+ editingAccount ? updateSuccessMessage : createSuccessMessage
177
198
  );
178
199
  } catch {
179
200
  showToastHandler?.(
180
201
  'error',
181
- editingAccount
182
- ? 'Erro ao atualizar conta bancária'
183
- : 'Erro ao cadastrar conta bancária'
202
+ editingAccount ? updateErrorMessage : createErrorMessage
184
203
  );
185
204
  }
186
205
  };
@@ -356,13 +375,33 @@ function NovaContaSheet({
356
375
 
357
376
  export default function ContasBancariasPage() {
358
377
  const t = useTranslations('finance.BankAccountsPage');
359
- const { request, showToastHandler } = useApp();
378
+ const { request, showToastHandler, currentLocaleCode } = useApp();
379
+
380
+ const deleteSuccessMessage = t.has('messages.deleteSuccess')
381
+ ? t('messages.deleteSuccess')
382
+ : 'Conta bancária inativada com sucesso';
383
+ const deleteErrorMessage = t.has('messages.deleteError')
384
+ ? t('messages.deleteError')
385
+ : 'Erro ao inativar conta bancária';
386
+ const deleteDialogTitle = t.has('deleteDialog.title')
387
+ ? t('deleteDialog.title')
388
+ : 'Inativar conta bancária';
389
+ const deleteDialogDescription = t.has('deleteDialog.description')
390
+ ? t('deleteDialog.description')
391
+ : 'Deseja realmente inativar esta conta bancária?';
392
+ const deleteDialogConfirm = t.has('deleteDialog.confirm')
393
+ ? t('deleteDialog.confirm')
394
+ : 'Inativar';
395
+
360
396
  const [sheetOpen, setSheetOpen] = useState(false);
361
397
  const [editingAccount, setEditingAccount] = useState<BankAccount | null>(
362
398
  null
363
399
  );
400
+ const [accountIdToDelete, setAccountIdToDelete] = useState<string | null>(
401
+ null
402
+ );
364
403
  const { data: contasBancarias, refetch } = useQuery<BankAccount[]>({
365
- queryKey: ['finance-bank-accounts'],
404
+ queryKey: ['finance-bank-accounts', currentLocaleCode],
366
405
  queryFn: async () => {
367
406
  const response = await request({
368
407
  url: '/finance/bank-accounts',
@@ -371,7 +410,7 @@ export default function ContasBancariasPage() {
371
410
 
372
411
  return (response.data || []) as BankAccount[];
373
412
  },
374
- initialData: [],
413
+ placeholderData: [],
375
414
  });
376
415
 
377
416
  const tipoConfig = {
@@ -399,25 +438,22 @@ export default function ContasBancariasPage() {
399
438
  setSheetOpen(true);
400
439
  };
401
440
 
402
- const handleDelete = async (accountId: string) => {
403
- const confirmed = window.confirm(
404
- 'Deseja realmente inativar esta conta bancária?'
405
- );
406
-
407
- if (!confirmed) {
441
+ const handleDelete = async () => {
442
+ if (!accountIdToDelete) {
408
443
  return;
409
444
  }
410
445
 
411
446
  try {
412
447
  await request({
413
- url: `/finance/bank-accounts/${accountId}`,
448
+ url: `/finance/bank-accounts/${accountIdToDelete}`,
414
449
  method: 'DELETE',
415
450
  });
416
451
 
417
452
  await refetch();
418
- showToastHandler?.('success', 'Conta bancária inativada com sucesso');
453
+ showToastHandler?.('success', deleteSuccessMessage);
454
+ setAccountIdToDelete(null);
419
455
  } catch {
420
- showToastHandler?.('error', 'Erro ao inativar conta bancária');
456
+ showToastHandler?.('error', deleteErrorMessage);
421
457
  }
422
458
  };
423
459
 
@@ -448,6 +484,30 @@ export default function ContasBancariasPage() {
448
484
  onEditingAccountChange={setEditingAccount}
449
485
  />
450
486
 
487
+ <AlertDialog
488
+ open={!!accountIdToDelete}
489
+ onOpenChange={(open) => {
490
+ if (!open) {
491
+ setAccountIdToDelete(null);
492
+ }
493
+ }}
494
+ >
495
+ <AlertDialogContent>
496
+ <AlertDialogHeader>
497
+ <AlertDialogTitle>{deleteDialogTitle}</AlertDialogTitle>
498
+ <AlertDialogDescription>
499
+ {deleteDialogDescription}
500
+ </AlertDialogDescription>
501
+ </AlertDialogHeader>
502
+ <AlertDialogFooter>
503
+ <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
504
+ <AlertDialogAction onClick={handleDelete}>
505
+ {deleteDialogConfirm}
506
+ </AlertDialogAction>
507
+ </AlertDialogFooter>
508
+ </AlertDialogContent>
509
+ </AlertDialog>
510
+
451
511
  <div className="grid gap-4 md:grid-cols-2">
452
512
  <Card>
453
513
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -551,14 +611,16 @@ export default function ContasBancariasPage() {
551
611
  </span>
552
612
  </div>
553
613
  )}
554
- <div className="flex gap-2 pt-2">
614
+ <div className="flex flex-wrap gap-2 pt-2">
555
615
  <Button
556
616
  variant="outline"
557
617
  size="sm"
558
- className="flex-1 bg-transparent"
618
+ className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
559
619
  asChild
560
620
  >
561
- <Link href="/finance/cash-and-banks/statements">
621
+ <Link
622
+ href={`/finance/cash-and-banks/statements?bank_account_id=${conta.id}`}
623
+ >
562
624
  <Eye className="mr-2 h-4 w-4" />
563
625
  {t('accountCard.statement')}
564
626
  </Link>
@@ -566,31 +628,35 @@ export default function ContasBancariasPage() {
566
628
  <Button
567
629
  variant="outline"
568
630
  size="sm"
569
- className="flex-1 bg-transparent"
631
+ className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
570
632
  asChild
571
633
  >
572
- <Link href="/finance/cash-and-banks/bank-reconciliation">
634
+ <Link
635
+ href={`/finance/cash-and-banks/bank-reconciliation?bank_account_id=${conta.id}`}
636
+ >
573
637
  <RefreshCw className="mr-2 h-4 w-4" />
574
638
  {t('accountCard.reconcile')}
575
639
  </Link>
576
640
  </Button>
577
- <Button variant="outline" size="sm">
578
- <Upload className="h-4 w-4" />
579
- </Button>
580
- <Button
581
- variant="outline"
582
- size="sm"
583
- onClick={() => handleEdit(conta)}
584
- >
585
- <Pencil className="h-4 w-4" />
586
- </Button>
587
- <Button
588
- variant="outline"
589
- size="sm"
590
- onClick={() => handleDelete(conta.id)}
591
- >
592
- <Trash2 className="h-4 w-4" />
593
- </Button>
641
+ <div className="ml-auto flex shrink-0 gap-2">
642
+ <Button variant="outline" size="sm">
643
+ <Upload className="h-4 w-4" />
644
+ </Button>
645
+ <Button
646
+ variant="outline"
647
+ size="sm"
648
+ onClick={() => handleEdit(conta)}
649
+ >
650
+ <Pencil className="h-4 w-4" />
651
+ </Button>
652
+ <Button
653
+ variant="outline"
654
+ size="sm"
655
+ onClick={() => setAccountIdToDelete(conta.id)}
656
+ >
657
+ <Trash2 className="h-4 w-4" />
658
+ </Button>
659
+ </div>
594
660
  </div>
595
661
  </div>
596
662
  </CardContent>
@@ -35,7 +35,8 @@ import {
35
35
  import { StatusBadge } from '@/components/ui/status-badge';
36
36
  import { Check, DollarSign, Link2, RefreshCw } from 'lucide-react';
37
37
  import { useTranslations } from 'next-intl';
38
- import { useState } from 'react';
38
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
39
+ import { useEffect, useState } from 'react';
39
40
  import { formatarData, formatarMoeda } from '../../_lib/formatters';
40
41
  import { useFinanceData } from '../../_lib/use-finance-data';
41
42
 
@@ -101,6 +102,10 @@ function CriarAjusteDialog({ t }: { t: ReturnType<typeof useTranslations> }) {
101
102
 
102
103
  export default function ConciliacaoPage() {
103
104
  const t = useTranslations('finance.BankReconciliationPage');
105
+ const pathname = usePathname();
106
+ const router = useRouter();
107
+ const searchParams = useSearchParams();
108
+ const bankAccountIdFromUrl = searchParams.get('bank_account_id');
104
109
  const { data } = useFinanceData();
105
110
  const { extratos, titulosPagar, titulosReceber, contasBancarias, pessoas } =
106
111
  data;
@@ -138,10 +143,43 @@ export default function ConciliacaoPage() {
138
143
  ),
139
144
  ];
140
145
 
141
- const [contaFilter, setContaFilter] = useState<string>('1');
146
+ const [contaFilter, setContaFilter] = useState<string>('');
142
147
  const [selectedExtrato, setSelectedExtrato] = useState<string | null>(null);
143
148
  const [selectedTitulo, setSelectedTitulo] = useState<string | null>(null);
144
149
 
150
+ useEffect(() => {
151
+ const firstAccount = contasBancarias[0];
152
+
153
+ if (!firstAccount) {
154
+ return;
155
+ }
156
+
157
+ const hasAccountFromUrl =
158
+ !!bankAccountIdFromUrl &&
159
+ contasBancarias.some((account) => account.id === bankAccountIdFromUrl);
160
+
161
+ const nextAccountId = hasAccountFromUrl
162
+ ? (bankAccountIdFromUrl as string)
163
+ : firstAccount.id;
164
+
165
+ if (contaFilter !== nextAccountId) {
166
+ setContaFilter(nextAccountId);
167
+ }
168
+
169
+ if (bankAccountIdFromUrl !== nextAccountId) {
170
+ const params = new URLSearchParams(searchParams.toString());
171
+ params.set('bank_account_id', nextAccountId);
172
+ router.replace(`${pathname}?${params.toString()}`);
173
+ }
174
+ }, [
175
+ bankAccountIdFromUrl,
176
+ contaFilter,
177
+ contasBancarias,
178
+ pathname,
179
+ router,
180
+ searchParams,
181
+ ]);
182
+
145
183
  const extratosFiltered = extratosPendentes.filter(
146
184
  (e) => e.contaBancariaId === contaFilter
147
185
  );
@@ -171,8 +209,17 @@ export default function ConciliacaoPage() {
171
209
  />
172
210
 
173
211
  <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
174
- <Select value={contaFilter} onValueChange={setContaFilter}>
175
- <SelectTrigger className="w-[280px]">
212
+ <Select
213
+ value={contaFilter}
214
+ onValueChange={(value) => {
215
+ setContaFilter(value);
216
+
217
+ const params = new URLSearchParams(searchParams.toString());
218
+ params.set('bank_account_id', value);
219
+ router.replace(`${pathname}?${params.toString()}`);
220
+ }}
221
+ >
222
+ <SelectTrigger className="w-full sm:w-[280px]">
176
223
  <SelectValue placeholder={t('filters.selectAccount')} />
177
224
  </SelectTrigger>
178
225
  <SelectContent>
@@ -38,11 +38,34 @@ import {
38
38
  TableHeader,
39
39
  TableRow,
40
40
  } from '@/components/ui/table';
41
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
41
42
  import { ArrowDownRight, ArrowUpRight, Download, Upload } from 'lucide-react';
42
43
  import { useTranslations } from 'next-intl';
43
- import { useState } from 'react';
44
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
45
+ import { useEffect, useMemo, useState } from 'react';
44
46
  import { formatarData } from '../../_lib/formatters';
45
- import { useFinanceData } from '../../_lib/use-finance-data';
47
+
48
+ type BankAccount = {
49
+ id: string;
50
+ banco: string;
51
+ descricao: string;
52
+ saldoAtual: number;
53
+ };
54
+
55
+ type Statement = {
56
+ id: string;
57
+ contaBancariaId: string;
58
+ data: string;
59
+ descricao: string;
60
+ valor: number;
61
+ tipo: 'entrada' | 'saida';
62
+ statusConciliacao:
63
+ | 'importado'
64
+ | 'pendente'
65
+ | 'conciliado'
66
+ | 'estornado'
67
+ | 'ajustado';
68
+ };
46
69
 
47
70
  function ImportarExtratoDialog({
48
71
  contasBancarias,
@@ -101,20 +124,86 @@ function ImportarExtratoDialog({
101
124
 
102
125
  export default function ExtratosPage() {
103
126
  const t = useTranslations('finance.StatementsPage');
104
- const { data } = useFinanceData();
105
- const { extratos, contasBancarias } = data;
127
+ const { request } = useApp();
128
+ const pathname = usePathname();
129
+ const router = useRouter();
130
+ const searchParams = useSearchParams();
131
+ const bankAccountIdFromUrl = searchParams.get('bank_account_id');
106
132
 
107
- const [contaFilter, setContaFilter] = useState<string>('1');
133
+ const [contaFilter, setContaFilter] = useState<string>('');
108
134
  const [search, setSearch] = useState('');
109
135
 
110
- const filteredExtratos = extratos.filter((extrato) => {
111
- const matchesConta = extrato.contaBancariaId === contaFilter;
112
- const matchesSearch = extrato.descricao
113
- .toLowerCase()
114
- .includes(search.toLowerCase());
115
- return matchesConta && matchesSearch;
136
+ const { data: contasBancarias } = useQuery<BankAccount[]>({
137
+ queryKey: ['finance-bank-accounts'],
138
+ queryFn: async () => {
139
+ const response = await request({
140
+ url: '/finance/bank-accounts',
141
+ method: 'GET',
142
+ });
143
+
144
+ return (response?.data || []) as BankAccount[];
145
+ },
146
+ initialData: [],
147
+ });
148
+
149
+ useEffect(() => {
150
+ const firstAccount = contasBancarias[0];
151
+
152
+ if (!firstAccount) {
153
+ return;
154
+ }
155
+
156
+ const hasAccountFromUrl =
157
+ !!bankAccountIdFromUrl &&
158
+ contasBancarias.some((account) => account.id === bankAccountIdFromUrl);
159
+
160
+ const nextAccountId = hasAccountFromUrl
161
+ ? (bankAccountIdFromUrl as string)
162
+ : firstAccount.id;
163
+
164
+ if (contaFilter !== nextAccountId) {
165
+ setContaFilter(nextAccountId);
166
+ }
167
+
168
+ if (bankAccountIdFromUrl !== nextAccountId) {
169
+ const params = new URLSearchParams(searchParams.toString());
170
+ params.set('bank_account_id', nextAccountId);
171
+ router.replace(`${pathname}?${params.toString()}`);
172
+ }
173
+ }, [
174
+ bankAccountIdFromUrl,
175
+ contaFilter,
176
+ contasBancarias,
177
+ pathname,
178
+ router,
179
+ searchParams,
180
+ ]);
181
+
182
+ const { data: extratos } = useQuery<Statement[]>({
183
+ queryKey: ['finance-statements', contaFilter],
184
+ queryFn: async () => {
185
+ if (!contaFilter) {
186
+ return [];
187
+ }
188
+
189
+ const response = await request({
190
+ url: `/finance/statements?bank_account_id=${contaFilter}`,
191
+ method: 'GET',
192
+ });
193
+
194
+ return (response?.data || []) as Statement[];
195
+ },
196
+ initialData: [],
116
197
  });
117
198
 
199
+ const filteredExtratos = useMemo(
200
+ () =>
201
+ extratos.filter((extrato) =>
202
+ extrato.descricao.toLowerCase().includes(search.toLowerCase())
203
+ ),
204
+ [extratos, search]
205
+ );
206
+
118
207
  const conta = contasBancarias.find((item) => item.id === contaFilter);
119
208
  const totalEntradas = filteredExtratos
120
209
  .filter((e) => e.tipo === 'entrada')
@@ -145,8 +234,17 @@ export default function ExtratosPage() {
145
234
  />
146
235
 
147
236
  <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
148
- <Select value={contaFilter} onValueChange={setContaFilter}>
149
- <SelectTrigger className="w-[280px]">
237
+ <Select
238
+ value={contaFilter}
239
+ onValueChange={(value) => {
240
+ setContaFilter(value);
241
+
242
+ const params = new URLSearchParams(searchParams.toString());
243
+ params.set('bank_account_id', value);
244
+ router.replace(`${pathname}?${params.toString()}`);
245
+ }}
246
+ >
247
+ <SelectTrigger className="w-full sm:w-[280px]">
150
248
  <SelectValue placeholder={t('filters.selectAccount')} />
151
249
  </SelectTrigger>
152
250
  <SelectContent>
@@ -446,8 +446,22 @@
446
446
  "common": {
447
447
  "select": "Select...",
448
448
  "cancel": "Cancel",
449
+ "edit": "Edit",
449
450
  "save": "Save"
450
451
  },
452
+ "messages": {
453
+ "createSuccess": "Bank account created successfully",
454
+ "createError": "Failed to create bank account",
455
+ "updateSuccess": "Bank account updated successfully",
456
+ "updateError": "Failed to update bank account",
457
+ "deleteSuccess": "Bank account deactivated successfully",
458
+ "deleteError": "Failed to deactivate bank account"
459
+ },
460
+ "deleteDialog": {
461
+ "title": "Deactivate bank account",
462
+ "description": "Do you really want to deactivate this bank account?",
463
+ "confirm": "Deactivate"
464
+ },
451
465
  "types": {
452
466
  "corrente": "Checking Account",
453
467
  "poupanca": "Savings Account",
@@ -446,8 +446,22 @@
446
446
  "common": {
447
447
  "select": "Selecione...",
448
448
  "cancel": "Cancelar",
449
+ "edit": "Editar",
449
450
  "save": "Salvar"
450
451
  },
452
+ "messages": {
453
+ "createSuccess": "Conta bancária cadastrada com sucesso",
454
+ "createError": "Erro ao cadastrar conta bancária",
455
+ "updateSuccess": "Conta bancária atualizada com sucesso",
456
+ "updateError": "Erro ao atualizar conta bancária",
457
+ "deleteSuccess": "Conta bancária inativada com sucesso",
458
+ "deleteError": "Erro ao inativar conta bancária"
459
+ },
460
+ "deleteDialog": {
461
+ "title": "Inativar conta bancária",
462
+ "description": "Deseja realmente inativar esta conta bancária?",
463
+ "confirm": "Inativar"
464
+ },
451
465
  "types": {
452
466
  "corrente": "Conta Corrente",
453
467
  "poupanca": "Poupança",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/finance",
3
- "version": "0.0.228",
3
+ "version": "0.0.231",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,12 +9,12 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.4",
13
- "@hed-hog/tag": "0.0.223",
14
12
  "@hed-hog/api-locale": "0.0.11",
13
+ "@hed-hog/api-prisma": "0.0.4",
15
14
  "@hed-hog/api-pagination": "0.0.5",
16
- "@hed-hog/api-types": "0.0.1",
15
+ "@hed-hog/tag": "0.0.223",
17
16
  "@hed-hog/api": "0.0.3",
17
+ "@hed-hog/api-types": "0.0.1",
18
18
  "@hed-hog/contact": "0.0.223"
19
19
  },
20
20
  "exports": {