@hed-hog/finance 0.0.301 → 0.0.302

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,6 @@
1
- 'use client';
2
-
3
- import { EmptyState, Page, PageHeader } from '@/components/entity-list';
1
+ 'use client';
2
+
3
+ import { EmptyState, Page, PageHeader } from '@/components/entity-list';
4
4
  import {
5
5
  AlertDialog,
6
6
  AlertDialogAction,
@@ -13,14 +13,14 @@ import {
13
13
  } from '@/components/ui/alert-dialog';
14
14
  import { Badge } from '@/components/ui/badge';
15
15
  import { Button } from '@/components/ui/button';
16
- import {
17
- Card,
18
- CardContent,
19
- CardDescription,
20
- CardHeader,
21
- CardTitle,
22
- } from '@/components/ui/card';
23
- import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
16
+ import {
17
+ Card,
18
+ CardContent,
19
+ CardDescription,
20
+ CardHeader,
21
+ CardTitle,
22
+ } from '@/components/ui/card';
23
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
24
24
  import {
25
25
  Form,
26
26
  FormControl,
@@ -39,18 +39,18 @@ import {
39
39
  SelectTrigger,
40
40
  SelectValue,
41
41
  } from '@/components/ui/select';
42
- import {
43
- Sheet,
44
- SheetContent,
45
- SheetDescription,
46
- SheetHeader,
47
- SheetTitle,
48
- } from '@/components/ui/sheet';
49
- import {
50
- FinancePageSection,
51
- FinanceSheetBody,
52
- FinanceSheetSection,
53
- } from '../../_components/finance-layout';
42
+ import {
43
+ Sheet,
44
+ SheetContent,
45
+ SheetDescription,
46
+ SheetHeader,
47
+ SheetTitle,
48
+ } from '@/components/ui/sheet';
49
+ import {
50
+ FinancePageSection,
51
+ FinanceSheetBody,
52
+ FinanceSheetSection,
53
+ } from '../../_components/finance-layout';
54
54
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
55
55
  import { zodResolver } from '@hookform/resolvers/zod';
56
56
  import {
@@ -475,52 +475,33 @@ function NovaContaSheet({
475
475
  }
476
476
  }}
477
477
  >
478
- <SheetContent className="flex h-full w-full flex-col sm:max-w-2xl">
478
+ <SheetContent className="flex h-full w-full flex-col sm:max-w-2xl">
479
479
  <SheetHeader>
480
480
  <SheetTitle>
481
481
  {editingAccount ? t('common.edit') : t('newAccount.title')}
482
482
  </SheetTitle>
483
483
  <SheetDescription>{t('newAccount.description')}</SheetDescription>
484
484
  </SheetHeader>
485
- <Form {...form}>
486
- <form
487
- className="flex h-full flex-col"
488
- onSubmit={form.handleSubmit(handleSubmit)}
489
- >
490
- <FinanceSheetBody>
491
- <FinanceSheetSection
492
- title={t('sections.accountData.title')}
493
- description={t('sections.accountData.description')}
494
- >
495
- <FormField
496
- control={form.control}
497
- name="banco"
498
- render={({ field }) => (
499
- <FormItem>
500
- <FormLabel>{t('fields.bank')}</FormLabel>
501
- <FormControl>
502
- <Input
503
- placeholder={t('fields.bankPlaceholder')}
504
- {...field}
505
- />
506
- </FormControl>
507
- <FormMessage />
508
- </FormItem>
509
- )}
510
- />
511
-
512
- <div className="grid gap-4 md:grid-cols-2">
485
+ <Form {...form}>
486
+ <form
487
+ className="flex h-full flex-col"
488
+ onSubmit={form.handleSubmit(handleSubmit)}
489
+ >
490
+ <FinanceSheetBody>
491
+ <FinanceSheetSection
492
+ title={t('sections.accountData.title')}
493
+ description={t('sections.accountData.description')}
494
+ >
513
495
  <FormField
514
496
  control={form.control}
515
- name="agencia"
497
+ name="banco"
516
498
  render={({ field }) => (
517
499
  <FormItem>
518
- <FormLabel>{t('fields.branch')}</FormLabel>
500
+ <FormLabel>{t('fields.bank')}</FormLabel>
519
501
  <FormControl>
520
502
  <Input
521
- placeholder="0000"
503
+ placeholder={t('fields.bankPlaceholder')}
522
504
  {...field}
523
- value={field.value || ''}
524
505
  />
525
506
  </FormControl>
526
507
  <FormMessage />
@@ -528,15 +509,114 @@ function NovaContaSheet({
528
509
  )}
529
510
  />
530
511
 
512
+ <div className="grid gap-4 md:grid-cols-2">
513
+ <FormField
514
+ control={form.control}
515
+ name="agencia"
516
+ render={({ field }) => (
517
+ <FormItem>
518
+ <FormLabel>{t('fields.branch')}</FormLabel>
519
+ <FormControl>
520
+ <Input
521
+ placeholder="0000"
522
+ {...field}
523
+ value={field.value || ''}
524
+ />
525
+ </FormControl>
526
+ <FormMessage />
527
+ </FormItem>
528
+ )}
529
+ />
530
+
531
+ <FormField
532
+ control={form.control}
533
+ name="conta"
534
+ render={({ field }) => (
535
+ <FormItem>
536
+ <FormLabel>{t('fields.account')}</FormLabel>
537
+ <FormControl>
538
+ <Input
539
+ placeholder="00000-0"
540
+ {...field}
541
+ value={field.value || ''}
542
+ />
543
+ </FormControl>
544
+ <FormMessage />
545
+ </FormItem>
546
+ )}
547
+ />
548
+ </div>
549
+
550
+ <div className="grid gap-4 md:grid-cols-2">
551
+ <FormField
552
+ control={form.control}
553
+ name="tipo"
554
+ render={({ field }) => (
555
+ <FormItem>
556
+ <FormLabel>{t('fields.type')}</FormLabel>
557
+ <Select
558
+ value={field.value}
559
+ onValueChange={field.onChange}
560
+ >
561
+ <FormControl>
562
+ <SelectTrigger className="w-full">
563
+ <SelectValue placeholder={t('common.select')} />
564
+ </SelectTrigger>
565
+ </FormControl>
566
+ <SelectContent>
567
+ <SelectItem value="corrente">
568
+ {t('types.corrente')}
569
+ </SelectItem>
570
+ <SelectItem value="poupanca">
571
+ {t('types.poupanca')}
572
+ </SelectItem>
573
+ <SelectItem value="investimento">
574
+ {t('types.investimento')}
575
+ </SelectItem>
576
+ <SelectItem value="caixa">
577
+ {t('types.caixa')}
578
+ </SelectItem>
579
+ </SelectContent>
580
+ </Select>
581
+ <FormMessage />
582
+ </FormItem>
583
+ )}
584
+ />
585
+
586
+ <FormField
587
+ control={form.control}
588
+ name="saldoInicial"
589
+ render={({ field }) => (
590
+ <FormItem>
591
+ <FormLabel>{t('fields.initialBalance')}</FormLabel>
592
+ <FormControl>
593
+ <InputMoney
594
+ ref={field.ref}
595
+ name={field.name}
596
+ value={field.value}
597
+ onBlur={field.onBlur}
598
+ onValueChange={(value) =>
599
+ field.onChange(value ?? 0)
600
+ }
601
+ placeholder="0,00"
602
+ disabled={!!editingAccount}
603
+ />
604
+ </FormControl>
605
+ <FormMessage />
606
+ </FormItem>
607
+ )}
608
+ />
609
+ </div>
610
+
531
611
  <FormField
532
612
  control={form.control}
533
- name="conta"
613
+ name="descricao"
534
614
  render={({ field }) => (
535
615
  <FormItem>
536
- <FormLabel>{t('fields.account')}</FormLabel>
616
+ <FormLabel>{t('fields.description')}</FormLabel>
537
617
  <FormControl>
538
618
  <Input
539
- placeholder="00000-0"
619
+ placeholder={t('fields.descriptionPlaceholder')}
540
620
  {...field}
541
621
  value={field.value || ''}
542
622
  />
@@ -545,156 +625,78 @@ function NovaContaSheet({
545
625
  </FormItem>
546
626
  )}
547
627
  />
548
- </div>
628
+ </FinanceSheetSection>
549
629
 
550
- <div className="grid gap-4 md:grid-cols-2">
551
- <FormField
552
- control={form.control}
553
- name="tipo"
554
- render={({ field }) => (
555
- <FormItem>
556
- <FormLabel>{t('fields.type')}</FormLabel>
557
- <Select
558
- value={field.value}
559
- onValueChange={field.onChange}
560
- >
561
- <FormControl>
562
- <SelectTrigger className="w-full">
563
- <SelectValue placeholder={t('common.select')} />
564
- </SelectTrigger>
565
- </FormControl>
566
- <SelectContent>
567
- <SelectItem value="corrente">
568
- {t('types.corrente')}
569
- </SelectItem>
570
- <SelectItem value="poupanca">
571
- {t('types.poupanca')}
572
- </SelectItem>
573
- <SelectItem value="investimento">
574
- {t('types.investimento')}
575
- </SelectItem>
576
- <SelectItem value="caixa">
577
- {t('types.caixa')}
578
- </SelectItem>
579
- </SelectContent>
580
- </Select>
581
- <FormMessage />
582
- </FormItem>
583
- )}
584
- />
585
-
586
- <FormField
587
- control={form.control}
588
- name="saldoInicial"
589
- render={({ field }) => (
590
- <FormItem>
591
- <FormLabel>{t('fields.initialBalance')}</FormLabel>
592
- <FormControl>
593
- <InputMoney
594
- ref={field.ref}
595
- name={field.name}
596
- value={field.value}
597
- onBlur={field.onBlur}
598
- onValueChange={(value) => field.onChange(value ?? 0)}
599
- placeholder="0,00"
600
- disabled={!!editingAccount}
630
+ <FinanceSheetSection
631
+ title={t('sections.logo.title')}
632
+ description={t('sections.logo.description')}
633
+ >
634
+ <FormItem>
635
+ <FormLabel>{t('fields.logo')}</FormLabel>
636
+ <div className="flex flex-col gap-4 rounded-xl border border-dashed border-border/70 bg-muted/20 p-4 sm:flex-row sm:items-start">
637
+ <div className="flex h-20 w-20 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
638
+ {logoPreviewUrl ? (
639
+ <img
640
+ src={logoPreviewUrl}
641
+ alt={t('fields.logo')}
642
+ className="h-full w-full object-cover"
601
643
  />
602
- </FormControl>
603
- <FormMessage />
604
- </FormItem>
605
- )}
606
- />
607
- </div>
608
-
609
- <FormField
610
- control={form.control}
611
- name="descricao"
612
- render={({ field }) => (
613
- <FormItem>
614
- <FormLabel>{t('fields.description')}</FormLabel>
615
- <FormControl>
616
- <Input
617
- placeholder={t('fields.descriptionPlaceholder')}
618
- {...field}
619
- value={field.value || ''}
644
+ ) : (
645
+ <Landmark className="h-8 w-8 text-muted-foreground" />
646
+ )}
647
+ </div>
648
+
649
+ <div className="flex-1 space-y-2">
650
+ <input
651
+ ref={logoInputRef}
652
+ type="file"
653
+ accept="image/*"
654
+ className="hidden"
655
+ onChange={handleLogoUpload}
620
656
  />
621
- </FormControl>
622
- <FormMessage />
623
- </FormItem>
624
- )}
625
- />
626
- </FinanceSheetSection>
627
-
628
- <FinanceSheetSection
629
- title={t('sections.logo.title')}
630
- description={t('sections.logo.description')}
631
- >
632
- <FormItem>
633
- <FormLabel>{t('fields.logo')}</FormLabel>
634
- <div className="flex flex-col gap-4 rounded-xl border border-dashed border-border/70 bg-muted/20 p-4 sm:flex-row sm:items-start">
635
- <div className="flex h-20 w-20 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
636
- {logoPreviewUrl ? (
637
- <img
638
- src={logoPreviewUrl}
639
- alt={t('fields.logo')}
640
- className="h-full w-full object-cover"
641
- />
642
- ) : (
643
- <Landmark className="h-8 w-8 text-muted-foreground" />
644
- )}
645
- </div>
646
-
647
- <div className="flex-1 space-y-2">
648
- <input
649
- ref={logoInputRef}
650
- type="file"
651
- accept="image/*"
652
- className="hidden"
653
- onChange={handleLogoUpload}
654
- />
655
-
656
- <div className="flex flex-wrap gap-2">
657
- <Button
658
- type="button"
659
- variant="outline"
660
- onClick={handleSelectLogo}
661
- disabled={isUploadingLogo}
662
- >
663
- <Upload className="mr-2 h-4 w-4" />
664
- {t('fields.logoAction')}
665
- </Button>
666
-
667
- {logoFileId ? (
668
- <Button
669
- type="button"
670
- variant="ghost"
671
- onClick={() => void handleRemoveLogo()}
672
- disabled={isUploadingLogo}
673
- >
674
- <Trash2 className="mr-2 h-4 w-4" />
675
- {t('fields.logoRemove')}
676
- </Button>
677
- ) : null}
678
- </div>
679
-
680
- <p className="text-xs text-muted-foreground">
681
- {t('fields.logoHint')}
682
- </p>
683
-
684
- {isUploadingLogo ? (
685
- <p className="text-xs text-muted-foreground">
686
- {t('fields.logoUploading', {
687
- progress: logoUploadProgress,
688
- })}
689
- </p>
690
- ) : null}
691
- </div>
692
- </div>
693
- </FormItem>
694
- </FinanceSheetSection>
695
- </FinanceSheetBody>
696
-
697
- <div className="flex justify-end gap-2 border-t px-4 py-4 sm:px-6">
657
+
658
+ <div className="flex flex-wrap gap-2">
659
+ <Button
660
+ type="button"
661
+ variant="outline"
662
+ onClick={handleSelectLogo}
663
+ disabled={isUploadingLogo}
664
+ >
665
+ <Upload className="mr-2 h-4 w-4" />
666
+ {t('fields.logoAction')}
667
+ </Button>
668
+
669
+ {logoFileId ? (
670
+ <Button
671
+ type="button"
672
+ variant="ghost"
673
+ onClick={() => void handleRemoveLogo()}
674
+ disabled={isUploadingLogo}
675
+ >
676
+ <Trash2 className="mr-2 h-4 w-4" />
677
+ {t('fields.logoRemove')}
678
+ </Button>
679
+ ) : null}
680
+ </div>
681
+
682
+ <p className="text-xs text-muted-foreground">
683
+ {t('fields.logoHint')}
684
+ </p>
685
+
686
+ {isUploadingLogo ? (
687
+ <p className="text-xs text-muted-foreground">
688
+ {t('fields.logoUploading', {
689
+ progress: logoUploadProgress,
690
+ })}
691
+ </p>
692
+ ) : null}
693
+ </div>
694
+ </div>
695
+ </FormItem>
696
+ </FinanceSheetSection>
697
+ </FinanceSheetBody>
698
+
699
+ <div className="flex justify-end gap-2 border-t px-4 py-4 sm:px-6">
698
700
  <Button type="button" variant="outline" onClick={handleCancel}>
699
701
  {t('common.cancel')}
700
702
  </Button>
@@ -764,47 +766,47 @@ export default function ContasBancariasPage() {
764
766
  .filter((c) => c.ativo)
765
767
  .reduce((acc, c) => acc + c.saldoAtual, 0);
766
768
 
767
- const saldoConciliadoTotal = accounts
768
- .filter((c) => c.ativo)
769
- .reduce((acc, c) => acc + c.saldoConciliado, 0);
770
- const activeAccountsCount = accounts.filter((c) => c.ativo).length;
771
- const inactiveAccountsCount = accounts.length - activeAccountsCount;
772
- const summaryCards = [
773
- {
774
- key: 'balance',
775
- title: t('cards.totalBalance'),
776
- value: <Money value={saldoTotal} />,
777
- description: t('cards.activeAccounts', {
778
- count: activeAccountsCount,
779
- }),
780
- icon: Landmark,
781
- layout: 'compact' as const,
782
- },
783
- {
784
- key: 'reconciled',
785
- title: t('cards.reconciledBalance'),
786
- value: <Money value={saldoConciliadoTotal} />,
787
- description: `${t('cards.difference')}: ${new Intl.NumberFormat(
788
- currentLocaleCode === 'pt' ? 'pt-BR' : 'en-US',
789
- {
790
- style: 'currency',
791
- currency: 'BRL',
792
- }
793
- ).format(saldoTotal - saldoConciliadoTotal)}`,
794
- icon: RefreshCw,
795
- layout: 'compact' as const,
796
- },
797
- {
798
- key: 'accounts',
799
- title: t('cards.accountsOverview'),
800
- value: activeAccountsCount,
801
- description: t('cards.inactiveAccounts', {
802
- count: inactiveAccountsCount,
803
- }),
804
- icon: Building2,
805
- layout: 'compact' as const,
806
- },
807
- ];
769
+ const saldoConciliadoTotal = accounts
770
+ .filter((c) => c.ativo)
771
+ .reduce((acc, c) => acc + c.saldoConciliado, 0);
772
+ const activeAccountsCount = accounts.filter((c) => c.ativo).length;
773
+ const inactiveAccountsCount = accounts.length - activeAccountsCount;
774
+ const summaryCards = [
775
+ {
776
+ key: 'balance',
777
+ title: t('cards.totalBalance'),
778
+ value: <Money value={saldoTotal} />,
779
+ description: t('cards.activeAccounts', {
780
+ count: activeAccountsCount,
781
+ }),
782
+ icon: Landmark,
783
+ layout: 'compact' as const,
784
+ },
785
+ {
786
+ key: 'reconciled',
787
+ title: t('cards.reconciledBalance'),
788
+ value: <Money value={saldoConciliadoTotal} />,
789
+ description: `${t('cards.difference')}: ${new Intl.NumberFormat(
790
+ currentLocaleCode === 'pt' ? 'pt-BR' : 'en-US',
791
+ {
792
+ style: 'currency',
793
+ currency: 'BRL',
794
+ }
795
+ ).format(saldoTotal - saldoConciliadoTotal)}`,
796
+ icon: RefreshCw,
797
+ layout: 'compact' as const,
798
+ },
799
+ {
800
+ key: 'accounts',
801
+ title: t('cards.accountsOverview'),
802
+ value: activeAccountsCount,
803
+ description: t('cards.inactiveAccounts', {
804
+ count: inactiveAccountsCount,
805
+ }),
806
+ icon: Building2,
807
+ layout: 'compact' as const,
808
+ },
809
+ ];
808
810
 
809
811
  const handleCreate = () => {
810
812
  setEditingAccount(null);
@@ -886,163 +888,162 @@ export default function ContasBancariasPage() {
886
888
  </AlertDialogContent>
887
889
  </AlertDialog>
888
890
 
889
- <KpiCardsGrid items={summaryCards} columns={3} />
890
-
891
- <FinancePageSection
892
- title={t('list.title')}
893
- description={t('list.description')}
894
- contentClassName="p-4 sm:p-5"
895
- >
896
- {accounts.length > 0 ? (
897
- <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
898
- {accounts.map((conta) => {
899
- const tipo =
900
- tipoConfig[conta.tipo as keyof typeof tipoConfig] ||
901
- tipoConfig.corrente;
902
- const TipoIcon = tipo.icon;
903
- const diferenca = conta.saldoAtual - conta.saldoConciliado;
904
-
905
- return (
906
- <Card
907
- key={conta.id}
908
- className={!conta.ativo ? 'border-border/60 opacity-70' : ''}
909
- >
910
- <CardHeader className="space-y-4">
911
- <div className="flex items-start justify-between gap-3">
912
- <div className="flex min-w-0 items-center gap-3">
913
- <BankAccountLogo account={conta} icon={TipoIcon} />
914
- <div className="min-w-0">
915
- <div className="flex flex-wrap items-center gap-2">
916
- <CardTitle className="text-base">
917
- {conta.banco}
918
- </CardTitle>
919
- {conta.descricao ? (
920
- <span className="text-xs text-muted-foreground">
921
- {conta.descricao}
922
- </span>
923
- ) : null}
924
- </div>
925
- <CardDescription className="space-y-0.5">
926
- {conta.agencia !== '-' ? (
927
- <span className="block">
928
- {t('accountCard.bankAccount', {
929
- agency: conta.agencia,
930
- account: conta.conta,
931
- })}
932
- </span>
933
- ) : null}
934
- <span className="block">{tipo.label}</span>
935
- </CardDescription>
936
- </div>
937
- </div>
938
- {!conta.ativo ? (
939
- <Badge
940
- variant="outline"
941
- className="text-muted-foreground"
942
- >
943
- {t('status.inactive')}
944
- </Badge>
945
- ) : null}
946
- </div>
947
- </CardHeader>
948
- <CardContent>
949
- <div className="space-y-3">
950
- <div>
951
- <p className="text-sm text-muted-foreground">
952
- {t('accountCard.currentBalance')}
953
- </p>
954
- <p className="text-2xl font-bold">
955
- <Money value={conta.saldoAtual} />
956
- </p>
957
- </div>
958
- <div className="flex items-center justify-between text-sm">
959
- <span className="text-muted-foreground">
960
- {t('accountCard.reconciledBalance')}
961
- </span>
962
- <Money value={conta.saldoConciliado} />
963
- </div>
964
- {diferenca !== 0 ? (
965
- <div className="flex items-center justify-between text-sm">
966
- <span className="text-muted-foreground">
967
- {t('accountCard.difference')}
968
- </span>
969
- <span
970
- className={
971
- diferenca > 0 ? 'text-green-600' : 'text-red-600'
972
- }
973
- >
974
- <Money value={diferenca} showSign />
975
- </span>
976
- </div>
977
- ) : null}
978
- <div className="flex flex-wrap gap-2 pt-2">
979
- <Button
980
- variant="outline"
981
- size="sm"
982
- className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
983
- asChild
984
- >
985
- <Link
986
- href={`/finance/cash-and-banks/statements?bank_account_id=${conta.id}`}
987
- >
988
- <Eye className="mr-2 h-4 w-4" />
989
- {t('accountCard.statement')}
990
- </Link>
991
- </Button>
992
- <Button
993
- variant="outline"
994
- size="sm"
995
- className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
996
- asChild
997
- >
998
- <Link
999
- href={`/finance/cash-and-banks/bank-reconciliation?bank_account_id=${conta.id}`}
1000
- >
1001
- <RefreshCw className="mr-2 h-4 w-4" />
1002
- {t('accountCard.reconcile')}
1003
- </Link>
1004
- </Button>
1005
- <div className="ml-auto inline-flex shrink-0 overflow-hidden rounded-md border bg-background shadow-sm">
1006
- <Button
1007
- variant="ghost"
1008
- size="sm"
1009
- className="rounded-none border-0 px-3 hover:bg-muted"
1010
- onClick={() => handleEdit(conta)}
1011
- aria-label={t('common.edit')}
1012
- title={t('common.edit')}
1013
- >
1014
- <Pencil className="h-4 w-4" />
1015
- </Button>
1016
- <Button
1017
- variant="ghost"
1018
- size="sm"
1019
- className="rounded-none border-0 border-l px-3 text-destructive hover:bg-destructive/10 hover:text-destructive"
1020
- onClick={() => setAccountIdToDelete(conta.id)}
1021
- aria-label={deleteDialogTitle}
1022
- title={deleteDialogTitle}
1023
- >
1024
- <Trash2 className="h-4 w-4" />
1025
- </Button>
1026
- </div>
1027
- </div>
1028
- </div>
1029
- </CardContent>
1030
- </Card>
1031
- );
1032
- })}
1033
- </div>
1034
- ) : (
1035
- <div className="p-6 sm:p-8">
1036
- <EmptyState
1037
- icon={<Landmark className="h-12 w-12" />}
1038
- title={t('empty.title')}
1039
- description={t('empty.description')}
1040
- actionLabel={t('newAccount.action')}
1041
- onAction={handleCreate}
1042
- />
1043
- </div>
1044
- )}
1045
- </FinancePageSection>
891
+ <KpiCardsGrid items={summaryCards} columns={3} />
892
+
893
+ <FinancePageSection
894
+ contentClassName="p-0"
895
+ className="border-none shadow-none"
896
+ >
897
+ {accounts.length > 0 ? (
898
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
899
+ {accounts.map((conta) => {
900
+ const tipo =
901
+ tipoConfig[conta.tipo as keyof typeof tipoConfig] ||
902
+ tipoConfig.corrente;
903
+ const TipoIcon = tipo.icon;
904
+ const diferenca = conta.saldoAtual - conta.saldoConciliado;
905
+
906
+ return (
907
+ <Card
908
+ key={conta.id}
909
+ className={!conta.ativo ? 'border-border/60 opacity-70' : ''}
910
+ >
911
+ <CardHeader className="space-y-4">
912
+ <div className="flex items-start justify-between gap-3">
913
+ <div className="flex min-w-0 items-center gap-3">
914
+ <BankAccountLogo account={conta} icon={TipoIcon} />
915
+ <div className="min-w-0">
916
+ <div className="flex flex-wrap items-center gap-2">
917
+ <CardTitle className="text-base">
918
+ {conta.banco}
919
+ </CardTitle>
920
+ {conta.descricao ? (
921
+ <span className="text-xs text-muted-foreground">
922
+ {conta.descricao}
923
+ </span>
924
+ ) : null}
925
+ </div>
926
+ <CardDescription className="space-y-0.5">
927
+ {conta.agencia !== '-' ? (
928
+ <span className="block">
929
+ {t('accountCard.bankAccount', {
930
+ agency: conta.agencia,
931
+ account: conta.conta,
932
+ })}
933
+ </span>
934
+ ) : null}
935
+ <span className="block">{tipo.label}</span>
936
+ </CardDescription>
937
+ </div>
938
+ </div>
939
+ {!conta.ativo ? (
940
+ <Badge
941
+ variant="outline"
942
+ className="text-muted-foreground"
943
+ >
944
+ {t('status.inactive')}
945
+ </Badge>
946
+ ) : null}
947
+ </div>
948
+ </CardHeader>
949
+ <CardContent>
950
+ <div className="space-y-3">
951
+ <div>
952
+ <p className="text-sm text-muted-foreground">
953
+ {t('accountCard.currentBalance')}
954
+ </p>
955
+ <p className="text-2xl font-bold">
956
+ <Money value={conta.saldoAtual} />
957
+ </p>
958
+ </div>
959
+ <div className="flex items-center justify-between text-sm">
960
+ <span className="text-muted-foreground">
961
+ {t('accountCard.reconciledBalance')}
962
+ </span>
963
+ <Money value={conta.saldoConciliado} />
964
+ </div>
965
+ {diferenca !== 0 ? (
966
+ <div className="flex items-center justify-between text-sm">
967
+ <span className="text-muted-foreground">
968
+ {t('accountCard.difference')}
969
+ </span>
970
+ <span
971
+ className={
972
+ diferenca > 0 ? 'text-green-600' : 'text-red-600'
973
+ }
974
+ >
975
+ <Money value={diferenca} showSign />
976
+ </span>
977
+ </div>
978
+ ) : null}
979
+ <div className="flex flex-wrap gap-2 pt-2">
980
+ <Button
981
+ variant="outline"
982
+ size="sm"
983
+ className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
984
+ asChild
985
+ >
986
+ <Link
987
+ href={`/finance/cash-and-banks/statements?bank_account_id=${conta.id}`}
988
+ >
989
+ <Eye className="mr-2 h-4 w-4" />
990
+ {t('accountCard.statement')}
991
+ </Link>
992
+ </Button>
993
+ <Button
994
+ variant="outline"
995
+ size="sm"
996
+ className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
997
+ asChild
998
+ >
999
+ <Link
1000
+ href={`/finance/cash-and-banks/bank-reconciliation?bank_account_id=${conta.id}`}
1001
+ >
1002
+ <RefreshCw className="mr-2 h-4 w-4" />
1003
+ {t('accountCard.reconcile')}
1004
+ </Link>
1005
+ </Button>
1006
+ <div className="ml-auto inline-flex shrink-0 overflow-hidden rounded-md border bg-background shadow-sm">
1007
+ <Button
1008
+ variant="ghost"
1009
+ size="sm"
1010
+ className="rounded-none border-0 px-3 hover:bg-muted"
1011
+ onClick={() => handleEdit(conta)}
1012
+ aria-label={t('common.edit')}
1013
+ title={t('common.edit')}
1014
+ >
1015
+ <Pencil className="h-4 w-4" />
1016
+ </Button>
1017
+ <Button
1018
+ variant="ghost"
1019
+ size="sm"
1020
+ className="rounded-none border-0 border-l px-3 text-destructive hover:bg-destructive/10 hover:text-destructive"
1021
+ onClick={() => setAccountIdToDelete(conta.id)}
1022
+ aria-label={deleteDialogTitle}
1023
+ title={deleteDialogTitle}
1024
+ >
1025
+ <Trash2 className="h-4 w-4" />
1026
+ </Button>
1027
+ </div>
1028
+ </div>
1029
+ </div>
1030
+ </CardContent>
1031
+ </Card>
1032
+ );
1033
+ })}
1034
+ </div>
1035
+ ) : (
1036
+ <div className="p-6 sm:p-8">
1037
+ <EmptyState
1038
+ icon={<Landmark className="h-12 w-12" />}
1039
+ title={t('empty.title')}
1040
+ description={t('empty.description')}
1041
+ actionLabel={t('newAccount.action')}
1042
+ onAction={handleCreate}
1043
+ />
1044
+ </div>
1045
+ )}
1046
+ </FinancePageSection>
1046
1047
  </Page>
1047
1048
  );
1048
1049
  }