@hed-hog/operations 0.0.306 → 0.0.310
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.
- package/dist/controllers/operations-approvals.controller.d.ts +114 -1
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
- package/dist/controllers/operations-approvals.controller.js +16 -3
- package/dist/controllers/operations-approvals.controller.js.map +1 -1
- package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +16 -3
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +14 -453
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.js +11 -112
- package/dist/controllers/operations-contracts.controller.js.map +1 -1
- package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
- package/dist/controllers/operations-org-structure.controller.js +18 -5
- package/dist/controllers/operations-org-structure.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +28 -4
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +17 -5
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +31 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +16 -11
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/list-approvals.dto.d.ts +6 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -0
- package/dist/dto/list-approvals.dto.js +28 -0
- package/dist/dto/list-approvals.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborator-types.dto.js +7 -1
- package/dist/dto/list-collaborator-types.dto.js.map +1 -1
- package/dist/dto/list-collaborators.dto.d.ts +1 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborators.dto.js +5 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -1
- package/dist/dto/list-contracts.dto.d.ts +8 -0
- package/dist/dto/list-contracts.dto.d.ts.map +1 -0
- package/dist/dto/list-contracts.dto.js +38 -0
- package/dist/dto/list-contracts.dto.js.map +1 -0
- package/dist/dto/list-departments.dto.d.ts +5 -0
- package/dist/dto/list-departments.dto.d.ts.map +1 -0
- package/dist/dto/list-departments.dto.js +23 -0
- package/dist/dto/list-departments.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +5 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-projects.dto.js +23 -0
- package/dist/dto/list-projects.dto.js.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.js +23 -0
- package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
- package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
- package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
- package/dist/dto/list-time-off-requests.dto.js +23 -0
- package/dist/dto/list-time-off-requests.dto.js.map +1 -0
- package/dist/dto/list-timesheets.dto.d.ts +5 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheets.dto.js +23 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.js +25 -0
- package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
- package/dist/operations.service.d.ts +340 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1007 -1043
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +0 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +0 -36
- package/hedhog/data/route.yaml +42 -73
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
- package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
- package/hedhog/frontend/app/approvals/page.tsx.ejs +842 -150
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
- package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
- package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +412 -147
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
- package/hedhog/frontend/messages/en.json +143 -14
- package/hedhog/frontend/messages/pt.json +192 -54
- package/hedhog/table/operations_contract.yaml +0 -9
- package/package.json +4 -4
- package/src/controllers/operations-approvals.controller.ts +9 -3
- package/src/controllers/operations-collaborators.controller.ts +15 -2
- package/src/controllers/operations-contracts.controller.ts +8 -92
- package/src/controllers/operations-org-structure.controller.ts +17 -4
- package/src/controllers/operations-projects.controller.ts +10 -4
- package/src/controllers/operations-timesheets.controller.ts +17 -8
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +7 -2
- package/src/dto/list-collaborators.dto.ts +4 -0
- package/src/dto/list-contracts.dto.ts +20 -0
- package/src/dto/list-departments.dto.ts +8 -0
- package/src/dto/list-projects.dto.ts +8 -0
- package/src/dto/list-schedule-adjustments.dto.ts +8 -0
- package/src/dto/list-time-off-requests.dto.ts +8 -0
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/reorder-collaborator-types.dto.ts +10 -0
- package/src/operations.service.spec.ts +0 -30
- package/src/operations.service.ts +1557 -1806
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
- package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
- package/hedhog/table/operations_contract_financial_term.yaml +0 -40
- package/hedhog/table/operations_contract_revision.yaml +0 -38
- package/hedhog/table/operations_contract_signature.yaml +0 -38
- package/hedhog/table/operations_contract_template.yaml +0 -58
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PaginationFooter,
|
|
7
|
+
SearchBar,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import {
|
|
5
10
|
AlertDialog,
|
|
6
11
|
AlertDialogAction,
|
|
@@ -12,7 +17,6 @@ import {
|
|
|
12
17
|
AlertDialogTitle,
|
|
13
18
|
} from '@/components/ui/alert-dialog';
|
|
14
19
|
import { Button } from '@/components/ui/button';
|
|
15
|
-
import { Card, CardContent } from '@/components/ui/card';
|
|
16
20
|
import {
|
|
17
21
|
DropdownMenu,
|
|
18
22
|
DropdownMenuContent,
|
|
@@ -36,24 +40,21 @@ import {
|
|
|
36
40
|
TableHeader,
|
|
37
41
|
TableRow,
|
|
38
42
|
} from '@/components/ui/table';
|
|
39
|
-
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
40
43
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
41
44
|
import {
|
|
42
45
|
Download,
|
|
43
|
-
|
|
46
|
+
Eye,
|
|
44
47
|
FileText,
|
|
45
|
-
LayoutGrid,
|
|
46
|
-
List,
|
|
47
48
|
MoreHorizontal,
|
|
48
49
|
Pencil,
|
|
49
|
-
|
|
50
|
+
ShieldCheck,
|
|
51
|
+
ShieldOff,
|
|
50
52
|
Trash2,
|
|
51
|
-
Upload,
|
|
52
53
|
} from 'lucide-react';
|
|
53
54
|
import { useTranslations } from 'next-intl';
|
|
54
55
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
55
|
-
import { useMemo,
|
|
56
|
-
import {
|
|
56
|
+
import { useMemo, useState } from 'react';
|
|
57
|
+
import { ContractFormScreen } from '../_components/contract-form-screen';
|
|
57
58
|
import { OperationsHeader } from '../_components/operations-header';
|
|
58
59
|
import { StatusBadge } from '../_components/status-badge';
|
|
59
60
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
@@ -61,13 +62,10 @@ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
|
61
62
|
import type {
|
|
62
63
|
OperationsContract,
|
|
63
64
|
OperationsContractDetails,
|
|
65
|
+
OperationsContractStats,
|
|
66
|
+
PaginatedResponse,
|
|
64
67
|
} from '../_lib/types';
|
|
65
|
-
import {
|
|
66
|
-
formatCurrency,
|
|
67
|
-
formatDate,
|
|
68
|
-
formatEnumLabel,
|
|
69
|
-
getStatusBadgeClass,
|
|
70
|
-
} from '../_lib/utils/format';
|
|
68
|
+
import { getStatusBadgeClass } from '../_lib/utils/format';
|
|
71
69
|
|
|
72
70
|
function downloadBase64File(
|
|
73
71
|
fileName: string,
|
|
@@ -81,254 +79,142 @@ function downloadBase64File(
|
|
|
81
79
|
link.click();
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
type ContractViewMode = 'table' | 'cards';
|
|
87
|
-
|
|
88
|
-
function openStoredFile(fileId?: number | null) {
|
|
89
|
-
if (!fileId) return;
|
|
82
|
+
function buildStoredFileUrl(fileId?: number | null) {
|
|
83
|
+
if (!fileId) return null;
|
|
90
84
|
const baseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
|
|
91
|
-
|
|
92
|
-
`${baseUrl}/file/open/${fileId}`,
|
|
93
|
-
'_blank',
|
|
94
|
-
'noopener,noreferrer'
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function fileToBase64(file: File) {
|
|
99
|
-
return new Promise<string>((resolve, reject) => {
|
|
100
|
-
const reader = new FileReader();
|
|
101
|
-
reader.onload = () => {
|
|
102
|
-
const result = String(reader.result ?? '');
|
|
103
|
-
const [, base64 = ''] = result.split(',');
|
|
104
|
-
resolve(base64);
|
|
105
|
-
};
|
|
106
|
-
reader.onerror = reject;
|
|
107
|
-
reader.readAsDataURL(file);
|
|
108
|
-
});
|
|
85
|
+
return `${baseUrl}/file/open/${fileId}`;
|
|
109
86
|
}
|
|
110
87
|
|
|
111
88
|
export default function OperationsContractsPage() {
|
|
112
89
|
const t = useTranslations('operations.ContractsPage');
|
|
113
90
|
const commonT = useTranslations('operations.Common');
|
|
114
|
-
const
|
|
91
|
+
const detailT = useTranslations('operations.ContractDetailsPage');
|
|
115
92
|
const { request, showToastHandler, currentLocaleCode } = useApp();
|
|
116
93
|
const access = useOperationsAccess();
|
|
117
94
|
const router = useRouter();
|
|
118
95
|
const pathname = usePathname();
|
|
119
96
|
const searchParams = useSearchParams();
|
|
120
97
|
const [search, setSearch] = useState('');
|
|
121
|
-
const [
|
|
122
|
-
const [
|
|
123
|
-
const [
|
|
124
|
-
const uploadTargetRef = useRef<number | null>(null);
|
|
125
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
98
|
+
const [isActiveFilter, setIsActiveFilter] = useState('all');
|
|
99
|
+
const [page, setPage] = useState(1);
|
|
100
|
+
const [pageSize, setPageSize] = useState(12);
|
|
126
101
|
const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
|
|
127
102
|
const [contractToDelete, setContractToDelete] =
|
|
128
103
|
useState<OperationsContract | null>(null);
|
|
129
104
|
const [isDeletingContract, setIsDeletingContract] = useState(false);
|
|
130
|
-
const [viewMode, setViewMode] = useState<ContractViewMode>(() => {
|
|
131
|
-
if (typeof window === 'undefined') {
|
|
132
|
-
return 'table';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const savedViewMode = window.localStorage.getItem(
|
|
136
|
-
CONTRACT_VIEW_STORAGE_KEY
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return savedViewMode === 'cards' ? 'cards' : 'table';
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const getContractOptionLabel = (group: string, value?: string | null) => {
|
|
143
|
-
if (!value) {
|
|
144
|
-
return '-';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const key = `options.${group}.${value}`;
|
|
148
|
-
return formT.has(key) ? formT(key) : formatEnumLabel(value);
|
|
149
|
-
};
|
|
150
105
|
|
|
151
106
|
const editParam = searchParams.get('edit');
|
|
152
|
-
const duplicateParam = searchParams.get('duplicate');
|
|
153
|
-
const templateParam = searchParams.get('template');
|
|
154
107
|
const editingContractId =
|
|
155
108
|
editParam && /^\d+$/.test(editParam) ? Number(editParam) : null;
|
|
156
|
-
const duplicatingContractId =
|
|
157
|
-
duplicateParam && /^\d+$/.test(duplicateParam)
|
|
158
|
-
? Number(duplicateParam)
|
|
159
|
-
: null;
|
|
160
|
-
const creatingFromTemplateId =
|
|
161
|
-
templateParam && /^\d+$/.test(templateParam) ? Number(templateParam) : null;
|
|
162
109
|
|
|
163
|
-
const updateSheetQuery = (options?: {
|
|
164
|
-
editId?: number | null;
|
|
165
|
-
duplicateId?: number | null;
|
|
166
|
-
templateId?: number | null;
|
|
167
|
-
}) => {
|
|
110
|
+
const updateSheetQuery = (options?: { editId?: number | null }) => {
|
|
168
111
|
const params = new URLSearchParams(searchParams.toString());
|
|
169
|
-
|
|
170
112
|
params.delete('edit');
|
|
171
|
-
params.delete('duplicate');
|
|
172
|
-
params.delete('template');
|
|
173
113
|
|
|
174
114
|
if (options?.editId && options.editId > 0) {
|
|
175
115
|
params.set('edit', String(options.editId));
|
|
176
116
|
}
|
|
177
117
|
|
|
178
|
-
if (options?.duplicateId && options.duplicateId > 0) {
|
|
179
|
-
params.set('duplicate', String(options.duplicateId));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (options?.templateId && options.templateId > 0) {
|
|
183
|
-
params.set('template', String(options.templateId));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
118
|
const nextUrl = params.size ? `${pathname}?${params.toString()}` : pathname;
|
|
187
119
|
router.replace(nextUrl, { scroll: false });
|
|
188
120
|
};
|
|
189
121
|
|
|
190
|
-
const openCreateSheet = (templateId?: number | null) => {
|
|
191
|
-
if (templateId && templateId > 0) {
|
|
192
|
-
updateSheetQuery({ templateId });
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
setIsCreateSheetOpen(true);
|
|
197
|
-
updateSheetQuery();
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const openEditSheet = (contractId: number) => {
|
|
201
|
-
setIsCreateSheetOpen(false);
|
|
202
|
-
updateSheetQuery({ editId: contractId });
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const openDuplicateSheet = (contractId: number) => {
|
|
206
|
-
setIsCreateSheetOpen(false);
|
|
207
|
-
updateSheetQuery({ duplicateId: contractId });
|
|
208
|
-
};
|
|
209
|
-
|
|
210
122
|
const closeFormSheet = () => {
|
|
211
123
|
setIsCreateSheetOpen(false);
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
editingContractId !== null ||
|
|
215
|
-
duplicatingContractId !== null ||
|
|
216
|
-
creatingFromTemplateId !== null
|
|
217
|
-
) {
|
|
124
|
+
if (editingContractId !== null) {
|
|
218
125
|
updateSheetQuery();
|
|
219
126
|
}
|
|
220
127
|
};
|
|
221
128
|
|
|
222
|
-
const { data:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
129
|
+
const { data: contractsResponse, refetch } = useQuery<
|
|
130
|
+
PaginatedResponse<OperationsContract>
|
|
131
|
+
>({
|
|
132
|
+
queryKey: [
|
|
133
|
+
'operations-contracts-list',
|
|
134
|
+
currentLocaleCode,
|
|
135
|
+
search,
|
|
136
|
+
isActiveFilter,
|
|
137
|
+
page,
|
|
138
|
+
pageSize,
|
|
139
|
+
],
|
|
140
|
+
queryFn: () => {
|
|
141
|
+
const params = new URLSearchParams({
|
|
142
|
+
page: String(page),
|
|
143
|
+
pageSize: String(pageSize),
|
|
144
|
+
});
|
|
227
145
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
? true
|
|
233
|
-
: [
|
|
234
|
-
item.name,
|
|
235
|
-
item.code,
|
|
236
|
-
item.mainRelatedPartyName,
|
|
237
|
-
item.clientName,
|
|
238
|
-
item.contractType,
|
|
239
|
-
]
|
|
240
|
-
.filter(Boolean)
|
|
241
|
-
.some((value) =>
|
|
242
|
-
String(value)
|
|
243
|
-
.toLowerCase()
|
|
244
|
-
.includes(search.trim().toLowerCase())
|
|
245
|
-
);
|
|
246
|
-
const matchesStatus =
|
|
247
|
-
statusFilter === 'all' ? true : item.status === statusFilter;
|
|
248
|
-
const matchesCategory =
|
|
249
|
-
categoryFilter === 'all'
|
|
250
|
-
? true
|
|
251
|
-
: item.contractCategory === categoryFilter;
|
|
252
|
-
const matchesOrigin =
|
|
253
|
-
originFilter === 'all' ? true : item.originType === originFilter;
|
|
254
|
-
return (
|
|
255
|
-
matchesSearch && matchesStatus && matchesCategory && matchesOrigin
|
|
256
|
-
);
|
|
257
|
-
}),
|
|
258
|
-
[contracts, search, statusFilter, categoryFilter, originFilter]
|
|
259
|
-
);
|
|
146
|
+
if (search.trim()) params.set('search', search.trim());
|
|
147
|
+
if (isActiveFilter !== 'all') {
|
|
148
|
+
params.set('isActive', String(isActiveFilter === 'active'));
|
|
149
|
+
}
|
|
260
150
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
151
|
+
return fetchOperations<PaginatedResponse<OperationsContract>>(
|
|
152
|
+
request,
|
|
153
|
+
`/operations/contracts?${params.toString()}`
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
placeholderData: (previous) => previous,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const { data: stats } = useQuery<OperationsContractStats>({
|
|
160
|
+
queryKey: ['operations-contracts-stats', currentLocaleCode],
|
|
161
|
+
queryFn: () =>
|
|
162
|
+
fetchOperations<OperationsContractStats>(request, '/operations/contracts/stats'),
|
|
163
|
+
});
|
|
268
164
|
|
|
165
|
+
const rows = contractsResponse?.data ?? [];
|
|
269
166
|
const statsCards = useMemo(
|
|
270
167
|
() => [
|
|
271
168
|
{
|
|
272
169
|
key: 'total',
|
|
273
170
|
title: t('cards.total'),
|
|
274
|
-
value:
|
|
171
|
+
value: stats?.total ?? 0,
|
|
275
172
|
icon: FileText,
|
|
276
173
|
},
|
|
277
174
|
{
|
|
278
175
|
key: 'active',
|
|
279
176
|
title: t('cards.active'),
|
|
280
|
-
value:
|
|
281
|
-
icon:
|
|
177
|
+
value: stats?.active ?? 0,
|
|
178
|
+
icon: ShieldCheck,
|
|
282
179
|
},
|
|
283
180
|
{
|
|
284
|
-
key: '
|
|
285
|
-
title: t('cards.
|
|
286
|
-
value:
|
|
287
|
-
|
|
288
|
-
icon: Pencil,
|
|
181
|
+
key: 'inactive',
|
|
182
|
+
title: t('cards.inactive'),
|
|
183
|
+
value: stats?.inactive ?? 0,
|
|
184
|
+
icon: ShieldOff,
|
|
289
185
|
},
|
|
290
186
|
{
|
|
291
|
-
key: '
|
|
292
|
-
title: t('cards.
|
|
293
|
-
value:
|
|
294
|
-
|
|
295
|
-
icon: FileStack,
|
|
187
|
+
key: 'withFile',
|
|
188
|
+
title: t('cards.withFile'),
|
|
189
|
+
value: stats?.withFile ?? 0,
|
|
190
|
+
icon: Download,
|
|
296
191
|
},
|
|
297
192
|
],
|
|
298
|
-
[
|
|
193
|
+
[stats, t]
|
|
299
194
|
);
|
|
300
195
|
|
|
301
|
-
const
|
|
302
|
-
if (value !== 'table' && value !== 'cards') {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
setViewMode(value);
|
|
307
|
-
|
|
308
|
-
if (typeof window !== 'undefined') {
|
|
309
|
-
window.localStorage.setItem(CONTRACT_VIEW_STORAGE_KEY, value);
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
const downloadPdf = async (contractId: number) => {
|
|
196
|
+
const downloadContract = async (contractId: number) => {
|
|
314
197
|
const detail = await fetchOperations<OperationsContractDetails>(
|
|
315
198
|
request,
|
|
316
199
|
`/operations/contracts/${contractId}`
|
|
317
200
|
);
|
|
318
|
-
const document =
|
|
319
|
-
(
|
|
320
|
-
item
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
201
|
+
const document =
|
|
202
|
+
detail.documents.find(
|
|
203
|
+
(item) =>
|
|
204
|
+
item.isCurrent &&
|
|
205
|
+
['generated_pdf', 'source_upload'].includes(item.documentType)
|
|
206
|
+
) ?? null;
|
|
207
|
+
|
|
324
208
|
if (document?.fileId) {
|
|
325
|
-
|
|
209
|
+
window.open(buildStoredFileUrl(document.fileId) || '', '_blank', 'noopener,noreferrer');
|
|
326
210
|
return;
|
|
327
211
|
}
|
|
212
|
+
|
|
328
213
|
if (!document?.fileContentBase64) {
|
|
329
214
|
showToastHandler?.('error', t('messages.noPdf'));
|
|
330
215
|
return;
|
|
331
216
|
}
|
|
217
|
+
|
|
332
218
|
downloadBase64File(
|
|
333
219
|
document.fileName,
|
|
334
220
|
document.mimeType,
|
|
@@ -336,10 +222,6 @@ export default function OperationsContractsPage() {
|
|
|
336
222
|
);
|
|
337
223
|
};
|
|
338
224
|
|
|
339
|
-
const duplicateContract = (contractId: number) => {
|
|
340
|
-
openDuplicateSheet(contractId);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
225
|
const handleDeleteContract = async () => {
|
|
344
226
|
if (!contractToDelete?.id) {
|
|
345
227
|
return;
|
|
@@ -361,15 +243,16 @@ export default function OperationsContractsPage() {
|
|
|
361
243
|
}
|
|
362
244
|
};
|
|
363
245
|
|
|
364
|
-
const
|
|
246
|
+
const toggleActive = async (contract: OperationsContract) => {
|
|
365
247
|
try {
|
|
248
|
+
const nextIsActive = !contract.isActive;
|
|
366
249
|
await mutateOperations(
|
|
367
250
|
request,
|
|
368
251
|
`/operations/contracts/${contract.id}`,
|
|
369
252
|
'PATCH',
|
|
370
253
|
{
|
|
371
|
-
|
|
372
|
-
|
|
254
|
+
isActive: nextIsActive,
|
|
255
|
+
status: nextIsActive ? 'active' : 'archived',
|
|
373
256
|
}
|
|
374
257
|
);
|
|
375
258
|
showToastHandler?.('success', t('messages.statusSuccess'));
|
|
@@ -379,10 +262,7 @@ export default function OperationsContractsPage() {
|
|
|
379
262
|
}
|
|
380
263
|
};
|
|
381
264
|
|
|
382
|
-
const
|
|
383
|
-
contract: OperationsContract,
|
|
384
|
-
align: 'start' | 'end' = 'end'
|
|
385
|
-
) => (
|
|
265
|
+
const renderActions = (contract: OperationsContract) => (
|
|
386
266
|
<DropdownMenu>
|
|
387
267
|
<DropdownMenuTrigger asChild>
|
|
388
268
|
<Button
|
|
@@ -390,47 +270,37 @@ export default function OperationsContractsPage() {
|
|
|
390
270
|
size="icon"
|
|
391
271
|
className="cursor-pointer"
|
|
392
272
|
aria-label={commonT('labels.actions')}
|
|
273
|
+
onClick={(event) => event.stopPropagation()}
|
|
393
274
|
>
|
|
394
275
|
<MoreHorizontal className="size-4" />
|
|
395
276
|
</Button>
|
|
396
277
|
</DropdownMenuTrigger>
|
|
397
|
-
<DropdownMenuContent align=
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
<DropdownMenuItem onSelect={() => void
|
|
278
|
+
<DropdownMenuContent align="end" className="w-52">
|
|
279
|
+
<DropdownMenuItem
|
|
280
|
+
onSelect={() => router.push(`/operations/contracts/${contract.id}`)}
|
|
281
|
+
>
|
|
282
|
+
<Eye className="size-4" />
|
|
283
|
+
{detailT('actions.preview')}
|
|
284
|
+
</DropdownMenuItem>
|
|
285
|
+
<DropdownMenuItem onSelect={() => void downloadContract(contract.id)}>
|
|
405
286
|
<Download className="size-4" />
|
|
406
287
|
{t('actions.downloadPdf')}
|
|
407
288
|
</DropdownMenuItem>
|
|
408
|
-
{access.isDirector ? (
|
|
409
|
-
<DropdownMenuItem
|
|
410
|
-
onSelect={() => {
|
|
411
|
-
uploadTargetRef.current = contract.id;
|
|
412
|
-
fileInputRef.current?.click();
|
|
413
|
-
}}
|
|
414
|
-
>
|
|
415
|
-
<Upload className="size-4" />
|
|
416
|
-
{t('actions.uploadPdf')}
|
|
417
|
-
</DropdownMenuItem>
|
|
418
|
-
) : null}
|
|
419
289
|
{access.isDirector ? <DropdownMenuSeparator /> : null}
|
|
420
290
|
{access.isDirector ? (
|
|
421
|
-
<DropdownMenuItem
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
<FileStack className="size-4" />
|
|
425
|
-
{t('actions.duplicate')}
|
|
291
|
+
<DropdownMenuItem onSelect={() => updateSheetQuery({ editId: contract.id })}>
|
|
292
|
+
<Pencil className="size-4" />
|
|
293
|
+
{commonT('actions.edit')}
|
|
426
294
|
</DropdownMenuItem>
|
|
427
295
|
) : null}
|
|
428
296
|
{access.isDirector ? (
|
|
429
|
-
<DropdownMenuItem onSelect={() => void
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
297
|
+
<DropdownMenuItem onSelect={() => void toggleActive(contract)}>
|
|
298
|
+
{contract.isActive ? (
|
|
299
|
+
<ShieldOff className="size-4" />
|
|
300
|
+
) : (
|
|
301
|
+
<ShieldCheck className="size-4" />
|
|
302
|
+
)}
|
|
303
|
+
{contract.isActive ? detailT('labels.inactive') : detailT('labels.active')}
|
|
434
304
|
</DropdownMenuItem>
|
|
435
305
|
) : null}
|
|
436
306
|
{access.isDirector ? <DropdownMenuSeparator /> : null}
|
|
@@ -455,376 +325,110 @@ export default function OperationsContractsPage() {
|
|
|
455
325
|
current={t('breadcrumb')}
|
|
456
326
|
actions={
|
|
457
327
|
access.isDirector ? (
|
|
458
|
-
<
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
</Button>
|
|
466
|
-
</div>
|
|
328
|
+
<Button
|
|
329
|
+
size="sm"
|
|
330
|
+
className="cursor-pointer"
|
|
331
|
+
onClick={() => setIsCreateSheetOpen(true)}
|
|
332
|
+
>
|
|
333
|
+
{commonT('actions.create')}
|
|
334
|
+
</Button>
|
|
467
335
|
) : undefined
|
|
468
336
|
}
|
|
469
337
|
/>
|
|
470
338
|
|
|
471
339
|
<KpiCardsGrid items={statsCards} columns={4} />
|
|
472
340
|
|
|
473
|
-
<
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
onSearch={() => undefined}
|
|
479
|
-
placeholder={t('searchPlaceholder')}
|
|
480
|
-
controls={[
|
|
481
|
-
{
|
|
482
|
-
id: 'category',
|
|
483
|
-
type: 'select',
|
|
484
|
-
value: categoryFilter,
|
|
485
|
-
onChange: setCategoryFilter,
|
|
486
|
-
placeholder: commonT('labels.contractCategory'),
|
|
487
|
-
options: [
|
|
488
|
-
{ value: 'all', label: commonT('filters.allTypes') },
|
|
489
|
-
...[
|
|
490
|
-
'employee',
|
|
491
|
-
'contractor',
|
|
492
|
-
'client',
|
|
493
|
-
'supplier',
|
|
494
|
-
'vendor',
|
|
495
|
-
'partner',
|
|
496
|
-
'internal',
|
|
497
|
-
'other',
|
|
498
|
-
].map((value) => ({
|
|
499
|
-
value,
|
|
500
|
-
label: getContractOptionLabel('contractCategories', value),
|
|
501
|
-
})),
|
|
502
|
-
],
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
id: 'origin',
|
|
506
|
-
type: 'select',
|
|
507
|
-
value: originFilter,
|
|
508
|
-
onChange: setOriginFilter,
|
|
509
|
-
placeholder: t('filters.originType'),
|
|
510
|
-
options: [
|
|
511
|
-
{ value: 'all', label: commonT('filters.allTypes') },
|
|
512
|
-
...['manual', 'employee_hiring', 'client_project'].map(
|
|
513
|
-
(value) => ({
|
|
514
|
-
value,
|
|
515
|
-
label: getContractOptionLabel('originTypes', value),
|
|
516
|
-
})
|
|
517
|
-
),
|
|
518
|
-
],
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
id: 'status',
|
|
522
|
-
type: 'select',
|
|
523
|
-
value: statusFilter,
|
|
524
|
-
onChange: setStatusFilter,
|
|
525
|
-
placeholder: commonT('labels.status'),
|
|
526
|
-
options: [
|
|
527
|
-
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
528
|
-
...[
|
|
529
|
-
'draft',
|
|
530
|
-
'under_review',
|
|
531
|
-
'active',
|
|
532
|
-
'renewal',
|
|
533
|
-
'expired',
|
|
534
|
-
'closed',
|
|
535
|
-
'archived',
|
|
536
|
-
].map((value) => ({
|
|
537
|
-
value,
|
|
538
|
-
label: getContractOptionLabel('statuses', value),
|
|
539
|
-
})),
|
|
540
|
-
],
|
|
541
|
-
},
|
|
542
|
-
]}
|
|
543
|
-
/>
|
|
544
|
-
</div>
|
|
545
|
-
|
|
546
|
-
<div className="flex items-center justify-between gap-3 xl:justify-end">
|
|
547
|
-
<span className="text-xs font-medium text-muted-foreground">
|
|
548
|
-
{t('viewMode')}
|
|
549
|
-
</span>
|
|
550
|
-
<ToggleGroup
|
|
551
|
-
type="single"
|
|
552
|
-
value={viewMode}
|
|
553
|
-
onValueChange={handleViewModeChange}
|
|
554
|
-
variant="outline"
|
|
555
|
-
size="sm"
|
|
556
|
-
aria-label={t('viewMode')}
|
|
557
|
-
>
|
|
558
|
-
<ToggleGroupItem
|
|
559
|
-
value="table"
|
|
560
|
-
className="gap-1.5 px-2.5"
|
|
561
|
-
aria-label={t('viewModeTable')}
|
|
562
|
-
>
|
|
563
|
-
<List className="h-4 w-4" />
|
|
564
|
-
<span className="hidden sm:inline">{t('viewModeTable')}</span>
|
|
565
|
-
</ToggleGroupItem>
|
|
566
|
-
<ToggleGroupItem
|
|
567
|
-
value="cards"
|
|
568
|
-
className="gap-1.5 px-2.5"
|
|
569
|
-
aria-label={t('viewModeCards')}
|
|
570
|
-
>
|
|
571
|
-
<LayoutGrid className="h-4 w-4" />
|
|
572
|
-
<span className="hidden sm:inline">{t('viewModeCards')}</span>
|
|
573
|
-
</ToggleGroupItem>
|
|
574
|
-
</ToggleGroup>
|
|
575
|
-
</div>
|
|
576
|
-
</div>
|
|
577
|
-
|
|
578
|
-
<input
|
|
579
|
-
ref={fileInputRef}
|
|
580
|
-
type="file"
|
|
581
|
-
accept="application/pdf"
|
|
582
|
-
className="hidden"
|
|
583
|
-
onChange={async (event) => {
|
|
584
|
-
const file = event.target.files?.[0];
|
|
585
|
-
const contractId = uploadTargetRef.current;
|
|
586
|
-
if (!file || !contractId) return;
|
|
587
|
-
try {
|
|
588
|
-
const fileContentBase64 = await fileToBase64(file);
|
|
589
|
-
await mutateOperations(
|
|
590
|
-
request,
|
|
591
|
-
`/operations/contracts/${contractId}`,
|
|
592
|
-
'PATCH',
|
|
593
|
-
{
|
|
594
|
-
replaceUploadedPdfDocument: {
|
|
595
|
-
fileName: file.name,
|
|
596
|
-
mimeType: file.type || 'application/pdf',
|
|
597
|
-
fileContentBase64,
|
|
598
|
-
},
|
|
599
|
-
}
|
|
600
|
-
);
|
|
601
|
-
showToastHandler?.('success', t('messages.uploadSuccess'));
|
|
602
|
-
await refetch();
|
|
603
|
-
} catch {
|
|
604
|
-
showToastHandler?.('error', t('messages.uploadError'));
|
|
605
|
-
} finally {
|
|
606
|
-
uploadTargetRef.current = null;
|
|
607
|
-
event.target.value = '';
|
|
608
|
-
}
|
|
341
|
+
<SearchBar
|
|
342
|
+
searchQuery={search}
|
|
343
|
+
onSearchChange={(value) => {
|
|
344
|
+
setSearch(value);
|
|
345
|
+
setPage(1);
|
|
609
346
|
}}
|
|
347
|
+
showSearchButton={false}
|
|
348
|
+
debounceMs={500}
|
|
349
|
+
placeholder={t('searchPlaceholder')}
|
|
350
|
+
controls={[
|
|
351
|
+
{
|
|
352
|
+
id: 'status',
|
|
353
|
+
type: 'select',
|
|
354
|
+
value: isActiveFilter,
|
|
355
|
+
onChange: (value) => {
|
|
356
|
+
setIsActiveFilter(value);
|
|
357
|
+
setPage(1);
|
|
358
|
+
},
|
|
359
|
+
placeholder: commonT('labels.status'),
|
|
360
|
+
options: [
|
|
361
|
+
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
362
|
+
{ value: 'active', label: detailT('labels.active') },
|
|
363
|
+
{ value: 'inactive', label: detailT('labels.inactive') },
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
]}
|
|
610
367
|
/>
|
|
611
368
|
|
|
612
|
-
{
|
|
613
|
-
|
|
614
|
-
<
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
.join(' • ');
|
|
629
|
-
|
|
630
|
-
return (
|
|
631
|
-
<Card
|
|
369
|
+
{rows.length ? (
|
|
370
|
+
<div className="overflow-x-auto rounded-md border">
|
|
371
|
+
<Table>
|
|
372
|
+
<TableHeader>
|
|
373
|
+
<TableRow>
|
|
374
|
+
<TableHead>{t('columns.title')}</TableHead>
|
|
375
|
+
<TableHead>{t('columns.file')}</TableHead>
|
|
376
|
+
<TableHead>{commonT('labels.status')}</TableHead>
|
|
377
|
+
<TableHead className="w-32 text-right">
|
|
378
|
+
{commonT('labels.actions')}
|
|
379
|
+
</TableHead>
|
|
380
|
+
</TableRow>
|
|
381
|
+
</TableHeader>
|
|
382
|
+
<TableBody>
|
|
383
|
+
{rows.map((contract) => (
|
|
384
|
+
<TableRow
|
|
632
385
|
key={contract.id}
|
|
633
|
-
className="
|
|
386
|
+
className="cursor-pointer hover:bg-muted/30"
|
|
387
|
+
onClick={() => router.push(`/operations/contracts/${contract.id}`)}
|
|
634
388
|
>
|
|
635
|
-
<
|
|
636
|
-
<div className="
|
|
637
|
-
<div className="
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
642
|
-
{secondaryLine || commonT('labels.notAvailable')}
|
|
643
|
-
</div>
|
|
644
|
-
</div>
|
|
645
|
-
<StatusBadge
|
|
646
|
-
label={getContractOptionLabel(
|
|
647
|
-
'statuses',
|
|
648
|
-
contract.status
|
|
649
|
-
)}
|
|
650
|
-
className={getStatusBadgeClass(contract.status)}
|
|
651
|
-
/>
|
|
652
|
-
</div>
|
|
653
|
-
|
|
654
|
-
<div className="flex flex-wrap gap-2">
|
|
655
|
-
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">
|
|
656
|
-
{getContractOptionLabel(
|
|
657
|
-
'contractCategories',
|
|
658
|
-
contract.contractCategory
|
|
659
|
-
)}
|
|
660
|
-
</span>
|
|
661
|
-
{contract.contractTemplateName ? (
|
|
662
|
-
<span className="inline-flex items-center rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground">
|
|
663
|
-
{contract.contractTemplateName}
|
|
664
|
-
</span>
|
|
665
|
-
) : null}
|
|
666
|
-
</div>
|
|
667
|
-
|
|
668
|
-
<div className="grid gap-2 text-sm text-muted-foreground lg:grid-cols-2">
|
|
669
|
-
<div>
|
|
670
|
-
<span className="font-medium text-foreground">
|
|
671
|
-
{commonT('labels.client')}:
|
|
672
|
-
</span>{' '}
|
|
673
|
-
{contract.clientName || commonT('labels.notAvailable')}
|
|
389
|
+
<TableCell>
|
|
390
|
+
<div className="min-w-0">
|
|
391
|
+
<div className="truncate font-medium">
|
|
392
|
+
{contract.name ||
|
|
393
|
+
contract.code ||
|
|
394
|
+
commonT('labels.notAvailable')}
|
|
674
395
|
</div>
|
|
675
|
-
<div>
|
|
676
|
-
<span className="font-medium text-foreground">
|
|
677
|
-
{t('columns.party')}:
|
|
678
|
-
</span>{' '}
|
|
396
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
679
397
|
{contract.mainRelatedPartyName ||
|
|
680
398
|
commonT('labels.notAvailable')}
|
|
681
399
|
</div>
|
|
682
|
-
<div className="flex items-center gap-2">
|
|
683
|
-
<span className="font-medium text-foreground">
|
|
684
|
-
{t('columns.signatureStatus')}:
|
|
685
|
-
</span>
|
|
686
|
-
<StatusBadge
|
|
687
|
-
label={getContractOptionLabel(
|
|
688
|
-
'signatureStatuses',
|
|
689
|
-
contract.signatureStatus
|
|
690
|
-
)}
|
|
691
|
-
className={getStatusBadgeClass(
|
|
692
|
-
contract.signatureStatus
|
|
693
|
-
)}
|
|
694
|
-
/>
|
|
695
|
-
</div>
|
|
696
|
-
<div>
|
|
697
|
-
<span className="font-medium text-foreground">
|
|
698
|
-
{t('columns.financials')}:
|
|
699
|
-
</span>{' '}
|
|
700
|
-
{formatCurrency(financialTotal)}
|
|
701
|
-
</div>
|
|
702
|
-
<div>
|
|
703
|
-
<span className="font-medium text-foreground">
|
|
704
|
-
{commonT('labels.startDate')}:
|
|
705
|
-
</span>{' '}
|
|
706
|
-
{formatDate(contract.startDate)}
|
|
707
|
-
</div>
|
|
708
|
-
<div>
|
|
709
|
-
<span className="font-medium text-foreground">
|
|
710
|
-
{commonT('labels.endDate')}:
|
|
711
|
-
</span>{' '}
|
|
712
|
-
{formatDate(contract.endDate)}
|
|
713
|
-
</div>
|
|
714
400
|
</div>
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
401
|
+
</TableCell>
|
|
402
|
+
<TableCell>
|
|
403
|
+
<span className="truncate text-sm text-muted-foreground">
|
|
404
|
+
{contract.currentPdfFileName || commonT('labels.notAvailable')}
|
|
405
|
+
</span>
|
|
406
|
+
</TableCell>
|
|
407
|
+
<TableCell>
|
|
408
|
+
<StatusBadge
|
|
409
|
+
label={
|
|
410
|
+
contract.isActive
|
|
411
|
+
? detailT('labels.active')
|
|
412
|
+
: detailT('labels.inactive')
|
|
413
|
+
}
|
|
414
|
+
className={getStatusBadgeClass(
|
|
415
|
+
contract.isActive ? 'active' : 'archived'
|
|
416
|
+
)}
|
|
417
|
+
/>
|
|
418
|
+
</TableCell>
|
|
419
|
+
<TableCell>
|
|
420
|
+
<div
|
|
421
|
+
className="flex justify-end"
|
|
422
|
+
onClick={(event) => event.stopPropagation()}
|
|
423
|
+
>
|
|
424
|
+
{renderActions(contract)}
|
|
718
425
|
</div>
|
|
719
|
-
</
|
|
720
|
-
</Card>
|
|
721
|
-
);
|
|
722
|
-
})}
|
|
723
|
-
</div>
|
|
724
|
-
) : (
|
|
725
|
-
<div className="overflow-x-auto rounded-md border">
|
|
726
|
-
<Table className="table-fixed">
|
|
727
|
-
<TableHeader>
|
|
728
|
-
<TableRow>
|
|
729
|
-
<TableHead className="w-[34%]">
|
|
730
|
-
{t('columns.title')}
|
|
731
|
-
</TableHead>
|
|
732
|
-
<TableHead>{commonT('labels.status')}</TableHead>
|
|
733
|
-
<TableHead className="hidden md:table-cell">
|
|
734
|
-
{t('columns.signatureStatus')}
|
|
735
|
-
</TableHead>
|
|
736
|
-
<TableHead className="hidden lg:table-cell">
|
|
737
|
-
{t('columns.type')}
|
|
738
|
-
</TableHead>
|
|
739
|
-
<TableHead className="hidden xl:table-cell">
|
|
740
|
-
{commonT('labels.client')}
|
|
741
|
-
</TableHead>
|
|
742
|
-
<TableHead className="hidden 2xl:table-cell">
|
|
743
|
-
{t('columns.financials')}
|
|
744
|
-
</TableHead>
|
|
745
|
-
<TableHead className="w-30 text-right sm:w-42.5">
|
|
746
|
-
{commonT('labels.actions')}
|
|
747
|
-
</TableHead>
|
|
426
|
+
</TableCell>
|
|
748
427
|
</TableRow>
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
(contract.valueAmount ?? 0) + (contract.revenueAmount ?? 0);
|
|
754
|
-
|
|
755
|
-
return (
|
|
756
|
-
<TableRow key={contract.id} className="hover:bg-muted/30">
|
|
757
|
-
<TableCell>
|
|
758
|
-
<div className="min-w-0">
|
|
759
|
-
<div className="truncate font-medium">
|
|
760
|
-
{contract.name ||
|
|
761
|
-
contract.code ||
|
|
762
|
-
commonT('labels.notAvailable')}
|
|
763
|
-
</div>
|
|
764
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
765
|
-
{[
|
|
766
|
-
contract.code,
|
|
767
|
-
contract.contractTemplateName,
|
|
768
|
-
contract.mainRelatedPartyName,
|
|
769
|
-
getContractOptionLabel(
|
|
770
|
-
'originTypes',
|
|
771
|
-
contract.originType
|
|
772
|
-
),
|
|
773
|
-
]
|
|
774
|
-
.filter(Boolean)
|
|
775
|
-
.join(' • ') || commonT('labels.notAvailable')}
|
|
776
|
-
</div>
|
|
777
|
-
</div>
|
|
778
|
-
</TableCell>
|
|
779
|
-
<TableCell>
|
|
780
|
-
<StatusBadge
|
|
781
|
-
label={getContractOptionLabel(
|
|
782
|
-
'statuses',
|
|
783
|
-
contract.status
|
|
784
|
-
)}
|
|
785
|
-
className={getStatusBadgeClass(contract.status)}
|
|
786
|
-
/>
|
|
787
|
-
</TableCell>
|
|
788
|
-
<TableCell className="hidden md:table-cell">
|
|
789
|
-
<StatusBadge
|
|
790
|
-
label={getContractOptionLabel(
|
|
791
|
-
'signatureStatuses',
|
|
792
|
-
contract.signatureStatus
|
|
793
|
-
)}
|
|
794
|
-
className={getStatusBadgeClass(
|
|
795
|
-
contract.signatureStatus
|
|
796
|
-
)}
|
|
797
|
-
/>
|
|
798
|
-
</TableCell>
|
|
799
|
-
<TableCell className="hidden lg:table-cell">
|
|
800
|
-
<div className="truncate">
|
|
801
|
-
{getContractOptionLabel(
|
|
802
|
-
'contractTypes',
|
|
803
|
-
contract.contractType
|
|
804
|
-
)}
|
|
805
|
-
</div>
|
|
806
|
-
</TableCell>
|
|
807
|
-
<TableCell className="hidden xl:table-cell">
|
|
808
|
-
<div className="truncate">
|
|
809
|
-
{contract.clientName ||
|
|
810
|
-
commonT('labels.notAvailable')}
|
|
811
|
-
</div>
|
|
812
|
-
</TableCell>
|
|
813
|
-
<TableCell className="hidden 2xl:table-cell">
|
|
814
|
-
{formatCurrency(financialTotal)}
|
|
815
|
-
</TableCell>
|
|
816
|
-
<TableCell>
|
|
817
|
-
<div className="flex justify-end">
|
|
818
|
-
{renderContractActions(contract, 'end')}
|
|
819
|
-
</div>
|
|
820
|
-
</TableCell>
|
|
821
|
-
</TableRow>
|
|
822
|
-
);
|
|
823
|
-
})}
|
|
824
|
-
</TableBody>
|
|
825
|
-
</Table>
|
|
826
|
-
</div>
|
|
827
|
-
)
|
|
428
|
+
))}
|
|
429
|
+
</TableBody>
|
|
430
|
+
</Table>
|
|
431
|
+
</div>
|
|
828
432
|
) : (
|
|
829
433
|
<EmptyState
|
|
830
434
|
icon={<FileText className="size-12" />}
|
|
@@ -836,11 +440,25 @@ export default function OperationsContractsPage() {
|
|
|
836
440
|
: commonT('actions.refresh')
|
|
837
441
|
}
|
|
838
442
|
onAction={
|
|
839
|
-
access.isDirector
|
|
443
|
+
access.isDirector
|
|
444
|
+
? () => setIsCreateSheetOpen(true)
|
|
445
|
+
: () => void refetch()
|
|
840
446
|
}
|
|
841
447
|
/>
|
|
842
448
|
)}
|
|
843
449
|
|
|
450
|
+
<PaginationFooter
|
|
451
|
+
currentPage={page}
|
|
452
|
+
pageSize={pageSize}
|
|
453
|
+
totalItems={contractsResponse?.total ?? 0}
|
|
454
|
+
pageSizeOptions={[12, 24, 48]}
|
|
455
|
+
onPageChange={setPage}
|
|
456
|
+
onPageSizeChange={(size) => {
|
|
457
|
+
setPageSize(size);
|
|
458
|
+
setPage(1);
|
|
459
|
+
}}
|
|
460
|
+
/>
|
|
461
|
+
|
|
844
462
|
<AlertDialog
|
|
845
463
|
open={contractToDelete !== null}
|
|
846
464
|
onOpenChange={(open) => {
|
|
@@ -882,12 +500,7 @@ export default function OperationsContractsPage() {
|
|
|
882
500
|
</AlertDialog>
|
|
883
501
|
|
|
884
502
|
<Sheet
|
|
885
|
-
open={
|
|
886
|
-
isCreateSheetOpen ||
|
|
887
|
-
editingContractId !== null ||
|
|
888
|
-
duplicatingContractId !== null ||
|
|
889
|
-
creatingFromTemplateId !== null
|
|
890
|
-
}
|
|
503
|
+
open={isCreateSheetOpen || editingContractId !== null}
|
|
891
504
|
onOpenChange={(open) => {
|
|
892
505
|
if (!open) {
|
|
893
506
|
closeFormSheet();
|
|
@@ -896,39 +509,24 @@ export default function OperationsContractsPage() {
|
|
|
896
509
|
>
|
|
897
510
|
<SheetContent className="flex h-full w-full flex-col overflow-hidden p-0 sm:max-w-xl lg:max-w-4xl">
|
|
898
511
|
<SheetHeader className="sr-only">
|
|
899
|
-
<SheetTitle>
|
|
512
|
+
<SheetTitle>
|
|
513
|
+
{editingContractId ? commonT('actions.edit') : commonT('actions.create')}
|
|
514
|
+
</SheetTitle>
|
|
900
515
|
<SheetDescription>{t('description')}</SheetDescription>
|
|
901
516
|
</SheetHeader>
|
|
902
|
-
<
|
|
517
|
+
<ContractFormScreen
|
|
903
518
|
key={
|
|
904
519
|
editingContractId
|
|
905
520
|
? `edit-${editingContractId}`
|
|
906
|
-
:
|
|
907
|
-
?
|
|
908
|
-
:
|
|
909
|
-
? `template-${creatingFromTemplateId}`
|
|
910
|
-
: isCreateSheetOpen
|
|
911
|
-
? 'create-contract'
|
|
912
|
-
: 'contract-sheet'
|
|
521
|
+
: isCreateSheetOpen
|
|
522
|
+
? 'create-contract'
|
|
523
|
+
: 'contract-sheet'
|
|
913
524
|
}
|
|
914
525
|
contractId={editingContractId ?? undefined}
|
|
915
|
-
duplicateFromId={duplicatingContractId ?? undefined}
|
|
916
|
-
initialTemplateId={creatingFromTemplateId ?? undefined}
|
|
917
|
-
isCreateFlow={isCreateSheetOpen}
|
|
918
526
|
onCancel={closeFormSheet}
|
|
919
|
-
onSaved={async (
|
|
920
|
-
await refetch();
|
|
921
|
-
|
|
922
|
-
if (
|
|
923
|
-
isCreateSheetOpen ||
|
|
924
|
-
duplicatingContractId !== null ||
|
|
925
|
-
creatingFromTemplateId !== null
|
|
926
|
-
) {
|
|
927
|
-
openEditSheet(contract.id);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
|
|
527
|
+
onSaved={async () => {
|
|
931
528
|
closeFormSheet();
|
|
529
|
+
await refetch();
|
|
932
530
|
}}
|
|
933
531
|
/>
|
|
934
532
|
</SheetContent>
|