@hed-hog/finance 0.0.300 → 0.0.301

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.
Files changed (39) hide show
  1. package/dist/finance.contract-activated.subscriber.d.ts +24 -0
  2. package/dist/finance.contract-activated.subscriber.d.ts.map +1 -0
  3. package/dist/finance.contract-activated.subscriber.js +519 -0
  4. package/dist/finance.contract-activated.subscriber.js.map +1 -0
  5. package/dist/finance.contract-activated.subscriber.spec.d.ts +2 -0
  6. package/dist/finance.contract-activated.subscriber.spec.d.ts.map +1 -0
  7. package/dist/finance.contract-activated.subscriber.spec.js +302 -0
  8. package/dist/finance.contract-activated.subscriber.spec.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +6 -1
  11. package/dist/finance.module.js.map +1 -1
  12. package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +108 -0
  13. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +91 -106
  14. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1306 -1145
  15. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +288 -268
  16. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +491 -351
  17. package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +157 -173
  18. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +44 -62
  19. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +62 -80
  20. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +151 -170
  21. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +332 -286
  22. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +204 -226
  23. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +122 -140
  24. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +32 -49
  25. package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +84 -108
  26. package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +53 -70
  27. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +98 -95
  28. package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +100 -125
  29. package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +77 -105
  30. package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +99 -134
  31. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +147 -182
  32. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +49 -61
  33. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +49 -67
  34. package/hedhog/frontend/messages/en.json +176 -68
  35. package/hedhog/frontend/messages/pt.json +176 -68
  36. package/package.json +7 -6
  37. package/src/finance.contract-activated.subscriber.spec.ts +392 -0
  38. package/src/finance.contract-activated.subscriber.ts +780 -0
  39. package/src/finance.module.ts +6 -1
@@ -1,14 +1,9 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { EmptyState, Page, PageHeader } from '@/components/entity-list';
4
5
  import { Button } from '@/components/ui/button';
5
- import {
6
- Card,
7
- CardContent,
8
- CardDescription,
9
- CardHeader,
10
- CardTitle,
11
- } from '@/components/ui/card';
6
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
12
7
  import {
13
8
  Dialog,
14
9
  DialogContent,
@@ -659,6 +654,35 @@ export default function ExtratosPage() {
659
654
  const totalSaidas = extratos
660
655
  .filter((e) => e.tipo === 'saida')
661
656
  .reduce((acc, e) => acc + e.valor, 0);
657
+ const summaryCards = [
658
+ {
659
+ key: 'inflows',
660
+ title: t('cards.inflows'),
661
+ value: <Money value={totalEntradas} />,
662
+ icon: ArrowUpRight,
663
+ layout: 'compact' as const,
664
+ valueClassName: 'text-green-600',
665
+ iconContainerClassName: 'bg-green-500/10 text-green-700',
666
+ accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
667
+ },
668
+ {
669
+ key: 'outflows',
670
+ title: t('cards.outflows'),
671
+ value: <Money value={totalSaidas} />,
672
+ icon: ArrowDownRight,
673
+ layout: 'compact' as const,
674
+ valueClassName: 'text-red-600',
675
+ iconContainerClassName: 'bg-red-500/10 text-red-700',
676
+ accentClassName: 'from-red-500/20 via-rose-500/10 to-transparent',
677
+ },
678
+ {
679
+ key: 'balance',
680
+ title: t('cards.accountBalance'),
681
+ value: <Money value={conta?.saldoAtual || 0} />,
682
+ description: conta?.descricao,
683
+ layout: 'compact' as const,
684
+ },
685
+ ];
662
686
 
663
687
  const handleExport = async () => {
664
688
  if (!contaFilter) {
@@ -749,7 +773,7 @@ export default function ExtratosPage() {
749
773
  router.replace(`${pathname}?${params.toString()}`);
750
774
  }}
751
775
  >
752
- <SelectTrigger className="w-full sm:w-[280px]">
776
+ <SelectTrigger className="w-full sm:w-70">
753
777
  <SelectValue placeholder={t('filters.selectAccount')} />
754
778
  </SelectTrigger>
755
779
  <SelectContent>
@@ -769,141 +793,99 @@ export default function ExtratosPage() {
769
793
  </div>
770
794
  </div>
771
795
 
772
- <div className="grid gap-4 md:grid-cols-3">
773
- <Card>
774
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
775
- <CardTitle className="text-sm font-medium">
776
- {t('cards.inflows')}
777
- </CardTitle>
778
- <ArrowUpRight className="h-4 w-4 text-green-500" />
779
- </CardHeader>
780
- <CardContent>
781
- <div className="text-2xl font-bold text-green-600">
782
- <Money value={totalEntradas} />
783
- </div>
784
- </CardContent>
785
- </Card>
786
- <Card>
787
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
788
- <CardTitle className="text-sm font-medium">
789
- {t('cards.outflows')}
790
- </CardTitle>
791
- <ArrowDownRight className="h-4 w-4 text-red-500" />
792
- </CardHeader>
793
- <CardContent>
794
- <div className="text-2xl font-bold text-red-600">
795
- <Money value={totalSaidas} />
796
- </div>
797
- </CardContent>
798
- </Card>
799
- <Card>
800
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
801
- <CardTitle className="text-sm font-medium">
802
- {t('cards.accountBalance')}
803
- </CardTitle>
804
- </CardHeader>
805
- <CardContent>
806
- <div className="text-2xl font-bold">
807
- <Money value={conta?.saldoAtual || 0} />
808
- </div>
809
- <p className="text-xs text-muted-foreground">{conta?.descricao}</p>
810
- </CardContent>
811
- </Card>
812
- </div>
796
+ <KpiCardsGrid items={summaryCards} columns={3} />
813
797
 
814
- {extratos.length > 0 ? (
815
- <Card>
816
- <CardHeader>
817
- <CardTitle>{t('table.title')}</CardTitle>
818
- <CardDescription>
819
- {t('table.foundTransactions', { count: extratos.length })}
820
- </CardDescription>
821
- </CardHeader>
822
- <CardContent>
823
- <div className="overflow-x-auto">
824
- <Table className="min-w-[760px] table-fixed">
825
- <TableHeader>
826
- <TableRow>
827
- <TableHead className="w-[110px]">
828
- {t('table.headers.date')}
829
- </TableHead>
830
- <TableHead>{t('table.headers.description')}</TableHead>
831
- <TableHead className="w-[130px] text-right">
832
- {t('table.headers.value')}
833
- </TableHead>
834
- <TableHead className="w-[110px]">
835
- {t('table.headers.type')}
836
- </TableHead>
837
- <TableHead className="w-[140px]">
838
- {t('table.headers.reconciliation')}
839
- </TableHead>
840
- </TableRow>
841
- </TableHeader>
842
- <TableBody>
843
- {extratos.map((extrato) => (
844
- <TableRow
845
- key={extrato.id}
846
- className="cursor-pointer"
847
- onClick={() => setExtratoSelecionado(extrato)}
848
- onKeyDown={(event) => {
849
- if (event.key === 'Enter' || event.key === ' ') {
850
- event.preventDefault();
851
- setExtratoSelecionado(extrato);
798
+ <FinancePageSection
799
+ title={t('table.title')}
800
+ description={t('table.foundTransactions', { count: extratos.length })}
801
+ contentClassName="p-4 sm:p-5"
802
+ >
803
+ {extratos.length > 0 ? (
804
+ <div className="overflow-x-auto">
805
+ <Table className="min-w-190 table-fixed">
806
+ <TableHeader>
807
+ <TableRow>
808
+ <TableHead className="w-27.5">
809
+ {t('table.headers.date')}
810
+ </TableHead>
811
+ <TableHead>{t('table.headers.description')}</TableHead>
812
+ <TableHead className="w-32.5 text-right">
813
+ {t('table.headers.value')}
814
+ </TableHead>
815
+ <TableHead className="w-27.5">
816
+ {t('table.headers.type')}
817
+ </TableHead>
818
+ <TableHead className="w-35">
819
+ {t('table.headers.reconciliation')}
820
+ </TableHead>
821
+ </TableRow>
822
+ </TableHeader>
823
+ <TableBody>
824
+ {extratos.map((extrato) => (
825
+ <TableRow
826
+ key={extrato.id}
827
+ className="cursor-pointer"
828
+ onClick={() => setExtratoSelecionado(extrato)}
829
+ onKeyDown={(event) => {
830
+ if (event.key === 'Enter' || event.key === ' ') {
831
+ event.preventDefault();
832
+ setExtratoSelecionado(extrato);
833
+ }
834
+ }}
835
+ role="button"
836
+ tabIndex={0}
837
+ >
838
+ <TableCell>{formatarData(extrato.data)}</TableCell>
839
+ <TableCell className="truncate" title={extrato.descricao}>
840
+ {extrato.descricao}
841
+ </TableCell>
842
+ <TableCell className="text-right">
843
+ <span
844
+ className={
845
+ extrato.tipo === 'entrada'
846
+ ? 'text-green-600'
847
+ : 'text-red-600'
852
848
  }
853
- }}
854
- role="button"
855
- tabIndex={0}
856
- >
857
- <TableCell>{formatarData(extrato.data)}</TableCell>
858
- <TableCell className="truncate" title={extrato.descricao}>
859
- {extrato.descricao}
860
- </TableCell>
861
- <TableCell className="text-right">
862
- <span
863
- className={
864
- extrato.tipo === 'entrada'
865
- ? 'text-green-600'
866
- : 'text-red-600'
867
- }
868
- >
869
- <Money value={extrato.valor} />
849
+ >
850
+ <Money value={extrato.valor} />
851
+ </span>
852
+ </TableCell>
853
+ <TableCell>
854
+ {extrato.tipo === 'entrada' ? (
855
+ <span className="flex items-center gap-1 text-green-600">
856
+ <ArrowUpRight className="h-4 w-4" />
857
+ {t('types.inflow')}
870
858
  </span>
871
- </TableCell>
872
- <TableCell>
873
- {extrato.tipo === 'entrada' ? (
874
- <span className="flex items-center gap-1 text-green-600">
875
- <ArrowUpRight className="h-4 w-4" />
876
- {t('types.inflow')}
877
- </span>
878
- ) : (
879
- <span className="flex items-center gap-1 text-red-600">
880
- <ArrowDownRight className="h-4 w-4" />
881
- {t('types.outflow')}
882
- </span>
883
- )}
884
- </TableCell>
885
- <TableCell>
886
- <StatusBadge
887
- status={extrato.statusConciliacao}
888
- type="conciliacao"
889
- />
890
- </TableCell>
891
- </TableRow>
892
- ))}
893
- </TableBody>
894
- </Table>
895
- </div>
896
- </CardContent>
897
- </Card>
898
- ) : (
899
- <EmptyState
900
- icon={<Upload className="h-12 w-12" />}
901
- title={t('empty.title')}
902
- description={t('empty.description')}
903
- actionLabel={t('importDialog.action')}
904
- onAction={() => setIsImportSheetOpen(true)}
905
- />
906
- )}
859
+ ) : (
860
+ <span className="flex items-center gap-1 text-red-600">
861
+ <ArrowDownRight className="h-4 w-4" />
862
+ {t('types.outflow')}
863
+ </span>
864
+ )}
865
+ </TableCell>
866
+ <TableCell>
867
+ <StatusBadge
868
+ status={extrato.statusConciliacao}
869
+ type="conciliacao"
870
+ />
871
+ </TableCell>
872
+ </TableRow>
873
+ ))}
874
+ </TableBody>
875
+ </Table>
876
+ </div>
877
+ ) : (
878
+ <div className="p-2 sm:p-4">
879
+ <EmptyState
880
+ icon={<Upload className="h-12 w-12" />}
881
+ title={t('empty.title')}
882
+ description={t('empty.description')}
883
+ actionLabel={t('importDialog.action')}
884
+ onAction={() => setIsImportSheetOpen(true)}
885
+ />
886
+ </div>
887
+ )}
888
+ </FinancePageSection>
907
889
 
908
890
  <Dialog
909
891
  open={!!extratoSelecionado}
@@ -1,14 +1,9 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { Page, PageHeader } from '@/components/entity-list';
4
5
  import { Button } from '@/components/ui/button';
5
- import {
6
- Card,
7
- CardContent,
8
- CardDescription,
9
- CardHeader,
10
- CardTitle,
11
- } from '@/components/ui/card';
6
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
12
7
  import { FilterBar } from '@/components/ui/filter-bar';
13
8
  import {
14
9
  Form,
@@ -347,6 +342,25 @@ export default function TransferenciasPage() {
347
342
  };
348
343
 
349
344
  const totalTransferido = transferencias.reduce((acc, t) => acc + t.valor, 0);
345
+ const summaryCards = [
346
+ {
347
+ key: 'transferred',
348
+ title: t('cards.totalTransferred'),
349
+ value: <Money value={totalTransferido} />,
350
+ description: t('cards.transferCount', { count: transferencias.length }),
351
+ icon: ArrowRight,
352
+ layout: 'compact' as const,
353
+ iconContainerClassName: 'bg-blue-500/10 text-blue-700',
354
+ accentClassName: 'from-blue-500/20 via-sky-500/10 to-transparent',
355
+ },
356
+ {
357
+ key: 'activeAccounts',
358
+ title: t('cards.activeAccounts'),
359
+ value: contasBancarias.filter((conta) => conta.ativo).length,
360
+ description: t('cards.available'),
361
+ layout: 'compact' as const,
362
+ },
363
+ ];
350
364
 
351
365
  return (
352
366
  <Page>
@@ -369,7 +383,7 @@ export default function TransferenciasPage() {
369
383
 
370
384
  <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
371
385
  <Select value={accountFilter} onValueChange={setAccountFilter}>
372
- <SelectTrigger className="w-full sm:w-[280px]">
386
+ <SelectTrigger className="w-full sm:w-70">
373
387
  <SelectValue placeholder={t('common.select')} />
374
388
  </SelectTrigger>
375
389
  <SelectContent>
@@ -391,45 +405,14 @@ export default function TransferenciasPage() {
391
405
  </div>
392
406
  </div>
393
407
 
394
- <div className="grid gap-4 md:grid-cols-2">
395
- <Card>
396
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
397
- <CardTitle className="text-sm font-medium">
398
- {t('cards.totalTransferred')}
399
- </CardTitle>
400
- </CardHeader>
401
- <CardContent>
402
- <div className="text-2xl font-bold">
403
- <Money value={totalTransferido} />
404
- </div>
405
- <p className="text-xs text-muted-foreground">
406
- {t('cards.transferCount', { count: transferencias.length })}
407
- </p>
408
- </CardContent>
409
- </Card>
410
- <Card>
411
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
412
- <CardTitle className="text-sm font-medium">
413
- {t('cards.activeAccounts')}
414
- </CardTitle>
415
- </CardHeader>
416
- <CardContent>
417
- <div className="text-2xl font-bold">
418
- {contasBancarias.filter((conta) => conta.ativo).length}
419
- </div>
420
- <p className="text-xs text-muted-foreground">
421
- {t('cards.available')}
422
- </p>
423
- </CardContent>
424
- </Card>
425
- </div>
408
+ <KpiCardsGrid items={summaryCards} columns={2} />
426
409
 
427
- <Card>
428
- <CardHeader>
429
- <CardTitle>{t('table.title')}</CardTitle>
430
- <CardDescription>{t('table.description')}</CardDescription>
431
- </CardHeader>
432
- <CardContent>
410
+ <FinancePageSection
411
+ title={t('table.title')}
412
+ description={t('table.description')}
413
+ contentClassName="p-4 sm:p-5"
414
+ >
415
+ <div className="overflow-x-auto">
433
416
  <Table>
434
417
  <TableHeader>
435
418
  <TableRow>
@@ -464,7 +447,7 @@ export default function TransferenciasPage() {
464
447
  </div>
465
448
  </TableCell>
466
449
  <TableCell className="text-center">
467
- <ArrowRight className="h-4 w-4 text-muted-foreground mx-auto" />
450
+ <ArrowRight className="mx-auto h-4 w-4 text-muted-foreground" />
468
451
  </TableCell>
469
452
  <TableCell>
470
453
  <div>
@@ -485,8 +468,8 @@ export default function TransferenciasPage() {
485
468
  })}
486
469
  </TableBody>
487
470
  </Table>
488
- </CardContent>
489
- </Card>
471
+ </div>
472
+ </FinancePageSection>
490
473
  </Page>
491
474
  );
492
475
  }
@@ -1,14 +1,10 @@
1
1
  'use client';
2
2
 
3
+ import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
3
4
  import { Page, PageHeader } from '@/components/entity-list';
4
5
  import { Button } from '@/components/ui/button';
5
- import {
6
- Card,
7
- CardContent,
8
- CardDescription,
9
- CardHeader,
10
- CardTitle,
11
- } from '@/components/ui/card';
6
+ import { Card, CardContent } from '@/components/ui/card';
7
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
12
8
  import { Money } from '@/components/ui/money';
13
9
  import {
14
10
  Select,
@@ -73,6 +69,42 @@ export default function FluxoCaixaPage() {
73
69
  const totalEntradas = entradasPrevistas.reduce((acc, e) => acc + e.valor, 0);
74
70
  const totalSaidas = saidasPrevistas.reduce((acc, s) => acc + s.valor, 0);
75
71
  const saldoFinal = kpis.saldoCaixa + totalEntradas - totalSaidas;
72
+ const summaryCards = [
73
+ {
74
+ key: 'currentBalance',
75
+ title: t('cards.currentBalance'),
76
+ value: <Money value={kpis.saldoCaixa} />,
77
+ icon: Wallet,
78
+ layout: 'compact' as const,
79
+ },
80
+ {
81
+ key: 'expectedInflows',
82
+ title: t('cards.expectedInflows'),
83
+ value: <Money value={totalEntradas} />,
84
+ icon: TrendingUp,
85
+ layout: 'compact' as const,
86
+ valueClassName: 'text-green-600',
87
+ iconContainerClassName: 'bg-green-500/10 text-green-700',
88
+ accentClassName: 'from-green-500/20 via-emerald-500/10 to-transparent',
89
+ },
90
+ {
91
+ key: 'expectedOutflows',
92
+ title: t('cards.expectedOutflows'),
93
+ value: <Money value={totalSaidas} />,
94
+ icon: TrendingDown,
95
+ layout: 'compact' as const,
96
+ valueClassName: 'text-red-600',
97
+ iconContainerClassName: 'bg-red-500/10 text-red-700',
98
+ accentClassName: 'from-red-500/20 via-rose-500/10 to-transparent',
99
+ },
100
+ {
101
+ key: 'projectedBalance',
102
+ title: t('cards.projectedBalance'),
103
+ value: <Money value={saldoFinal} />,
104
+ layout: 'compact' as const,
105
+ valueClassName: saldoFinal >= 0 ? 'text-green-600' : 'text-red-600',
106
+ },
107
+ ];
76
108
 
77
109
  return (
78
110
  <Page>
@@ -98,7 +130,7 @@ export default function FluxoCaixaPage() {
98
130
  onValueChange={setHorizonte}
99
131
  disabled={isFetching}
100
132
  >
101
- <SelectTrigger className="w-[180px]">
133
+ <SelectTrigger className="w-45">
102
134
  <SelectValue placeholder={t('filters.horizon')} />
103
135
  </SelectTrigger>
104
136
  <SelectContent>
@@ -116,7 +148,7 @@ export default function FluxoCaixaPage() {
116
148
  }
117
149
  disabled={isFetching}
118
150
  >
119
- <SelectTrigger className="w-[180px]">
151
+ <SelectTrigger className="w-45">
120
152
  <SelectValue placeholder={t('filters.scenario')} />
121
153
  </SelectTrigger>
122
154
  <SelectContent>
@@ -137,106 +169,50 @@ export default function FluxoCaixaPage() {
137
169
  ) : null}
138
170
  </div>
139
171
 
140
- <div className="grid gap-4 md:grid-cols-4">
141
- <Card>
142
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
143
- <CardTitle className="text-sm font-medium">
144
- {t('cards.currentBalance')}
145
- </CardTitle>
146
- <Wallet className="h-4 w-4 text-muted-foreground" />
147
- </CardHeader>
148
- <CardContent>
149
- <div className="text-2xl font-bold">
150
- <Money value={kpis.saldoCaixa} />
151
- </div>
152
- </CardContent>
153
- </Card>
154
- <Card>
155
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
156
- <CardTitle className="text-sm font-medium">
157
- {t('cards.expectedInflows')}
158
- </CardTitle>
159
- <TrendingUp className="h-4 w-4 text-green-500" />
160
- </CardHeader>
161
- <CardContent>
162
- <div className="text-2xl font-bold text-green-600">
163
- <Money value={totalEntradas} />
164
- </div>
165
- </CardContent>
166
- </Card>
167
- <Card>
168
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
169
- <CardTitle className="text-sm font-medium">
170
- {t('cards.expectedOutflows')}
171
- </CardTitle>
172
- <TrendingDown className="h-4 w-4 text-red-500" />
173
- </CardHeader>
174
- <CardContent>
175
- <div className="text-2xl font-bold text-red-600">
176
- <Money value={totalSaidas} />
177
- </div>
178
- </CardContent>
179
- </Card>
180
- <Card>
181
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
182
- <CardTitle className="text-sm font-medium">
183
- {t('cards.projectedBalance')}
184
- </CardTitle>
185
- </CardHeader>
186
- <CardContent>
187
- <div
188
- className={`text-2xl font-bold ${saldoFinal >= 0 ? 'text-green-600' : 'text-red-600'}`}
189
- >
190
- <Money value={saldoFinal} />
191
- </div>
192
- </CardContent>
193
- </Card>
194
- </div>
172
+ <KpiCardsGrid items={summaryCards} columns={4} />
195
173
 
196
- <Card>
197
- <CardHeader>
198
- <CardTitle>{t('projection.title')}</CardTitle>
199
- <CardDescription>{t('projection.description')}</CardDescription>
200
- </CardHeader>
201
- <CardContent>
202
- <ResponsiveContainer width="100%" height={350}>
203
- <AreaChart data={chartData}>
204
- <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
205
- <XAxis dataKey="data" tick={{ fontSize: 12 }} />
206
- <YAxis
207
- tick={{ fontSize: 12 }}
208
- tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
209
- />
210
- <Tooltip
211
- formatter={(value: number) => formatarMoeda(value)}
212
- contentStyle={{
213
- backgroundColor: 'hsl(var(--background))',
214
- border: '1px solid hsl(var(--border))',
215
- borderRadius: '8px',
216
- }}
217
- />
218
- <Legend />
219
- <Area
220
- type="monotone"
221
- dataKey="saldoPrevisto"
222
- name={t('projection.predictedBalance')}
223
- stroke="hsl(var(--primary))"
224
- fill="hsl(var(--primary))"
225
- fillOpacity={0.2}
226
- />
227
- <Area
228
- type="monotone"
229
- dataKey="saldoRealizado"
230
- name={t('projection.actualBalance')}
231
- stroke="hsl(var(--chart-2))"
232
- fill="hsl(var(--chart-2))"
233
- fillOpacity={0.2}
234
- connectNulls
235
- />
236
- </AreaChart>
237
- </ResponsiveContainer>
238
- </CardContent>
239
- </Card>
174
+ <FinancePageSection
175
+ title={t('projection.title')}
176
+ description={t('projection.description')}
177
+ contentClassName="p-4 sm:p-5"
178
+ >
179
+ <ResponsiveContainer width="100%" height={350}>
180
+ <AreaChart data={chartData}>
181
+ <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
182
+ <XAxis dataKey="data" tick={{ fontSize: 12 }} />
183
+ <YAxis
184
+ tick={{ fontSize: 12 }}
185
+ tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
186
+ />
187
+ <Tooltip
188
+ formatter={(value: number) => formatarMoeda(value)}
189
+ contentStyle={{
190
+ backgroundColor: 'hsl(var(--background))',
191
+ border: '1px solid hsl(var(--border))',
192
+ borderRadius: '8px',
193
+ }}
194
+ />
195
+ <Legend />
196
+ <Area
197
+ type="monotone"
198
+ dataKey="saldoPrevisto"
199
+ name={t('projection.predictedBalance')}
200
+ stroke="hsl(var(--primary))"
201
+ fill="hsl(var(--primary))"
202
+ fillOpacity={0.2}
203
+ />
204
+ <Area
205
+ type="monotone"
206
+ dataKey="saldoRealizado"
207
+ name={t('projection.actualBalance')}
208
+ stroke="hsl(var(--chart-2))"
209
+ fill="hsl(var(--chart-2))"
210
+ fillOpacity={0.2}
211
+ connectNulls
212
+ />
213
+ </AreaChart>
214
+ </ResponsiveContainer>
215
+ </FinancePageSection>
240
216
 
241
217
  <Tabs defaultValue="entradas">
242
218
  <TabsList>