@hed-hog/operations 0.0.305 → 0.0.309

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-approvals.controller.d.ts +114 -1
  2. package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-approvals.controller.js +16 -3
  4. package/dist/controllers/operations-approvals.controller.js.map +1 -1
  5. package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
  6. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-collaborators.controller.js +16 -3
  8. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  9. package/dist/controllers/operations-contracts.controller.d.ts +14 -453
  10. package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
  11. package/dist/controllers/operations-contracts.controller.js +11 -112
  12. package/dist/controllers/operations-contracts.controller.js.map +1 -1
  13. package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
  14. package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
  15. package/dist/controllers/operations-org-structure.controller.js +18 -5
  16. package/dist/controllers/operations-org-structure.controller.js.map +1 -1
  17. package/dist/controllers/operations-projects.controller.d.ts +28 -4
  18. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  19. package/dist/controllers/operations-projects.controller.js +17 -5
  20. package/dist/controllers/operations-projects.controller.js.map +1 -1
  21. package/dist/controllers/operations-timesheets.controller.d.ts +52 -4
  22. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
  23. package/dist/controllers/operations-timesheets.controller.js +28 -11
  24. package/dist/controllers/operations-timesheets.controller.js.map +1 -1
  25. package/dist/dto/list-approvals.dto.d.ts +6 -0
  26. package/dist/dto/list-approvals.dto.d.ts.map +1 -0
  27. package/dist/dto/list-approvals.dto.js +28 -0
  28. package/dist/dto/list-approvals.dto.js.map +1 -0
  29. package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
  30. package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
  31. package/dist/dto/list-collaborator-types.dto.js +7 -1
  32. package/dist/dto/list-collaborator-types.dto.js.map +1 -1
  33. package/dist/dto/list-collaborators.dto.d.ts +1 -0
  34. package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
  35. package/dist/dto/list-collaborators.dto.js +5 -0
  36. package/dist/dto/list-collaborators.dto.js.map +1 -1
  37. package/dist/dto/list-contracts.dto.d.ts +8 -0
  38. package/dist/dto/list-contracts.dto.d.ts.map +1 -0
  39. package/dist/dto/list-contracts.dto.js +38 -0
  40. package/dist/dto/list-contracts.dto.js.map +1 -0
  41. package/dist/dto/list-departments.dto.d.ts +5 -0
  42. package/dist/dto/list-departments.dto.d.ts.map +1 -0
  43. package/dist/dto/list-departments.dto.js +23 -0
  44. package/dist/dto/list-departments.dto.js.map +1 -0
  45. package/dist/dto/list-projects.dto.d.ts +5 -0
  46. package/dist/dto/list-projects.dto.d.ts.map +1 -0
  47. package/dist/dto/list-projects.dto.js +23 -0
  48. package/dist/dto/list-projects.dto.js.map +1 -0
  49. package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
  50. package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
  51. package/dist/dto/list-schedule-adjustments.dto.js +23 -0
  52. package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
  53. package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
  54. package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
  55. package/dist/dto/list-time-off-requests.dto.js +23 -0
  56. package/dist/dto/list-time-off-requests.dto.js.map +1 -0
  57. package/dist/dto/list-timesheets.dto.d.ts +5 -0
  58. package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
  59. package/dist/dto/list-timesheets.dto.js +23 -0
  60. package/dist/dto/list-timesheets.dto.js.map +1 -0
  61. package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
  62. package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
  63. package/dist/dto/reorder-collaborator-types.dto.js +25 -0
  64. package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
  65. package/dist/dto/update-collaborator-type.dto.d.ts +3 -1
  66. package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -1
  67. package/dist/dto/update-collaborator-type.dto.js +2 -1
  68. package/dist/dto/update-collaborator-type.dto.js.map +1 -1
  69. package/dist/operations.service.d.ts +362 -271
  70. package/dist/operations.service.d.ts.map +1 -1
  71. package/dist/operations.service.js +1195 -1098
  72. package/dist/operations.service.js.map +1 -1
  73. package/dist/operations.service.spec.js +73 -22
  74. package/dist/operations.service.spec.js.map +1 -1
  75. package/hedhog/data/menu.yaml +19 -55
  76. package/hedhog/data/operations_collaborator_type.yaml +76 -76
  77. package/hedhog/data/route.yaml +52 -70
  78. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +5 -3
  79. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
  80. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
  81. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
  82. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
  83. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
  84. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
  85. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
  86. package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +1 -0
  87. package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
  88. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
  89. package/hedhog/frontend/app/approvals/page.tsx.ejs +843 -151
  90. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +457 -154
  91. package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
  92. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  93. package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
  94. package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
  95. package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
  96. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +546 -118
  97. package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
  98. package/hedhog/frontend/app/timesheets/page.tsx.ejs +647 -342
  99. package/hedhog/frontend/messages/en.json +148 -14
  100. package/hedhog/frontend/messages/pt.json +199 -56
  101. package/hedhog/table/operations_collaborator.yaml +18 -18
  102. package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -43
  103. package/hedhog/table/operations_collaborator_type.yaml +33 -33
  104. package/hedhog/table/operations_contract.yaml +0 -9
  105. package/hedhog/table/operations_contract_document.yaml +33 -33
  106. package/package.json +4 -4
  107. package/src/controllers/operations-approvals.controller.ts +9 -3
  108. package/src/controllers/operations-collaborators.controller.ts +15 -2
  109. package/src/controllers/operations-contracts.controller.ts +8 -92
  110. package/src/controllers/operations-org-structure.controller.ts +17 -4
  111. package/src/controllers/operations-projects.controller.ts +10 -4
  112. package/src/controllers/operations-timesheets.controller.ts +30 -8
  113. package/src/dto/create-collaborator-type.dto.ts +43 -43
  114. package/src/dto/create-collaborator.dto.ts +223 -223
  115. package/src/dto/list-approvals.dto.ts +12 -0
  116. package/src/dto/list-collaborator-types.dto.ts +20 -15
  117. package/src/dto/list-collaborators.dto.ts +34 -30
  118. package/src/dto/list-contracts.dto.ts +20 -0
  119. package/src/dto/list-departments.dto.ts +8 -0
  120. package/src/dto/list-projects.dto.ts +8 -0
  121. package/src/dto/list-schedule-adjustments.dto.ts +8 -0
  122. package/src/dto/list-time-off-requests.dto.ts +8 -0
  123. package/src/dto/list-timesheets.dto.ts +8 -0
  124. package/src/dto/reorder-collaborator-types.dto.ts +10 -0
  125. package/src/dto/update-collaborator-type.dto.ts +4 -3
  126. package/src/dto/update-collaborator.dto.ts +3 -3
  127. package/src/operations.service.spec.ts +96 -30
  128. package/src/operations.service.ts +1738 -1777
  129. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
  130. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
  131. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
  132. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
  133. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
  134. package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
  135. package/hedhog/table/operations_contract_financial_term.yaml +0 -40
  136. package/hedhog/table/operations_contract_revision.yaml +0 -38
  137. package/hedhog/table/operations_contract_signature.yaml +0 -38
  138. package/hedhog/table/operations_contract_template.yaml +0 -58
@@ -599,7 +599,13 @@ export function CollaboratorFormScreen({
599
599
  refetchOnMount: 'always',
600
600
  queryFn: async () => {
601
601
  const response = await request<{
602
- paginate: { data: Array<{ id: number; name: string | null }> };
602
+ paginate: {
603
+ data: Array<{
604
+ id: number;
605
+ name: string | null;
606
+ photo_id?: number | null;
607
+ }>;
608
+ };
603
609
  }>({
604
610
  url: '/user?pageSize=100',
605
611
  method: 'GET',
@@ -609,6 +615,7 @@ export function CollaboratorFormScreen({
609
615
  const options: SystemUserOption[] = raw.map((u) => ({
610
616
  id: u.id,
611
617
  name: u.name || `#${u.id}`,
618
+ photoId: u.photo_id ?? null,
612
619
  }));
613
620
 
614
621
  return options.sort((left, right) => left.name.localeCompare(right.name));
@@ -23,9 +23,13 @@ import {
23
23
  } from '@/components/ui/sheet';
24
24
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
25
25
  import { Check, ChevronsUpDown, Plus, X } from 'lucide-react';
26
+ import { useTranslations } from 'next-intl';
26
27
  import { useEffect, useRef, useState } from 'react';
27
- import type { OperationsCollaborator, OperationsCollaboratorDetails } from '../_lib/types';
28
28
  import { fetchOperations } from '../_lib/api';
29
+ import type {
30
+ OperationsCollaborator,
31
+ OperationsCollaboratorDetails,
32
+ } from '../_lib/types';
29
33
  import { CollaboratorFormScreen } from './collaborator-form-screen';
30
34
 
31
35
  type CollaboratorSelectWithCreateProps = {
@@ -46,6 +50,7 @@ export function CollaboratorSelectWithCreate({
46
50
  onChange,
47
51
  }: CollaboratorSelectWithCreateProps) {
48
52
  const { request, currentLocaleCode } = useApp();
53
+ const commonT = useTranslations('operations.Common');
49
54
  const [open, setOpen] = useState(false);
50
55
  const [search, setSearch] = useState('');
51
56
  const [createOpen, setCreateOpen] = useState(false);
@@ -56,7 +61,10 @@ export function CollaboratorSelectWithCreate({
56
61
  const { data: collaborators = [] } = useQuery<OperationsCollaborator[]>({
57
62
  queryKey: ['operations-inline-collaborators', currentLocaleCode],
58
63
  queryFn: () =>
59
- fetchOperations<OperationsCollaborator[]>(request, '/operations/collaborators'),
64
+ fetchOperations<OperationsCollaborator[]>(
65
+ request,
66
+ '/operations/collaborators'
67
+ ),
60
68
  placeholderData: (old) => old ?? [],
61
69
  });
62
70
 
@@ -77,12 +85,7 @@ export function CollaboratorSelectWithCreate({
77
85
  }, [collaborators, value]);
78
86
 
79
87
  const filteredOptions = collaborators.filter((item) => {
80
- const haystack = [
81
- item.displayName,
82
- item.code,
83
- item.title,
84
- item.department,
85
- ]
88
+ const haystack = [item.displayName, item.code, item.title, item.department]
86
89
  .filter(Boolean)
87
90
  .join(' ')
88
91
  .toLowerCase();
@@ -151,10 +154,12 @@ export function CollaboratorSelectWithCreate({
151
154
  <CommandInput
152
155
  value={search}
153
156
  onValueChange={setSearch}
154
- placeholder="Buscar colaborador..."
157
+ placeholder={commonT('labels.searchCollaborator')}
155
158
  />
156
159
  <CommandList>
157
- <CommandEmpty>Nenhum colaborador encontrado.</CommandEmpty>
160
+ <CommandEmpty>
161
+ {commonT('labels.noCollaboratorsFound')}
162
+ </CommandEmpty>
158
163
  <CommandGroup>
159
164
  {filteredOptions.map((option) => (
160
165
  <CommandItem
@@ -19,8 +19,6 @@ import { fetchOperations } from '../_lib/api';
19
19
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
20
20
  import type { OperationsContractDetails } from '../_lib/types';
21
21
  import {
22
- formatCurrency,
23
- formatDate,
24
22
  formatDateTime,
25
23
  formatEnumLabel,
26
24
  getStatusBadgeClass,
@@ -41,14 +39,10 @@ function downloadBase64File(
41
39
  link.click();
42
40
  }
43
41
 
44
- function openStoredFile(fileId?: number | null) {
45
- if (!fileId) return;
42
+ function buildStoredFileUrl(fileId?: number | null) {
43
+ if (!fileId) return null;
46
44
  const baseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
47
- window.open(
48
- `${baseUrl}/file/open/${fileId}`,
49
- '_blank',
50
- 'noopener,noreferrer'
51
- );
45
+ return `${baseUrl}/file/open/${fileId}`;
52
46
  }
53
47
 
54
48
  export function ContractDetailsScreen({ contractId }: { contractId: number }) {
@@ -86,9 +80,23 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
86
80
  );
87
81
  }
88
82
 
89
- const currentPdf = contract.documents.find((document) => document.isCurrent);
83
+ const currentDocument =
84
+ contract.documents.find(
85
+ (document) =>
86
+ document.isCurrent &&
87
+ ['generated_pdf', 'source_upload'].includes(document.documentType)
88
+ ) ??
89
+ contract.documents.find((document) => document.isCurrent) ??
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');
90
97
  const contractTitle =
91
98
  contract.name || contract.code || commonT('labels.notAvailable');
99
+
92
100
  const getOptionLabel = (group: string, value?: string | null) => {
93
101
  if (!value) {
94
102
  return '-';
@@ -105,24 +113,35 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
105
113
  description={t('description')}
106
114
  current={t('breadcrumb')}
107
115
  actions={
108
- <div className="flex gap-2">
109
- {currentPdf?.fileId || currentPdf?.fileContentBase64 ? (
110
- <Button
111
- variant="outline"
112
- size="sm"
113
- onClick={() =>
114
- currentPdf?.fileId
115
- ? openStoredFile(currentPdf.fileId)
116
- : downloadBase64File(
117
- currentPdf.fileName,
118
- currentPdf.mimeType,
119
- currentPdf.fileContentBase64 || ''
120
- )
121
- }
122
- >
123
- <Download className="size-4" />
124
- {t('actions.downloadPdf')}
125
- </Button>
116
+ <div className="flex flex-wrap gap-2">
117
+ {currentDocument?.fileId || currentDocument?.fileContentBase64 ? (
118
+ currentDocument.fileId ? (
119
+ <Button variant="outline" size="sm" asChild>
120
+ <a
121
+ href={buildStoredFileUrl(currentDocument.fileId) || '#'}
122
+ target="_blank"
123
+ rel="noreferrer"
124
+ >
125
+ <Download className="size-4" />
126
+ {t('actions.downloadPdf')}
127
+ </a>
128
+ </Button>
129
+ ) : (
130
+ <Button
131
+ variant="outline"
132
+ size="sm"
133
+ onClick={() =>
134
+ downloadBase64File(
135
+ currentDocument.fileName,
136
+ currentDocument.mimeType,
137
+ currentDocument.fileContentBase64 || ''
138
+ )
139
+ }
140
+ >
141
+ <Download className="size-4" />
142
+ {t('actions.downloadPdf')}
143
+ </Button>
144
+ )
126
145
  ) : null}
127
146
  {access.isDirector ? (
128
147
  <Button size="sm" asChild>
@@ -136,105 +155,73 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
136
155
  }
137
156
  />
138
157
 
139
- <Tabs defaultValue="overview">
158
+ <Tabs defaultValue="preview">
140
159
  <TabsList className="flex-wrap">
160
+ <TabsTrigger value="preview">{t('tabs.preview')}</TabsTrigger>
141
161
  <TabsTrigger value="overview">{t('tabs.overview')}</TabsTrigger>
142
- <TabsTrigger value="parties">{t('tabs.parties')}</TabsTrigger>
143
- <TabsTrigger value="signatures">{t('tabs.signatures')}</TabsTrigger>
144
- <TabsTrigger value="financials">{t('tabs.financials')}</TabsTrigger>
145
162
  <TabsTrigger value="documents">{t('tabs.documents')}</TabsTrigger>
146
- <TabsTrigger value="revisions">{t('tabs.revisions')}</TabsTrigger>
147
- <TabsTrigger value="history">{t('tabs.history')}</TabsTrigger>
148
163
  </TabsList>
149
164
 
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
+
150
181
  <TabsContent value="overview">
151
182
  <SectionCard title={t('sections.overview')}>
152
- <dl className="grid gap-3 text-sm md:grid-cols-3">
183
+ <dl className="grid gap-3 text-sm md:grid-cols-4">
153
184
  <div>
154
185
  <dt className="text-muted-foreground">
155
186
  {commonT('labels.contract')}
156
187
  </dt>
157
188
  <dd className="font-medium">{contractTitle}</dd>
158
189
  </div>
159
- <div>
160
- <dt className="text-muted-foreground">{t('labels.origin')}</dt>
161
- <dd className="font-medium">
162
- {getOptionLabel('originTypes', contract.originType)}
163
- </dd>
164
- </div>
165
190
  <div>
166
191
  <dt className="text-muted-foreground">
167
- {t('labels.mainParty')}
168
- </dt>
169
- <dd className="font-medium">
170
- {contract.mainRelatedPartyName ||
171
- commonT('labels.notAvailable')}
172
- </dd>
173
- </div>
174
- <div>
175
- <dt className="text-muted-foreground">
176
- {t('labels.contractType')}
177
- </dt>
178
- <dd className="font-medium">
179
- {getOptionLabel('contractTypes', contract.contractType)}
180
- </dd>
181
- </div>
182
- <div>
183
- <dt className="text-muted-foreground">
184
- {t('labels.signatureStatus')}
192
+ {commonT('labels.status')}
185
193
  </dt>
186
194
  <dd className="font-medium">
187
195
  <StatusBadge
188
- label={getOptionLabel(
189
- 'signatureStatuses',
190
- contract.signatureStatus
196
+ label={
197
+ contract.isActive
198
+ ? t('labels.active')
199
+ : t('labels.inactive')
200
+ }
201
+ className={getStatusBadgeClass(
202
+ contract.isActive ? 'active' : 'archived'
191
203
  )}
192
- className={getStatusBadgeClass(contract.signatureStatus)}
193
204
  />
194
205
  </dd>
195
206
  </div>
196
207
  <div>
197
208
  <dt className="text-muted-foreground">
198
- {t('labels.activeState')}
199
- </dt>
200
- <dd className="font-medium">
201
- {contract.isActive
202
- ? t('labels.active')
203
- : t('labels.inactive')}
204
- </dd>
205
- </div>
206
- <div>
207
- <dt className="text-muted-foreground">
208
- {commonT('labels.startDate')}
209
+ {t('labels.mainParty')}
209
210
  </dt>
210
211
  <dd className="font-medium">
211
- {formatDate(contract.startDate)}
212
+ {contract.mainRelatedPartyName ||
213
+ commonT('labels.notAvailable')}
212
214
  </dd>
213
215
  </div>
214
216
  <div>
215
217
  <dt className="text-muted-foreground">
216
- {commonT('labels.endDate')}
217
- </dt>
218
- <dd className="font-medium">{formatDate(contract.endDate)}</dd>
219
- </div>
220
- <div>
221
- <dt className="text-muted-foreground">
222
- {commonT('labels.status')}
218
+ {t('labels.contractType')}
223
219
  </dt>
224
220
  <dd className="font-medium">
225
- <StatusBadge
226
- label={getOptionLabel('statuses', contract.status)}
227
- className={getStatusBadgeClass(contract.status)}
228
- />
221
+ {getOptionLabel('contractTypes', contract.contractType)}
229
222
  </dd>
230
223
  </div>
231
224
  </dl>
232
- {contract.contentHtml ? (
233
- <div
234
- className="prose prose-sm mt-4 max-w-none rounded-lg border p-4"
235
- dangerouslySetInnerHTML={{ __html: contract.contentHtml }}
236
- />
237
- ) : null}
238
225
  </SectionCard>
239
226
  </TabsContent>
240
227
 
@@ -277,74 +264,7 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
277
264
  )}
278
265
  </SectionCard>
279
266
  </TabsContent>
280
- <TabsContent value="signatures">
281
- <SectionCard title={t('sections.signatures')}>
282
- {contract.signatures.length ? (
283
- <div className="overflow-x-auto rounded-md border">
284
- <Table>
285
- <TableHeader>
286
- <TableRow>
287
- <TableHead>{t('labels.signer')}</TableHead>
288
- <TableHead>{commonT('labels.status')}</TableHead>
289
- <TableHead>{t('labels.signedAt')}</TableHead>
290
- </TableRow>
291
- </TableHeader>
292
- <TableBody>
293
- {contract.signatures.map((signature) => (
294
- <TableRow key={signature.id}>
295
- <TableCell>{signature.signerName}</TableCell>
296
- <TableCell>
297
- <StatusBadge
298
- label={getOptionLabel(
299
- 'signatureStatuses',
300
- signature.status
301
- )}
302
- className={getStatusBadgeClass(signature.status)}
303
- />
304
- </TableCell>
305
- <TableCell>{formatDate(signature.signedAt)}</TableCell>
306
- </TableRow>
307
- ))}
308
- </TableBody>
309
- </Table>
310
- </div>
311
- ) : (
312
- <p className="text-sm text-muted-foreground">
313
- {t('states.noSignatures')}
314
- </p>
315
- )}
316
- </SectionCard>
317
- </TabsContent>
318
- <TabsContent value="financials">
319
- <SectionCard title={t('sections.financials')}>
320
- <dl className="grid gap-3 text-sm md:grid-cols-4">
321
- <div>
322
- <dt className="text-muted-foreground">{t('labels.value')}</dt>
323
- <dd className="font-medium">
324
- {formatCurrency(contract.valueAmount ?? 0)}
325
- </dd>
326
- </div>
327
- <div>
328
- <dt className="text-muted-foreground">{t('labels.payment')}</dt>
329
- <dd className="font-medium">
330
- {formatCurrency(contract.paymentAmount ?? 0)}
331
- </dd>
332
- </div>
333
- <div>
334
- <dt className="text-muted-foreground">{t('labels.revenue')}</dt>
335
- <dd className="font-medium">
336
- {formatCurrency(contract.revenueAmount ?? 0)}
337
- </dd>
338
- </div>
339
- <div>
340
- <dt className="text-muted-foreground">{t('labels.fine')}</dt>
341
- <dd className="font-medium">
342
- {formatCurrency(contract.fineAmount ?? 0)}
343
- </dd>
344
- </div>
345
- </dl>
346
- </SectionCard>
347
- </TabsContent>
267
+
348
268
  <TabsContent value="documents">
349
269
  <SectionCard title={t('sections.documents')}>
350
270
  {contract.documents.length ? (
@@ -362,22 +282,33 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
362
282
  </div>
363
283
  </div>
364
284
  {document.fileId || document.fileContentBase64 ? (
365
- <Button
366
- variant="outline"
367
- size="sm"
368
- onClick={() =>
369
- document.fileId
370
- ? openStoredFile(document.fileId)
371
- : downloadBase64File(
372
- document.fileName,
373
- document.mimeType,
374
- document.fileContentBase64 || ''
375
- )
376
- }
377
- >
378
- <Download className="size-4" />
379
- {t('actions.download')}
380
- </Button>
285
+ document.fileId ? (
286
+ <Button variant="outline" size="sm" asChild>
287
+ <a
288
+ href={buildStoredFileUrl(document.fileId) || '#'}
289
+ target="_blank"
290
+ rel="noreferrer"
291
+ >
292
+ <Download className="size-4" />
293
+ {t('actions.download')}
294
+ </a>
295
+ </Button>
296
+ ) : (
297
+ <Button
298
+ variant="outline"
299
+ size="sm"
300
+ onClick={() =>
301
+ downloadBase64File(
302
+ document.fileName,
303
+ document.mimeType,
304
+ document.fileContentBase64 || ''
305
+ )
306
+ }
307
+ >
308
+ <Download className="size-4" />
309
+ {t('actions.download')}
310
+ </Button>
311
+ )
381
312
  ) : null}
382
313
  </div>
383
314
  ))}
@@ -389,43 +320,7 @@ export function ContractDetailsScreen({ contractId }: { contractId: number }) {
389
320
  )}
390
321
  </SectionCard>
391
322
  </TabsContent>
392
- <TabsContent value="revisions">
393
- <SectionCard title={t('sections.revisions')}>
394
- {contract.revisions.length ? (
395
- <div className="overflow-x-auto rounded-md border">
396
- <Table>
397
- <TableHeader>
398
- <TableRow>
399
- <TableHead>{t('labels.revision')}</TableHead>
400
- <TableHead>{commonT('labels.status')}</TableHead>
401
- <TableHead>{commonT('labels.startDate')}</TableHead>
402
- </TableRow>
403
- </TableHeader>
404
- <TableBody>
405
- {contract.revisions.map((revision) => (
406
- <TableRow key={revision.id}>
407
- <TableCell>{revision.title}</TableCell>
408
- <TableCell>
409
- <StatusBadge
410
- label={getOptionLabel('statuses', revision.status)}
411
- className={getStatusBadgeClass(revision.status)}
412
- />
413
- </TableCell>
414
- <TableCell>
415
- {formatDate(revision.effectiveDate)}
416
- </TableCell>
417
- </TableRow>
418
- ))}
419
- </TableBody>
420
- </Table>
421
- </div>
422
- ) : (
423
- <p className="text-sm text-muted-foreground">
424
- {t('states.noRevisions')}
425
- </p>
426
- )}
427
- </SectionCard>
428
- </TabsContent>
323
+
429
324
  <TabsContent value="history">
430
325
  <SectionCard title={t('sections.history')}>
431
326
  {contract.history.length ? (