@hed-hog/operations 0.0.295 → 0.0.296

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 (126) hide show
  1. package/dist/operations.controller.d.ts +415 -0
  2. package/dist/operations.controller.d.ts.map +1 -0
  3. package/dist/operations.controller.js +333 -0
  4. package/dist/operations.controller.js.map +1 -0
  5. package/dist/operations.module.d.ts.map +1 -1
  6. package/dist/operations.module.js +4 -3
  7. package/dist/operations.module.js.map +1 -1
  8. package/dist/operations.service.d.ts +589 -153
  9. package/dist/operations.service.d.ts.map +1 -1
  10. package/dist/operations.service.js +2229 -100
  11. package/dist/operations.service.js.map +1 -1
  12. package/hedhog/data/menu.yaml +198 -251
  13. package/hedhog/data/role.yaml +23 -14
  14. package/hedhog/data/route.yaml +317 -143
  15. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +310 -0
  16. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +631 -0
  17. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +132 -0
  18. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +558 -0
  19. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +291 -0
  20. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +689 -0
  21. package/hedhog/frontend/app/_lib/api.ts.ejs +32 -0
  22. package/hedhog/frontend/app/_lib/hooks/use-operations-access.ts.ejs +44 -0
  23. package/hedhog/frontend/app/_lib/types.ts.ejs +360 -0
  24. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +129 -25
  25. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +14 -0
  26. package/hedhog/frontend/app/approvals/page.tsx.ejs +386 -147
  27. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +11 -0
  28. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +11 -0
  29. package/hedhog/frontend/app/collaborators/new/page.tsx.ejs +5 -0
  30. package/hedhog/frontend/app/collaborators/page.tsx.ejs +261 -0
  31. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +11 -108
  33. package/hedhog/frontend/app/contracts/new/page.tsx.ejs +17 -0
  34. package/hedhog/frontend/app/contracts/page.tsx.ejs +262 -181
  35. package/hedhog/frontend/app/page.tsx.ejs +319 -177
  36. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +11 -0
  37. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +11 -936
  38. package/hedhog/frontend/app/projects/new/page.tsx.ejs +5 -0
  39. package/hedhog/frontend/app/projects/page.tsx.ejs +236 -1074
  40. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +418 -0
  41. package/hedhog/frontend/app/team/page.tsx.ejs +339 -0
  42. package/hedhog/frontend/app/time-off/page.tsx.ejs +328 -0
  43. package/hedhog/frontend/app/timesheets/page.tsx.ejs +636 -126
  44. package/hedhog/frontend/messages/en.json +648 -454
  45. package/hedhog/frontend/messages/pt.json +647 -454
  46. package/hedhog/table/operations_approval.yaml +49 -0
  47. package/hedhog/table/operations_approval_history.yaml +29 -0
  48. package/hedhog/table/{operations_employee.yaml → operations_collaborator.yaml} +67 -64
  49. package/hedhog/table/operations_collaborator_schedule_day.yaml +34 -0
  50. package/hedhog/table/operations_contract.yaml +100 -48
  51. package/hedhog/table/operations_contract_document.yaml +39 -0
  52. package/hedhog/table/operations_contract_financial_term.yaml +40 -0
  53. package/hedhog/table/operations_contract_history.yaml +27 -0
  54. package/hedhog/table/operations_contract_party.yaml +46 -0
  55. package/hedhog/table/operations_contract_revision.yaml +38 -0
  56. package/hedhog/table/operations_contract_signature.yaml +38 -0
  57. package/hedhog/table/operations_project.yaml +54 -50
  58. package/hedhog/table/{operations_allocation.yaml → operations_project_assignment.yaml} +55 -52
  59. package/hedhog/table/operations_schedule_adjustment_day.yaml +34 -0
  60. package/hedhog/table/operations_schedule_adjustment_request.yaml +53 -0
  61. package/hedhog/table/operations_time_off_request.yaml +57 -0
  62. package/hedhog/table/operations_timesheet.yaml +41 -36
  63. package/hedhog/table/operations_timesheet_entry.yaml +40 -50
  64. package/package.json +8 -7
  65. package/src/operations.controller.ts +182 -0
  66. package/src/operations.module.ts +22 -21
  67. package/src/operations.service.ts +3595 -137
  68. package/hedhog/data/operations_career_level.yaml +0 -102
  69. package/hedhog/data/operations_career_track.yaml +0 -8
  70. package/hedhog/data/operations_certification.yaml +0 -38
  71. package/hedhog/data/operations_evaluation_cycle.yaml +0 -18
  72. package/hedhog/data/operations_performance_criterion.yaml +0 -48
  73. package/hedhog/frontend/app/_components/allocation-calendar.tsx.ejs +0 -56
  74. package/hedhog/frontend/app/_components/kanban-board.tsx.ejs +0 -626
  75. package/hedhog/frontend/app/_components/timesheet-entry-dialog.tsx.ejs +0 -142
  76. package/hedhog/frontend/app/_lib/hooks/use-operations-data.ts.ejs +0 -41
  77. package/hedhog/frontend/app/_lib/hooks/use-operations-growth-data.ts.ejs +0 -63
  78. package/hedhog/frontend/app/_lib/mocks/allocations.mock.ts.ejs +0 -74
  79. package/hedhog/frontend/app/_lib/mocks/contracts.mock.ts.ejs +0 -74
  80. package/hedhog/frontend/app/_lib/mocks/operations-growth.mock.ts.ejs +0 -824
  81. package/hedhog/frontend/app/_lib/mocks/projects.mock.ts.ejs +0 -455
  82. package/hedhog/frontend/app/_lib/mocks/tasks.mock.ts.ejs +0 -117
  83. package/hedhog/frontend/app/_lib/mocks/timesheets.mock.ts.ejs +0 -84
  84. package/hedhog/frontend/app/_lib/mocks/users.mock.ts.ejs +0 -67
  85. package/hedhog/frontend/app/_lib/services/contracts.service.ts.ejs +0 -10
  86. package/hedhog/frontend/app/_lib/services/operations-growth.service.ts.ejs +0 -31
  87. package/hedhog/frontend/app/_lib/services/projects.service.ts.ejs +0 -10
  88. package/hedhog/frontend/app/_lib/services/tasks.service.ts.ejs +0 -10
  89. package/hedhog/frontend/app/_lib/services/timesheets.service.ts.ejs +0 -10
  90. package/hedhog/frontend/app/_lib/types/operations-growth.ts.ejs +0 -209
  91. package/hedhog/frontend/app/_lib/types/operations.ts.ejs +0 -156
  92. package/hedhog/frontend/app/_lib/utils/growth.ts.ejs +0 -62
  93. package/hedhog/frontend/app/_lib/utils/metrics.ts.ejs +0 -103
  94. package/hedhog/frontend/app/_lib/utils/status.ts.ejs +0 -80
  95. package/hedhog/frontend/app/allocations/page.tsx.ejs +0 -155
  96. package/hedhog/frontend/app/career/page.tsx.ejs +0 -143
  97. package/hedhog/frontend/app/certifications/page.tsx.ejs +0 -202
  98. package/hedhog/frontend/app/evaluations/page.tsx.ejs +0 -278
  99. package/hedhog/frontend/app/goals/page.tsx.ejs +0 -171
  100. package/hedhog/frontend/app/growth/page.tsx.ejs +0 -288
  101. package/hedhog/frontend/app/manager/page.tsx.ejs +0 -175
  102. package/hedhog/frontend/app/rewards/page.tsx.ejs +0 -196
  103. package/hedhog/frontend/app/tasks/page.tsx.ejs +0 -999
  104. package/hedhog/table/operations_calibration_item.yaml +0 -61
  105. package/hedhog/table/operations_calibration_session.yaml +0 -25
  106. package/hedhog/table/operations_career_level.yaml +0 -75
  107. package/hedhog/table/operations_career_track.yaml +0 -21
  108. package/hedhog/table/operations_certification.yaml +0 -48
  109. package/hedhog/table/operations_employee_certification.yaml +0 -43
  110. package/hedhog/table/operations_employee_connect.yaml +0 -61
  111. package/hedhog/table/operations_employee_evaluation.yaml +0 -113
  112. package/hedhog/table/operations_employee_evaluation_item.yaml +0 -39
  113. package/hedhog/table/operations_employee_profile.yaml +0 -80
  114. package/hedhog/table/operations_employee_skill_matrix.yaml +0 -30
  115. package/hedhog/table/operations_evaluation_cycle.yaml +0 -31
  116. package/hedhog/table/operations_goal.yaml +0 -67
  117. package/hedhog/table/operations_goal_progress.yaml +0 -31
  118. package/hedhog/table/operations_performance_criterion.yaml +0 -29
  119. package/hedhog/table/operations_promotion_readiness.yaml +0 -49
  120. package/hedhog/table/operations_promotion_recommendation.yaml +0 -63
  121. package/hedhog/table/operations_public_recognition.yaml +0 -46
  122. package/hedhog/table/operations_reward.yaml +0 -100
  123. package/hedhog/table/operations_score_event.yaml +0 -81
  124. package/hedhog/table/operations_task.yaml +0 -60
  125. package/src/operations-data.controller.ts +0 -54
  126. package/src/operations-growth.controller.ts +0 -44
@@ -1,181 +1,262 @@
1
- 'use client';
2
-
3
- import {
4
- EmptyState,
5
- Page,
6
- PaginationFooter,
7
- SearchBar,
8
- } from '@/components/entity-list';
9
- import { Input } from '@/components/ui/input';
10
- import {
11
- Select,
12
- SelectContent,
13
- SelectItem,
14
- SelectTrigger,
15
- SelectValue,
16
- } from '@/components/ui/select';
17
- import {
18
- Table,
19
- TableBody,
20
- TableCell,
21
- TableHead,
22
- TableHeader,
23
- TableRow,
24
- } from '@/components/ui/table';
25
- import { useTranslations } from 'next-intl';
26
- import Link from 'next/link';
27
- import { useMemo, useState } from 'react';
28
- import { Layers } from 'lucide-react';
29
- import { OperationsHeader } from '../_components/operations-header';
30
- import { SectionCard } from '../_components/section-card';
31
- import { StatusBadge } from '../_components/status-badge';
32
- import { useOperationsData } from '../_lib/hooks/use-operations-data';
33
- import { formatCurrency, formatDate } from '../_lib/utils/format';
34
- import {
35
- getContractBadgeClasses,
36
- getContractTypeLabel,
37
- } from '../_lib/utils/status';
38
-
39
- const PAGE_SIZE_OPTIONS = [10, 20, 30, 50];
40
- const DEFAULT_PAGE_SIZE = PAGE_SIZE_OPTIONS[0] ?? 10;
41
-
42
- export default function ContractsPage() {
43
- const t = useTranslations('operations.ContractsPage');
44
- const { contracts } = useOperationsData();
45
- const [searchInput, setSearchInput] = useState('');
46
- const [search, setSearch] = useState('');
47
- const [status, setStatus] = useState('all');
48
- const [currentPage, setCurrentPage] = useState(1);
49
- const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
50
-
51
- const filteredContracts = useMemo(
52
- () =>
53
- contracts.filter((contract) => {
54
- const matchesSearch =
55
- `${contract.name} ${contract.client}`
56
- .toLowerCase()
57
- .includes(search.toLowerCase());
58
- const matchesStatus = status === 'all' || contract.status === status;
59
- return matchesSearch && matchesStatus;
60
- }),
61
- [contracts, search, status]
62
- );
63
-
64
- const totalPages = Math.max(1, Math.ceil(filteredContracts.length / pageSize));
65
- const safePage = Math.min(Math.max(currentPage, 1), totalPages);
66
- const paginatedContracts = useMemo(() => {
67
- const start = (safePage - 1) * pageSize;
68
- return filteredContracts.slice(start, start + pageSize);
69
- }, [filteredContracts, safePage, pageSize]);
70
-
71
- return (
72
- <Page>
73
- <OperationsHeader
74
- title={t('title')}
75
- description={t('description')}
76
- current={t('breadcrumb')}
77
- />
78
-
79
- <SearchBar
80
- className="mb-6"
81
- searchQuery={searchInput}
82
- onSearchChange={setSearchInput}
83
- placeholder={t('searchPlaceholder')}
84
- onSearch={() => {
85
- setSearch(searchInput);
86
- setCurrentPage(1);
87
- }}
88
- controls={[
89
- {
90
- id: 'status-filter',
91
- type: 'select',
92
- value: status,
93
- onChange: (value) => {
94
- setStatus(value);
95
- setCurrentPage(1);
96
- },
97
- placeholder: t('filters.all'),
98
- options: [
99
- { value: 'all', label: t('filters.all') },
100
- { value: 'active', label: t('filters.active') },
101
- { value: 'renewal', label: t('filters.renewal') },
102
- { value: 'expired', label: t('filters.expired') },
103
- { value: 'draft', label: t('filters.draft') },
104
- ],
105
- },
106
- ]}
107
- />
108
-
109
- <SectionCard title={t('tableTitle')} description={t('tableDescription')}>
110
- {filteredContracts.length === 0 ? (
111
- <EmptyState
112
- icon={<Layers className="h-12 w-12" />}
113
- title={t('emptyState.title')}
114
- description={t('emptyState.description')}
115
- actionLabel={t('emptyState.action')}
116
- onAction={() => {
117
- setSearch('');
118
- setSearchInput('');
119
- setStatus('all');
120
- setCurrentPage(1);
121
- }}
122
- />
123
- ) : (
124
- <div className="space-y-4">
125
- <Table>
126
- <TableHeader>
127
- <TableRow>
128
- <TableHead>{t('columns.name')}</TableHead>
129
- <TableHead>{t('columns.client')}</TableHead>
130
- <TableHead>{t('columns.type')}</TableHead>
131
- <TableHead>{t('columns.startDate')}</TableHead>
132
- <TableHead>{t('columns.endDate')}</TableHead>
133
- <TableHead>{t('columns.hourRate')}</TableHead>
134
- <TableHead>{t('columns.hourLimit')}</TableHead>
135
- <TableHead>{t('columns.status')}</TableHead>
136
- </TableRow>
137
- </TableHeader>
138
- <TableBody>
139
- {paginatedContracts.map((contract) => (
140
- <TableRow key={contract.id}>
141
- <TableCell>
142
- <Link
143
- className="font-medium text-primary hover:underline"
144
- href={`/operations/contracts/${contract.id}`}
145
- >
146
- {contract.name}
147
- </Link>
148
- </TableCell>
149
- <TableCell>{contract.client}</TableCell>
150
- <TableCell>{getContractTypeLabel(contract.type)}</TableCell>
151
- <TableCell>{formatDate(contract.startDate)}</TableCell>
152
- <TableCell>{formatDate(contract.endDate)}</TableCell>
153
- <TableCell>{formatCurrency(contract.hourlyRate)}</TableCell>
154
- <TableCell>{contract.hourLimit}h</TableCell>
155
- <TableCell>
156
- <StatusBadge
157
- label={contract.status}
158
- className={getContractBadgeClasses(contract.status)}
159
- />
160
- </TableCell>
161
- </TableRow>
162
- ))}
163
- </TableBody>
164
- </Table>
165
- <PaginationFooter
166
- currentPage={safePage}
167
- pageSize={pageSize}
168
- totalItems={filteredContracts.length}
169
- onPageChange={setCurrentPage}
170
- onPageSizeChange={(nextSize) => {
171
- setPageSize(nextSize);
172
- setCurrentPage(1);
173
- }}
174
- pageSizeOptions={PAGE_SIZE_OPTIONS}
175
- />
176
- </div>
177
- )}
178
- </SectionCard>
179
- </Page>
180
- );
181
- }
1
+ 'use client';
2
+
3
+ import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@/components/ui/table';
13
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
14
+ import { Download, Eye, FileText, Pencil, Upload } from 'lucide-react';
15
+ import Link from 'next/link';
16
+ import { useMemo, useRef, useState } from 'react';
17
+ import { useTranslations } from 'next-intl';
18
+ import { OperationsHeader } from '../_components/operations-header';
19
+ import { StatusBadge } from '../_components/status-badge';
20
+ import { fetchOperations, mutateOperations } from '../_lib/api';
21
+ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
22
+ import type { OperationsContract, OperationsContractDetails } from '../_lib/types';
23
+ import {
24
+ formatCurrency,
25
+ formatDate,
26
+ formatEnumLabel,
27
+ getStatusBadgeClass,
28
+ } from '../_lib/utils/format';
29
+
30
+ function downloadBase64File(fileName: string, mimeType: string, base64: string) {
31
+ const href = `data:${mimeType};base64,${base64}`;
32
+ const link = document.createElement('a');
33
+ link.href = href;
34
+ link.download = fileName;
35
+ link.click();
36
+ }
37
+
38
+ async function fileToBase64(file: File) {
39
+ return new Promise<string>((resolve, reject) => {
40
+ const reader = new FileReader();
41
+ reader.onload = () => {
42
+ const result = String(reader.result ?? '');
43
+ const [, base64 = ''] = result.split(',');
44
+ resolve(base64);
45
+ };
46
+ reader.onerror = reject;
47
+ reader.readAsDataURL(file);
48
+ });
49
+ }
50
+
51
+ export default function OperationsContractsPage() {
52
+ const t = useTranslations('operations.ContractsPage');
53
+ const commonT = useTranslations('operations.Common');
54
+ const { request, showToastHandler, currentLocaleCode } = useApp();
55
+ const access = useOperationsAccess();
56
+ const [search, setSearch] = useState('');
57
+ const [statusFilter, setStatusFilter] = useState('all');
58
+ const [categoryFilter, setCategoryFilter] = useState('all');
59
+ const [originFilter, setOriginFilter] = useState('all');
60
+ const uploadTargetRef = useRef<number | null>(null);
61
+ const fileInputRef = useRef<HTMLInputElement>(null);
62
+
63
+ const { data: contracts = [], refetch } = useQuery<OperationsContract[]>({
64
+ queryKey: ['operations-contracts-list', currentLocaleCode],
65
+ queryFn: () => fetchOperations<OperationsContract[]>(request, '/operations/contracts'),
66
+ });
67
+
68
+ const filteredRows = useMemo(
69
+ () =>
70
+ contracts.filter((item) => {
71
+ const matchesSearch = !search.trim()
72
+ ? true
73
+ : [
74
+ item.name,
75
+ item.code,
76
+ item.mainRelatedPartyName,
77
+ item.clientName,
78
+ item.contractType,
79
+ ]
80
+ .filter(Boolean)
81
+ .some((value) =>
82
+ String(value).toLowerCase().includes(search.trim().toLowerCase())
83
+ );
84
+ const matchesStatus = statusFilter === 'all' ? true : item.status === statusFilter;
85
+ const matchesCategory =
86
+ categoryFilter === 'all' ? true : item.contractCategory === categoryFilter;
87
+ const matchesOrigin =
88
+ originFilter === 'all' ? true : item.originType === originFilter;
89
+ return matchesSearch && matchesStatus && matchesCategory && matchesOrigin;
90
+ }),
91
+ [contracts, search, statusFilter, categoryFilter, originFilter]
92
+ );
93
+
94
+ const downloadPdf = async (contractId: number) => {
95
+ const detail = await fetchOperations<OperationsContractDetails>(
96
+ request,
97
+ `/operations/contracts/${contractId}`
98
+ );
99
+ const document = detail.documents.find((item) => item.isCurrent && item.fileContentBase64);
100
+ if (!document?.fileContentBase64) {
101
+ showToastHandler?.('error', t('messages.noPdf'));
102
+ return;
103
+ }
104
+ downloadBase64File(document.fileName, document.mimeType, document.fileContentBase64);
105
+ };
106
+
107
+ const duplicateContract = async (contractId: number) => {
108
+ window.location.href = `/operations/contracts/new?duplicate=${contractId}`;
109
+ };
110
+
111
+ const toggleArchived = async (contract: OperationsContract) => {
112
+ try {
113
+ await mutateOperations(request, `/operations/contracts/${contract.id}`, 'PATCH', {
114
+ status: contract.status === 'archived' ? 'active' : 'archived',
115
+ isActive: contract.status === 'archived',
116
+ });
117
+ showToastHandler?.('success', t('messages.statusSuccess'));
118
+ await refetch();
119
+ } catch {
120
+ showToastHandler?.('error', t('messages.statusError'));
121
+ }
122
+ };
123
+
124
+ return (
125
+ <Page>
126
+ <OperationsHeader
127
+ title={t('title')}
128
+ description={t('description')}
129
+ current={t('breadcrumb')}
130
+ actions={
131
+ access.isDirector ? (
132
+ <Button size="sm" asChild>
133
+ <Link href="/operations/contracts/new">{commonT('actions.create')}</Link>
134
+ </Button>
135
+ ) : undefined
136
+ }
137
+ />
138
+
139
+ <SearchBar
140
+ searchQuery={search}
141
+ onSearchChange={setSearch}
142
+ onSearch={() => undefined}
143
+ placeholder={t('searchPlaceholder')}
144
+ controls={[
145
+ {
146
+ id: 'category',
147
+ type: 'select',
148
+ value: categoryFilter,
149
+ onChange: setCategoryFilter,
150
+ placeholder: commonT('labels.contractCategory'),
151
+ options: [{ value: 'all', label: commonT('filters.allTypes') }, ...['employee','contractor','client','supplier','vendor','partner','internal','other'].map((value) => ({ value, label: formatEnumLabel(value) }))],
152
+ },
153
+ {
154
+ id: 'origin',
155
+ type: 'select',
156
+ value: originFilter,
157
+ onChange: setOriginFilter,
158
+ placeholder: t('filters.originType'),
159
+ options: [{ value: 'all', label: commonT('filters.allTypes') }, ...['manual','employee_hiring','client_project'].map((value) => ({ value, label: formatEnumLabel(value) }))],
160
+ },
161
+ {
162
+ id: 'status',
163
+ type: 'select',
164
+ value: statusFilter,
165
+ onChange: setStatusFilter,
166
+ placeholder: commonT('labels.status'),
167
+ options: [{ value: 'all', label: commonT('filters.allStatuses') }, ...['draft','under_review','active','renewal','expired','closed','archived'].map((value) => ({ value, label: formatEnumLabel(value) }))],
168
+ },
169
+ ]}
170
+ />
171
+
172
+ <input
173
+ ref={fileInputRef}
174
+ type="file"
175
+ accept="application/pdf"
176
+ className="hidden"
177
+ onChange={async (event) => {
178
+ const file = event.target.files?.[0];
179
+ const contractId = uploadTargetRef.current;
180
+ if (!file || !contractId) return;
181
+ try {
182
+ const fileContentBase64 = await fileToBase64(file);
183
+ await mutateOperations(request, `/operations/contracts/${contractId}`, 'PATCH', {
184
+ replaceUploadedPdfDocument: {
185
+ fileName: file.name,
186
+ mimeType: file.type || 'application/pdf',
187
+ fileContentBase64,
188
+ },
189
+ });
190
+ showToastHandler?.('success', t('messages.uploadSuccess'));
191
+ await refetch();
192
+ } catch {
193
+ showToastHandler?.('error', t('messages.uploadError'));
194
+ } finally {
195
+ uploadTargetRef.current = null;
196
+ event.target.value = '';
197
+ }
198
+ }}
199
+ />
200
+
201
+ {filteredRows.length ? (
202
+ <div className="overflow-x-auto rounded-md border">
203
+ <Table>
204
+ <TableHeader>
205
+ <TableRow>
206
+ <TableHead>{t('columns.title')}</TableHead>
207
+ <TableHead>{t('columns.code')}</TableHead>
208
+ <TableHead>{commonT('labels.contractCategory')}</TableHead>
209
+ <TableHead>{t('columns.type')}</TableHead>
210
+ <TableHead>{t('columns.origin')}</TableHead>
211
+ <TableHead>{t('columns.party')}</TableHead>
212
+ <TableHead>{commonT('labels.startDate')}</TableHead>
213
+ <TableHead>{commonT('labels.endDate')}</TableHead>
214
+ <TableHead>{t('columns.signatureStatus')}</TableHead>
215
+ <TableHead>{t('columns.active')}</TableHead>
216
+ <TableHead>{t('columns.financials')}</TableHead>
217
+ <TableHead>{commonT('labels.status')}</TableHead>
218
+ <TableHead className="w-[320px] text-right">{commonT('labels.actions')}</TableHead>
219
+ </TableRow>
220
+ </TableHeader>
221
+ <TableBody>
222
+ {filteredRows.map((contract) => (
223
+ <TableRow key={contract.id}>
224
+ <TableCell>{contract.name}</TableCell>
225
+ <TableCell>{contract.code}</TableCell>
226
+ <TableCell>{formatEnumLabel(contract.contractCategory)}</TableCell>
227
+ <TableCell>{formatEnumLabel(contract.contractType)}</TableCell>
228
+ <TableCell>{formatEnumLabel(contract.originType)}</TableCell>
229
+ <TableCell>{contract.mainRelatedPartyName || commonT('labels.notAvailable')}</TableCell>
230
+ <TableCell>{formatDate(contract.startDate)}</TableCell>
231
+ <TableCell>{formatDate(contract.endDate)}</TableCell>
232
+ <TableCell><StatusBadge label={formatEnumLabel(contract.signatureStatus)} className={getStatusBadgeClass(contract.signatureStatus)} /></TableCell>
233
+ <TableCell>{contract.isActive ? t('labels.active') : t('labels.inactive')}</TableCell>
234
+ <TableCell>{formatCurrency((contract.valueAmount ?? 0) + (contract.revenueAmount ?? 0))}</TableCell>
235
+ <TableCell><StatusBadge label={formatEnumLabel(contract.status)} className={getStatusBadgeClass(contract.status)} /></TableCell>
236
+ <TableCell>
237
+ <div className="flex justify-end gap-2">
238
+ <Button variant="outline" size="icon" asChild><Link href={`/operations/contracts/${contract.id}`}><Eye className="size-4" /></Link></Button>
239
+ {access.isDirector ? <Button variant="outline" size="icon" asChild><Link href={`/operations/contracts/${contract.id}/edit`}><Pencil className="size-4" /></Link></Button> : null}
240
+ <Button variant="outline" size="icon" onClick={() => void downloadPdf(contract.id)}><Download className="size-4" /></Button>
241
+ {access.isDirector ? <Button variant="outline" size="icon" onClick={() => { uploadTargetRef.current = contract.id; fileInputRef.current?.click(); }}><Upload className="size-4" /></Button> : null}
242
+ {access.isDirector ? <Button variant="outline" size="sm" onClick={() => void duplicateContract(contract.id)}>{t('actions.duplicate')}</Button> : null}
243
+ {access.isDirector ? <Button variant="outline" size="sm" onClick={() => void toggleArchived(contract)}>{contract.status === 'archived' ? commonT('actions.activate') : t('actions.archive')}</Button> : null}
244
+ </div>
245
+ </TableCell>
246
+ </TableRow>
247
+ ))}
248
+ </TableBody>
249
+ </Table>
250
+ </div>
251
+ ) : (
252
+ <EmptyState
253
+ icon={<FileText className="size-12" />}
254
+ title={commonT('states.emptyTitle')}
255
+ description={t('emptyDescription')}
256
+ actionLabel={access.isDirector ? commonT('actions.create') : commonT('actions.refresh')}
257
+ onAction={access.isDirector ? () => { window.location.href = '/operations/contracts/new'; } : () => void refetch()}
258
+ />
259
+ )}
260
+ </Page>
261
+ );
262
+ }