@hed-hog/finance 0.0.279 → 0.0.285

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 (35) hide show
  1. package/dist/dto/finance-report-query.dto.d.ts +16 -0
  2. package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
  3. package/dist/dto/finance-report-query.dto.js +59 -0
  4. package/dist/dto/finance-report-query.dto.js.map +1 -0
  5. package/dist/finance-reports.controller.d.ts +71 -0
  6. package/dist/finance-reports.controller.d.ts.map +1 -0
  7. package/dist/finance-reports.controller.js +61 -0
  8. package/dist/finance-reports.controller.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +2 -0
  11. package/dist/finance.module.js.map +1 -1
  12. package/dist/finance.service.d.ts +93 -0
  13. package/dist/finance.service.d.ts.map +1 -1
  14. package/dist/finance.service.js +456 -0
  15. package/dist/finance.service.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/hedhog/data/route.yaml +27 -0
  21. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
  22. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
  23. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
  24. package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
  25. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +96 -78
  26. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +247 -130
  27. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +250 -135
  28. package/hedhog/frontend/messages/en.json +33 -2
  29. package/hedhog/frontend/messages/pt.json +33 -2
  30. package/package.json +6 -6
  31. package/src/dto/finance-report-query.dto.ts +49 -0
  32. package/src/finance-reports.controller.ts +28 -0
  33. package/src/finance.module.ts +2 -0
  34. package/src/finance.service.ts +645 -10
  35. package/src/index.ts +1 -0
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './finance-cost-centers.controller';
7
7
  export * from './finance-data.controller';
8
8
  export * from './finance-installments.controller';
9
9
  export * from './finance-period-close.controller';
10
+ export * from './finance-reports.controller';
10
11
  export * from './finance-statements.controller';
11
12
  export * from './finance-transfers.controller';
12
13
  export * from './finance.module';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,oCAAoC,CAAC;AACnD,cAAc,iCAAiC,CAAC;AAChD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ __exportStar(require("./finance-cost-centers.controller"), exports);
23
23
  __exportStar(require("./finance-data.controller"), exports);
24
24
  __exportStar(require("./finance-installments.controller"), exports);
25
25
  __exportStar(require("./finance-period-close.controller"), exports);
26
+ __exportStar(require("./finance-reports.controller"), exports);
26
27
  __exportStar(require("./finance-statements.controller"), exports);
27
28
  __exportStar(require("./finance-transfers.controller"), exports);
28
29
  __exportStar(require("./finance.module"), exports);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mEAAiD;AACjD,kEAAgD;AAChD,qEAAmD;AACnD,kEAAgD;AAChD,mEAAiD;AACjD,oEAAkD;AAClD,4DAA0C;AAC1C,oEAAkD;AAClD,oEAAkD;AAClD,kEAAgD;AAChD,iEAA+C;AAC/C,mDAAiC;AACjC,oDAAkC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mEAAiD;AACjD,kEAAgD;AAChD,qEAAmD;AACnD,kEAAgD;AAChD,mEAAiD;AACjD,oEAAkD;AAClD,4DAA0C;AAC1C,oEAAkD;AAClD,oEAAkD;AAClD,+DAA6C;AAC7C,kEAAgD;AAChD,iEAA+C;AAC/C,mDAAiC;AACjC,oDAAkC"}
@@ -7,6 +7,33 @@
7
7
  - where:
8
8
  slug: admin-finance
9
9
 
10
+ - url: /finance/reports/overview-results
11
+ method: GET
12
+ relations:
13
+ role:
14
+ - where:
15
+ slug: admin
16
+ - where:
17
+ slug: admin-finance
18
+
19
+ - url: /finance/reports/top-customers
20
+ method: GET
21
+ relations:
22
+ role:
23
+ - where:
24
+ slug: admin
25
+ - where:
26
+ slug: admin-finance
27
+
28
+ - url: /finance/reports/top-operational-expenses
29
+ method: GET
30
+ relations:
31
+ role:
32
+ - where:
33
+ slug: admin
34
+ - where:
35
+ slug: admin-finance
36
+
10
37
  - url: /finance/scenarios/:cenario
11
38
  method: PATCH
12
39
  relations:
@@ -6,7 +6,7 @@ import {
6
6
  } from '@/app/(app)/(libraries)/finance/_components/finance-entity-field-with-create';
7
7
  import { FinanceTitleActionsMenu } from '@/app/(app)/(libraries)/finance/_components/finance-title-actions-menu';
8
8
  import { PersonFieldWithCreate } from '@/app/(app)/(libraries)/contact/person/_components/person-field-with-create';
9
- import { Page, PageHeader } from '@/components/entity-list';
9
+ import { EmptyState, Page, PageHeader } from '@/components/entity-list';
10
10
  import { Button } from '@/components/ui/button';
11
11
  import { Checkbox } from '@/components/ui/checkbox';
12
12
  import { FilterBar } from '@/components/ui/filter-bar';
@@ -55,7 +55,14 @@ import {
55
55
  } from '@/components/ui/tooltip';
56
56
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
57
57
  import { zodResolver } from '@hookform/resolvers/zod';
58
- import { Loader2, Paperclip, Plus, Trash2, Upload } from 'lucide-react';
58
+ import {
59
+ FileText,
60
+ Loader2,
61
+ Paperclip,
62
+ Plus,
63
+ Trash2,
64
+ Upload,
65
+ } from 'lucide-react';
59
66
  import { useTranslations } from 'next-intl';
60
67
  import Link from 'next/link';
61
68
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
@@ -244,6 +251,8 @@ function NovoTituloSheet({
244
251
  onCreated,
245
252
  onCategoriesUpdated,
246
253
  onCostCentersUpdated,
254
+ open,
255
+ onOpenChange,
247
256
  }: {
248
257
  categorias: any[];
249
258
  centrosCusto: any[];
@@ -251,9 +260,11 @@ function NovoTituloSheet({
251
260
  onCreated: () => Promise<any> | void;
252
261
  onCategoriesUpdated?: () => Promise<any> | void;
253
262
  onCostCentersUpdated?: () => Promise<any> | void;
263
+ open?: boolean;
264
+ onOpenChange?: (open: boolean) => void;
254
265
  }) {
255
266
  const { request, showToastHandler } = useApp();
256
- const [open, setOpen] = useState(false);
267
+ const [internalOpen, setInternalOpen] = useState(false);
257
268
  const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
258
269
  const [uploadedFileName, setUploadedFileName] = useState('');
259
270
  const [isUploadingFile, setIsUploadingFile] = useState(false);
@@ -270,6 +281,13 @@ function NovoTituloSheet({
270
281
  const [extractionWarnings, setExtractionWarnings] = useState<string[]>([]);
271
282
  const [uploadProgress, setUploadProgress] = useState(0);
272
283
  const fileInputRef = useRef<HTMLInputElement | null>(null);
284
+ const isOpen = open ?? internalOpen;
285
+ const handleOpenChange = (nextOpen: boolean) => {
286
+ onOpenChange?.(nextOpen);
287
+ if (open === undefined) {
288
+ setInternalOpen(nextOpen);
289
+ }
290
+ };
273
291
 
274
292
  const normalizeFilenameForDisplay = (filename: string) => {
275
293
  if (!filename) {
@@ -441,7 +459,7 @@ function NovoTituloSheet({
441
459
  setExtractionWarnings([]);
442
460
  setIsInstallmentsEdited(false);
443
461
  setAutoRedistributeInstallments(true);
444
- setOpen(false);
462
+ handleOpenChange(false);
445
463
  showToastHandler?.('success', t('messages.createSuccess'));
446
464
  } catch {
447
465
  showToastHandler?.('error', t('messages.createError'));
@@ -457,7 +475,7 @@ function NovoTituloSheet({
457
475
  setUploadProgress(0);
458
476
  setIsInstallmentsEdited(false);
459
477
  setAutoRedistributeInstallments(true);
460
- setOpen(false);
478
+ handleOpenChange(false);
461
479
  };
462
480
 
463
481
  const clearUploadedFile = () => {
@@ -624,7 +642,7 @@ function NovoTituloSheet({
624
642
  };
625
643
 
626
644
  return (
627
- <Sheet open={open} onOpenChange={setOpen}>
645
+ <Sheet open={isOpen} onOpenChange={handleOpenChange}>
628
646
  <SheetTrigger asChild>
629
647
  <Button>
630
648
  <Plus className="mr-2 h-4 w-4" />
@@ -2126,6 +2144,7 @@ export default function TitulosPagarPage() {
2126
2144
  const [statusFilter, setStatusFilter] = useState<string>('');
2127
2145
  const [page, setPage] = useState(1);
2128
2146
  const pageSize = 10;
2147
+ const [isNewTitleSheetOpen, setIsNewTitleSheetOpen] = useState(false);
2129
2148
  const [editingTitleId, setEditingTitleId] = useState<string | null>(null);
2130
2149
  const [approvingTitleId, setApprovingTitleId] = useState<string | null>(null);
2131
2150
  const [reversingTitleId, setReversingTitleId] = useState<string | null>(null);
@@ -2464,6 +2483,8 @@ export default function TitulosPagarPage() {
2464
2483
  categorias={categorias}
2465
2484
  centrosCusto={centrosCusto}
2466
2485
  t={t}
2486
+ open={isNewTitleSheetOpen}
2487
+ onOpenChange={setIsNewTitleSheetOpen}
2467
2488
  onCreated={async () => {
2468
2489
  await Promise.all([refetchTitles(), refetchFinanceData()]);
2469
2490
  }}
@@ -2637,126 +2658,138 @@ export default function TitulosPagarPage() {
2637
2658
  </SheetContent>
2638
2659
  </Sheet>
2639
2660
 
2640
- <div className="rounded-md border">
2641
- <Table>
2642
- <TableHeader>
2643
- <TableRow>
2644
- <TableHead>{t('table.headers.document')}</TableHead>
2645
- <TableHead>{t('table.headers.supplier')}</TableHead>
2646
- <TableHead>{t('table.headers.competency')}</TableHead>
2647
- <TableHead>{t('table.headers.dueDate')}</TableHead>
2648
- <TableHead className="text-right">
2649
- {t('table.headers.value')}
2650
- </TableHead>
2651
- <TableHead>{t('table.headers.category')}</TableHead>
2652
- <TableHead>{t('table.headers.status')}</TableHead>
2653
- <TableHead className="w-[50px]" />
2654
- </TableRow>
2655
- </TableHeader>
2656
- <TableBody>
2657
- {titulosPagar.map((titulo) => {
2658
- const fornecedor = getPessoaById(titulo.fornecedorId);
2659
- const categoria = getCategoriaById(titulo.categoriaId);
2660
- const proximaParcela = titulo.parcelas.find(
2661
- (p: any) => p.status === 'aberto' || p.status === 'vencido'
2662
- );
2663
-
2664
- return (
2665
- <TableRow key={titulo.id}>
2666
- <TableCell className="font-medium">
2667
- <Link
2668
- href={`/finance/accounts-payable/installments/${titulo.id}`}
2669
- className="hover:underline"
2670
- >
2671
- {titulo.documento}
2672
- </Link>
2673
- {titulo.anexos.length > 0 && (
2674
- <Button
2675
- type="button"
2676
- variant="ghost"
2677
- size="icon"
2678
- className="ml-1 inline-flex h-5 w-5 align-middle text-muted-foreground"
2679
- onClick={(event) => {
2680
- event.preventDefault();
2681
- event.stopPropagation();
2682
- const firstAttachmentId =
2683
- titulo.anexosDetalhes?.[0]?.id;
2684
- void handleOpenAttachment(firstAttachmentId);
2685
- }}
2686
- aria-label={t('table.actions.openAttachment')}
2661
+ {titulosPagar.length > 0 ? (
2662
+ <div className="rounded-md border">
2663
+ <Table>
2664
+ <TableHeader>
2665
+ <TableRow>
2666
+ <TableHead>{t('table.headers.document')}</TableHead>
2667
+ <TableHead>{t('table.headers.supplier')}</TableHead>
2668
+ <TableHead>{t('table.headers.competency')}</TableHead>
2669
+ <TableHead>{t('table.headers.dueDate')}</TableHead>
2670
+ <TableHead className="text-right">
2671
+ {t('table.headers.value')}
2672
+ </TableHead>
2673
+ <TableHead>{t('table.headers.category')}</TableHead>
2674
+ <TableHead>{t('table.headers.status')}</TableHead>
2675
+ <TableHead className="w-[50px]" />
2676
+ </TableRow>
2677
+ </TableHeader>
2678
+ <TableBody>
2679
+ {titulosPagar.map((titulo) => {
2680
+ const fornecedor = getPessoaById(titulo.fornecedorId);
2681
+ const categoria = getCategoriaById(titulo.categoriaId);
2682
+ const proximaParcela = titulo.parcelas.find(
2683
+ (p: any) => p.status === 'aberto' || p.status === 'vencido'
2684
+ );
2685
+
2686
+ return (
2687
+ <TableRow key={titulo.id}>
2688
+ <TableCell className="font-medium">
2689
+ <Link
2690
+ href={`/finance/accounts-payable/installments/${titulo.id}`}
2691
+ className="hover:underline"
2687
2692
  >
2688
- <Paperclip className="h-3 w-3" />
2689
- </Button>
2690
- )}
2691
- </TableCell>
2692
- <TableCell>{fornecedor?.nome}</TableCell>
2693
- <TableCell>{titulo.competencia}</TableCell>
2694
- <TableCell>
2695
- {proximaParcela
2696
- ? formatarData(proximaParcela.vencimento)
2697
- : '-'}
2698
- </TableCell>
2699
- <TableCell className="text-right">
2700
- <Money value={titulo.valorTotal} />
2701
- </TableCell>
2702
- <TableCell>{categoria?.nome}</TableCell>
2703
- <TableCell>
2704
- <StatusBadge status={titulo.status} />
2705
- </TableCell>
2706
- <TableCell>
2707
- <FinanceTitleActionsMenu
2708
- triggerVariant="ghost"
2709
- detailHref={`/finance/accounts-payable/installments/${titulo.id}`}
2710
- canEdit={canEditTitle(titulo.status)}
2711
- canApprove={canApproveTitle(titulo.status)}
2712
- canSettle={canSettleTitle(titulo.status)}
2713
- canReverse={
2714
- canReverseTitle(titulo.status) &&
2715
- !!getFirstActiveSettlementId(titulo)
2716
- }
2717
- canCancel={canCancelTitle(titulo.status)}
2718
- isApproving={approvingTitleId === titulo.id}
2719
- isReversing={reversingTitleId === titulo.id}
2720
- isCanceling={cancelingTitleId === titulo.id}
2721
- labels={{
2722
- menu: t.has('actions.title')
2723
- ? t('actions.title')
2724
- : t('table.actions.srActions'),
2725
- srActions: t('table.actions.srActions'),
2726
- viewDetails: t('table.actions.viewDetails'),
2727
- edit: t('table.actions.edit'),
2728
- approve: t('table.actions.approve'),
2729
- settle: t('table.actions.settle'),
2730
- reverse: t('table.actions.reverse'),
2731
- cancel: t('table.actions.cancel'),
2732
- }}
2733
- dialogs={{
2734
- cancelTitle: t('dialogs.cancel.title'),
2735
- cancelDescription: t('dialogs.cancel.description'),
2736
- cancelButton: t('dialogs.cancel.cancel'),
2737
- confirmCancelButton: t('dialogs.cancel.confirm'),
2738
- reverseTitle: t('dialogs.reverse.title'),
2739
- reverseDescription: t('dialogs.reverse.description'),
2740
- reverseReasonLabel: t('dialogs.reverse.reasonLabel'),
2741
- reverseReasonPlaceholder: t(
2742
- 'dialogs.reverse.reasonPlaceholder'
2743
- ),
2744
- reverseButton: t('dialogs.reverse.cancel'),
2745
- confirmReverseButton: t('dialogs.reverse.confirm'),
2746
- }}
2747
- onEdit={() => setEditingTitleId(titulo.id)}
2748
- onApprove={() => void handleApproveTitle(titulo.id)}
2749
- onSettle={() => handleSettleTitle(titulo)}
2750
- onReverse={(reason) => handleReverseTitle(titulo, reason)}
2751
- onCancel={() => handleCancelTitle(titulo.id)}
2752
- />
2753
- </TableCell>
2754
- </TableRow>
2755
- );
2756
- })}
2757
- </TableBody>
2758
- </Table>
2759
- </div>
2693
+ {titulo.documento}
2694
+ </Link>
2695
+ {titulo.anexos.length > 0 && (
2696
+ <Button
2697
+ type="button"
2698
+ variant="ghost"
2699
+ size="icon"
2700
+ className="ml-1 inline-flex h-5 w-5 align-middle text-muted-foreground"
2701
+ onClick={(event) => {
2702
+ event.preventDefault();
2703
+ event.stopPropagation();
2704
+ const firstAttachmentId =
2705
+ titulo.anexosDetalhes?.[0]?.id;
2706
+ void handleOpenAttachment(firstAttachmentId);
2707
+ }}
2708
+ aria-label={t('table.actions.openAttachment')}
2709
+ >
2710
+ <Paperclip className="h-3 w-3" />
2711
+ </Button>
2712
+ )}
2713
+ </TableCell>
2714
+ <TableCell>{fornecedor?.nome}</TableCell>
2715
+ <TableCell>{titulo.competencia}</TableCell>
2716
+ <TableCell>
2717
+ {proximaParcela
2718
+ ? formatarData(proximaParcela.vencimento)
2719
+ : '-'}
2720
+ </TableCell>
2721
+ <TableCell className="text-right">
2722
+ <Money value={titulo.valorTotal} />
2723
+ </TableCell>
2724
+ <TableCell>{categoria?.nome}</TableCell>
2725
+ <TableCell>
2726
+ <StatusBadge status={titulo.status} />
2727
+ </TableCell>
2728
+ <TableCell>
2729
+ <FinanceTitleActionsMenu
2730
+ triggerVariant="ghost"
2731
+ detailHref={`/finance/accounts-payable/installments/${titulo.id}`}
2732
+ canEdit={canEditTitle(titulo.status)}
2733
+ canApprove={canApproveTitle(titulo.status)}
2734
+ canSettle={canSettleTitle(titulo.status)}
2735
+ canReverse={
2736
+ canReverseTitle(titulo.status) &&
2737
+ !!getFirstActiveSettlementId(titulo)
2738
+ }
2739
+ canCancel={canCancelTitle(titulo.status)}
2740
+ isApproving={approvingTitleId === titulo.id}
2741
+ isReversing={reversingTitleId === titulo.id}
2742
+ isCanceling={cancelingTitleId === titulo.id}
2743
+ labels={{
2744
+ menu: t.has('actions.title')
2745
+ ? t('actions.title')
2746
+ : t('table.actions.srActions'),
2747
+ srActions: t('table.actions.srActions'),
2748
+ viewDetails: t('table.actions.viewDetails'),
2749
+ edit: t('table.actions.edit'),
2750
+ approve: t('table.actions.approve'),
2751
+ settle: t('table.actions.settle'),
2752
+ reverse: t('table.actions.reverse'),
2753
+ cancel: t('table.actions.cancel'),
2754
+ }}
2755
+ dialogs={{
2756
+ cancelTitle: t('dialogs.cancel.title'),
2757
+ cancelDescription: t('dialogs.cancel.description'),
2758
+ cancelButton: t('dialogs.cancel.cancel'),
2759
+ confirmCancelButton: t('dialogs.cancel.confirm'),
2760
+ reverseTitle: t('dialogs.reverse.title'),
2761
+ reverseDescription: t('dialogs.reverse.description'),
2762
+ reverseReasonLabel: t('dialogs.reverse.reasonLabel'),
2763
+ reverseReasonPlaceholder: t(
2764
+ 'dialogs.reverse.reasonPlaceholder'
2765
+ ),
2766
+ reverseButton: t('dialogs.reverse.cancel'),
2767
+ confirmReverseButton: t('dialogs.reverse.confirm'),
2768
+ }}
2769
+ onEdit={() => setEditingTitleId(titulo.id)}
2770
+ onApprove={() => void handleApproveTitle(titulo.id)}
2771
+ onSettle={() => handleSettleTitle(titulo)}
2772
+ onReverse={(reason) =>
2773
+ handleReverseTitle(titulo, reason)
2774
+ }
2775
+ onCancel={() => handleCancelTitle(titulo.id)}
2776
+ />
2777
+ </TableCell>
2778
+ </TableRow>
2779
+ );
2780
+ })}
2781
+ </TableBody>
2782
+ </Table>
2783
+ </div>
2784
+ ) : (
2785
+ <EmptyState
2786
+ icon={<FileText className="h-12 w-12" />}
2787
+ title={t('empty.title')}
2788
+ description={t('empty.description')}
2789
+ actionLabel={t('newTitle.action')}
2790
+ onAction={() => setIsNewTitleSheetOpen(true)}
2791
+ />
2792
+ )}
2760
2793
 
2761
2794
  <div className="flex items-center justify-between">
2762
2795
  <p className="text-sm text-muted-foreground">
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Page, PageHeader } from '@/components/entity-list';
3
+ import { EmptyState, Page, PageHeader } from '@/components/entity-list';
4
4
  import { RichTextEditor } from '@/components/rich-text-editor';
5
5
  import { Button } from '@/components/ui/button';
6
6
  import {
@@ -548,94 +548,108 @@ export default function CobrancaPage() {
548
548
  <CardDescription>{t('table.description')}</CardDescription>
549
549
  </CardHeader>
550
550
  <CardContent>
551
- <Table>
552
- <TableHeader>
553
- <TableRow>
554
- <TableHead>{t('table.headers.client')}</TableHead>
555
- <TableHead className="text-right">
556
- {t('table.headers.range0to30')}
557
- </TableHead>
558
- <TableHead className="text-right">
559
- {t('table.headers.range31to60')}
560
- </TableHead>
561
- <TableHead className="text-right">
562
- {t('table.headers.range61to90')}
563
- </TableHead>
564
- <TableHead className="text-right">
565
- {t('table.headers.range90plus')}
566
- </TableHead>
567
- <TableHead className="text-right">
568
- {t('table.headers.total')}
569
- </TableHead>
570
- <TableHead className="text-right">
571
- {t('table.headers.actions')}
572
- </TableHead>
573
- </TableRow>
574
- </TableHeader>
575
- <TableBody>
576
- {agingInadimplencia.map((item) => (
577
- <TableRow key={item.clienteId}>
578
- <TableCell className="font-medium">{item.cliente}</TableCell>
579
- <TableCell className="text-right">
580
- {item.bucket0_30 > 0 ? (
581
- <Money value={item.bucket0_30} />
582
- ) : (
583
- '-'
584
- )}
585
- </TableCell>
586
- <TableCell className="text-right">
587
- {item.bucket31_60 > 0 ? (
588
- <Money value={item.bucket31_60} />
589
- ) : (
590
- '-'
591
- )}
592
- </TableCell>
593
- <TableCell className="text-right">
594
- {item.bucket61_90 > 0 ? (
595
- <Money value={item.bucket61_90} />
596
- ) : (
597
- '-'
598
- )}
599
- </TableCell>
600
- <TableCell className="text-right">
601
- {item.bucket90plus > 0 ? (
602
- <span className="font-medium text-destructive">
603
- <Money value={item.bucket90plus} />
604
- </span>
605
- ) : (
606
- '-'
607
- )}
608
- </TableCell>
609
- <TableCell className="text-right font-semibold">
610
- <Money value={item.total} />
611
- </TableCell>
612
- <TableCell>
613
- <div className="flex justify-end gap-2">
614
- <HistoricoContatosSheet
615
- cliente={item.cliente}
616
- historicoContatos={historicoContatos.filter(
617
- (contato) => contato.clienteId === item.clienteId
618
- )}
619
- t={t}
620
- />
621
- <EnviarCobrancaDialog
622
- cliente={item.cliente}
623
- clienteId={item.clienteId}
624
- onSuccess={refetch}
625
- t={t}
626
- />
627
- <RegistrarAcordoDialog
628
- cliente={item.cliente}
629
- clienteId={item.clienteId}
630
- onSuccess={refetch}
631
- t={t}
632
- />
633
- </div>
634
- </TableCell>
551
+ {agingInadimplencia.length > 0 ? (
552
+ <Table>
553
+ <TableHeader>
554
+ <TableRow>
555
+ <TableHead>{t('table.headers.client')}</TableHead>
556
+ <TableHead className="text-right">
557
+ {t('table.headers.range0to30')}
558
+ </TableHead>
559
+ <TableHead className="text-right">
560
+ {t('table.headers.range31to60')}
561
+ </TableHead>
562
+ <TableHead className="text-right">
563
+ {t('table.headers.range61to90')}
564
+ </TableHead>
565
+ <TableHead className="text-right">
566
+ {t('table.headers.range90plus')}
567
+ </TableHead>
568
+ <TableHead className="text-right">
569
+ {t('table.headers.total')}
570
+ </TableHead>
571
+ <TableHead className="text-right">
572
+ {t('table.headers.actions')}
573
+ </TableHead>
635
574
  </TableRow>
636
- ))}
637
- </TableBody>
638
- </Table>
575
+ </TableHeader>
576
+ <TableBody>
577
+ {agingInadimplencia.map((item) => (
578
+ <TableRow key={item.clienteId}>
579
+ <TableCell className="font-medium">
580
+ {item.cliente}
581
+ </TableCell>
582
+ <TableCell className="text-right">
583
+ {item.bucket0_30 > 0 ? (
584
+ <Money value={item.bucket0_30} />
585
+ ) : (
586
+ '-'
587
+ )}
588
+ </TableCell>
589
+ <TableCell className="text-right">
590
+ {item.bucket31_60 > 0 ? (
591
+ <Money value={item.bucket31_60} />
592
+ ) : (
593
+ '-'
594
+ )}
595
+ </TableCell>
596
+ <TableCell className="text-right">
597
+ {item.bucket61_90 > 0 ? (
598
+ <Money value={item.bucket61_90} />
599
+ ) : (
600
+ '-'
601
+ )}
602
+ </TableCell>
603
+ <TableCell className="text-right">
604
+ {item.bucket90plus > 0 ? (
605
+ <span className="font-medium text-destructive">
606
+ <Money value={item.bucket90plus} />
607
+ </span>
608
+ ) : (
609
+ '-'
610
+ )}
611
+ </TableCell>
612
+ <TableCell className="text-right font-semibold">
613
+ <Money value={item.total} />
614
+ </TableCell>
615
+ <TableCell>
616
+ <div className="flex justify-end gap-2">
617
+ <HistoricoContatosSheet
618
+ cliente={item.cliente}
619
+ historicoContatos={historicoContatos.filter(
620
+ (contato) => contato.clienteId === item.clienteId
621
+ )}
622
+ t={t}
623
+ />
624
+ <EnviarCobrancaDialog
625
+ cliente={item.cliente}
626
+ clienteId={item.clienteId}
627
+ onSuccess={refetch}
628
+ t={t}
629
+ />
630
+ <RegistrarAcordoDialog
631
+ cliente={item.cliente}
632
+ clienteId={item.clienteId}
633
+ onSuccess={refetch}
634
+ t={t}
635
+ />
636
+ </div>
637
+ </TableCell>
638
+ </TableRow>
639
+ ))}
640
+ </TableBody>
641
+ </Table>
642
+ ) : (
643
+ <EmptyState
644
+ icon={<FileText className="h-12 w-12" />}
645
+ title={t('empty.title')}
646
+ description={t('empty.description')}
647
+ actionLabel={t('empty.action')}
648
+ onAction={() => {
649
+ void refetch();
650
+ }}
651
+ />
652
+ )}
639
653
  </CardContent>
640
654
  </Card>
641
655
  </Page>