@hed-hog/finance 0.0.299 → 0.0.300

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 (44) hide show
  1. package/dist/dto/create-bank-account.dto.d.ts +1 -0
  2. package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
  3. package/dist/dto/create-bank-account.dto.js +7 -0
  4. package/dist/dto/create-bank-account.dto.js.map +1 -1
  5. package/dist/dto/update-bank-account.dto.d.ts +1 -0
  6. package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
  7. package/dist/dto/update-bank-account.dto.js +7 -0
  8. package/dist/dto/update-bank-account.dto.js.map +1 -1
  9. package/dist/finance-bank-accounts.controller.d.ts +3 -0
  10. package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
  11. package/dist/finance-data.controller.d.ts +1 -0
  12. package/dist/finance-data.controller.d.ts.map +1 -1
  13. package/dist/finance.service.d.ts +4 -0
  14. package/dist/finance.service.d.ts.map +1 -1
  15. package/dist/finance.service.js +5 -0
  16. package/dist/finance.service.js.map +1 -1
  17. package/hedhog/data/dashboard.yaml +6 -0
  18. package/hedhog/data/dashboard_component.yaml +72 -17
  19. package/hedhog/data/dashboard_component_role.yaml +30 -0
  20. package/hedhog/data/dashboard_item.yaml +155 -0
  21. package/hedhog/data/dashboard_role.yaml +6 -0
  22. package/hedhog/data/role_menu.yaml +6 -0
  23. package/hedhog/data/role_route.yaml +127 -0
  24. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +328 -12
  25. package/hedhog/frontend/messages/en.json +48 -2
  26. package/hedhog/frontend/messages/pt.json +48 -2
  27. package/hedhog/frontend/widgets/alerts.tsx.ejs +1 -1
  28. package/hedhog/frontend/widgets/bank-reconciliation-status.tsx.ejs +142 -0
  29. package/hedhog/frontend/widgets/cash-balance-kpi.tsx.ejs +9 -9
  30. package/hedhog/frontend/widgets/cash-flow-chart.tsx.ejs +1 -1
  31. package/hedhog/frontend/widgets/default-kpi.tsx.ejs +9 -9
  32. package/hedhog/frontend/widgets/payable-30d-kpi.tsx.ejs +9 -9
  33. package/hedhog/frontend/widgets/pending-approvals-kpi.tsx.ejs +78 -0
  34. package/hedhog/frontend/widgets/pending-approvals-list.tsx.ejs +147 -0
  35. package/hedhog/frontend/widgets/pending-reconciliation-kpi.tsx.ejs +84 -0
  36. package/hedhog/frontend/widgets/receivable-30d-kpi.tsx.ejs +9 -9
  37. package/hedhog/frontend/widgets/receivable-aging-analysis.tsx.ejs +163 -0
  38. package/hedhog/frontend/widgets/upcoming-payable.tsx.ejs +1 -1
  39. package/hedhog/frontend/widgets/upcoming-receivable.tsx.ejs +1 -1
  40. package/hedhog/table/bank_account.yaml +8 -0
  41. package/package.json +5 -5
  42. package/src/dto/create-bank-account.dto.ts +7 -1
  43. package/src/dto/update-bank-account.dto.ts +7 -1
  44. package/src/finance.service.ts +4 -0
@@ -59,10 +59,11 @@ import {
59
59
  TrendingUp,
60
60
  Upload,
61
61
  Wallet,
62
+ type LucideIcon,
62
63
  } from 'lucide-react';
63
64
  import { useTranslations } from 'next-intl';
64
65
  import Link from 'next/link';
65
- import { useEffect, useState } from 'react';
66
+ import { useEffect, useRef, useState, type ChangeEvent } from 'react';
66
67
  import { useForm } from 'react-hook-form';
67
68
  import { z } from 'zod';
68
69
 
@@ -72,6 +73,7 @@ const bankAccountFormSchema = z.object({
72
73
  conta: z.string().optional(),
73
74
  tipo: z.string().min(1, 'Tipo é obrigatório'),
74
75
  descricao: z.string().optional(),
76
+ logoFileId: z.number().int().nullable().optional(),
75
77
  saldoInicial: z.number().min(0, 'Saldo inicial inválido'),
76
78
  });
77
79
 
@@ -85,11 +87,48 @@ type BankAccount = {
85
87
  agencia: string;
86
88
  conta: string;
87
89
  tipo: 'corrente' | 'poupanca' | 'investimento' | 'caixa';
90
+ logoFileId: number | null;
88
91
  saldoAtual: number;
89
92
  saldoConciliado: number;
90
93
  ativo: boolean;
91
94
  };
92
95
 
96
+ type UploadedFilePayload = {
97
+ id?: number | string | null;
98
+ };
99
+
100
+ function BankAccountLogo({
101
+ account,
102
+ icon: Icon,
103
+ }: {
104
+ account: BankAccount;
105
+ icon: LucideIcon;
106
+ }) {
107
+ const [hasImageError, setHasImageError] = useState(false);
108
+ const apiBaseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
109
+ const logoUrl =
110
+ account.logoFileId && account.logoFileId > 0
111
+ ? `${apiBaseUrl}/file/open/${account.logoFileId}?v=${account.logoFileId}`
112
+ : null;
113
+
114
+ if (!logoUrl || hasImageError) {
115
+ return (
116
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted">
117
+ <Icon className="h-5 w-5" />
118
+ </div>
119
+ );
120
+ }
121
+
122
+ return (
123
+ <img
124
+ src={logoUrl}
125
+ alt={account.banco}
126
+ className="h-10 w-10 rounded-lg border bg-muted object-cover"
127
+ onError={() => setHasImageError(true)}
128
+ />
129
+ );
130
+ }
131
+
93
132
  function NovaContaSheet({
94
133
  t,
95
134
  onCreated,
@@ -119,6 +158,21 @@ function NovaContaSheet({
119
158
  const updateErrorMessage = t.has('messages.updateError')
120
159
  ? t('messages.updateError')
121
160
  : 'Erro ao atualizar conta bancária';
161
+ const logoUploadSuccessMessage = t.has('messages.logoUploadSuccess')
162
+ ? t('messages.logoUploadSuccess')
163
+ : 'Logo enviado com sucesso';
164
+ const logoUploadErrorMessage = t.has('messages.logoUploadError')
165
+ ? t('messages.logoUploadError')
166
+ : 'Erro ao enviar logo';
167
+ const logoRemoveSuccessMessage = t.has('messages.logoRemoveSuccess')
168
+ ? t('messages.logoRemoveSuccess')
169
+ : 'Logo removido com sucesso';
170
+ const logoInvalidTypeMessage = t.has('messages.logoInvalidType')
171
+ ? t('messages.logoInvalidType')
172
+ : 'Selecione um arquivo de imagem válido';
173
+ const logoTooLargeMessage = t.has('messages.logoTooLarge')
174
+ ? t('messages.logoTooLarge')
175
+ : 'O logo deve ter no máximo 5 MB';
122
176
 
123
177
  const form = useForm<BankAccountFormValues>({
124
178
  resolver: zodResolver(bankAccountFormSchema),
@@ -128,38 +182,204 @@ function NovaContaSheet({
128
182
  conta: '',
129
183
  tipo: '',
130
184
  descricao: '',
185
+ logoFileId: null,
131
186
  saldoInicial: 0,
132
187
  },
133
188
  });
134
189
 
190
+ const [logoFileId, setLogoFileId] = useState<number | null>(null);
191
+ const [persistedLogoFileId, setPersistedLogoFileId] = useState<number | null>(
192
+ null
193
+ );
194
+ const [logoPreviewUrl, setLogoPreviewUrl] = useState<string | null>(null);
195
+ const [isUploadingLogo, setIsUploadingLogo] = useState(false);
196
+ const [logoUploadProgress, setLogoUploadProgress] = useState(0);
197
+ const logoInputRef = useRef<HTMLInputElement | null>(null);
198
+
199
+ const getLogoUrl = (fileId?: number | null) => {
200
+ if (typeof fileId !== 'number' || fileId <= 0) {
201
+ return null;
202
+ }
203
+
204
+ return `${String(process.env.NEXT_PUBLIC_API_BASE_URL || '')}/file/open/${fileId}`;
205
+ };
206
+
207
+ const deleteFileById = async (fileId?: number | null) => {
208
+ if (!fileId || fileId <= 0) {
209
+ return;
210
+ }
211
+
212
+ try {
213
+ await request({
214
+ url: '/file',
215
+ method: 'DELETE',
216
+ data: { ids: [fileId] },
217
+ });
218
+ } catch {
219
+ // Ignore cleanup failures to keep the form stable.
220
+ }
221
+ };
222
+
223
+ const cleanupUnsavedLogo = async () => {
224
+ if (logoFileId && logoFileId !== persistedLogoFileId) {
225
+ await deleteFileById(logoFileId);
226
+ }
227
+ };
228
+
229
+ const handleSelectLogo = () => {
230
+ if (!logoInputRef.current || isUploadingLogo) {
231
+ return;
232
+ }
233
+
234
+ logoInputRef.current.value = '';
235
+ logoInputRef.current.click();
236
+ };
237
+
238
+ const handleLogoUpload = async (event: ChangeEvent<HTMLInputElement>) => {
239
+ const file = event.target.files?.[0];
240
+
241
+ if (!file) {
242
+ return;
243
+ }
244
+
245
+ if (!file.type.startsWith('image/')) {
246
+ showToastHandler?.('error', logoInvalidTypeMessage);
247
+ event.target.value = '';
248
+ return;
249
+ }
250
+
251
+ if (file.size > 5 * 1024 * 1024) {
252
+ showToastHandler?.('error', logoTooLargeMessage);
253
+ event.target.value = '';
254
+ return;
255
+ }
256
+
257
+ setIsUploadingLogo(true);
258
+ setLogoUploadProgress(0);
259
+
260
+ try {
261
+ const previousUploadedLogoId = logoFileId;
262
+ const formData = new FormData();
263
+ formData.append('file', file);
264
+ formData.append('destination', 'finance/bank-account/logo');
265
+
266
+ const { data } = await request<UploadedFilePayload>({
267
+ url: '/file',
268
+ method: 'POST',
269
+ data: formData,
270
+ headers: {
271
+ 'Content-Type': 'multipart/form-data',
272
+ },
273
+ onUploadProgress: (progressEvent) => {
274
+ if (!progressEvent.total) {
275
+ return;
276
+ }
277
+
278
+ const progress = Math.round(
279
+ (progressEvent.loaded * 100) / progressEvent.total
280
+ );
281
+ setLogoUploadProgress(progress);
282
+ },
283
+ });
284
+
285
+ const nextLogoFileId = Number(data?.id);
286
+
287
+ if (!nextLogoFileId) {
288
+ throw new Error('Logo upload failed');
289
+ }
290
+
291
+ if (
292
+ previousUploadedLogoId &&
293
+ previousUploadedLogoId !== persistedLogoFileId
294
+ ) {
295
+ await deleteFileById(previousUploadedLogoId);
296
+ }
297
+
298
+ setLogoFileId(nextLogoFileId);
299
+ setLogoPreviewUrl(`${getLogoUrl(nextLogoFileId)}?ts=${Date.now()}`);
300
+ setLogoUploadProgress(100);
301
+ form.setValue('logoFileId', nextLogoFileId, {
302
+ shouldDirty: true,
303
+ shouldValidate: true,
304
+ });
305
+ showToastHandler?.('success', logoUploadSuccessMessage);
306
+ } catch {
307
+ showToastHandler?.('error', logoUploadErrorMessage);
308
+ setLogoUploadProgress(0);
309
+ } finally {
310
+ setIsUploadingLogo(false);
311
+ event.target.value = '';
312
+ }
313
+ };
314
+
315
+ const handleRemoveLogo = async () => {
316
+ if (isUploadingLogo) {
317
+ return;
318
+ }
319
+
320
+ if (logoFileId && logoFileId !== persistedLogoFileId) {
321
+ await deleteFileById(logoFileId);
322
+ }
323
+
324
+ setLogoFileId(null);
325
+ setLogoPreviewUrl(null);
326
+ setLogoUploadProgress(0);
327
+ form.setValue('logoFileId', null, {
328
+ shouldDirty: true,
329
+ shouldValidate: true,
330
+ });
331
+ showToastHandler?.('success', logoRemoveSuccessMessage);
332
+ };
333
+
135
334
  useEffect(() => {
136
335
  if (!open) {
137
336
  return;
138
337
  }
139
338
 
140
339
  if (editingAccount) {
340
+ const currentLogoFileId = editingAccount.logoFileId ?? null;
341
+
342
+ setLogoFileId(currentLogoFileId);
343
+ setPersistedLogoFileId(currentLogoFileId);
344
+ setLogoPreviewUrl(
345
+ currentLogoFileId
346
+ ? `${getLogoUrl(currentLogoFileId)}?ts=${Date.now()}`
347
+ : null
348
+ );
349
+ setLogoUploadProgress(0);
350
+
141
351
  form.reset({
142
352
  banco: editingAccount.banco,
143
353
  agencia: editingAccount.agencia === '-' ? '' : editingAccount.agencia,
144
354
  conta: editingAccount.conta === '-' ? '' : editingAccount.conta,
145
355
  tipo: editingAccount.tipo,
146
356
  descricao: editingAccount.descricao,
357
+ logoFileId: currentLogoFileId,
147
358
  saldoInicial: editingAccount.saldoAtual,
148
359
  });
149
360
  return;
150
361
  }
151
362
 
363
+ setLogoFileId(null);
364
+ setPersistedLogoFileId(null);
365
+ setLogoPreviewUrl(null);
366
+ setLogoUploadProgress(0);
367
+
152
368
  form.reset({
153
369
  banco: '',
154
370
  agencia: '',
155
371
  conta: '',
156
372
  tipo: '',
157
373
  descricao: '',
374
+ logoFileId: null,
158
375
  saldoInicial: 0,
159
376
  });
160
377
  }, [editingAccount, form, open]);
161
378
 
162
379
  const handleSubmit = async (values: BankAccountFormValues) => {
380
+ const nextLogoFileId = values.logoFileId ?? null;
381
+ const previousPersistedLogoId = persistedLogoFileId;
382
+
163
383
  try {
164
384
  if (editingAccount) {
165
385
  await request({
@@ -171,6 +391,7 @@ function NovaContaSheet({
171
391
  account: values.conta || undefined,
172
392
  type: values.tipo,
173
393
  description: values.descricao?.trim() || undefined,
394
+ logo_file_id: nextLogoFileId,
174
395
  },
175
396
  });
176
397
  } else {
@@ -183,13 +404,36 @@ function NovaContaSheet({
183
404
  account: values.conta || undefined,
184
405
  type: values.tipo,
185
406
  description: values.descricao?.trim() || undefined,
407
+ logo_file_id: nextLogoFileId,
186
408
  initial_balance: values.saldoInicial,
187
409
  },
188
410
  });
189
411
  }
190
412
 
191
413
  await onCreated();
192
- form.reset();
414
+
415
+ if (
416
+ previousPersistedLogoId &&
417
+ previousPersistedLogoId !== nextLogoFileId
418
+ ) {
419
+ await deleteFileById(previousPersistedLogoId);
420
+ }
421
+
422
+ setPersistedLogoFileId(nextLogoFileId);
423
+ setLogoFileId(nextLogoFileId);
424
+ setLogoPreviewUrl(
425
+ nextLogoFileId ? `${getLogoUrl(nextLogoFileId)}?ts=${Date.now()}` : null
426
+ );
427
+ setLogoUploadProgress(0);
428
+ form.reset({
429
+ banco: '',
430
+ agencia: '',
431
+ conta: '',
432
+ tipo: '',
433
+ descricao: '',
434
+ logoFileId: null,
435
+ saldoInicial: 0,
436
+ });
193
437
  onOpenChange(false);
194
438
  onEditingAccountChange(null);
195
439
  showToastHandler?.(
@@ -205,6 +449,7 @@ function NovaContaSheet({
205
449
  };
206
450
 
207
451
  const handleCancel = () => {
452
+ void cleanupUnsavedLogo();
208
453
  form.reset();
209
454
  onEditingAccountChange(null);
210
455
  onOpenChange(false);
@@ -214,6 +459,10 @@ function NovaContaSheet({
214
459
  <Sheet
215
460
  open={open}
216
461
  onOpenChange={(nextOpen) => {
462
+ if (!nextOpen) {
463
+ void cleanupUnsavedLogo();
464
+ }
465
+
217
466
  onOpenChange(nextOpen);
218
467
  if (!nextOpen) {
219
468
  onEditingAccountChange(null);
@@ -361,13 +610,79 @@ function NovaContaSheet({
361
610
  </FormItem>
362
611
  )}
363
612
  />
613
+
614
+ <FormItem>
615
+ <FormLabel>{t('fields.logo')}</FormLabel>
616
+ <div className="flex items-start gap-4 rounded-lg border p-3">
617
+ <div className="flex h-20 w-20 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
618
+ {logoPreviewUrl ? (
619
+ <img
620
+ src={logoPreviewUrl}
621
+ alt={t('fields.logo')}
622
+ className="h-full w-full object-cover"
623
+ />
624
+ ) : (
625
+ <Landmark className="h-8 w-8 text-muted-foreground" />
626
+ )}
627
+ </div>
628
+
629
+ <div className="flex-1 space-y-2">
630
+ <input
631
+ ref={logoInputRef}
632
+ type="file"
633
+ accept="image/*"
634
+ className="hidden"
635
+ onChange={handleLogoUpload}
636
+ />
637
+
638
+ <div className="flex flex-wrap gap-2">
639
+ <Button
640
+ type="button"
641
+ variant="outline"
642
+ onClick={handleSelectLogo}
643
+ disabled={isUploadingLogo}
644
+ >
645
+ <Upload className="mr-2 h-4 w-4" />
646
+ {t('fields.logoAction')}
647
+ </Button>
648
+
649
+ {logoFileId ? (
650
+ <Button
651
+ type="button"
652
+ variant="ghost"
653
+ onClick={() => void handleRemoveLogo()}
654
+ disabled={isUploadingLogo}
655
+ >
656
+ <Trash2 className="mr-2 h-4 w-4" />
657
+ {t('fields.logoRemove')}
658
+ </Button>
659
+ ) : null}
660
+ </div>
661
+
662
+ <p className="text-xs text-muted-foreground">
663
+ {t('fields.logoHint')}
664
+ </p>
665
+
666
+ {isUploadingLogo ? (
667
+ <p className="text-xs text-muted-foreground">
668
+ {t('fields.logoUploading', {
669
+ progress: logoUploadProgress,
670
+ })}
671
+ </p>
672
+ ) : null}
673
+ </div>
674
+ </div>
675
+ </FormItem>
364
676
  </div>
365
677
 
366
678
  <div className="flex justify-end gap-2 pt-4">
367
679
  <Button type="button" variant="outline" onClick={handleCancel}>
368
680
  {t('common.cancel')}
369
681
  </Button>
370
- <Button type="submit" disabled={form.formState.isSubmitting}>
682
+ <Button
683
+ type="submit"
684
+ disabled={form.formState.isSubmitting || isUploadingLogo}
685
+ >
371
686
  {t('common.save')}
372
687
  </Button>
373
688
  </div>
@@ -565,9 +880,7 @@ export default function ContasBancariasPage() {
565
880
  <CardHeader>
566
881
  <div className="flex items-center justify-between">
567
882
  <div className="flex items-center gap-2">
568
- <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted">
569
- <TipoIcon className="h-5 w-5" />
570
- </div>
883
+ <BankAccountLogo account={conta} icon={TipoIcon} />
571
884
  <div>
572
885
  <div className="flex gap-4 items-center">
573
886
  <CardTitle className="text-base">
@@ -655,21 +968,24 @@ export default function ContasBancariasPage() {
655
968
  {t('accountCard.reconcile')}
656
969
  </Link>
657
970
  </Button>
658
- <div className="ml-auto flex shrink-0 gap-2">
659
- <Button variant="outline" size="sm">
660
- <Upload className="h-4 w-4" />
661
- </Button>
971
+ <div className="ml-auto inline-flex shrink-0 overflow-hidden rounded-md border bg-background shadow-sm">
662
972
  <Button
663
- variant="outline"
973
+ variant="ghost"
664
974
  size="sm"
975
+ className="rounded-none border-0 px-3 hover:bg-muted"
665
976
  onClick={() => handleEdit(conta)}
977
+ aria-label={t('common.edit')}
978
+ title={t('common.edit')}
666
979
  >
667
980
  <Pencil className="h-4 w-4" />
668
981
  </Button>
669
982
  <Button
670
- variant="outline"
983
+ variant="ghost"
671
984
  size="sm"
985
+ className="rounded-none border-0 border-l px-3 text-destructive hover:bg-destructive/10 hover:text-destructive"
672
986
  onClick={() => setAccountIdToDelete(conta.id)}
987
+ aria-label={deleteDialogTitle}
988
+ title={deleteDialogTitle}
673
989
  >
674
990
  <Trash2 className="h-4 w-4" />
675
991
  </Button>
@@ -38,8 +38,44 @@
38
38
  "default": {
39
39
  "title": "Default",
40
40
  "description": "Overdue titles"
41
+ },
42
+ "pendingApprovals": {
43
+ "title": "Approvals",
44
+ "countLabel": "open",
45
+ "description": "In review: {value}"
46
+ },
47
+ "pendingReconciliation": {
48
+ "title": "Reconciliation",
49
+ "countLabel": "pending",
50
+ "description": "Active accounts: {value}"
41
51
  }
42
52
  },
53
+ "bankReconciliation": {
54
+ "title": "Bank Reconciliation",
55
+ "description": "Pending statements and differences by account",
56
+ "pending": "Pending",
57
+ "reconciled": "Reconciled",
58
+ "accounts": "Accounts",
59
+ "difference": "Difference to review",
60
+ "allClear": "All active accounts are fully reconciled.",
61
+ "unnamedAccount": "Unnamed account"
62
+ },
63
+ "approvalQueue": {
64
+ "title": "Approval Queue",
65
+ "description": "Most recent financial approvals waiting for action",
66
+ "defaultPolicy": "Financial approval",
67
+ "empty": "No approvals pending right now."
68
+ },
69
+ "aging": {
70
+ "title": "Receivable Aging",
71
+ "description": "Delinquency split by overdue range",
72
+ "totalLabel": "Total overdue",
73
+ "clientsLabel": "Clients impacted",
74
+ "range0to30": "0-30 days",
75
+ "range31to60": "31-60 days",
76
+ "range61to90": "61-90 days",
77
+ "range90plus": "90+ days"
78
+ },
43
79
  "cashFlow": {
44
80
  "title": "Cash Flow",
45
81
  "description": "Predicted vs Actual"
@@ -837,7 +873,12 @@
837
873
  "updateSuccess": "Bank account updated successfully",
838
874
  "updateError": "Failed to update bank account",
839
875
  "deleteSuccess": "Bank account deactivated successfully",
840
- "deleteError": "Failed to deactivate bank account"
876
+ "deleteError": "Failed to deactivate bank account",
877
+ "logoUploadSuccess": "Logo uploaded successfully",
878
+ "logoUploadError": "Failed to upload logo",
879
+ "logoRemoveSuccess": "Logo removed successfully",
880
+ "logoInvalidType": "Please select a valid image file",
881
+ "logoTooLarge": "The logo must be 5 MB or smaller"
841
882
  },
842
883
  "deleteDialog": {
843
884
  "title": "Deactivate bank account",
@@ -863,7 +904,12 @@
863
904
  "type": "Type",
864
905
  "description": "Description",
865
906
  "descriptionPlaceholder": "Example: Main Account",
866
- "initialBalance": "Initial Balance"
907
+ "initialBalance": "Initial Balance",
908
+ "logo": "Logo",
909
+ "logoAction": "Upload logo",
910
+ "logoRemove": "Remove logo",
911
+ "logoHint": "Optional. Prefer a square image for the best result.",
912
+ "logoUploading": "Uploading logo... {progress}%"
867
913
  },
868
914
  "header": {
869
915
  "title": "Bank Accounts",
@@ -38,8 +38,44 @@
38
38
  "default": {
39
39
  "title": "Inadimplência",
40
40
  "description": "Títulos vencidos"
41
+ },
42
+ "pendingApprovals": {
43
+ "title": "Aprovações",
44
+ "countLabel": "abertas",
45
+ "description": "Em análise: {value}"
46
+ },
47
+ "pendingReconciliation": {
48
+ "title": "Conciliação",
49
+ "countLabel": "pendentes",
50
+ "description": "Contas ativas: {value}"
41
51
  }
42
52
  },
53
+ "bankReconciliation": {
54
+ "title": "Conciliação Bancária",
55
+ "description": "Extratos pendentes e diferenças por conta",
56
+ "pending": "Pendentes",
57
+ "reconciled": "Conciliados",
58
+ "accounts": "Contas",
59
+ "difference": "Diferença a revisar",
60
+ "allClear": "Todas as contas ativas estão conciliadas.",
61
+ "unnamedAccount": "Conta sem nome"
62
+ },
63
+ "approvalQueue": {
64
+ "title": "Fila de Aprovações",
65
+ "description": "Aprovações financeiras mais recentes aguardando ação",
66
+ "defaultPolicy": "Aprovação financeira",
67
+ "empty": "Não há aprovações pendentes no momento."
68
+ },
69
+ "aging": {
70
+ "title": "Aging de Recebíveis",
71
+ "description": "Inadimplência segmentada por faixa de atraso",
72
+ "totalLabel": "Total em atraso",
73
+ "clientsLabel": "Clientes impactados",
74
+ "range0to30": "0-30 dias",
75
+ "range31to60": "31-60 dias",
76
+ "range61to90": "61-90 dias",
77
+ "range90plus": "90+ dias"
78
+ },
43
79
  "cashFlow": {
44
80
  "title": "Fluxo de Caixa",
45
81
  "description": "Previsto vs Realizado"
@@ -837,7 +873,12 @@
837
873
  "updateSuccess": "Conta bancária atualizada com sucesso",
838
874
  "updateError": "Erro ao atualizar conta bancária",
839
875
  "deleteSuccess": "Conta bancária inativada com sucesso",
840
- "deleteError": "Erro ao inativar conta bancária"
876
+ "deleteError": "Erro ao inativar conta bancária",
877
+ "logoUploadSuccess": "Logo enviado com sucesso",
878
+ "logoUploadError": "Erro ao enviar logo",
879
+ "logoRemoveSuccess": "Logo removido com sucesso",
880
+ "logoInvalidType": "Selecione um arquivo de imagem válido",
881
+ "logoTooLarge": "O logo deve ter no máximo 5 MB"
841
882
  },
842
883
  "deleteDialog": {
843
884
  "title": "Inativar conta bancária",
@@ -863,7 +904,12 @@
863
904
  "type": "Tipo",
864
905
  "description": "Descrição",
865
906
  "descriptionPlaceholder": "Ex: Conta Principal",
866
- "initialBalance": "Saldo Inicial"
907
+ "initialBalance": "Saldo Inicial",
908
+ "logo": "Logo",
909
+ "logoAction": "Enviar logo",
910
+ "logoRemove": "Remover logo",
911
+ "logoHint": "Opcional. Use preferencialmente uma imagem quadrada.",
912
+ "logoUploading": "Enviando logo... {progress}%"
867
913
  },
868
914
  "header": {
869
915
  "title": "Contas Bancárias",
@@ -5,7 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5
5
  import { useWidgetData } from '@/hooks/use-widget-data';
6
6
  import { AlertTriangle } from 'lucide-react';
7
7
  import { useLocale, useTranslations } from 'next-intl';
8
- import { WidgetWrapper } from '../widget-wrapper';
8
+ import { WidgetWrapper } from '@/app/(app)/(libraries)/core/dashboard/components';
9
9
 
10
10
  interface FinanceData {
11
11
  titulosPagar?: Array<{