@hed-hog/finance 0.0.329 → 0.0.330

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 (23) hide show
  1. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +18 -15
  2. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +6 -14
  3. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +4 -8
  4. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +6 -14
  5. package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +10 -4
  6. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +3 -5
  7. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +3 -5
  8. package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +3 -5
  9. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +3 -5
  10. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +48 -72
  11. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +7 -9
  12. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +91 -73
  13. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +33 -25
  14. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +2 -2
  15. package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +3 -1
  16. package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +3 -1
  17. package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +3 -1
  18. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +15 -8
  19. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +9 -5
  20. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +9 -5
  21. package/hedhog/frontend/messages/en.json +110 -2
  22. package/hedhog/frontend/messages/pt.json +92 -2
  23. package/package.json +6 -6
@@ -118,38 +118,46 @@ type PaginatedStatementsResponse = {
118
118
  };
119
119
  };
120
120
 
121
- const statementEntrySchema = z.object({
122
- date: z.string().trim().min(1, 'Data é obrigatória'),
123
- description: z.string().trim().min(1, 'Descrição é obrigatória'),
124
- type: z.enum(['entrada', 'saida']),
125
- amount: z.number().min(0.01, 'Valor deve ser maior que zero'),
126
- });
127
-
128
- type StatementEntryFormValues = z.infer<typeof statementEntrySchema>;
129
-
130
- const bankAccountFormSchema = z.object({
131
- banco: z.string().trim().min(1, 'Banco é obrigatório'),
132
- agencia: z.string().optional(),
133
- conta: z.string().optional(),
134
- tipo: z.string().min(1, 'Tipo é obrigatório'),
135
- descricao: z.string().optional(),
136
- dataInicial: z.string().optional(),
137
- saldoInicial: z.number().min(0, 'Saldo inicial inválido'),
138
- });
139
-
140
- const importStatementSchema = z.object({
141
- bankAccountId: z.string().trim().min(1, 'Conta bancária é obrigatória'),
142
- file: z.instanceof(File, { message: 'Arquivo é obrigatório' }).refine(
143
- (value) => {
144
- const fileName = value.name.toLowerCase();
145
- return fileName.endsWith('.csv') || fileName.endsWith('.ofx');
146
- },
147
- { message: 'Apenas arquivos CSV ou OFX são permitidos' }
148
- ),
149
- });
121
+ const createStatementEntrySchema = (t: ReturnType<typeof useTranslations>) =>
122
+ z.object({
123
+ date: z.string().trim().min(1, t('formErrors.dateRequired')),
124
+ description: z.string().trim().min(1, t('formErrors.descriptionRequired')),
125
+ type: z.enum(['entrada', 'saida']),
126
+ amount: z.number().min(0.01, t('formErrors.amountMustBePositive')),
127
+ });
150
128
 
151
- type ImportStatementFormValues = z.infer<typeof importStatementSchema>;
152
- type BankAccountFormValues = z.infer<typeof bankAccountFormSchema>;
129
+ type StatementEntryFormValues = z.infer<ReturnType<typeof createStatementEntrySchema>>;
130
+
131
+ const createBankAccountFormSchema = (
132
+ tBank: ReturnType<typeof useTranslations>
133
+ ) =>
134
+ z.object({
135
+ banco: z.string().trim().min(1, tBank('validation.bankRequired')),
136
+ agencia: z.string().optional(),
137
+ conta: z.string().optional(),
138
+ tipo: z.string().min(1, tBank('validation.typeRequired')),
139
+ descricao: z.string().optional(),
140
+ dataInicial: z.string().optional(),
141
+ saldoInicial: z.number().min(0, tBank('validation.initialBalanceInvalid')),
142
+ });
143
+
144
+ const createImportStatementSchema = (t: ReturnType<typeof useTranslations>) =>
145
+ z.object({
146
+ bankAccountId: z
147
+ .string()
148
+ .trim()
149
+ .min(1, t('formErrors.bankAccountRequired')),
150
+ file: z.instanceof(File, { message: t('formErrors.fileRequired') }).refine(
151
+ (value) => {
152
+ const fileName = value.name.toLowerCase();
153
+ return fileName.endsWith('.csv') || fileName.endsWith('.ofx');
154
+ },
155
+ { message: t('formErrors.fileTypeNotAllowed') }
156
+ ),
157
+ });
158
+
159
+ type ImportStatementFormValues = z.infer<ReturnType<typeof createImportStatementSchema>>;
160
+ type BankAccountFormValues = z.infer<ReturnType<typeof createBankAccountFormSchema>>;
153
161
 
154
162
  type StatementBankAccountDraftPayload = {
155
163
  values: BankAccountFormValues;
@@ -180,7 +188,7 @@ function NovaContaBancariaSheet({
180
188
  useApp();
181
189
 
182
190
  const form = useForm<BankAccountFormValues>({
183
- resolver: zodResolver(bankAccountFormSchema),
191
+ resolver: zodResolver(createBankAccountFormSchema(tBank)),
184
192
  defaultValues: {
185
193
  banco: '',
186
194
  agencia: '',
@@ -250,10 +258,11 @@ function NovaContaBancariaSheet({
250
258
  currentLocaleCode
251
259
  );
252
260
 
253
- return currentLocaleCode.startsWith('pt')
254
- ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
255
- : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
256
- }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
261
+ return tBank('draftStatus', {
262
+ relativeLabel,
263
+ absoluteLabel,
264
+ });
265
+ }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft, tBank]);
257
266
 
258
267
  useEffect(() => {
259
268
  if (!open) {
@@ -534,7 +543,7 @@ function ImportarExtratoSheet({
534
543
  };
535
544
 
536
545
  const form = useForm<ImportStatementFormValues>({
537
- resolver: zodResolver(importStatementSchema),
546
+ resolver: zodResolver(createImportStatementSchema(t)),
538
547
  defaultValues: {
539
548
  bankAccountId: defaultBankAccountId || '',
540
549
  },
@@ -585,10 +594,11 @@ function ImportarExtratoSheet({
585
594
  currentLocaleCode
586
595
  );
587
596
 
588
- return currentLocaleCode.startsWith('pt')
589
- ? `Rascunho salvo ${relativeLabel} • Último salvamento: ${absoluteLabel}`
590
- : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
591
- }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
597
+ return t('draftStatus', {
598
+ relativeLabel,
599
+ absoluteLabel,
600
+ });
601
+ }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft, t]);
592
602
 
593
603
  useEffect(() => {
594
604
  if (!isOpen) {
@@ -618,10 +628,10 @@ function ImportarExtratoSheet({
618
628
 
619
629
  clearDraft();
620
630
  await onImported();
621
- showToastHandler?.('success', 'Extrato importado com sucesso');
631
+ showToastHandler?.('success', t('messages.importSuccess'));
622
632
  handleOpenChange(false);
623
633
  } catch {
624
- showToastHandler?.('error', 'Não foi possível importar o extrato');
634
+ showToastHandler?.('error', t('messages.importError'));
625
635
  }
626
636
  };
627
637
 
@@ -676,7 +686,7 @@ function ImportarExtratoSheet({
676
686
  variant="outline"
677
687
  size="icon"
678
688
  onClick={() => setOpenNovaContaSheet(true)}
679
- aria-label="Nova conta bancária"
689
+ aria-label={t('aria.newBankAccount')}
680
690
  >
681
691
  <Plus className="h-4 w-4" />
682
692
  </Button>
@@ -751,11 +761,13 @@ function ImportarExtratoSheet({
751
761
  }
752
762
 
753
763
  function NovoLancamentoSheet({
764
+ t,
754
765
  open,
755
766
  onOpenChange,
756
767
  bankAccountId,
757
768
  onCreated,
758
769
  }: {
770
+ t: ReturnType<typeof useTranslations>;
759
771
  open: boolean;
760
772
  onOpenChange: (open: boolean) => void;
761
773
  bankAccountId?: string;
@@ -763,7 +775,7 @@ function NovoLancamentoSheet({
763
775
  }) {
764
776
  const { request, showToastHandler } = useApp();
765
777
  const form = useForm<StatementEntryFormValues>({
766
- resolver: zodResolver(statementEntrySchema),
778
+ resolver: zodResolver(createStatementEntrySchema(t)),
767
779
  defaultValues: {
768
780
  date: new Date().toISOString().slice(0, 10),
769
781
  description: '',
@@ -785,7 +797,7 @@ function NovoLancamentoSheet({
785
797
 
786
798
  const handleSubmit = async (values: StatementEntryFormValues) => {
787
799
  if (!bankAccountId) {
788
- showToastHandler?.('error', 'Selecione uma conta bancária');
800
+ showToastHandler?.('error', t('messages.selectBankAccount'));
789
801
  return;
790
802
  }
791
803
 
@@ -804,14 +816,14 @@ function NovoLancamentoSheet({
804
816
 
805
817
  await onCreated();
806
818
  onOpenChange(false);
807
- showToastHandler?.('success', 'Movimentação adicionada com sucesso');
819
+ showToastHandler?.('success', t('messages.createEntrySuccess'));
808
820
  } catch (error: any) {
809
821
  const message = error?.response?.data?.message;
810
822
  showToastHandler?.(
811
823
  'error',
812
824
  typeof message === 'string' && message.trim()
813
825
  ? message
814
- : 'Não foi possível adicionar a movimentação'
826
+ : t('messages.createEntryError')
815
827
  );
816
828
  }
817
829
  };
@@ -820,10 +832,8 @@ function NovoLancamentoSheet({
820
832
  <Sheet open={open} onOpenChange={onOpenChange}>
821
833
  <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
822
834
  <SheetHeader>
823
- <SheetTitle>Novo lançamento</SheetTitle>
824
- <SheetDescription>
825
- Adicione uma movimentação manual ao extrato da conta bancária.
826
- </SheetDescription>
835
+ <SheetTitle>{t('newEntry.title')}</SheetTitle>
836
+ <SheetDescription>{t('newEntry.description')}</SheetDescription>
827
837
  </SheetHeader>
828
838
 
829
839
  <Form {...form}>
@@ -837,7 +847,7 @@ function NovoLancamentoSheet({
837
847
  name="date"
838
848
  render={({ field }) => (
839
849
  <FormItem>
840
- <FormLabel>Data</FormLabel>
850
+ <FormLabel>{t('newEntry.fields.date')}</FormLabel>
841
851
  <FormControl>
842
852
  <Input type="date" {...field} />
843
853
  </FormControl>
@@ -851,7 +861,7 @@ function NovoLancamentoSheet({
851
861
  name="type"
852
862
  render={({ field }) => (
853
863
  <FormItem>
854
- <FormLabel>Tipo</FormLabel>
864
+ <FormLabel>{t('newEntry.fields.type')}</FormLabel>
855
865
  <Select value={field.value} onValueChange={field.onChange}>
856
866
  <FormControl>
857
867
  <SelectTrigger className="w-full">
@@ -859,8 +869,12 @@ function NovoLancamentoSheet({
859
869
  </SelectTrigger>
860
870
  </FormControl>
861
871
  <SelectContent>
862
- <SelectItem value="entrada">Entrada</SelectItem>
863
- <SelectItem value="saida">Saída</SelectItem>
872
+ <SelectItem value="entrada">
873
+ {t('types.inflow')}
874
+ </SelectItem>
875
+ <SelectItem value="saida">
876
+ {t('types.outflow')}
877
+ </SelectItem>
864
878
  </SelectContent>
865
879
  </Select>
866
880
  <FormMessage />
@@ -874,7 +888,7 @@ function NovoLancamentoSheet({
874
888
  name="description"
875
889
  render={({ field }) => (
876
890
  <FormItem>
877
- <FormLabel>Descrição</FormLabel>
891
+ <FormLabel>{t('newEntry.fields.description')}</FormLabel>
878
892
  <FormControl>
879
893
  <Input {...field} />
880
894
  </FormControl>
@@ -888,7 +902,7 @@ function NovoLancamentoSheet({
888
902
  name="amount"
889
903
  render={({ field }) => (
890
904
  <FormItem>
891
- <FormLabel>Valor</FormLabel>
905
+ <FormLabel>{t('newEntry.fields.amount')}</FormLabel>
892
906
  <FormControl>
893
907
  <InputMoney
894
908
  ref={field.ref}
@@ -910,10 +924,10 @@ function NovoLancamentoSheet({
910
924
  variant="outline"
911
925
  onClick={() => onOpenChange(false)}
912
926
  >
913
- Cancelar
927
+ {t('common.cancel')}
914
928
  </Button>
915
929
  <Button type="submit" disabled={form.formState.isSubmitting}>
916
- Salvar
930
+ {t('newEntry.submit')}
917
931
  </Button>
918
932
  </div>
919
933
  </form>
@@ -1166,7 +1180,7 @@ export default function ExtratosPage() {
1166
1180
  ];
1167
1181
  const handleExport = async () => {
1168
1182
  if (!activeContaFilter) {
1169
- showToastHandler?.('error', 'Selecione uma conta bancária para exportar');
1183
+ showToastHandler?.('error', t('messages.selectBankAccountToExport'));
1170
1184
  return;
1171
1185
  }
1172
1186
 
@@ -1217,7 +1231,7 @@ export default function ExtratosPage() {
1217
1231
  link.remove();
1218
1232
  window.URL.revokeObjectURL(url);
1219
1233
  } catch {
1220
- showToastHandler?.('error', 'Não foi possível exportar o extrato');
1234
+ showToastHandler?.('error', t('messages.exportError'));
1221
1235
  }
1222
1236
  };
1223
1237
 
@@ -1298,7 +1312,7 @@ export default function ExtratosPage() {
1298
1312
  'error',
1299
1313
  typeof message === 'string' && message.trim()
1300
1314
  ? message
1301
- : 'Não foi possível salvar as alterações'
1315
+ : t('messages.saveChangesError')
1302
1316
  );
1303
1317
  } finally {
1304
1318
  setIsSavingEdits(false);
@@ -1310,7 +1324,7 @@ export default function ExtratosPage() {
1310
1324
  setEditingDescId(null);
1311
1325
  setEditingDateId(null);
1312
1326
  await Promise.all([refetchExtratos(), refetchContasBancarias()]);
1313
- showToastHandler?.('success', 'Alterações salvas com sucesso');
1327
+ showToastHandler?.('success', t('messages.saveChangesSuccess'));
1314
1328
  }
1315
1329
  };
1316
1330
 
@@ -1337,14 +1351,14 @@ export default function ExtratosPage() {
1337
1351
 
1338
1352
  setStatementToDelete(null);
1339
1353
  await Promise.all([refetchExtratos(), refetchContasBancarias()]);
1340
- showToastHandler?.('success', 'Movimentação excluída com sucesso');
1354
+ showToastHandler?.('success', t('messages.deleteEntrySuccess'));
1341
1355
  } catch (error: any) {
1342
1356
  const message = error?.response?.data?.message;
1343
1357
  showToastHandler?.(
1344
1358
  'error',
1345
1359
  typeof message === 'string' && message.trim()
1346
1360
  ? message
1347
- : 'Não foi possível excluir a movimentação'
1361
+ : t('messages.deleteEntryError')
1348
1362
  );
1349
1363
  } finally {
1350
1364
  setIsDeletingStatement(false);
@@ -1369,7 +1383,7 @@ export default function ExtratosPage() {
1369
1383
  </Button>
1370
1384
  <Button onClick={() => setIsNewEntrySheetOpen(true)}>
1371
1385
  <Plus className="mr-2 h-4 w-4" />
1372
- Novo lançamento
1386
+ {t('newEntry.action')}
1373
1387
  </Button>
1374
1388
  <ImportarExtratoSheet
1375
1389
  contasBancarias={contasBancarias}
@@ -1385,6 +1399,7 @@ export default function ExtratosPage() {
1385
1399
  />
1386
1400
 
1387
1401
  <NovoLancamentoSheet
1402
+ t={t}
1388
1403
  open={isNewEntrySheetOpen}
1389
1404
  onOpenChange={setIsNewEntrySheetOpen}
1390
1405
  bankAccountId={activeContaFilter}
@@ -1773,7 +1788,9 @@ export default function ExtratosPage() {
1773
1788
  onClick={() => void handleSaveAllPendingEdits()}
1774
1789
  disabled={isSavingEdits}
1775
1790
  >
1776
- {isSavingEdits ? 'Salvando...' : 'Salvar alterações'}
1791
+ {isSavingEdits
1792
+ ? t('actions.savingChanges')
1793
+ : t('actions.saveChanges')}
1777
1794
  </Button>
1778
1795
  </div>
1779
1796
  ) : null}
@@ -1786,10 +1803,9 @@ export default function ExtratosPage() {
1786
1803
  >
1787
1804
  <DialogContent className="sm:max-w-sm">
1788
1805
  <DialogHeader>
1789
- <DialogTitle>Excluir movimentação</DialogTitle>
1806
+ <DialogTitle>{t('deleteDialog.title')}</DialogTitle>
1790
1807
  <DialogDescription>
1791
- Esta ação não pode ser desfeita. A movimentação será removida
1792
- permanentemente do extrato.
1808
+ {t('deleteDialog.description')}
1793
1809
  </DialogDescription>
1794
1810
  </DialogHeader>
1795
1811
  <DialogFooter className="mt-2">
@@ -1798,14 +1814,16 @@ export default function ExtratosPage() {
1798
1814
  onClick={() => setStatementToDelete(null)}
1799
1815
  disabled={isDeletingStatement}
1800
1816
  >
1801
- Cancelar
1817
+ {t('common.cancel')}
1802
1818
  </Button>
1803
1819
  <Button
1804
1820
  variant="destructive"
1805
1821
  onClick={() => void confirmDeleteStatement()}
1806
1822
  disabled={isDeletingStatement}
1807
1823
  >
1808
- {isDeletingStatement ? 'Excluindo...' : 'Excluir'}
1824
+ {isDeletingStatement
1825
+ ? t('deleteDialog.deleting')
1826
+ : t('deleteDialog.confirm')}
1809
1827
  </Button>
1810
1828
  </DialogFooter>
1811
1829
  </DialogContent>
@@ -88,23 +88,30 @@ type TransferDraftPayload = {
88
88
 
89
89
  const TRANSFER_FORM_DRAFT_STORAGE_KEY = 'finance-transfer-form-draft';
90
90
 
91
- const transferFormSchema = z
92
- .object({
93
- sourceAccountId: z.string().trim().min(1, 'Conta de origem é obrigatória'),
94
- destinationAccountId: z
95
- .string()
96
- .trim()
97
- .min(1, 'Conta de destino é obrigatória'),
98
- date: z.string().trim().min(1, 'Data é obrigatória'),
99
- amount: z.number().min(0.01, 'Valor deve ser maior que zero'),
100
- description: z.string().optional(),
101
- })
102
- .refine((values) => values.sourceAccountId !== values.destinationAccountId, {
103
- message: 'As contas de origem e destino devem ser diferentes',
104
- path: ['destinationAccountId'],
105
- });
91
+ const createTransferFormSchema = (t: ReturnType<typeof useTranslations>) =>
92
+ z
93
+ .object({
94
+ sourceAccountId: z
95
+ .string()
96
+ .trim()
97
+ .min(1, t('formErrors.sourceAccountRequired')),
98
+ destinationAccountId: z
99
+ .string()
100
+ .trim()
101
+ .min(1, t('formErrors.destinationAccountRequired')),
102
+ date: z.string().trim().min(1, t('formErrors.dateRequired')),
103
+ amount: z.number().min(0.01, t('formErrors.amountMustBePositive')),
104
+ description: z.string().optional(),
105
+ })
106
+ .refine(
107
+ (values) => values.sourceAccountId !== values.destinationAccountId,
108
+ {
109
+ message: t('formErrors.differentAccounts'),
110
+ path: ['destinationAccountId'],
111
+ }
112
+ );
106
113
 
107
- type TransferFormValues = z.infer<typeof transferFormSchema>;
114
+ type TransferFormValues = z.infer<ReturnType<typeof createTransferFormSchema>>;
108
115
 
109
116
  function NovaTransferenciaSheet({
110
117
  contasBancarias,
@@ -118,6 +125,7 @@ function NovaTransferenciaSheet({
118
125
  const { request, showToastHandler, currentLocaleCode, getSettingValue } =
119
126
  useApp();
120
127
  const [open, setOpen] = useState(false);
128
+ const transferFormSchema = useMemo(() => createTransferFormSchema(t), [t]);
121
129
 
122
130
  const form = useForm<TransferFormValues>({
123
131
  resolver: zodResolver(transferFormSchema),
@@ -187,10 +195,8 @@ function NovaTransferenciaSheet({
187
195
  currentLocaleCode
188
196
  );
189
197
 
190
- return currentLocaleCode.startsWith('pt')
191
- ? `Rascunho salvo ${relativeLabel} Último salvamento: ${absoluteLabel}`
192
- : `Draft saved ${relativeLabel} • Last saved: ${absoluteLabel}`;
193
- }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft]);
198
+ return t('draftStatus', { relativeLabel, absoluteLabel });
199
+ }, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft, t]);
194
200
 
195
201
  useEffect(() => {
196
202
  if (!open) {
@@ -227,9 +233,9 @@ function NovaTransferenciaSheet({
227
233
  clearDraft();
228
234
  await onCreated();
229
235
  setOpen(false);
230
- showToastHandler?.('success', 'Transferência cadastrada com sucesso');
236
+ showToastHandler?.('success', t('transferCreatedSuccess'));
231
237
  } catch {
232
- showToastHandler?.('error', 'Erro ao cadastrar transferência');
238
+ showToastHandler?.('error', t('transferCreateError'));
233
239
  }
234
240
  };
235
241
 
@@ -540,7 +546,7 @@ export default function TransferenciasPage() {
540
546
  <SelectValue placeholder={t('common.select')} />
541
547
  </SelectTrigger>
542
548
  <SelectContent>
543
- <SelectItem value="all">Todas as contas</SelectItem>
549
+ <SelectItem value="all">{t('allAccounts')}</SelectItem>
544
550
  {contasBancarias.map((conta) => (
545
551
  <SelectItem key={conta.id} value={conta.id}>
546
552
  {conta.banco} - {conta.descricao}
@@ -551,7 +557,7 @@ export default function TransferenciasPage() {
551
557
 
552
558
  <div className="flex-1">
553
559
  <FilterBar
554
- searchPlaceholder="Buscar na descrição..."
560
+ searchPlaceholder={t('searchPlaceholder')}
555
561
  searchValue={search}
556
562
  onSearchChange={setSearch}
557
563
  />
@@ -575,7 +581,9 @@ export default function TransferenciasPage() {
575
581
  <TableRow>
576
582
  <TableHead>{t('table.headers.date')}</TableHead>
577
583
  <TableHead>{t('table.headers.source')}</TableHead>
578
- <TableHead className="text-center">→</TableHead>
584
+ <TableHead className="text-center">
585
+ {t('table.headers.arrow')}
586
+ </TableHead>
579
587
  <TableHead>{t('table.headers.destination')}</TableHead>
580
588
  <TableHead className="text-right">
581
589
  {t('table.headers.value')}
@@ -316,9 +316,9 @@ export default function CenariosPage() {
316
316
  });
317
317
 
318
318
  await refetch();
319
- showToastHandler?.('success', 'Cenário salvo com sucesso');
319
+ showToastHandler?.('success', t('messages.saveSuccess'));
320
320
  } catch {
321
- showToastHandler?.('error', 'Erro ao salvar cenário');
321
+ showToastHandler?.('error', t('messages.saveError'));
322
322
  } finally {
323
323
  setIsSaving(false);
324
324
  }
@@ -21,6 +21,7 @@ import {
21
21
  TableHeader,
22
22
  TableRow,
23
23
  } from '@/components/ui/table';
24
+ import { useApp } from '@hed-hog/next-app-provider';
24
25
  import {
25
26
  Download,
26
27
  Loader2,
@@ -47,6 +48,7 @@ type Scenario = 'base' | 'pessimista' | 'otimista';
47
48
 
48
49
  export default function ActualVsForecastPage() {
49
50
  const t = useTranslations('finance.ActualVsForecastPage');
51
+ const { currentLocaleCode } = useApp();
50
52
 
51
53
  const [horizonte, setHorizonte] = useState<string | null>(null);
52
54
  const [cenario, setCenario] = useState<Scenario | null>(null);
@@ -219,7 +221,7 @@ export default function ActualVsForecastPage() {
219
221
  />
220
222
  <Tooltip
221
223
  formatter={(value: number) =>
222
- new Intl.NumberFormat('pt-BR', {
224
+ new Intl.NumberFormat(currentLocaleCode || undefined, {
223
225
  style: 'currency',
224
226
  currency: 'BRL',
225
227
  }).format(value)
@@ -20,6 +20,7 @@ import {
20
20
  TableHeader,
21
21
  TableRow,
22
22
  } from '@/components/ui/table';
23
+ import { useApp } from '@hed-hog/next-app-provider';
23
24
  import { AlertTriangle, Loader2 } from 'lucide-react';
24
25
  import { useTranslations } from 'next-intl';
25
26
  import { useMemo, useState } from 'react';
@@ -46,6 +47,7 @@ function toPercent(value: number) {
46
47
 
47
48
  export default function AgingDefaultReportPage() {
48
49
  const t = useTranslations('finance.AgingDefaultReportPage');
50
+ const { currentLocaleCode } = useApp();
49
51
 
50
52
  const [horizonte, setHorizonte] = useState<string | null>(null);
51
53
  const [cenario, setCenario] = useState<Scenario | null>(null);
@@ -213,7 +215,7 @@ export default function AgingDefaultReportPage() {
213
215
  />
214
216
  <Tooltip
215
217
  formatter={(value: number) =>
216
- new Intl.NumberFormat('pt-BR', {
218
+ new Intl.NumberFormat(currentLocaleCode || undefined, {
217
219
  style: 'currency',
218
220
  currency: 'BRL',
219
221
  }).format(value)
@@ -21,6 +21,7 @@ import {
21
21
  TableHeader,
22
22
  TableRow,
23
23
  } from '@/components/ui/table';
24
+ import { useApp } from '@hed-hog/next-app-provider';
24
25
  import {
25
26
  Download,
26
27
  Loader2,
@@ -46,6 +47,7 @@ type Scenario = 'base' | 'pessimista' | 'otimista';
46
47
 
47
48
  export default function CashPositionReportPage() {
48
49
  const t = useTranslations('finance.CashPositionReportPage');
50
+ const { currentLocaleCode } = useApp();
49
51
 
50
52
  const [horizonte, setHorizonte] = useState<string | null>(null);
51
53
  const [cenario, setCenario] = useState<Scenario | null>(null);
@@ -309,7 +311,7 @@ export default function CashPositionReportPage() {
309
311
  />
310
312
  <Tooltip
311
313
  formatter={(value: number) =>
312
- new Intl.NumberFormat('pt-BR', {
314
+ new Intl.NumberFormat(currentLocaleCode || undefined, {
313
315
  style: 'currency',
314
316
  currency: 'BRL',
315
317
  }).format(value)
@@ -3,8 +3,8 @@
3
3
  import { FinancePageSection } from '@/app/(app)/(libraries)/finance/_components/finance-layout';
4
4
  import { Page, PageHeader } from '@/components/entity-list';
5
5
  import { Badge } from '@/components/ui/badge';
6
- import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
6
  import { Input } from '@/components/ui/input';
7
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
8
8
  import { Label } from '@/components/ui/label';
9
9
  import { Money } from '@/components/ui/money';
10
10
  import {
@@ -49,16 +49,12 @@ const LINE_COLORS = {
49
49
  diferenca: '#16a34a',
50
50
  };
51
51
 
52
- const currencyFormatter = new Intl.NumberFormat('pt-BR', {
53
- style: 'currency',
54
- currency: 'BRL',
55
- });
56
-
57
52
  function renderSolidTooltip({
58
53
  active,
59
54
  payload,
60
55
  label,
61
- }: TooltipProps<number, string>) {
56
+ currencyFormatter,
57
+ }: TooltipProps<number, string> & { currencyFormatter: Intl.NumberFormat }) {
62
58
  if (!active || !payload?.length) return null;
63
59
  return (
64
60
  <div className="rounded-lg border bg-popover px-3 py-2 shadow-xl text-popover-foreground text-sm">
@@ -84,6 +80,10 @@ function renderSolidTooltip({
84
80
  export default function OverviewResultsReportPage() {
85
81
  const t = useTranslations('finance.OverviewResultsReportPage');
86
82
  const locale = useLocale();
83
+ const currencyFormatter = new Intl.NumberFormat(locale || undefined, {
84
+ style: 'currency',
85
+ currency: 'BRL',
86
+ });
87
87
  const defaults = getDefaultDateRange();
88
88
  const [from, setFrom] = useState(defaults.from);
89
89
  const [to, setTo] = useState(defaults.to);
@@ -225,7 +225,14 @@ export default function OverviewResultsReportPage() {
225
225
  tick={{ fontSize: 12 }}
226
226
  tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
227
227
  />
228
- <Tooltip content={renderSolidTooltip} />
228
+ <Tooltip
229
+ content={(props) =>
230
+ renderSolidTooltip({
231
+ ...(props as TooltipProps<number, string>),
232
+ currencyFormatter,
233
+ })
234
+ }
235
+ />
229
236
  <Legend />
230
237
  <Line
231
238
  type="monotone"
@@ -28,6 +28,7 @@ import {
28
28
  TableHeader,
29
29
  TableRow,
30
30
  } from '@/components/ui/table';
31
+ import { useApp } from '@hed-hog/next-app-provider';
31
32
  import { Crown, PieChartIcon, Users } from 'lucide-react';
32
33
  import { useTranslations } from 'next-intl';
33
34
  import { type FormEvent, useState } from 'react';
@@ -64,13 +65,9 @@ const rankingColors = [
64
65
  '#B45309',
65
66
  ];
66
67
 
67
- const currencyFormatter = new Intl.NumberFormat('pt-BR', {
68
- style: 'currency',
69
- currency: 'BRL',
70
- });
71
-
72
68
  export default function TopCustomersReportPage() {
73
69
  const t = useTranslations('finance.TopCustomersReportPage');
70
+ const { currentLocaleCode } = useApp();
74
71
  const defaults = getDefaultDateRange();
75
72
  const [filters, setFilters] = useState({
76
73
  from: defaults.from,
@@ -79,6 +76,13 @@ export default function TopCustomersReportPage() {
79
76
  search: '',
80
77
  });
81
78
  const [appliedFilters, setAppliedFilters] = useState(filters);
79
+ const currencyFormatter = new Intl.NumberFormat(
80
+ currentLocaleCode || undefined,
81
+ {
82
+ style: 'currency',
83
+ currency: 'BRL',
84
+ }
85
+ );
82
86
  const { data: viewData, isFetching } = useTopCustomersReport({
83
87
  from: appliedFilters.from,
84
88
  to: appliedFilters.to,