@hed-hog/finance 0.0.229 → 0.0.232

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.
@@ -15,5 +15,6 @@ export declare class CreateFinancialTitleDto {
15
15
  payment_channel?: string;
16
16
  description?: string;
17
17
  installments?: CreateFinancialInstallmentDto[];
18
+ attachment_file_ids?: number[];
18
19
  }
19
20
  //# sourceMappingURL=create-financial-title.dto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-financial-title.dto.d.ts","sourceRoot":"","sources":["../../src/dto/create-financial-title.dto.ts"],"names":[],"mappings":"AAcA,qBAAa,6BAA6B;IAUxC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAS5B,QAAQ,EAAE,MAAM,CAAC;IAajB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,uBAAuB;IAOlC,eAAe,EAAE,MAAM,CAAC;IAKxB,SAAS,EAAE,MAAM,CAAC;IAUlB,eAAe,CAAC,EAAE,MAAM,CAAC;IAUzB,UAAU,CAAC,EAAE,MAAM,CAAC;IASpB,QAAQ,EAAE,MAAM,CAAC;IAajB,YAAY,EAAE,MAAM,CAAC;IAOrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAO7B,cAAc,CAAC,EAAE,MAAM,CAAC;IAOxB,eAAe,CAAC,EAAE,MAAM,CAAC;IAOzB,WAAW,CAAC,EAAE,MAAM,CAAC;IASrB,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;CAChD"}
1
+ {"version":3,"file":"create-financial-title.dto.d.ts","sourceRoot":"","sources":["../../src/dto/create-financial-title.dto.ts"],"names":[],"mappings":"AAcA,qBAAa,6BAA6B;IAUxC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAS5B,QAAQ,EAAE,MAAM,CAAC;IAajB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,uBAAuB;IAOlC,eAAe,EAAE,MAAM,CAAC;IAKxB,SAAS,EAAE,MAAM,CAAC;IAUlB,eAAe,CAAC,EAAE,MAAM,CAAC;IAUzB,UAAU,CAAC,EAAE,MAAM,CAAC;IASpB,QAAQ,EAAE,MAAM,CAAC;IAajB,YAAY,EAAE,MAAM,CAAC;IAOrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAO7B,cAAc,CAAC,EAAE,MAAM,CAAC;IAOxB,eAAe,CAAC,EAAE,MAAM,CAAC;IAOzB,WAAW,CAAC,EAAE,MAAM,CAAC;IASrB,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAY/C,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC"}
@@ -125,4 +125,15 @@ __decorate([
125
125
  (0, class_transformer_1.Type)(() => CreateFinancialInstallmentDto),
126
126
  __metadata("design:type", Array)
127
127
  ], CreateFinancialTitleDto.prototype, "installments", void 0);
128
+ __decorate([
129
+ (0, class_validator_1.IsOptional)(),
130
+ (0, class_validator_1.IsArray)({
131
+ message: (args) => (0, api_locale_1.getLocaleText)('validation.attachmentFileIdsMustBeArray', args.value),
132
+ }),
133
+ (0, class_validator_1.IsInt)({
134
+ each: true,
135
+ message: (args) => (0, api_locale_1.getLocaleText)('validation.attachmentFileIdMustBeNumber', args.value),
136
+ }),
137
+ __metadata("design:type", Array)
138
+ ], CreateFinancialTitleDto.prototype, "attachment_file_ids", void 0);
128
139
  //# sourceMappingURL=create-financial-title.dto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-financial-title.dto.js","sourceRoot":"","sources":["../../src/dto/create-financial-title.dto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-financial-title.dto.js","sourceRoot":"","sources":["../../src/dto/create-financial-title.dto.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,7 @@ import {
21
21
  import { Input } from '@/components/ui/input';
22
22
  import { InputMoney } from '@/components/ui/input-money';
23
23
  import { Money } from '@/components/ui/money';
24
+ import { Progress } from '@/components/ui/progress';
24
25
  import {
25
26
  Select,
26
27
  SelectContent,
@@ -96,6 +97,28 @@ function NovoTituloSheet({
96
97
  }) {
97
98
  const { request, showToastHandler } = useApp();
98
99
  const [open, setOpen] = useState(false);
100
+ const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
101
+ const [uploadedFileName, setUploadedFileName] = useState('');
102
+ const [isUploadingFile, setIsUploadingFile] = useState(false);
103
+ const [uploadProgress, setUploadProgress] = useState(0);
104
+
105
+ const normalizeFilenameForDisplay = (filename: string) => {
106
+ if (!filename) {
107
+ return filename;
108
+ }
109
+
110
+ if (!/Ã.|Â.|â[\u0080-\u00BF]/.test(filename)) {
111
+ return filename;
112
+ }
113
+
114
+ try {
115
+ const bytes = Uint8Array.from(filename, (char) => char.charCodeAt(0));
116
+ const decoded = new TextDecoder('utf-8').decode(bytes);
117
+ return /Ã.|Â.|â[\u0080-\u00BF]/.test(decoded) ? filename : decoded;
118
+ } catch {
119
+ return filename;
120
+ }
121
+ };
99
122
 
100
123
  const form = useForm<NewTitleFormValues>({
101
124
  resolver: zodResolver(newTitleFormSchema),
@@ -133,11 +156,14 @@ function NovoTituloSheet({
133
156
  : undefined,
134
157
  payment_channel: values.metodo || undefined,
135
158
  description: values.descricao?.trim() || undefined,
159
+ attachment_file_ids: uploadedFileId ? [uploadedFileId] : undefined,
136
160
  },
137
161
  });
138
162
 
139
163
  await onCreated();
140
164
  form.reset();
165
+ setUploadedFileId(null);
166
+ setUploadedFileName('');
141
167
  setOpen(false);
142
168
  showToastHandler?.('success', 'Título criado com sucesso');
143
169
  } catch {
@@ -147,9 +173,83 @@ function NovoTituloSheet({
147
173
 
148
174
  const handleCancel = () => {
149
175
  form.reset();
176
+ setUploadedFileId(null);
177
+ setUploadedFileName('');
178
+ setUploadProgress(0);
150
179
  setOpen(false);
151
180
  };
152
181
 
182
+ const uploadRelatedFile = async (file: File) => {
183
+ setIsUploadingFile(true);
184
+ setUploadProgress(0);
185
+
186
+ try {
187
+ const formData = new FormData();
188
+ formData.append('file', file);
189
+ formData.append('destination', 'finance/titles');
190
+
191
+ const { data } = await request<{ id: number; filename: string }>({
192
+ url: '/file',
193
+ method: 'POST',
194
+ data: formData,
195
+ headers: {
196
+ 'Content-Type': 'multipart/form-data',
197
+ },
198
+ onUploadProgress: (event) => {
199
+ if (!event.total) {
200
+ return;
201
+ }
202
+
203
+ const progress = Math.round((event.loaded * 100) / event.total);
204
+ setUploadProgress(progress);
205
+ },
206
+ });
207
+
208
+ if (!data?.id) {
209
+ throw new Error('Arquivo inválido');
210
+ }
211
+
212
+ setUploadedFileId(data.id);
213
+ setUploadedFileName(
214
+ normalizeFilenameForDisplay(data.filename || file.name)
215
+ );
216
+ setUploadProgress(100);
217
+ showToastHandler?.('success', 'Arquivo relacionado com sucesso');
218
+ } catch {
219
+ setUploadedFileId(null);
220
+ setUploadedFileName('');
221
+ setUploadProgress(0);
222
+ showToastHandler?.('error', 'Não foi possível enviar o arquivo');
223
+ } finally {
224
+ setIsUploadingFile(false);
225
+ }
226
+ };
227
+
228
+ const parseDateToIso = (value: string) => {
229
+ const sanitized = value.trim();
230
+
231
+ if (/^\d{4}-\d{2}-\d{2}$/.test(sanitized)) {
232
+ return sanitized;
233
+ }
234
+
235
+ const br = sanitized.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
236
+ if (br) {
237
+ return `${br[3]}-${br[2]}-${br[1]}`;
238
+ }
239
+
240
+ return '';
241
+ };
242
+
243
+ const parseMoneyToNumber = (value: string) => {
244
+ const normalized = value
245
+ .replace(/\s/g, '')
246
+ .replace(/\./g, '')
247
+ .replace(',', '.');
248
+
249
+ const parsed = Number(normalized);
250
+ return Number.isFinite(parsed) ? parsed : 0;
251
+ };
252
+
153
253
  return (
154
254
  <Sheet open={open} onOpenChange={setOpen}>
155
255
  <SheetTrigger asChild>
@@ -166,6 +266,41 @@ function NovoTituloSheet({
166
266
  <Form {...form}>
167
267
  <form className="px-4" onSubmit={form.handleSubmit(handleSubmit)}>
168
268
  <div className="grid gap-4">
269
+ <div className="grid gap-2">
270
+ <FormLabel>Arquivo da fatura (opcional)</FormLabel>
271
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
272
+ <Input
273
+ type="file"
274
+ accept=".pdf,.png,.jpg,.jpeg,.xml,.txt"
275
+ onChange={(event) => {
276
+ const file = event.target.files?.[0];
277
+ if (!file) {
278
+ return;
279
+ }
280
+
281
+ setUploadedFileId(null);
282
+ setUploadedFileName('');
283
+ setUploadProgress(0);
284
+ void uploadRelatedFile(file);
285
+ }}
286
+ disabled={isUploadingFile || form.formState.isSubmitting}
287
+ />
288
+ </div>
289
+ {isUploadingFile && (
290
+ <div className="space-y-1">
291
+ <Progress value={uploadProgress} className="h-2" />
292
+ <p className="text-xs text-muted-foreground">
293
+ Upload em andamento: {uploadProgress}%
294
+ </p>
295
+ </div>
296
+ )}
297
+ {uploadedFileId && (
298
+ <p className="text-xs text-muted-foreground">
299
+ Arquivo relacionado: {uploadedFileName}
300
+ </p>
301
+ )}
302
+ </div>
303
+
169
304
  <FormField
170
305
  control={form.control}
171
306
  name="documento"
@@ -188,7 +323,7 @@ function NovoTituloSheet({
188
323
  <FormLabel>{t('fields.supplier')}</FormLabel>
189
324
  <Select value={field.value} onValueChange={field.onChange}>
190
325
  <FormControl>
191
- <SelectTrigger>
326
+ <SelectTrigger className="w-full">
192
327
  <SelectValue placeholder={t('common.select')} />
193
328
  </SelectTrigger>
194
329
  </FormControl>
@@ -276,7 +411,7 @@ function NovoTituloSheet({
276
411
  <FormLabel>{t('fields.category')}</FormLabel>
277
412
  <Select value={field.value} onValueChange={field.onChange}>
278
413
  <FormControl>
279
- <SelectTrigger>
414
+ <SelectTrigger className="w-full">
280
415
  <SelectValue placeholder={t('common.select')} />
281
416
  </SelectTrigger>
282
417
  </FormControl>
@@ -303,7 +438,7 @@ function NovoTituloSheet({
303
438
  <FormLabel>{t('fields.costCenter')}</FormLabel>
304
439
  <Select value={field.value} onValueChange={field.onChange}>
305
440
  <FormControl>
306
- <SelectTrigger>
441
+ <SelectTrigger className="w-full">
307
442
  <SelectValue placeholder={t('common.select')} />
308
443
  </SelectTrigger>
309
444
  </FormControl>
@@ -328,7 +463,7 @@ function NovoTituloSheet({
328
463
  <FormLabel>{t('fields.paymentMethod')}</FormLabel>
329
464
  <Select value={field.value} onValueChange={field.onChange}>
330
465
  <FormControl>
331
- <SelectTrigger>
466
+ <SelectTrigger className="w-full">
332
467
  <SelectValue placeholder={t('common.select')} />
333
468
  </SelectTrigger>
334
469
  </FormControl>
@@ -1,6 +1,16 @@
1
1
  'use client';
2
2
 
3
3
  import { Page, PageHeader } from '@/components/entity-list';
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogCancel,
8
+ AlertDialogContent,
9
+ AlertDialogDescription,
10
+ AlertDialogFooter,
11
+ AlertDialogHeader,
12
+ AlertDialogTitle,
13
+ } from '@/components/ui/alert-dialog';
4
14
  import { Badge } from '@/components/ui/badge';
5
15
  import { Button } from '@/components/ui/button';
6
16
  import {
@@ -97,6 +107,19 @@ function NovaContaSheet({
97
107
  }) {
98
108
  const { request, showToastHandler } = useApp();
99
109
 
110
+ const createSuccessMessage = t.has('messages.createSuccess')
111
+ ? t('messages.createSuccess')
112
+ : 'Conta bancária cadastrada com sucesso';
113
+ const createErrorMessage = t.has('messages.createError')
114
+ ? t('messages.createError')
115
+ : 'Erro ao cadastrar conta bancária';
116
+ const updateSuccessMessage = t.has('messages.updateSuccess')
117
+ ? t('messages.updateSuccess')
118
+ : 'Conta bancária atualizada com sucesso';
119
+ const updateErrorMessage = t.has('messages.updateError')
120
+ ? t('messages.updateError')
121
+ : 'Erro ao atualizar conta bancária';
122
+
100
123
  const form = useForm<BankAccountFormValues>({
101
124
  resolver: zodResolver(bankAccountFormSchema),
102
125
  defaultValues: {
@@ -171,16 +194,12 @@ function NovaContaSheet({
171
194
  onEditingAccountChange(null);
172
195
  showToastHandler?.(
173
196
  'success',
174
- editingAccount
175
- ? 'Conta bancária atualizada com sucesso'
176
- : 'Conta bancária cadastrada com sucesso'
197
+ editingAccount ? updateSuccessMessage : createSuccessMessage
177
198
  );
178
199
  } catch {
179
200
  showToastHandler?.(
180
201
  'error',
181
- editingAccount
182
- ? 'Erro ao atualizar conta bancária'
183
- : 'Erro ao cadastrar conta bancária'
202
+ editingAccount ? updateErrorMessage : createErrorMessage
184
203
  );
185
204
  }
186
205
  };
@@ -266,37 +285,64 @@ function NovaContaSheet({
266
285
  />
267
286
  </div>
268
287
 
269
- <FormField
270
- control={form.control}
271
- name="tipo"
272
- render={({ field }) => (
273
- <FormItem>
274
- <FormLabel>{t('fields.type')}</FormLabel>
275
- <Select value={field.value} onValueChange={field.onChange}>
288
+ <div className="grid grid-cols-2 gap-4">
289
+ <FormField
290
+ control={form.control}
291
+ name="tipo"
292
+ render={({ field }) => (
293
+ <FormItem>
294
+ <FormLabel>{t('fields.type')}</FormLabel>
295
+ <Select
296
+ value={field.value}
297
+ onValueChange={field.onChange}
298
+ >
299
+ <FormControl>
300
+ <SelectTrigger className="w-full">
301
+ <SelectValue placeholder={t('common.select')} />
302
+ </SelectTrigger>
303
+ </FormControl>
304
+ <SelectContent>
305
+ <SelectItem value="corrente">
306
+ {t('types.corrente')}
307
+ </SelectItem>
308
+ <SelectItem value="poupanca">
309
+ {t('types.poupanca')}
310
+ </SelectItem>
311
+ <SelectItem value="investimento">
312
+ {t('types.investimento')}
313
+ </SelectItem>
314
+ <SelectItem value="caixa">
315
+ {t('types.caixa')}
316
+ </SelectItem>
317
+ </SelectContent>
318
+ </Select>
319
+ <FormMessage />
320
+ </FormItem>
321
+ )}
322
+ />
323
+
324
+ <FormField
325
+ control={form.control}
326
+ name="saldoInicial"
327
+ render={({ field }) => (
328
+ <FormItem>
329
+ <FormLabel>{t('fields.initialBalance')}</FormLabel>
276
330
  <FormControl>
277
- <SelectTrigger>
278
- <SelectValue placeholder={t('common.select')} />
279
- </SelectTrigger>
331
+ <InputMoney
332
+ ref={field.ref}
333
+ name={field.name}
334
+ value={field.value}
335
+ onBlur={field.onBlur}
336
+ onValueChange={(value) => field.onChange(value ?? 0)}
337
+ placeholder="0,00"
338
+ disabled={!!editingAccount}
339
+ />
280
340
  </FormControl>
281
- <SelectContent>
282
- <SelectItem value="corrente">
283
- {t('types.corrente')}
284
- </SelectItem>
285
- <SelectItem value="poupanca">
286
- {t('types.poupanca')}
287
- </SelectItem>
288
- <SelectItem value="investimento">
289
- {t('types.investimento')}
290
- </SelectItem>
291
- <SelectItem value="caixa">
292
- {t('types.caixa')}
293
- </SelectItem>
294
- </SelectContent>
295
- </Select>
296
- <FormMessage />
297
- </FormItem>
298
- )}
299
- />
341
+ <FormMessage />
342
+ </FormItem>
343
+ )}
344
+ />
345
+ </div>
300
346
 
301
347
  <FormField
302
348
  control={form.control}
@@ -315,28 +361,6 @@ function NovaContaSheet({
315
361
  </FormItem>
316
362
  )}
317
363
  />
318
-
319
- <FormField
320
- control={form.control}
321
- name="saldoInicial"
322
- render={({ field }) => (
323
- <FormItem>
324
- <FormLabel>{t('fields.initialBalance')}</FormLabel>
325
- <FormControl>
326
- <InputMoney
327
- ref={field.ref}
328
- name={field.name}
329
- value={field.value}
330
- onBlur={field.onBlur}
331
- onValueChange={(value) => field.onChange(value ?? 0)}
332
- placeholder="0,00"
333
- disabled={!!editingAccount}
334
- />
335
- </FormControl>
336
- <FormMessage />
337
- </FormItem>
338
- )}
339
- />
340
364
  </div>
341
365
 
342
366
  <div className="flex justify-end gap-2 pt-4">
@@ -356,13 +380,33 @@ function NovaContaSheet({
356
380
 
357
381
  export default function ContasBancariasPage() {
358
382
  const t = useTranslations('finance.BankAccountsPage');
359
- const { request, showToastHandler } = useApp();
383
+ const { request, showToastHandler, currentLocaleCode } = useApp();
384
+
385
+ const deleteSuccessMessage = t.has('messages.deleteSuccess')
386
+ ? t('messages.deleteSuccess')
387
+ : 'Conta bancária inativada com sucesso';
388
+ const deleteErrorMessage = t.has('messages.deleteError')
389
+ ? t('messages.deleteError')
390
+ : 'Erro ao inativar conta bancária';
391
+ const deleteDialogTitle = t.has('deleteDialog.title')
392
+ ? t('deleteDialog.title')
393
+ : 'Inativar conta bancária';
394
+ const deleteDialogDescription = t.has('deleteDialog.description')
395
+ ? t('deleteDialog.description')
396
+ : 'Deseja realmente inativar esta conta bancária?';
397
+ const deleteDialogConfirm = t.has('deleteDialog.confirm')
398
+ ? t('deleteDialog.confirm')
399
+ : 'Inativar';
400
+
360
401
  const [sheetOpen, setSheetOpen] = useState(false);
361
402
  const [editingAccount, setEditingAccount] = useState<BankAccount | null>(
362
403
  null
363
404
  );
405
+ const [accountIdToDelete, setAccountIdToDelete] = useState<string | null>(
406
+ null
407
+ );
364
408
  const { data: contasBancarias, refetch } = useQuery<BankAccount[]>({
365
- queryKey: ['finance-bank-accounts'],
409
+ queryKey: ['finance-bank-accounts', currentLocaleCode],
366
410
  queryFn: async () => {
367
411
  const response = await request({
368
412
  url: '/finance/bank-accounts',
@@ -371,8 +415,9 @@ export default function ContasBancariasPage() {
371
415
 
372
416
  return (response.data || []) as BankAccount[];
373
417
  },
374
- initialData: [],
418
+ placeholderData: [],
375
419
  });
420
+ const accounts = contasBancarias ?? [];
376
421
 
377
422
  const tipoConfig = {
378
423
  corrente: { label: t('types.corrente'), icon: Building2 },
@@ -381,11 +426,11 @@ export default function ContasBancariasPage() {
381
426
  caixa: { label: t('types.caixa'), icon: Wallet },
382
427
  };
383
428
 
384
- const saldoTotal = contasBancarias
429
+ const saldoTotal = accounts
385
430
  .filter((c) => c.ativo)
386
431
  .reduce((acc, c) => acc + c.saldoAtual, 0);
387
432
 
388
- const saldoConciliadoTotal = contasBancarias
433
+ const saldoConciliadoTotal = accounts
389
434
  .filter((c) => c.ativo)
390
435
  .reduce((acc, c) => acc + c.saldoConciliado, 0);
391
436
 
@@ -399,25 +444,22 @@ export default function ContasBancariasPage() {
399
444
  setSheetOpen(true);
400
445
  };
401
446
 
402
- const handleDelete = async (accountId: string) => {
403
- const confirmed = window.confirm(
404
- 'Deseja realmente inativar esta conta bancária?'
405
- );
406
-
407
- if (!confirmed) {
447
+ const handleDelete = async () => {
448
+ if (!accountIdToDelete) {
408
449
  return;
409
450
  }
410
451
 
411
452
  try {
412
453
  await request({
413
- url: `/finance/bank-accounts/${accountId}`,
454
+ url: `/finance/bank-accounts/${accountIdToDelete}`,
414
455
  method: 'DELETE',
415
456
  });
416
457
 
417
458
  await refetch();
418
- showToastHandler?.('success', 'Conta bancária inativada com sucesso');
459
+ showToastHandler?.('success', deleteSuccessMessage);
460
+ setAccountIdToDelete(null);
419
461
  } catch {
420
- showToastHandler?.('error', 'Erro ao inativar conta bancária');
462
+ showToastHandler?.('error', deleteErrorMessage);
421
463
  }
422
464
  };
423
465
 
@@ -448,6 +490,30 @@ export default function ContasBancariasPage() {
448
490
  onEditingAccountChange={setEditingAccount}
449
491
  />
450
492
 
493
+ <AlertDialog
494
+ open={!!accountIdToDelete}
495
+ onOpenChange={(open) => {
496
+ if (!open) {
497
+ setAccountIdToDelete(null);
498
+ }
499
+ }}
500
+ >
501
+ <AlertDialogContent>
502
+ <AlertDialogHeader>
503
+ <AlertDialogTitle>{deleteDialogTitle}</AlertDialogTitle>
504
+ <AlertDialogDescription>
505
+ {deleteDialogDescription}
506
+ </AlertDialogDescription>
507
+ </AlertDialogHeader>
508
+ <AlertDialogFooter>
509
+ <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
510
+ <AlertDialogAction onClick={handleDelete}>
511
+ {deleteDialogConfirm}
512
+ </AlertDialogAction>
513
+ </AlertDialogFooter>
514
+ </AlertDialogContent>
515
+ </AlertDialog>
516
+
451
517
  <div className="grid gap-4 md:grid-cols-2">
452
518
  <Card>
453
519
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -462,7 +528,7 @@ export default function ContasBancariasPage() {
462
528
  </div>
463
529
  <p className="text-xs text-muted-foreground">
464
530
  {t('cards.activeAccounts', {
465
- count: contasBancarias.filter((c) => c.ativo).length,
531
+ count: accounts.filter((c) => c.ativo).length,
466
532
  })}
467
533
  </p>
468
534
  </CardContent>
@@ -487,7 +553,7 @@ export default function ContasBancariasPage() {
487
553
  </div>
488
554
 
489
555
  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
490
- {contasBancarias.map((conta) => {
556
+ {accounts.map((conta) => {
491
557
  const tipo =
492
558
  tipoConfig[conta.tipo as keyof typeof tipoConfig] ||
493
559
  tipoConfig.corrente;
@@ -503,14 +569,25 @@ export default function ContasBancariasPage() {
503
569
  <TipoIcon className="h-5 w-5" />
504
570
  </div>
505
571
  <div>
506
- <CardTitle className="text-base">{conta.banco}</CardTitle>
507
- <CardDescription>
508
- {conta.agencia !== '-'
509
- ? t('accountCard.bankAccount', {
572
+ <div className="flex gap-4 items-center">
573
+ <CardTitle className="text-base">
574
+ {conta.banco}
575
+ </CardTitle>
576
+ {conta.descricao && (
577
+ <span className="block text-muted-foreground text-xs">
578
+ {conta.descricao}
579
+ </span>
580
+ )}
581
+ </div>
582
+ <CardDescription className="space-y-0.5">
583
+ {conta.agencia !== '-' && (
584
+ <span className="block">
585
+ {t('accountCard.bankAccount', {
510
586
  agency: conta.agencia,
511
587
  account: conta.conta,
512
- })
513
- : conta.descricao}
588
+ })}
589
+ </span>
590
+ )}
514
591
  </CardDescription>
515
592
  </div>
516
593
  </div>
@@ -592,7 +669,7 @@ export default function ContasBancariasPage() {
592
669
  <Button
593
670
  variant="outline"
594
671
  size="sm"
595
- onClick={() => handleDelete(conta.id)}
672
+ onClick={() => setAccountIdToDelete(conta.id)}
596
673
  >
597
674
  <Trash2 className="h-4 w-4" />
598
675
  </Button>
@@ -446,8 +446,22 @@
446
446
  "common": {
447
447
  "select": "Select...",
448
448
  "cancel": "Cancel",
449
+ "edit": "Edit",
449
450
  "save": "Save"
450
451
  },
452
+ "messages": {
453
+ "createSuccess": "Bank account created successfully",
454
+ "createError": "Failed to create bank account",
455
+ "updateSuccess": "Bank account updated successfully",
456
+ "updateError": "Failed to update bank account",
457
+ "deleteSuccess": "Bank account deactivated successfully",
458
+ "deleteError": "Failed to deactivate bank account"
459
+ },
460
+ "deleteDialog": {
461
+ "title": "Deactivate bank account",
462
+ "description": "Do you really want to deactivate this bank account?",
463
+ "confirm": "Deactivate"
464
+ },
451
465
  "types": {
452
466
  "corrente": "Checking Account",
453
467
  "poupanca": "Savings Account",
@@ -446,8 +446,22 @@
446
446
  "common": {
447
447
  "select": "Selecione...",
448
448
  "cancel": "Cancelar",
449
+ "edit": "Editar",
449
450
  "save": "Salvar"
450
451
  },
452
+ "messages": {
453
+ "createSuccess": "Conta bancária cadastrada com sucesso",
454
+ "createError": "Erro ao cadastrar conta bancária",
455
+ "updateSuccess": "Conta bancária atualizada com sucesso",
456
+ "updateError": "Erro ao atualizar conta bancária",
457
+ "deleteSuccess": "Conta bancária inativada com sucesso",
458
+ "deleteError": "Erro ao inativar conta bancária"
459
+ },
460
+ "deleteDialog": {
461
+ "title": "Inativar conta bancária",
462
+ "description": "Deseja realmente inativar esta conta bancária?",
463
+ "confirm": "Inativar"
464
+ },
451
465
  "types": {
452
466
  "corrente": "Conta Corrente",
453
467
  "poupanca": "Poupança",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/finance",
3
- "version": "0.0.229",
3
+ "version": "0.0.232",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,13 +9,13 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.4",
13
12
  "@hed-hog/api-locale": "0.0.11",
13
+ "@hed-hog/api-prisma": "0.0.4",
14
14
  "@hed-hog/api-pagination": "0.0.5",
15
- "@hed-hog/tag": "0.0.223",
16
- "@hed-hog/api": "0.0.3",
17
15
  "@hed-hog/api-types": "0.0.1",
18
- "@hed-hog/contact": "0.0.223"
16
+ "@hed-hog/api": "0.0.3",
17
+ "@hed-hog/tag": "0.0.232",
18
+ "@hed-hog/contact": "0.0.232"
19
19
  },
20
20
  "exports": {
21
21
  ".": {
@@ -139,4 +139,16 @@ export class CreateFinancialTitleDto {
139
139
  @ValidateNested({ each: true })
140
140
  @Type(() => CreateFinancialInstallmentDto)
141
141
  installments?: CreateFinancialInstallmentDto[];
142
+
143
+ @IsOptional()
144
+ @IsArray({
145
+ message: (args) =>
146
+ getLocaleText('validation.attachmentFileIdsMustBeArray', args.value),
147
+ })
148
+ @IsInt({
149
+ each: true,
150
+ message: (args) =>
151
+ getLocaleText('validation.attachmentFileIdMustBeNumber', args.value),
152
+ })
153
+ attachment_file_ids?: number[];
142
154
  }