@hed-hog/operations 0.0.318 → 0.0.321

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 (138) hide show
  1. package/dist/controllers/operations-collaborator-costs.controller.d.ts +144 -0
  2. package/dist/controllers/operations-collaborator-costs.controller.d.ts.map +1 -0
  3. package/dist/controllers/operations-collaborator-costs.controller.js +162 -0
  4. package/dist/controllers/operations-collaborator-costs.controller.js.map +1 -0
  5. package/dist/controllers/operations-collaborators.controller.d.ts +14 -0
  6. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-collaborators.controller.js +11 -0
  8. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  9. package/dist/controllers/operations-contracts.controller.d.ts +9 -9
  10. package/dist/controllers/operations-projects.controller.d.ts +31 -0
  11. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  12. package/dist/controllers/operations-projects.controller.js +23 -0
  13. package/dist/controllers/operations-projects.controller.js.map +1 -1
  14. package/dist/controllers/operations-reports.controller.d.ts +199 -0
  15. package/dist/controllers/operations-reports.controller.d.ts.map +1 -0
  16. package/dist/controllers/operations-reports.controller.js +53 -0
  17. package/dist/controllers/operations-reports.controller.js.map +1 -0
  18. package/dist/controllers/operations-tasks.controller.d.ts +41 -2
  19. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  20. package/dist/controllers/operations-tasks.controller.js +17 -5
  21. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  22. package/dist/dto/create-collaborator-cost.dto.d.ts +16 -0
  23. package/dist/dto/create-collaborator-cost.dto.d.ts.map +1 -0
  24. package/dist/dto/create-collaborator-cost.dto.js +88 -0
  25. package/dist/dto/create-collaborator-cost.dto.js.map +1 -0
  26. package/dist/dto/create-collaborator.dto.d.ts +0 -1
  27. package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
  28. package/dist/dto/create-collaborator.dto.js +0 -6
  29. package/dist/dto/create-collaborator.dto.js.map +1 -1
  30. package/dist/dto/create-cost-type.dto.d.ts +13 -0
  31. package/dist/dto/create-cost-type.dto.d.ts.map +1 -0
  32. package/dist/dto/create-cost-type.dto.js +87 -0
  33. package/dist/dto/create-cost-type.dto.js.map +1 -0
  34. package/dist/dto/list-approvals.dto.d.ts +2 -0
  35. package/dist/dto/list-approvals.dto.d.ts.map +1 -1
  36. package/dist/dto/list-approvals.dto.js +10 -0
  37. package/dist/dto/list-approvals.dto.js.map +1 -1
  38. package/dist/dto/list-collaborator-costs.dto.d.ts +5 -0
  39. package/dist/dto/list-collaborator-costs.dto.d.ts.map +1 -0
  40. package/dist/dto/list-collaborator-costs.dto.js +23 -0
  41. package/dist/dto/list-collaborator-costs.dto.js.map +1 -0
  42. package/dist/dto/list-cost-types.dto.d.ts +6 -0
  43. package/dist/dto/list-cost-types.dto.d.ts.map +1 -0
  44. package/dist/dto/list-cost-types.dto.js +35 -0
  45. package/dist/dto/list-cost-types.dto.js.map +1 -0
  46. package/dist/dto/list-my-projects.dto.d.ts +5 -0
  47. package/dist/dto/list-my-projects.dto.d.ts.map +1 -0
  48. package/dist/dto/list-my-projects.dto.js +23 -0
  49. package/dist/dto/list-my-projects.dto.js.map +1 -0
  50. package/dist/dto/list-my-tasks.dto.d.ts +6 -0
  51. package/dist/dto/list-my-tasks.dto.d.ts.map +1 -0
  52. package/dist/dto/list-my-tasks.dto.js +33 -0
  53. package/dist/dto/list-my-tasks.dto.js.map +1 -0
  54. package/dist/dto/list-projects.dto.d.ts +1 -0
  55. package/dist/dto/list-projects.dto.d.ts.map +1 -1
  56. package/dist/dto/list-projects.dto.js +7 -0
  57. package/dist/dto/list-projects.dto.js.map +1 -1
  58. package/dist/dto/list-reports.dto.d.ts +16 -0
  59. package/dist/dto/list-reports.dto.d.ts.map +1 -0
  60. package/dist/dto/list-reports.dto.js +75 -0
  61. package/dist/dto/list-reports.dto.js.map +1 -0
  62. package/dist/dto/list-tasks.dto.d.ts +2 -0
  63. package/dist/dto/list-tasks.dto.d.ts.map +1 -1
  64. package/dist/dto/list-tasks.dto.js +12 -0
  65. package/dist/dto/list-tasks.dto.js.map +1 -1
  66. package/dist/dto/list-timesheets.dto.d.ts +2 -0
  67. package/dist/dto/list-timesheets.dto.d.ts.map +1 -1
  68. package/dist/dto/list-timesheets.dto.js +10 -0
  69. package/dist/dto/list-timesheets.dto.js.map +1 -1
  70. package/dist/dto/update-collaborator-cost.dto.d.ts +6 -0
  71. package/dist/dto/update-collaborator-cost.dto.d.ts.map +1 -0
  72. package/dist/dto/update-collaborator-cost.dto.js +9 -0
  73. package/dist/dto/update-collaborator-cost.dto.js.map +1 -0
  74. package/dist/dto/update-task.dto.d.ts +1 -0
  75. package/dist/dto/update-task.dto.d.ts.map +1 -1
  76. package/dist/dto/update-task.dto.js +6 -0
  77. package/dist/dto/update-task.dto.js.map +1 -1
  78. package/dist/operations.module.d.ts.map +1 -1
  79. package/dist/operations.module.js +4 -0
  80. package/dist/operations.module.js.map +1 -1
  81. package/dist/operations.service.d.ts +457 -3
  82. package/dist/operations.service.d.ts.map +1 -1
  83. package/dist/operations.service.js +1445 -208
  84. package/dist/operations.service.js.map +1 -1
  85. package/dist/operations.service.spec.js +31 -7
  86. package/dist/operations.service.spec.js.map +1 -1
  87. package/hedhog/data/menu.yaml +112 -7
  88. package/hedhog/data/operations_cost_type.yaml +166 -0
  89. package/hedhog/data/route.yaml +185 -0
  90. package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -0
  91. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +80 -1
  92. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +219 -94
  93. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +21 -32
  94. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +178 -89
  95. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +1185 -0
  96. package/hedhog/frontend/app/_components/operations-calendar-view.tsx.ejs +306 -0
  97. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +943 -782
  98. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +223 -0
  99. package/hedhog/frontend/app/_lib/api.ts.ejs +162 -0
  100. package/hedhog/frontend/app/_lib/types.ts.ejs +227 -1
  101. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +11 -3
  102. package/hedhog/frontend/app/approvals/page.tsx.ejs +191 -46
  103. package/hedhog/frontend/app/collaborators/page.tsx.ejs +133 -25
  104. package/hedhog/frontend/app/my-projects/[id]/page.tsx.ejs +11 -0
  105. package/hedhog/frontend/app/my-projects/page.tsx.ejs +440 -0
  106. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +1304 -0
  107. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -0
  108. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -0
  109. package/hedhog/frontend/app/timesheets/page.tsx.ejs +322 -58
  110. package/hedhog/frontend/messages/en.json +234 -25
  111. package/hedhog/frontend/messages/pt.json +234 -25
  112. package/hedhog/table/operations_collaborator.yaml +0 -4
  113. package/hedhog/table/operations_collaborator_compensation_history.yaml +28 -0
  114. package/hedhog/table/operations_collaborator_cost.yaml +56 -0
  115. package/hedhog/table/operations_cost_type.yaml +38 -0
  116. package/package.json +7 -7
  117. package/src/controllers/operations-collaborator-costs.controller.ts +147 -0
  118. package/src/controllers/operations-collaborators.controller.ts +19 -8
  119. package/src/controllers/operations-projects.controller.ts +19 -8
  120. package/src/controllers/operations-reports.controller.ts +32 -0
  121. package/src/controllers/operations-tasks.controller.ts +32 -12
  122. package/src/dto/create-collaborator-cost.dto.ts +78 -0
  123. package/src/dto/create-collaborator.dto.ts +9 -14
  124. package/src/dto/create-cost-type.dto.ts +62 -0
  125. package/src/dto/list-approvals.dto.ts +8 -0
  126. package/src/dto/list-collaborator-costs.dto.ts +8 -0
  127. package/src/dto/list-cost-types.dto.ts +19 -0
  128. package/src/dto/list-my-projects.dto.ts +8 -0
  129. package/src/dto/list-my-tasks.dto.ts +17 -0
  130. package/src/dto/list-projects.dto.ts +7 -1
  131. package/src/dto/list-reports.dto.ts +51 -0
  132. package/src/dto/list-tasks.dto.ts +11 -1
  133. package/src/dto/list-timesheets.dto.ts +8 -0
  134. package/src/dto/update-collaborator-cost.dto.ts +4 -0
  135. package/src/dto/update-task.dto.ts +6 -0
  136. package/src/operations.module.ts +4 -0
  137. package/src/operations.service.spec.ts +45 -7
  138. package/src/operations.service.ts +1988 -221
@@ -88,12 +88,6 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
88
88
  ) ??
89
89
  contract.documents.find((document) => document.isCurrent) ??
90
90
  null;
91
-
92
- const previewSource = currentDocument?.fileContentBase64
93
- ? `data:${currentDocument.mimeType};base64,${currentDocument.fileContentBase64}`
94
- : buildStoredFileUrl(currentDocument?.fileId);
95
- const canPreview =
96
- Boolean(previewSource) && currentDocument?.mimeType?.includes('pdf');
97
91
  const contractTitle =
98
92
  contract.name || contract.code || commonT('labels.notAvailable');
99
93
 
@@ -106,6 +100,21 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
106
100
  return formT.has(key) ? formT(key) : formatEnumLabel(value);
107
101
  };
108
102
 
103
+ const getHistoryNoteLabel = (note?: string | null) => {
104
+ if (!note) {
105
+ return commonT('labels.noNotes');
106
+ }
107
+
108
+ const normalizedNote = note.trim();
109
+ const historyNoteMap: Record<string, string> = {
110
+ 'Manual contract created from registry.': t('historyNotes.manualCreated'),
111
+ 'Contract registry data updated.': t('historyNotes.registryUpdated'),
112
+ 'Contract documents removed.': t('historyNotes.documentsRemoved'),
113
+ };
114
+
115
+ return historyNoteMap[normalizedNote] ?? normalizedNote;
116
+ };
117
+
109
118
  return (
110
119
  <Page>
111
120
  <OperationsHeader
@@ -117,11 +126,7 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
117
126
  {currentDocument?.fileId || currentDocument?.fileContentBase64 ? (
118
127
  currentDocument.fileId ? (
119
128
  <Button variant="outline" size="sm" asChild>
120
- <a
121
- href={buildStoredFileUrl(currentDocument.fileId) || '#'}
122
- target="_blank"
123
- rel="noreferrer"
124
- >
129
+ <a href={buildStoredFileUrl(currentDocument.fileId) || '#'} download>
125
130
  <Download className="size-4" />
126
131
  {t('actions.downloadPdf')}
127
132
  </a>
@@ -155,29 +160,14 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
155
160
  }
156
161
  />
157
162
 
158
- <Tabs defaultValue="preview">
163
+ <Tabs defaultValue="overview">
159
164
  <TabsList className="flex-wrap">
160
- <TabsTrigger value="preview">{t('tabs.preview')}</TabsTrigger>
161
165
  <TabsTrigger value="overview">{t('tabs.overview')}</TabsTrigger>
166
+ <TabsTrigger value="parties">{t('tabs.parties')}</TabsTrigger>
162
167
  <TabsTrigger value="documents">{t('tabs.documents')}</TabsTrigger>
168
+ <TabsTrigger value="history">{t('tabs.history')}</TabsTrigger>
163
169
  </TabsList>
164
170
 
165
- <TabsContent value="preview">
166
- <SectionCard title={t('sections.preview')}>
167
- {canPreview && previewSource ? (
168
- <iframe
169
- title={t('sections.preview')}
170
- src={previewSource}
171
- className="h-180 w-full rounded-lg border"
172
- />
173
- ) : (
174
- <div className="rounded-lg border border-dashed px-4 py-12 text-center text-sm text-muted-foreground">
175
- {t('states.previewUnavailable')}
176
- </div>
177
- )}
178
- </SectionCard>
179
- </TabsContent>
180
-
181
171
  <TabsContent value="overview">
182
172
  <SectionCard title={t('sections.overview')}>
183
173
  <dl className="grid gap-3 text-sm md:grid-cols-4">
@@ -286,8 +276,7 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
286
276
  <Button variant="outline" size="sm" asChild>
287
277
  <a
288
278
  href={buildStoredFileUrl(document.fileId) || '#'}
289
- target="_blank"
290
- rel="noreferrer"
279
+ download
291
280
  >
292
281
  <Download className="size-4" />
293
282
  {t('actions.download')}
@@ -336,7 +325,7 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
336
325
  <div className="text-muted-foreground">
337
326
  {formatDateTime(item.createdAt)}
338
327
  </div>
339
- <div>{item.note || commonT('labels.noNotes')}</div>
328
+ <div>{getHistoryNoteLabel(item.note)}</div>
340
329
  </div>
341
330
  ))}
342
331
  </div>
@@ -29,6 +29,13 @@ type PartyState = {
29
29
  isPrimary: boolean;
30
30
  };
31
31
 
32
+ type ContractUploadDocument = {
33
+ fileId?: number | null;
34
+ fileName: string;
35
+ mimeType: string;
36
+ fileContentBase64?: string | null;
37
+ };
38
+
32
39
  export type ContractFormState = {
33
40
  code: string;
34
41
  name: string;
@@ -58,6 +65,8 @@ export type ContractFormState = {
58
65
  mimeType: string;
59
66
  fileContentBase64?: string | null;
60
67
  } | null;
68
+ additionalUploadedDocuments: ContractUploadDocument[];
69
+ deletedDocumentIds: number[];
61
70
  };
62
71
 
63
72
  function emptyParty(): PartyState {
@@ -97,6 +106,8 @@ function buildEmptyForm(): ContractFormState {
97
106
  contentHtml: '',
98
107
  parties: [emptyParty()],
99
108
  pdfDocument: null,
109
+ additionalUploadedDocuments: [],
110
+ deletedDocumentIds: [],
100
111
  };
101
112
  }
102
113
 
@@ -148,6 +159,8 @@ function toFormState(contract: OperationsContractDetails): ContractFormState {
148
159
  }))
149
160
  : [emptyParty()],
150
161
  pdfDocument: null,
162
+ additionalUploadedDocuments: [],
163
+ deletedDocumentIds: [],
151
164
  };
152
165
  }
153
166
 
@@ -276,26 +289,39 @@ export function ContractFormScreen({
276
289
  );
277
290
  }, [contract]);
278
291
 
279
- const previewSource = useMemo(() => {
280
- if (form.pdfDocument?.fileContentBase64 && form.pdfDocument.mimeType) {
281
- return `data:${form.pdfDocument.mimeType};base64,${form.pdfDocument.fileContentBase64}`;
282
- }
283
-
284
- if (form.pdfDocument?.fileId) {
285
- return buildStoredFileUrl(form.pdfDocument.fileId);
286
- }
287
-
288
- if (currentDocument?.fileContentBase64 && currentDocument.mimeType) {
289
- return `data:${currentDocument.mimeType};base64,${currentDocument.fileContentBase64}`;
290
- }
291
-
292
- return buildStoredFileUrl(currentDocument?.fileId);
293
- }, [currentDocument, form.pdfDocument]);
294
-
295
- const canPreview =
296
- Boolean(previewSource) &&
297
- (form.pdfDocument?.mimeType?.includes('pdf') ||
298
- currentDocument?.mimeType?.includes('pdf'));
292
+ const downloadableDocuments = useMemo(
293
+ () => [
294
+ ...(form.pdfDocument
295
+ ? [{ ...form.pdfDocument, id: 'draft-primary', isPending: true }]
296
+ : []),
297
+ ...form.additionalUploadedDocuments.map((document, index) => ({
298
+ ...document,
299
+ id: `draft-attachment-${index}`,
300
+ isPending: true,
301
+ })),
302
+ ...(contract?.documents
303
+ .filter((document) => !form.deletedDocumentIds.includes(document.id))
304
+ .filter(
305
+ (document) =>
306
+ !(
307
+ form.pdfDocument?.fileId &&
308
+ document.fileId === form.pdfDocument.fileId &&
309
+ document.fileName === form.pdfDocument.fileName
310
+ )
311
+ )
312
+ .map((document) => ({
313
+ ...document,
314
+ id: document.id,
315
+ isPending: false,
316
+ })) ?? []),
317
+ ],
318
+ [
319
+ contract?.documents,
320
+ form.additionalUploadedDocuments,
321
+ form.deletedDocumentIds,
322
+ form.pdfDocument,
323
+ ]
324
+ );
299
325
 
300
326
  const handleSubmit = async () => {
301
327
  if (!form.name.trim()) {
@@ -344,6 +370,8 @@ export function ContractFormScreen({
344
370
  isPrimary: party.isPrimary,
345
371
  })),
346
372
  replaceUploadedPdfDocument: form.pdfDocument,
373
+ additionalUploadedDocuments: form.additionalUploadedDocuments,
374
+ deletedDocumentIds: form.deletedDocumentIds,
347
375
  };
348
376
 
349
377
  try {
@@ -380,6 +408,74 @@ export function ContractFormScreen({
380
408
  }
381
409
  };
382
410
 
411
+ const handleFilesSelected = async (files: FileList | null) => {
412
+ if (!files?.length) {
413
+ return;
414
+ }
415
+
416
+ const uploadedDocuments = await Promise.all(
417
+ Array.from(files).map(async (file) => ({
418
+ fileName: file.name,
419
+ mimeType:
420
+ file.type ||
421
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
422
+ fileContentBase64: await fileToBase64(file),
423
+ }))
424
+ );
425
+
426
+ setForm((current) => {
427
+ const hasPrimaryDocument = Boolean(current.pdfDocument || currentDocument);
428
+ const nextPrimaryDocument = hasPrimaryDocument
429
+ ? current.pdfDocument
430
+ : uploadedDocuments[0] ?? null;
431
+ const extraDocuments = hasPrimaryDocument
432
+ ? uploadedDocuments
433
+ : uploadedDocuments.slice(1);
434
+
435
+ return {
436
+ ...current,
437
+ pdfDocument: nextPrimaryDocument,
438
+ additionalUploadedDocuments: [
439
+ ...current.additionalUploadedDocuments,
440
+ ...extraDocuments,
441
+ ],
442
+ };
443
+ });
444
+ };
445
+
446
+ const handleRemoveDocument = (documentId: string | number) => {
447
+ setForm((current) => {
448
+ if (documentId === 'draft-primary') {
449
+ return {
450
+ ...current,
451
+ pdfDocument: null,
452
+ };
453
+ }
454
+
455
+ if (String(documentId).startsWith('draft-attachment-')) {
456
+ const index = Number(String(documentId).replace('draft-attachment-', ''));
457
+ return {
458
+ ...current,
459
+ additionalUploadedDocuments: current.additionalUploadedDocuments.filter(
460
+ (_item, itemIndex) => itemIndex !== index
461
+ ),
462
+ };
463
+ }
464
+
465
+ const numericId = Number(documentId);
466
+ if (!Number.isInteger(numericId) || numericId <= 0) {
467
+ return current;
468
+ }
469
+
470
+ return {
471
+ ...current,
472
+ deletedDocumentIds: current.deletedDocumentIds.includes(numericId)
473
+ ? current.deletedDocumentIds
474
+ : [...current.deletedDocumentIds, numericId],
475
+ };
476
+ });
477
+ };
478
+
383
479
  const noAccessState = (
384
480
  <EmptyState
385
481
  icon={<FileText className="size-12" />}
@@ -480,82 +576,75 @@ export function ContractFormScreen({
480
576
  <input
481
577
  type="file"
482
578
  className="hidden"
579
+ multiple
483
580
  accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
484
581
  onChange={async (event) => {
485
- const file = event.target.files?.[0];
486
- if (!file) {
487
- return;
488
- }
489
-
490
- const fileContentBase64 = await fileToBase64(file);
491
- setForm((current) => ({
492
- ...current,
493
- pdfDocument: {
494
- fileName: file.name,
495
- mimeType:
496
- file.type ||
497
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
498
- fileContentBase64,
499
- },
500
- }));
582
+ await handleFilesSelected(event.target.files);
583
+ event.target.value = '';
501
584
  }}
502
585
  />
503
586
  </label>
504
- </div>
505
- </div>
506
- </SectionCard>
507
587
 
508
- <SectionCard
509
- title={detailT('sections.preview')}
510
- description="Pré-visualização rápida do arquivo atual do contrato."
511
- compact={isSheetMode}
512
- descriptionMode={isSheetMode ? 'tooltip' : 'inline'}
513
- >
514
- {canPreview && previewSource ? (
515
- <iframe
516
- title={detailT('sections.preview')}
517
- src={previewSource}
518
- className={
519
- isSheetMode
520
- ? 'h-112 w-full rounded-lg border'
521
- : 'h-130 w-full rounded-lg border'
522
- }
523
- />
524
- ) : (
525
- <div className="rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground">
526
- {detailT('states.previewUnavailable')}
588
+ {downloadableDocuments.length ? (
589
+ <div className="space-y-2">
590
+ {downloadableDocuments.map((document) => {
591
+ const storedUrl =
592
+ 'fileId' in document
593
+ ? buildStoredFileUrl(document.fileId)
594
+ : null;
595
+
596
+ return (
597
+ <div
598
+ key={String(document.id)}
599
+ className="flex flex-col gap-3 rounded-lg border px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
600
+ >
601
+ <div className="min-w-0">
602
+ <div className="truncate font-medium">
603
+ {document.fileName}
604
+ </div>
605
+ <div className="text-xs text-muted-foreground">
606
+ {document.mimeType}
607
+ </div>
608
+ </div>
609
+
610
+ <div className="flex flex-wrap gap-2">
611
+ {storedUrl ? (
612
+ <Button type="button" variant="outline" asChild>
613
+ <a href={storedUrl} download>
614
+ <Download className="size-4" />
615
+ {detailT('actions.download')}
616
+ </a>
617
+ </Button>
618
+ ) : document.fileContentBase64 ? (
619
+ <Button
620
+ type="button"
621
+ variant="outline"
622
+ onClick={() =>
623
+ downloadBase64File(
624
+ document.fileName,
625
+ document.mimeType,
626
+ document.fileContentBase64 || ''
627
+ )
628
+ }
629
+ >
630
+ <Download className="size-4" />
631
+ {detailT('actions.download')}
632
+ </Button>
633
+ ) : null}
634
+ <Button
635
+ type="button"
636
+ variant="outline"
637
+ onClick={() => handleRemoveDocument(document.id)}
638
+ >
639
+ {commonT('actions.delete')}
640
+ </Button>
641
+ </div>
642
+ </div>
643
+ );
644
+ })}
645
+ </div>
646
+ ) : null}
527
647
  </div>
528
- )}
529
-
530
- <div className="mt-4 flex flex-wrap gap-2">
531
- {currentDocument?.fileId ? (
532
- <Button type="button" variant="outline" asChild>
533
- <a
534
- href={buildStoredFileUrl(currentDocument.fileId) || '#'}
535
- target="_blank"
536
- rel="noreferrer"
537
- >
538
- <Download className="size-4" />
539
- {detailT('actions.downloadPdf')}
540
- </a>
541
- </Button>
542
- ) : null}
543
- {currentDocument?.fileContentBase64 ? (
544
- <Button
545
- type="button"
546
- variant="outline"
547
- onClick={() =>
548
- downloadBase64File(
549
- currentDocument.fileName,
550
- currentDocument.mimeType,
551
- currentDocument.fileContentBase64 || ''
552
- )
553
- }
554
- >
555
- <Download className="size-4" />
556
- {detailT('actions.downloadPdf')}
557
- </Button>
558
- ) : null}
559
648
  </div>
560
649
  </SectionCard>
561
650