@hed-hog/operations 0.0.303 → 0.0.305
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/README.md +200 -43
- package/dist/controllers/operations-approvals.controller.d.ts +9 -0
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -0
- package/dist/controllers/operations-approvals.controller.js +64 -0
- package/dist/controllers/operations-approvals.controller.js.map +1 -0
- package/dist/controllers/operations-collaborators.controller.d.ts +223 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -0
- package/dist/controllers/operations-collaborators.controller.js +96 -0
- package/dist/controllers/operations-collaborators.controller.js.map +1 -0
- package/dist/controllers/operations-contracts.controller.d.ts +683 -0
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -0
- package/dist/controllers/operations-contracts.controller.js +198 -0
- package/dist/controllers/operations-contracts.controller.js.map +1 -0
- package/dist/controllers/operations-org-structure.controller.d.ts +108 -0
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -0
- package/dist/controllers/operations-org-structure.controller.js +143 -0
- package/dist/controllers/operations-org-structure.controller.js.map +1 -0
- package/dist/controllers/operations-projects.controller.d.ts +184 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -0
- package/dist/controllers/operations-projects.controller.js +87 -0
- package/dist/controllers/operations-projects.controller.js.map +1 -0
- package/dist/controllers/operations-tasks.controller.d.ts +85 -0
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -0
- package/dist/controllers/operations-tasks.controller.js +90 -0
- package/dist/controllers/operations-tasks.controller.js.map +1 -0
- package/dist/controllers/operations-timesheets.controller.d.ts +99 -0
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -0
- package/dist/controllers/operations-timesheets.controller.js +154 -0
- package/dist/controllers/operations-timesheets.controller.js.map +1 -0
- package/dist/dto/create-collaborator-type.dto.d.ts +10 -0
- package/dist/dto/create-collaborator-type.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-type.dto.js +56 -0
- package/dist/dto/create-collaborator-type.dto.js.map +1 -0
- package/dist/dto/create-collaborator.dto.d.ts +42 -0
- package/dist/dto/create-collaborator.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator.dto.js +228 -0
- package/dist/dto/create-collaborator.dto.js.map +1 -0
- package/dist/dto/create-schedule-adjustment-request.dto.d.ts +17 -0
- package/dist/dto/create-schedule-adjustment-request.dto.d.ts.map +1 -0
- package/dist/dto/create-schedule-adjustment-request.dto.js +89 -0
- package/dist/dto/create-schedule-adjustment-request.dto.js.map +1 -0
- package/dist/dto/create-task.dto.d.ts +14 -0
- package/dist/dto/create-task.dto.d.ts.map +1 -0
- package/dist/dto/create-task.dto.js +83 -0
- package/dist/dto/create-task.dto.js.map +1 -0
- package/dist/dto/create-time-off-request.dto.d.ts +9 -0
- package/dist/dto/create-time-off-request.dto.d.ts.map +1 -0
- package/dist/dto/create-time-off-request.dto.js +54 -0
- package/dist/dto/create-time-off-request.dto.js.map +1 -0
- package/dist/dto/create-timesheet-entry.dto.d.ts +12 -0
- package/dist/dto/create-timesheet-entry.dto.d.ts.map +1 -0
- package/dist/dto/create-timesheet-entry.dto.js +75 -0
- package/dist/dto/create-timesheet-entry.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-types.dto.js +29 -0
- package/dist/dto/list-collaborator-types.dto.js.map +1 -0
- package/dist/dto/list-collaborators.dto.d.ts +8 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborators.dto.js +42 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -0
- package/dist/dto/list-project-options.dto.d.ts +4 -0
- package/dist/dto/list-project-options.dto.d.ts.map +1 -0
- package/dist/dto/list-project-options.dto.js +8 -0
- package/dist/dto/list-project-options.dto.js.map +1 -0
- package/dist/dto/list-tasks.dto.d.ts +7 -0
- package/dist/dto/list-tasks.dto.d.ts.map +1 -0
- package/dist/dto/list-tasks.dto.js +38 -0
- package/dist/dto/list-tasks.dto.js.map +1 -0
- package/dist/dto/list-timesheet-entries.dto.d.ts +10 -0
- package/dist/dto/list-timesheet-entries.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheet-entries.dto.js +54 -0
- package/dist/dto/list-timesheet-entries.dto.js.map +1 -0
- package/dist/dto/update-collaborator-type.dto.d.ts +4 -0
- package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-type.dto.js +8 -0
- package/dist/dto/update-collaborator-type.dto.js.map +1 -0
- package/dist/dto/update-collaborator.dto.d.ts +4 -0
- package/dist/dto/update-collaborator.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator.dto.js +8 -0
- package/dist/dto/update-collaborator.dto.js.map +1 -0
- package/dist/dto/update-task.dto.d.ts +14 -0
- package/dist/dto/update-task.dto.d.ts.map +1 -0
- package/dist/dto/update-task.dto.js +84 -0
- package/dist/dto/update-task.dto.js.map +1 -0
- package/dist/operations.controller.d.ts +0 -1045
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.controller.js +0 -429
- package/dist/operations.controller.js.map +1 -1
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +23 -2
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.service.d.ts +429 -8
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1931 -165
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +315 -1
- package/dist/operations.service.spec.js.map +1 -1
- package/dist/services/shared/operations-access.service.d.ts +16 -0
- package/dist/services/shared/operations-access.service.d.ts.map +1 -0
- package/dist/services/shared/operations-access.service.js +48 -0
- package/dist/services/shared/operations-access.service.js.map +1 -0
- package/hedhog/data/dashboard.yaml +20 -0
- package/hedhog/data/dashboard_component.yaml +274 -0
- package/hedhog/data/dashboard_component_role.yaml +174 -0
- package/hedhog/data/dashboard_item.yaml +299 -0
- package/hedhog/data/dashboard_role.yaml +20 -0
- package/hedhog/data/menu.yaml +30 -13
- package/hedhog/data/operations_collaborator_type.yaml +76 -0
- package/hedhog/data/route.yaml +196 -0
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +231 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +125 -40
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +740 -106
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
- package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +38 -16
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1017 -649
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
- package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +213 -0
- package/hedhog/frontend/app/_lib/api.ts.ejs +30 -1
- package/hedhog/frontend/app/_lib/types.ts.ejs +147 -39
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +40 -9
- package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
- package/hedhog/frontend/app/approvals/page.tsx.ejs +116 -98
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +502 -0
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +116 -72
- package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +11 -9
- package/hedhog/frontend/app/departments/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/page.tsx.ejs +364 -133
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +244 -120
- package/hedhog/frontend/app/team/page.tsx.ejs +15 -2
- package/hedhog/frontend/app/time-off/page.tsx.ejs +158 -82
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +814 -357
- package/hedhog/frontend/messages/en.json +268 -53
- package/hedhog/frontend/messages/pt.json +484 -271
- package/hedhog/table/operations_collaborator.yaml +26 -13
- package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -0
- package/hedhog/table/operations_collaborator_type.yaml +33 -0
- package/hedhog/table/operations_job_title.yaml +24 -0
- package/hedhog/table/operations_project.yaml +9 -0
- package/hedhog/table/operations_project_assignment.yaml +9 -0
- package/hedhog/table/operations_project_role.yaml +39 -0
- package/hedhog/table/operations_task.yaml +69 -0
- package/hedhog/table/operations_timesheet_entry.yaml +12 -0
- package/package.json +6 -6
- package/src/controllers/operations-approvals.controller.ts +24 -0
- package/src/controllers/operations-collaborators.controller.ts +60 -0
- package/src/controllers/operations-contracts.controller.ts +138 -0
- package/src/controllers/operations-org-structure.controller.ts +92 -0
- package/src/controllers/operations-projects.controller.ts +50 -0
- package/src/controllers/operations-tasks.controller.ts +63 -0
- package/src/controllers/operations-timesheets.controller.ts +100 -0
- package/src/dto/create-collaborator-type.dto.ts +43 -0
- package/src/dto/create-collaborator.dto.ts +223 -0
- package/src/dto/create-schedule-adjustment-request.dto.ts +91 -0
- package/src/dto/create-task.dto.ts +75 -0
- package/src/dto/create-time-off-request.dto.ts +53 -0
- package/src/dto/create-timesheet-entry.dto.ts +67 -0
- package/src/dto/list-collaborator-types.dto.ts +15 -0
- package/src/dto/list-collaborators.dto.ts +30 -0
- package/src/dto/list-project-options.dto.ts +3 -0
- package/src/dto/list-tasks.dto.ts +25 -0
- package/src/dto/list-timesheet-entries.dto.ts +40 -0
- package/src/dto/update-collaborator-type.dto.ts +3 -0
- package/src/dto/update-collaborator.dto.ts +3 -0
- package/src/dto/update-task.dto.ts +76 -0
- package/src/operations.controller.ts +1 -278
- package/src/operations.module.ts +23 -2
- package/src/operations.service.spec.ts +450 -0
- package/src/operations.service.ts +4507 -1561
- package/src/services/shared/operations-access.service.ts +52 -0
|
@@ -47,12 +47,18 @@ import { useTranslations } from 'next-intl';
|
|
|
47
47
|
import Link from 'next/link';
|
|
48
48
|
import { useRouter } from 'next/navigation';
|
|
49
49
|
import { useEffect, useMemo, useState } from 'react';
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
fetchOperations,
|
|
52
|
+
getOperationsErrorMessage,
|
|
53
|
+
mutateOperations,
|
|
54
|
+
} from '../_lib/api';
|
|
51
55
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
52
56
|
import type {
|
|
53
57
|
OperationsCollaborator,
|
|
54
58
|
OperationsCollaboratorDetails,
|
|
59
|
+
OperationsCollaboratorType,
|
|
55
60
|
OperationsDepartment,
|
|
61
|
+
OperationsJobTitle,
|
|
56
62
|
OperationsWeeklyScheduleDay,
|
|
57
63
|
} from '../_lib/types';
|
|
58
64
|
import {
|
|
@@ -60,13 +66,22 @@ import {
|
|
|
60
66
|
formatDateRange,
|
|
61
67
|
formatEnumLabel,
|
|
62
68
|
formatHours,
|
|
69
|
+
formatWeekdayLabel,
|
|
63
70
|
getStatusBadgeClass,
|
|
64
71
|
} from '../_lib/utils/format';
|
|
65
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
normalizePercentInput,
|
|
74
|
+
parseNumberInput,
|
|
75
|
+
trimToNull,
|
|
76
|
+
} from '../_lib/utils/forms';
|
|
66
77
|
import { DepartmentSelectWithCreate } from './department-select-with-create';
|
|
67
78
|
import { OperationsHeader } from './operations-header';
|
|
68
79
|
import { PersonSelectWithCreate } from './person-select-with-create';
|
|
69
80
|
import { StatusBadge } from './status-badge';
|
|
81
|
+
import {
|
|
82
|
+
SystemUserSelectWithCreate,
|
|
83
|
+
type SystemUserOption,
|
|
84
|
+
} from './system-user-select-with-create';
|
|
70
85
|
|
|
71
86
|
const weekdays = [
|
|
72
87
|
'monday',
|
|
@@ -79,6 +94,45 @@ const weekdays = [
|
|
|
79
94
|
] as const;
|
|
80
95
|
|
|
81
96
|
const SUPERVISOR_PAGE_SIZE = 10;
|
|
97
|
+
const COLLABORATOR_LEVEL_OPTIONS = [
|
|
98
|
+
'junior',
|
|
99
|
+
'mid',
|
|
100
|
+
'senior',
|
|
101
|
+
'coordinator',
|
|
102
|
+
'manager',
|
|
103
|
+
] as const;
|
|
104
|
+
|
|
105
|
+
type CollaboratorLevel = (typeof COLLABORATOR_LEVEL_OPTIONS)[number];
|
|
106
|
+
|
|
107
|
+
function normalizeCollaboratorLevel(
|
|
108
|
+
value?: string | null
|
|
109
|
+
): CollaboratorLevel | '' {
|
|
110
|
+
const normalizedValue = String(value ?? '')
|
|
111
|
+
.trim()
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.normalize('NFD')
|
|
114
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
115
|
+
|
|
116
|
+
switch (normalizedValue) {
|
|
117
|
+
case 'junior':
|
|
118
|
+
case 'junior level':
|
|
119
|
+
return 'junior';
|
|
120
|
+
case 'mid':
|
|
121
|
+
case 'middle':
|
|
122
|
+
case 'pleno':
|
|
123
|
+
return 'mid';
|
|
124
|
+
case 'senior':
|
|
125
|
+
return 'senior';
|
|
126
|
+
case 'coordinator':
|
|
127
|
+
case 'coordenador':
|
|
128
|
+
return 'coordinator';
|
|
129
|
+
case 'manager':
|
|
130
|
+
case 'gerente':
|
|
131
|
+
return 'manager';
|
|
132
|
+
default:
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
82
136
|
|
|
83
137
|
function getPersonAvatarUrl(avatarId?: number | null) {
|
|
84
138
|
return typeof avatarId === 'number' && avatarId > 0
|
|
@@ -120,14 +174,50 @@ function normalizeTimeValue(value?: string | null, fallback = '') {
|
|
|
120
174
|
return fallback || String(value);
|
|
121
175
|
}
|
|
122
176
|
|
|
177
|
+
function normalizeDateInputValue(value?: string | null) {
|
|
178
|
+
if (!value) {
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const normalizedValue = String(value).trim();
|
|
183
|
+
|
|
184
|
+
if (!normalizedValue) {
|
|
185
|
+
return '';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
|
|
189
|
+
|
|
190
|
+
if (directMatch?.[0]) {
|
|
191
|
+
return directMatch[0];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const parsedDate = new Date(normalizedValue);
|
|
195
|
+
|
|
196
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
197
|
+
return normalizedValue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return parsedDate.toISOString().slice(0, 10);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const EQUITY_ENABLED_TYPE_SLUGS = new Set([
|
|
204
|
+
'socio',
|
|
205
|
+
'acionista',
|
|
206
|
+
'administrador',
|
|
207
|
+
'diretor',
|
|
208
|
+
'conselheiro',
|
|
209
|
+
'parceiro',
|
|
210
|
+
]);
|
|
211
|
+
|
|
123
212
|
type CollaboratorFormState = {
|
|
124
213
|
userId: string;
|
|
125
214
|
personId: string;
|
|
126
215
|
code: string;
|
|
127
216
|
displayName: string;
|
|
128
|
-
|
|
217
|
+
collaboratorTypeId: string;
|
|
129
218
|
departmentId: string;
|
|
130
219
|
department: string;
|
|
220
|
+
jobTitleId: string;
|
|
131
221
|
title: string;
|
|
132
222
|
levelLabel: string;
|
|
133
223
|
weeklyCapacityHours: string;
|
|
@@ -139,6 +229,14 @@ type CollaboratorFormState = {
|
|
|
139
229
|
contractDescription: string;
|
|
140
230
|
autoGenerateContractDraft: boolean;
|
|
141
231
|
notes: string;
|
|
232
|
+
equityParticipation: {
|
|
233
|
+
participationType: string;
|
|
234
|
+
percentage: string;
|
|
235
|
+
votingPower: string;
|
|
236
|
+
startDate: string;
|
|
237
|
+
endDate: string;
|
|
238
|
+
notes: string;
|
|
239
|
+
};
|
|
142
240
|
weeklySchedule: Array<{
|
|
143
241
|
weekday: string;
|
|
144
242
|
isWorkingDay: boolean;
|
|
@@ -164,9 +262,10 @@ function buildEmptyForm(): CollaboratorFormState {
|
|
|
164
262
|
personId: '',
|
|
165
263
|
code: '',
|
|
166
264
|
displayName: '',
|
|
167
|
-
|
|
265
|
+
collaboratorTypeId: '',
|
|
168
266
|
departmentId: '',
|
|
169
267
|
department: '',
|
|
268
|
+
jobTitleId: '',
|
|
170
269
|
title: '',
|
|
171
270
|
levelLabel: '',
|
|
172
271
|
weeklyCapacityHours: '40',
|
|
@@ -178,6 +277,14 @@ function buildEmptyForm(): CollaboratorFormState {
|
|
|
178
277
|
contractDescription: '',
|
|
179
278
|
autoGenerateContractDraft: true,
|
|
180
279
|
notes: '',
|
|
280
|
+
equityParticipation: {
|
|
281
|
+
participationType: 'partner_quota_holder',
|
|
282
|
+
percentage: '',
|
|
283
|
+
votingPower: '',
|
|
284
|
+
startDate: '',
|
|
285
|
+
endDate: '',
|
|
286
|
+
notes: '',
|
|
287
|
+
},
|
|
181
288
|
weeklySchedule: defaultSchedule(),
|
|
182
289
|
};
|
|
183
290
|
}
|
|
@@ -214,32 +321,60 @@ function toFormState(
|
|
|
214
321
|
personId: collaborator.personId ? String(collaborator.personId) : '',
|
|
215
322
|
code: collaborator.code ?? '',
|
|
216
323
|
displayName: collaborator.displayName ?? '',
|
|
217
|
-
|
|
324
|
+
collaboratorTypeId: collaborator.collaboratorTypeId
|
|
325
|
+
? String(collaborator.collaboratorTypeId)
|
|
326
|
+
: '',
|
|
218
327
|
departmentId: collaborator.departmentId
|
|
219
328
|
? String(collaborator.departmentId)
|
|
220
329
|
: '',
|
|
221
330
|
department: collaborator.department ?? '',
|
|
331
|
+
jobTitleId: collaborator.jobTitleId ? String(collaborator.jobTitleId) : '',
|
|
222
332
|
title: collaborator.title ?? '',
|
|
223
|
-
levelLabel: collaborator.levelLabel
|
|
333
|
+
levelLabel: normalizeCollaboratorLevel(collaborator.levelLabel),
|
|
224
334
|
weeklyCapacityHours:
|
|
225
335
|
collaborator.weeklyCapacityHours !== null &&
|
|
226
336
|
collaborator.weeklyCapacityHours !== undefined
|
|
227
337
|
? String(collaborator.weeklyCapacityHours)
|
|
228
338
|
: '',
|
|
229
339
|
status: collaborator.status ?? 'active',
|
|
230
|
-
joinedAt: collaborator.joinedAt
|
|
231
|
-
leftAt: collaborator.leftAt
|
|
340
|
+
joinedAt: normalizeDateInputValue(collaborator.joinedAt),
|
|
341
|
+
leftAt: normalizeDateInputValue(collaborator.leftAt),
|
|
232
342
|
supervisorCollaboratorId: collaborator.supervisorId
|
|
233
343
|
? String(collaborator.supervisorId)
|
|
234
344
|
: 'none',
|
|
235
345
|
compensationAmount:
|
|
236
|
-
collaborator.
|
|
237
|
-
collaborator.
|
|
238
|
-
? String(collaborator.
|
|
239
|
-
:
|
|
346
|
+
collaborator.compensationAmount !== null &&
|
|
347
|
+
collaborator.compensationAmount !== undefined
|
|
348
|
+
? String(collaborator.compensationAmount)
|
|
349
|
+
: collaborator.relatedContracts?.[0]?.budgetAmount !== null &&
|
|
350
|
+
collaborator.relatedContracts?.[0]?.budgetAmount !== undefined
|
|
351
|
+
? String(collaborator.relatedContracts[0].budgetAmount)
|
|
352
|
+
: '',
|
|
240
353
|
contractDescription: collaborator.relatedContracts?.[0]?.description ?? '',
|
|
241
354
|
autoGenerateContractDraft: true,
|
|
242
355
|
notes: collaborator.notes ?? '',
|
|
356
|
+
equityParticipation: {
|
|
357
|
+
participationType:
|
|
358
|
+
collaborator.equityParticipation?.participationType ??
|
|
359
|
+
'partner_quota_holder',
|
|
360
|
+
percentage:
|
|
361
|
+
collaborator.equityParticipation?.percentage !== null &&
|
|
362
|
+
collaborator.equityParticipation?.percentage !== undefined
|
|
363
|
+
? String(collaborator.equityParticipation.percentage)
|
|
364
|
+
: '',
|
|
365
|
+
votingPower:
|
|
366
|
+
collaborator.equityParticipation?.votingPower !== null &&
|
|
367
|
+
collaborator.equityParticipation?.votingPower !== undefined
|
|
368
|
+
? String(collaborator.equityParticipation.votingPower)
|
|
369
|
+
: '',
|
|
370
|
+
startDate: normalizeDateInputValue(
|
|
371
|
+
collaborator.equityParticipation?.startDate
|
|
372
|
+
),
|
|
373
|
+
endDate: normalizeDateInputValue(
|
|
374
|
+
collaborator.equityParticipation?.endDate
|
|
375
|
+
),
|
|
376
|
+
notes: collaborator.equityParticipation?.notes ?? '',
|
|
377
|
+
},
|
|
243
378
|
weeklySchedule: normalizeSchedule(collaborator.weeklySchedule),
|
|
244
379
|
};
|
|
245
380
|
}
|
|
@@ -418,23 +553,7 @@ export function CollaboratorFormScreen({
|
|
|
418
553
|
const router = useRouter();
|
|
419
554
|
const [form, setForm] = useState<CollaboratorFormState>(buildEmptyForm());
|
|
420
555
|
const isSheetMode = Boolean(onCancel);
|
|
421
|
-
|
|
422
|
-
const getCollaboratorTypeLabel = (value?: string | null) => {
|
|
423
|
-
switch (value) {
|
|
424
|
-
case 'clt':
|
|
425
|
-
return t('options.collaboratorTypes.clt');
|
|
426
|
-
case 'pj':
|
|
427
|
-
return t('options.collaboratorTypes.pj');
|
|
428
|
-
case 'freelancer':
|
|
429
|
-
return t('options.collaboratorTypes.freelancer');
|
|
430
|
-
case 'intern':
|
|
431
|
-
return t('options.collaboratorTypes.intern');
|
|
432
|
-
case 'other':
|
|
433
|
-
return t('options.collaboratorTypes.other');
|
|
434
|
-
default:
|
|
435
|
-
return formatEnumLabel(value);
|
|
436
|
-
}
|
|
437
|
-
};
|
|
556
|
+
const isCreateMode = !collaboratorId;
|
|
438
557
|
|
|
439
558
|
const getStatusLabel = (value?: string | null) => {
|
|
440
559
|
switch (value) {
|
|
@@ -451,24 +570,74 @@ export function CollaboratorFormScreen({
|
|
|
451
570
|
}
|
|
452
571
|
};
|
|
453
572
|
|
|
454
|
-
const {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
573
|
+
const {
|
|
574
|
+
data: collaborator,
|
|
575
|
+
isLoading: isLoadingCollaborator,
|
|
576
|
+
refetch: refetchCollaborator,
|
|
577
|
+
} = useQuery<OperationsCollaboratorDetails>({
|
|
578
|
+
queryKey: [
|
|
579
|
+
'operations-collaborator-form',
|
|
580
|
+
currentLocaleCode,
|
|
581
|
+
collaboratorId,
|
|
582
|
+
],
|
|
583
|
+
enabled: Boolean(collaboratorId),
|
|
584
|
+
staleTime: 0,
|
|
585
|
+
refetchOnMount: 'always',
|
|
586
|
+
queryFn: () =>
|
|
587
|
+
fetchOperations<OperationsCollaboratorDetails>(
|
|
588
|
+
request,
|
|
589
|
+
`/operations/collaborators/${collaboratorId}`
|
|
590
|
+
),
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const { data: systemUsers = [], refetch: refetchSystemUsers } = useQuery<
|
|
594
|
+
SystemUserOption[]
|
|
595
|
+
>({
|
|
596
|
+
queryKey: ['operations-collaborator-form-system-users', currentLocaleCode],
|
|
597
|
+
enabled: access.isDirector,
|
|
598
|
+
staleTime: 0,
|
|
599
|
+
refetchOnMount: 'always',
|
|
600
|
+
queryFn: async () => {
|
|
601
|
+
const response = await request<{
|
|
602
|
+
paginate: { data: Array<{ id: number; name: string | null }> };
|
|
603
|
+
}>({
|
|
604
|
+
url: '/user?pageSize=100',
|
|
605
|
+
method: 'GET',
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const raw = response.data?.paginate?.data ?? [];
|
|
609
|
+
const options: SystemUserOption[] = raw.map((u) => ({
|
|
610
|
+
id: u.id,
|
|
611
|
+
name: u.name || `#${u.id}`,
|
|
612
|
+
}));
|
|
613
|
+
|
|
614
|
+
return options.sort((left, right) => left.name.localeCompare(right.name));
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const {
|
|
619
|
+
data: collaboratorTypes = [],
|
|
620
|
+
refetch: refetchCollaboratorTypes,
|
|
621
|
+
isLoading: isLoadingCollaboratorTypes,
|
|
622
|
+
} = useQuery<OperationsCollaboratorType[]>({
|
|
623
|
+
queryKey: ['operations-collaborator-types', currentLocaleCode],
|
|
624
|
+
enabled: access.isDirector,
|
|
625
|
+
staleTime: 0,
|
|
626
|
+
refetchOnMount: 'always',
|
|
627
|
+
queryFn: () =>
|
|
628
|
+
fetchOperations<OperationsCollaboratorType[]>(
|
|
629
|
+
request,
|
|
630
|
+
'/operations/collaborator-types?active=true'
|
|
631
|
+
),
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const { data: collaborators = [], refetch: refetchCollaborators } = useQuery<
|
|
635
|
+
OperationsCollaborator[]
|
|
636
|
+
>({
|
|
470
637
|
queryKey: ['operations-collaborator-form-supervisors', currentLocaleCode],
|
|
471
638
|
enabled: access.isDirector,
|
|
639
|
+
staleTime: 0,
|
|
640
|
+
refetchOnMount: 'always',
|
|
472
641
|
queryFn: () =>
|
|
473
642
|
fetchOperations<OperationsCollaborator[]>(
|
|
474
643
|
request,
|
|
@@ -476,9 +645,13 @@ export function CollaboratorFormScreen({
|
|
|
476
645
|
),
|
|
477
646
|
});
|
|
478
647
|
|
|
479
|
-
const { data: departments = [] } = useQuery<
|
|
648
|
+
const { data: departments = [], refetch: refetchDepartments } = useQuery<
|
|
649
|
+
OperationsDepartment[]
|
|
650
|
+
>({
|
|
480
651
|
queryKey: ['operations-collaborator-form-departments', currentLocaleCode],
|
|
481
652
|
enabled: access.isDirector,
|
|
653
|
+
staleTime: 0,
|
|
654
|
+
refetchOnMount: 'always',
|
|
482
655
|
queryFn: () =>
|
|
483
656
|
fetchOperations<OperationsDepartment[]>(
|
|
484
657
|
request,
|
|
@@ -486,6 +659,17 @@ export function CollaboratorFormScreen({
|
|
|
486
659
|
),
|
|
487
660
|
});
|
|
488
661
|
|
|
662
|
+
const { data: jobTitles = [], refetch: refetchJobTitles } = useQuery<
|
|
663
|
+
OperationsJobTitle[]
|
|
664
|
+
>({
|
|
665
|
+
queryKey: ['operations-collaborator-form-job-titles', currentLocaleCode],
|
|
666
|
+
enabled: access.isDirector,
|
|
667
|
+
staleTime: 0,
|
|
668
|
+
refetchOnMount: 'always',
|
|
669
|
+
queryFn: () =>
|
|
670
|
+
fetchOperations<OperationsJobTitle[]>(request, '/operations/job-titles'),
|
|
671
|
+
});
|
|
672
|
+
|
|
489
673
|
useEffect(() => {
|
|
490
674
|
if (collaborator) {
|
|
491
675
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
@@ -493,6 +677,114 @@ export function CollaboratorFormScreen({
|
|
|
493
677
|
}
|
|
494
678
|
}, [collaborator]);
|
|
495
679
|
|
|
680
|
+
useEffect(() => {
|
|
681
|
+
if (
|
|
682
|
+
collaboratorId ||
|
|
683
|
+
form.collaboratorTypeId ||
|
|
684
|
+
!collaboratorTypes.length
|
|
685
|
+
) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const defaultType =
|
|
690
|
+
collaboratorTypes.find((item) => item.slug === 'clt') ??
|
|
691
|
+
collaboratorTypes[0];
|
|
692
|
+
|
|
693
|
+
if (!defaultType) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
698
|
+
setForm((current) => ({
|
|
699
|
+
...current,
|
|
700
|
+
collaboratorTypeId: String(defaultType.id),
|
|
701
|
+
}));
|
|
702
|
+
}, [collaboratorId, collaboratorTypes, form.collaboratorTypeId]);
|
|
703
|
+
|
|
704
|
+
const selectedCollaboratorType = useMemo(() => {
|
|
705
|
+
const availableTypes = [...collaboratorTypes];
|
|
706
|
+
|
|
707
|
+
if (
|
|
708
|
+
collaborator?.collaboratorTypeId &&
|
|
709
|
+
collaborator.collaboratorType &&
|
|
710
|
+
!availableTypes.some(
|
|
711
|
+
(item) => item.id === collaborator.collaboratorTypeId
|
|
712
|
+
)
|
|
713
|
+
) {
|
|
714
|
+
availableTypes.push({
|
|
715
|
+
id: collaborator.collaboratorTypeId,
|
|
716
|
+
slug: collaborator.collaboratorTypeSlug ?? '',
|
|
717
|
+
name: collaborator.collaboratorType,
|
|
718
|
+
status: 'active',
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return (
|
|
723
|
+
availableTypes.find(
|
|
724
|
+
(item) => String(item.id) === String(form.collaboratorTypeId)
|
|
725
|
+
) ?? null
|
|
726
|
+
);
|
|
727
|
+
}, [collaborator, collaboratorTypes, form.collaboratorTypeId]);
|
|
728
|
+
|
|
729
|
+
const supervisorOptions = useMemo(() => {
|
|
730
|
+
const options = new Map<number, OperationsCollaborator>();
|
|
731
|
+
|
|
732
|
+
for (const item of collaborators) {
|
|
733
|
+
if (item.id !== collaboratorId) {
|
|
734
|
+
options.set(item.id, item);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (
|
|
739
|
+
collaborator?.supervisorId &&
|
|
740
|
+
collaborator.supervisorName &&
|
|
741
|
+
collaborator.supervisorId !== collaboratorId &&
|
|
742
|
+
!options.has(collaborator.supervisorId)
|
|
743
|
+
) {
|
|
744
|
+
options.set(collaborator.supervisorId, {
|
|
745
|
+
id: collaborator.supervisorId,
|
|
746
|
+
code: '',
|
|
747
|
+
displayName: collaborator.supervisorName,
|
|
748
|
+
department: null,
|
|
749
|
+
title: null,
|
|
750
|
+
status: 'active',
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return Array.from(options.values()).sort((left, right) =>
|
|
755
|
+
left.displayName.localeCompare(right.displayName)
|
|
756
|
+
);
|
|
757
|
+
}, [collaborator, collaboratorId, collaborators]);
|
|
758
|
+
|
|
759
|
+
const shouldShowEquitySection =
|
|
760
|
+
!isCreateMode &&
|
|
761
|
+
Boolean(
|
|
762
|
+
selectedCollaboratorType?.slug &&
|
|
763
|
+
EQUITY_ENABLED_TYPE_SLUGS.has(selectedCollaboratorType.slug)
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
const getCollaboratorTypeLabel = (
|
|
767
|
+
value?: string | null,
|
|
768
|
+
fallbackName?: string | null
|
|
769
|
+
) => {
|
|
770
|
+
const normalizedValue = String(value ?? '').trim();
|
|
771
|
+
|
|
772
|
+
if (fallbackName) {
|
|
773
|
+
return fallbackName;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const matchedType = collaboratorTypes.find(
|
|
777
|
+
(item) =>
|
|
778
|
+
item.slug === normalizedValue || String(item.id) === normalizedValue
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
if (matchedType?.name) {
|
|
782
|
+
return matchedType.name;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return formatEnumLabel(normalizedValue);
|
|
786
|
+
};
|
|
787
|
+
|
|
496
788
|
const departmentOptions = useMemo(() => {
|
|
497
789
|
const selectedDepartment = trimToNull(form.department);
|
|
498
790
|
const options: Array<{
|
|
@@ -529,6 +821,91 @@ export function CollaboratorFormScreen({
|
|
|
529
821
|
return options.sort((left, right) => left.name.localeCompare(right.name));
|
|
530
822
|
}, [departments, form.department, form.departmentId]);
|
|
531
823
|
|
|
824
|
+
const titleOptions = useMemo(() => {
|
|
825
|
+
const selectedTitle = trimToNull(form.title);
|
|
826
|
+
const options: Array<{
|
|
827
|
+
id?: number | null;
|
|
828
|
+
name: string;
|
|
829
|
+
code?: string | null;
|
|
830
|
+
description?: string | null;
|
|
831
|
+
}> = jobTitles
|
|
832
|
+
.filter((item) => item.status === 'active' || item.name === selectedTitle)
|
|
833
|
+
.map((item) => ({
|
|
834
|
+
id: item.id,
|
|
835
|
+
name: item.name,
|
|
836
|
+
code: item.code ?? null,
|
|
837
|
+
description: item.description ?? null,
|
|
838
|
+
}));
|
|
839
|
+
|
|
840
|
+
if (selectedTitle) {
|
|
841
|
+
const alreadyIncluded = options.some(
|
|
842
|
+
(item) => item.name === selectedTitle
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
if (!alreadyIncluded) {
|
|
846
|
+
options.push({
|
|
847
|
+
id: form.jobTitleId ? Number(form.jobTitleId) : undefined,
|
|
848
|
+
name: selectedTitle,
|
|
849
|
+
code: null,
|
|
850
|
+
description: null,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return options.sort((left, right) => left.name.localeCompare(right.name));
|
|
856
|
+
}, [jobTitles, form.title, form.jobTitleId]);
|
|
857
|
+
|
|
858
|
+
const createJobTitle = async (jobTitleName: string) => {
|
|
859
|
+
try {
|
|
860
|
+
const createdJobTitle = await mutateOperations<OperationsJobTitle>(
|
|
861
|
+
request,
|
|
862
|
+
'/operations/job-titles',
|
|
863
|
+
'POST',
|
|
864
|
+
{
|
|
865
|
+
name: jobTitleName,
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
await refetchJobTitles();
|
|
870
|
+
|
|
871
|
+
return {
|
|
872
|
+
id: createdJobTitle.id,
|
|
873
|
+
name: createdJobTitle.name,
|
|
874
|
+
code: createdJobTitle.code ?? null,
|
|
875
|
+
description: createdJobTitle.description ?? null,
|
|
876
|
+
};
|
|
877
|
+
} catch (error) {
|
|
878
|
+
showToastHandler?.(
|
|
879
|
+
'error',
|
|
880
|
+
getOperationsErrorMessage(error, 'Unable to create the job title.')
|
|
881
|
+
);
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const createSystemUser = async (
|
|
887
|
+
name: string,
|
|
888
|
+
email: string,
|
|
889
|
+
password: string
|
|
890
|
+
): Promise<SystemUserOption | null> => {
|
|
891
|
+
try {
|
|
892
|
+
const response = await request<{ id: number; name: string }>({
|
|
893
|
+
url: '/user',
|
|
894
|
+
method: 'POST',
|
|
895
|
+
data: { name, email, password },
|
|
896
|
+
});
|
|
897
|
+
await refetchSystemUsers();
|
|
898
|
+
showToastHandler?.('success', t('messages.createUserSuccess'));
|
|
899
|
+
return { id: response.data.id, name: response.data.name };
|
|
900
|
+
} catch (error) {
|
|
901
|
+
showToastHandler?.(
|
|
902
|
+
'error',
|
|
903
|
+
getOperationsErrorMessage(error, t('messages.createUserError'))
|
|
904
|
+
);
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
532
909
|
const updateScheduleDay = (
|
|
533
910
|
weekday: string,
|
|
534
911
|
patch: Partial<CollaboratorFormState['weeklySchedule'][number]>
|
|
@@ -565,6 +942,7 @@ export function CollaboratorFormScreen({
|
|
|
565
942
|
const onSubmit = async () => {
|
|
566
943
|
const userId = parseNumberInput(form.userId);
|
|
567
944
|
const personId = parseNumberInput(form.personId);
|
|
945
|
+
const compensationAmount = parseNumberInput(form.compensationAmount);
|
|
568
946
|
|
|
569
947
|
if (!personId) {
|
|
570
948
|
showToastHandler?.('error', t('messages.personRequired'));
|
|
@@ -572,15 +950,23 @@ export function CollaboratorFormScreen({
|
|
|
572
950
|
}
|
|
573
951
|
|
|
574
952
|
const departmentId = parseNumberInput(form.departmentId);
|
|
953
|
+
const jobTitleId = parseNumberInput(form.jobTitleId);
|
|
954
|
+
const collaboratorTypeId = parseNumberInput(form.collaboratorTypeId);
|
|
955
|
+
|
|
956
|
+
if (!collaboratorTypeId) {
|
|
957
|
+
showToastHandler?.('error', t('messages.collaboratorTypeRequired'));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
575
960
|
|
|
576
961
|
const payload = {
|
|
577
962
|
userId: userId ?? undefined,
|
|
578
963
|
personId: personId ?? undefined,
|
|
579
964
|
code: trimToNull(form.code) ?? undefined,
|
|
580
965
|
displayName: trimToNull(form.displayName),
|
|
581
|
-
|
|
966
|
+
collaboratorTypeId,
|
|
582
967
|
departmentId: departmentId ?? undefined,
|
|
583
968
|
department: departmentId ? undefined : trimToNull(form.department),
|
|
969
|
+
jobTitleId: jobTitleId ?? undefined,
|
|
584
970
|
title: trimToNull(form.title),
|
|
585
971
|
levelLabel: trimToNull(form.levelLabel),
|
|
586
972
|
weeklyCapacityHours: parseNumberInput(form.weeklyCapacityHours),
|
|
@@ -591,10 +977,21 @@ export function CollaboratorFormScreen({
|
|
|
591
977
|
form.supervisorCollaboratorId === 'none'
|
|
592
978
|
? null
|
|
593
979
|
: parseNumberInput(form.supervisorCollaboratorId),
|
|
594
|
-
compensationAmount:
|
|
980
|
+
compensationAmount:
|
|
981
|
+
compensationAmount !== null ? compensationAmount : null,
|
|
595
982
|
contractDescription: trimToNull(form.contractDescription),
|
|
596
983
|
autoGenerateContractDraft: form.autoGenerateContractDraft,
|
|
597
984
|
notes: trimToNull(form.notes),
|
|
985
|
+
equityParticipation: shouldShowEquitySection
|
|
986
|
+
? {
|
|
987
|
+
participationType: form.equityParticipation.participationType,
|
|
988
|
+
percentage: parseNumberInput(form.equityParticipation.percentage),
|
|
989
|
+
votingPower: parseNumberInput(form.equityParticipation.votingPower),
|
|
990
|
+
startDate: trimToNull(form.equityParticipation.startDate),
|
|
991
|
+
endDate: trimToNull(form.equityParticipation.endDate),
|
|
992
|
+
notes: trimToNull(form.equityParticipation.notes),
|
|
993
|
+
}
|
|
994
|
+
: null,
|
|
598
995
|
weeklySchedule: form.weeklySchedule.map((day) => ({
|
|
599
996
|
weekday: day.weekday,
|
|
600
997
|
isWorkingDay: day.isWorkingDay,
|
|
@@ -623,6 +1020,16 @@ export function CollaboratorFormScreen({
|
|
|
623
1020
|
payload
|
|
624
1021
|
);
|
|
625
1022
|
|
|
1023
|
+
setForm(toFormState(response));
|
|
1024
|
+
|
|
1025
|
+
await Promise.all([
|
|
1026
|
+
collaboratorId ? refetchCollaborator() : Promise.resolve(),
|
|
1027
|
+
refetchCollaboratorTypes(),
|
|
1028
|
+
refetchDepartments(),
|
|
1029
|
+
refetchCollaborators(),
|
|
1030
|
+
refetchJobTitles(),
|
|
1031
|
+
]);
|
|
1032
|
+
|
|
626
1033
|
showToastHandler?.(
|
|
627
1034
|
'success',
|
|
628
1035
|
collaboratorId
|
|
@@ -636,10 +1043,13 @@ export function CollaboratorFormScreen({
|
|
|
636
1043
|
}
|
|
637
1044
|
|
|
638
1045
|
router.push(`/operations/collaborators/${response.id}`);
|
|
639
|
-
} catch {
|
|
1046
|
+
} catch (error) {
|
|
640
1047
|
showToastHandler?.(
|
|
641
1048
|
'error',
|
|
642
|
-
|
|
1049
|
+
getOperationsErrorMessage(
|
|
1050
|
+
error,
|
|
1051
|
+
collaboratorId ? t('messages.updateError') : t('messages.createError')
|
|
1052
|
+
)
|
|
643
1053
|
);
|
|
644
1054
|
}
|
|
645
1055
|
};
|
|
@@ -690,7 +1100,11 @@ export function CollaboratorFormScreen({
|
|
|
690
1100
|
className={getStatusBadgeClass(collaborator.status)}
|
|
691
1101
|
/>
|
|
692
1102
|
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">
|
|
693
|
-
{getCollaboratorTypeLabel(
|
|
1103
|
+
{getCollaboratorTypeLabel(
|
|
1104
|
+
collaborator.collaboratorTypeSlug ??
|
|
1105
|
+
collaborator.collaboratorType,
|
|
1106
|
+
collaborator.collaboratorTypeName
|
|
1107
|
+
)}
|
|
694
1108
|
</span>
|
|
695
1109
|
</div>
|
|
696
1110
|
</div>
|
|
@@ -760,12 +1174,12 @@ export function CollaboratorFormScreen({
|
|
|
760
1174
|
</p>
|
|
761
1175
|
</div>
|
|
762
1176
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
763
|
-
<div className="space-y-2 md:col-span-2
|
|
1177
|
+
<div className="space-y-2 md:col-span-2">
|
|
764
1178
|
<PersonSelectWithCreate
|
|
765
1179
|
label={t('fields.person')}
|
|
766
1180
|
entityLabel={t('fields.personEntityLabel')}
|
|
767
1181
|
value={form.personId ? Number(form.personId) : null}
|
|
768
|
-
initialSelectedLabel={form.displayName}
|
|
1182
|
+
initialSelectedLabel={collaborator?.personName ?? form.displayName}
|
|
769
1183
|
selectPlaceholder={t('placeholders.person')}
|
|
770
1184
|
onChange={(personId, personName) =>
|
|
771
1185
|
setForm((current) => ({
|
|
@@ -776,33 +1190,64 @@ export function CollaboratorFormScreen({
|
|
|
776
1190
|
}
|
|
777
1191
|
/>
|
|
778
1192
|
</div>
|
|
779
|
-
<div className="space-y-2">
|
|
780
|
-
<
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
placeholder=
|
|
785
|
-
|
|
1193
|
+
<div className="space-y-2 md:col-span-2 xl:col-span-2">
|
|
1194
|
+
<SystemUserSelectWithCreate
|
|
1195
|
+
label={t('fields.userIdOptional')}
|
|
1196
|
+
value={form.userId}
|
|
1197
|
+
options={systemUsers}
|
|
1198
|
+
placeholder={t('placeholders.userIdOptional')}
|
|
1199
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
1200
|
+
onChange={(value) =>
|
|
786
1201
|
setForm((current) => ({
|
|
787
1202
|
...current,
|
|
788
|
-
|
|
1203
|
+
userId: value,
|
|
789
1204
|
}))
|
|
790
1205
|
}
|
|
1206
|
+
onCreate={createSystemUser}
|
|
791
1207
|
/>
|
|
1208
|
+
<p className="text-xs text-muted-foreground">
|
|
1209
|
+
{t('fields.userIdDescription')}
|
|
1210
|
+
</p>
|
|
792
1211
|
</div>
|
|
793
1212
|
<div className="space-y-2">
|
|
794
|
-
<Label>{t('fields.
|
|
1213
|
+
<Label>{t('fields.code')}</Label>
|
|
795
1214
|
<Input
|
|
796
1215
|
className="h-10"
|
|
797
|
-
value={form.
|
|
1216
|
+
value={form.code}
|
|
1217
|
+
placeholder="COL-001"
|
|
798
1218
|
onChange={(event) =>
|
|
799
1219
|
setForm((current) => ({
|
|
800
1220
|
...current,
|
|
801
|
-
|
|
1221
|
+
code: event.target.value,
|
|
802
1222
|
}))
|
|
803
1223
|
}
|
|
804
1224
|
/>
|
|
805
1225
|
</div>
|
|
1226
|
+
{!isCreateMode ? (
|
|
1227
|
+
<div className="space-y-2">
|
|
1228
|
+
<Label>{t('fields.levelLabel')}</Label>
|
|
1229
|
+
<Select
|
|
1230
|
+
value={form.levelLabel}
|
|
1231
|
+
onValueChange={(value) =>
|
|
1232
|
+
setForm((current) => ({
|
|
1233
|
+
...current,
|
|
1234
|
+
levelLabel: value,
|
|
1235
|
+
}))
|
|
1236
|
+
}
|
|
1237
|
+
>
|
|
1238
|
+
<SelectTrigger className="w-full">
|
|
1239
|
+
<SelectValue placeholder={t('placeholders.levelLabel')} />
|
|
1240
|
+
</SelectTrigger>
|
|
1241
|
+
<SelectContent>
|
|
1242
|
+
{COLLABORATOR_LEVEL_OPTIONS.map((level) => (
|
|
1243
|
+
<SelectItem key={level} value={level}>
|
|
1244
|
+
{t(`options.levels.${level}`)}
|
|
1245
|
+
</SelectItem>
|
|
1246
|
+
))}
|
|
1247
|
+
</SelectContent>
|
|
1248
|
+
</Select>
|
|
1249
|
+
</div>
|
|
1250
|
+
) : null}
|
|
806
1251
|
<div className="space-y-2 xl:col-span-1">
|
|
807
1252
|
<DepartmentSelectWithCreate
|
|
808
1253
|
label={t('fields.department')}
|
|
@@ -821,14 +1266,19 @@ export function CollaboratorFormScreen({
|
|
|
821
1266
|
/>
|
|
822
1267
|
</div>
|
|
823
1268
|
<div className="space-y-2 xl:col-span-1">
|
|
824
|
-
<
|
|
825
|
-
|
|
826
|
-
className="h-10"
|
|
1269
|
+
<DepartmentSelectWithCreate
|
|
1270
|
+
label={t('fields.title')}
|
|
827
1271
|
value={form.title}
|
|
828
|
-
|
|
1272
|
+
options={titleOptions}
|
|
1273
|
+
onCreate={createJobTitle}
|
|
1274
|
+
selectPlaceholder={t('placeholders.title')}
|
|
1275
|
+
createDescription={t('fields.titleDescription')}
|
|
1276
|
+
createPlaceholder={t('placeholders.titleCreate')}
|
|
1277
|
+
onChange={(titleOption) =>
|
|
829
1278
|
setForm((current) => ({
|
|
830
1279
|
...current,
|
|
831
|
-
|
|
1280
|
+
jobTitleId: titleOption.id ? String(titleOption.id) : '',
|
|
1281
|
+
title: titleOption.name,
|
|
832
1282
|
}))
|
|
833
1283
|
}
|
|
834
1284
|
/>
|
|
@@ -844,42 +1294,44 @@ export function CollaboratorFormScreen({
|
|
|
844
1294
|
{t('sections.employmentInfo')}
|
|
845
1295
|
</h3>
|
|
846
1296
|
<p className="text-[11px] text-muted-foreground/80">
|
|
847
|
-
{
|
|
1297
|
+
{isCreateMode
|
|
1298
|
+
? t('sections.employmentInfoCreateDescription')
|
|
1299
|
+
: t('sections.employmentInfoDescription')}
|
|
848
1300
|
</p>
|
|
849
1301
|
</div>
|
|
850
1302
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
851
|
-
<div className="space-y-2">
|
|
1303
|
+
<div className="space-y-2 xl:col-span-1">
|
|
852
1304
|
<label className="text-sm font-medium">
|
|
853
1305
|
{t('fields.collaboratorType')}
|
|
854
1306
|
</label>
|
|
1307
|
+
|
|
855
1308
|
<Select
|
|
856
|
-
value={form.
|
|
1309
|
+
value={form.collaboratorTypeId}
|
|
857
1310
|
onValueChange={(value) =>
|
|
858
1311
|
setForm((current) => ({
|
|
859
1312
|
...current,
|
|
860
|
-
|
|
1313
|
+
collaboratorTypeId: value,
|
|
861
1314
|
}))
|
|
862
1315
|
}
|
|
863
1316
|
>
|
|
864
1317
|
<SelectTrigger className="w-full">
|
|
865
|
-
<SelectValue
|
|
1318
|
+
<SelectValue
|
|
1319
|
+
placeholder={
|
|
1320
|
+
isLoadingCollaboratorTypes
|
|
1321
|
+
? t('states.loadingCollaboratorTypes')
|
|
1322
|
+
: t('placeholders.collaboratorType')
|
|
1323
|
+
}
|
|
1324
|
+
/>
|
|
866
1325
|
</SelectTrigger>
|
|
867
1326
|
<SelectContent>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
</SelectItem>
|
|
877
|
-
<SelectItem value="intern">
|
|
878
|
-
{t('options.collaboratorTypes.intern')}
|
|
879
|
-
</SelectItem>
|
|
880
|
-
<SelectItem value="other">
|
|
881
|
-
{t('options.collaboratorTypes.other')}
|
|
882
|
-
</SelectItem>
|
|
1327
|
+
{collaboratorTypes.map((collaboratorType) => (
|
|
1328
|
+
<SelectItem
|
|
1329
|
+
key={collaboratorType.id}
|
|
1330
|
+
value={String(collaboratorType.id)}
|
|
1331
|
+
>
|
|
1332
|
+
{collaboratorType.name}
|
|
1333
|
+
</SelectItem>
|
|
1334
|
+
))}
|
|
883
1335
|
</SelectContent>
|
|
884
1336
|
</Select>
|
|
885
1337
|
</div>
|
|
@@ -942,10 +1394,187 @@ export function CollaboratorFormScreen({
|
|
|
942
1394
|
}
|
|
943
1395
|
/>
|
|
944
1396
|
</div>
|
|
1397
|
+
{isCreateMode ? (
|
|
1398
|
+
<>
|
|
1399
|
+
<div className="space-y-2">
|
|
1400
|
+
<label className="text-sm font-medium">
|
|
1401
|
+
{t('fields.weeklyCapacityHours')}
|
|
1402
|
+
</label>
|
|
1403
|
+
<Input
|
|
1404
|
+
type="number"
|
|
1405
|
+
step="0.5"
|
|
1406
|
+
value={form.weeklyCapacityHours}
|
|
1407
|
+
onChange={(event) =>
|
|
1408
|
+
setForm((current) => ({
|
|
1409
|
+
...current,
|
|
1410
|
+
weeklyCapacityHours: event.target.value,
|
|
1411
|
+
}))
|
|
1412
|
+
}
|
|
1413
|
+
/>
|
|
1414
|
+
</div>
|
|
1415
|
+
<div className="space-y-2">
|
|
1416
|
+
<label className="text-sm font-medium">
|
|
1417
|
+
{t('fields.compensationAmount')}
|
|
1418
|
+
</label>
|
|
1419
|
+
<InputMoney
|
|
1420
|
+
step="0.01"
|
|
1421
|
+
value={
|
|
1422
|
+
form.compensationAmount === ''
|
|
1423
|
+
? ''
|
|
1424
|
+
: Number(form.compensationAmount)
|
|
1425
|
+
}
|
|
1426
|
+
onValueChange={(value) =>
|
|
1427
|
+
setForm((current) => ({
|
|
1428
|
+
...current,
|
|
1429
|
+
compensationAmount: value !== null ? String(value) : '',
|
|
1430
|
+
}))
|
|
1431
|
+
}
|
|
1432
|
+
/>
|
|
1433
|
+
</div>
|
|
1434
|
+
</>
|
|
1435
|
+
) : null}
|
|
945
1436
|
</div>
|
|
946
1437
|
</div>
|
|
947
1438
|
);
|
|
948
1439
|
|
|
1440
|
+
const equitySection = shouldShowEquitySection ? (
|
|
1441
|
+
<div className="space-y-2">
|
|
1442
|
+
<div className="space-y-0.5">
|
|
1443
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1444
|
+
{t('sections.equity')}
|
|
1445
|
+
</h3>
|
|
1446
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1447
|
+
{t('sections.equityDescription')}
|
|
1448
|
+
</p>
|
|
1449
|
+
</div>
|
|
1450
|
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
1451
|
+
<div className="space-y-2 xl:col-span-2">
|
|
1452
|
+
<label className="text-sm font-medium">
|
|
1453
|
+
{t('fields.equityParticipationType')}
|
|
1454
|
+
</label>
|
|
1455
|
+
<Select
|
|
1456
|
+
value={form.equityParticipation.participationType}
|
|
1457
|
+
onValueChange={(value) =>
|
|
1458
|
+
setForm((current) => ({
|
|
1459
|
+
...current,
|
|
1460
|
+
equityParticipation: {
|
|
1461
|
+
...current.equityParticipation,
|
|
1462
|
+
participationType: value,
|
|
1463
|
+
},
|
|
1464
|
+
}))
|
|
1465
|
+
}
|
|
1466
|
+
>
|
|
1467
|
+
<SelectTrigger className="w-full">
|
|
1468
|
+
<SelectValue />
|
|
1469
|
+
</SelectTrigger>
|
|
1470
|
+
<SelectContent>
|
|
1471
|
+
<SelectItem value="partner_quota_holder">
|
|
1472
|
+
{t('options.equityParticipationTypes.partner_quota_holder')}
|
|
1473
|
+
</SelectItem>
|
|
1474
|
+
<SelectItem value="common_shareholder">
|
|
1475
|
+
{t('options.equityParticipationTypes.common_shareholder')}
|
|
1476
|
+
</SelectItem>
|
|
1477
|
+
<SelectItem value="preferred_shareholder">
|
|
1478
|
+
{t('options.equityParticipationTypes.preferred_shareholder')}
|
|
1479
|
+
</SelectItem>
|
|
1480
|
+
<SelectItem value="administrator">
|
|
1481
|
+
{t('options.equityParticipationTypes.administrator')}
|
|
1482
|
+
</SelectItem>
|
|
1483
|
+
<SelectItem value="other">
|
|
1484
|
+
{t('options.equityParticipationTypes.other')}
|
|
1485
|
+
</SelectItem>
|
|
1486
|
+
</SelectContent>
|
|
1487
|
+
</Select>
|
|
1488
|
+
</div>
|
|
1489
|
+
<div className="space-y-2">
|
|
1490
|
+
<Label>{t('fields.equityPercentage')}</Label>
|
|
1491
|
+
<Input
|
|
1492
|
+
type="text"
|
|
1493
|
+
inputMode="decimal"
|
|
1494
|
+
placeholder={t('placeholders.equityPercentage')}
|
|
1495
|
+
value={form.equityParticipation.percentage}
|
|
1496
|
+
onChange={(event) =>
|
|
1497
|
+
setForm((current) => ({
|
|
1498
|
+
...current,
|
|
1499
|
+
equityParticipation: {
|
|
1500
|
+
...current.equityParticipation,
|
|
1501
|
+
percentage: normalizePercentInput(event.target.value),
|
|
1502
|
+
},
|
|
1503
|
+
}))
|
|
1504
|
+
}
|
|
1505
|
+
/>
|
|
1506
|
+
</div>
|
|
1507
|
+
<div className="space-y-2">
|
|
1508
|
+
<Label>{t('fields.votingPower')}</Label>
|
|
1509
|
+
<Input
|
|
1510
|
+
type="text"
|
|
1511
|
+
inputMode="decimal"
|
|
1512
|
+
placeholder={t('placeholders.votingPower')}
|
|
1513
|
+
value={form.equityParticipation.votingPower}
|
|
1514
|
+
onChange={(event) =>
|
|
1515
|
+
setForm((current) => ({
|
|
1516
|
+
...current,
|
|
1517
|
+
equityParticipation: {
|
|
1518
|
+
...current.equityParticipation,
|
|
1519
|
+
votingPower: normalizePercentInput(event.target.value),
|
|
1520
|
+
},
|
|
1521
|
+
}))
|
|
1522
|
+
}
|
|
1523
|
+
/>
|
|
1524
|
+
</div>
|
|
1525
|
+
<div className="space-y-2">
|
|
1526
|
+
<Label>{commonT('labels.startDate')}</Label>
|
|
1527
|
+
<Input
|
|
1528
|
+
type="date"
|
|
1529
|
+
value={form.equityParticipation.startDate}
|
|
1530
|
+
onChange={(event) =>
|
|
1531
|
+
setForm((current) => ({
|
|
1532
|
+
...current,
|
|
1533
|
+
equityParticipation: {
|
|
1534
|
+
...current.equityParticipation,
|
|
1535
|
+
startDate: event.target.value,
|
|
1536
|
+
},
|
|
1537
|
+
}))
|
|
1538
|
+
}
|
|
1539
|
+
/>
|
|
1540
|
+
</div>
|
|
1541
|
+
<div className="space-y-2">
|
|
1542
|
+
<Label>{commonT('labels.endDate')}</Label>
|
|
1543
|
+
<Input
|
|
1544
|
+
type="date"
|
|
1545
|
+
value={form.equityParticipation.endDate}
|
|
1546
|
+
onChange={(event) =>
|
|
1547
|
+
setForm((current) => ({
|
|
1548
|
+
...current,
|
|
1549
|
+
equityParticipation: {
|
|
1550
|
+
...current.equityParticipation,
|
|
1551
|
+
endDate: event.target.value,
|
|
1552
|
+
},
|
|
1553
|
+
}))
|
|
1554
|
+
}
|
|
1555
|
+
/>
|
|
1556
|
+
</div>
|
|
1557
|
+
<div className="space-y-2 md:col-span-2 xl:col-span-4">
|
|
1558
|
+
<Label>{t('fields.equityNotes')}</Label>
|
|
1559
|
+
<Textarea
|
|
1560
|
+
rows={4}
|
|
1561
|
+
placeholder={t('placeholders.equityNotes')}
|
|
1562
|
+
value={form.equityParticipation.notes}
|
|
1563
|
+
onChange={(event) =>
|
|
1564
|
+
setForm((current) => ({
|
|
1565
|
+
...current,
|
|
1566
|
+
equityParticipation: {
|
|
1567
|
+
...current.equityParticipation,
|
|
1568
|
+
notes: event.target.value,
|
|
1569
|
+
},
|
|
1570
|
+
}))
|
|
1571
|
+
}
|
|
1572
|
+
/>
|
|
1573
|
+
</div>
|
|
1574
|
+
</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
) : null;
|
|
1577
|
+
|
|
949
1578
|
const supervisorSection = (
|
|
950
1579
|
<div className="space-y-2">
|
|
951
1580
|
<div className="space-y-0.5">
|
|
@@ -959,7 +1588,7 @@ export function CollaboratorFormScreen({
|
|
|
959
1588
|
<SupervisorAutocomplete
|
|
960
1589
|
label={commonT('labels.supervisor')}
|
|
961
1590
|
value={form.supervisorCollaboratorId}
|
|
962
|
-
options={
|
|
1591
|
+
options={supervisorOptions}
|
|
963
1592
|
placeholder={t('placeholders.supervisor')}
|
|
964
1593
|
emptyLabel={commonT('labels.notAssigned')}
|
|
965
1594
|
onChange={(value) =>
|
|
@@ -1005,11 +1634,15 @@ export function CollaboratorFormScreen({
|
|
|
1005
1634
|
</label>
|
|
1006
1635
|
<InputMoney
|
|
1007
1636
|
step="0.01"
|
|
1008
|
-
value={
|
|
1009
|
-
|
|
1637
|
+
value={
|
|
1638
|
+
form.compensationAmount === ''
|
|
1639
|
+
? ''
|
|
1640
|
+
: Number(form.compensationAmount)
|
|
1641
|
+
}
|
|
1642
|
+
onValueChange={(value) =>
|
|
1010
1643
|
setForm((current) => ({
|
|
1011
1644
|
...current,
|
|
1012
|
-
compensationAmount:
|
|
1645
|
+
compensationAmount: value !== null ? String(value) : '',
|
|
1013
1646
|
}))
|
|
1014
1647
|
}
|
|
1015
1648
|
/>
|
|
@@ -1070,7 +1703,7 @@ export function CollaboratorFormScreen({
|
|
|
1070
1703
|
>
|
|
1071
1704
|
<div className="space-y-0.5 md:pr-1">
|
|
1072
1705
|
<div className="text-sm font-medium leading-none">
|
|
1073
|
-
{
|
|
1706
|
+
{formatWeekdayLabel(day.weekday, currentLocaleCode)}
|
|
1074
1707
|
</div>
|
|
1075
1708
|
<div className="text-[10px] leading-none text-muted-foreground">
|
|
1076
1709
|
{day.isWorkingDay
|
|
@@ -1309,13 +1942,13 @@ export function CollaboratorFormScreen({
|
|
|
1309
1942
|
href={`/operations/contracts?edit=${contract.id}`}
|
|
1310
1943
|
className="flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-2.5 transition-colors hover:bg-muted/20"
|
|
1311
1944
|
>
|
|
1312
|
-
<div className="min-w-0">
|
|
1313
|
-
<div className="truncate font-medium">
|
|
1314
|
-
{contract.name || contract.code}
|
|
1315
|
-
</div>
|
|
1316
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
1317
|
-
{[
|
|
1318
|
-
contract.code,
|
|
1945
|
+
<div className="min-w-0">
|
|
1946
|
+
<div className="truncate font-medium">
|
|
1947
|
+
{contract.name || contract.code}
|
|
1948
|
+
</div>
|
|
1949
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1950
|
+
{[
|
|
1951
|
+
contract.code,
|
|
1319
1952
|
formatEnumLabel(contract.contractCategory),
|
|
1320
1953
|
]
|
|
1321
1954
|
.filter(Boolean)
|
|
@@ -1388,6 +2021,7 @@ export function CollaboratorFormScreen({
|
|
|
1388
2021
|
<div className="space-y-4">
|
|
1389
2022
|
{basicInfoSection}
|
|
1390
2023
|
{employmentInfoSection}
|
|
2024
|
+
{!isCreateMode ? equitySection : null}
|
|
1391
2025
|
{supervisorSection}
|
|
1392
2026
|
</div>
|
|
1393
2027
|
);
|
|
@@ -1395,15 +2029,15 @@ export function CollaboratorFormScreen({
|
|
|
1395
2029
|
const formContent = isSheetMode ? (
|
|
1396
2030
|
<div className="space-y-4 px-4">
|
|
1397
2031
|
{profileContent}
|
|
1398
|
-
{contractSection}
|
|
1399
|
-
{scheduleSection}
|
|
2032
|
+
{!isCreateMode ? contractSection : null}
|
|
2033
|
+
{!isCreateMode ? scheduleSection : null}
|
|
1400
2034
|
{activitySection}
|
|
1401
2035
|
</div>
|
|
1402
2036
|
) : (
|
|
1403
2037
|
<div className="space-y-4 px-4">
|
|
1404
2038
|
{profileContent}
|
|
1405
|
-
{contractSection}
|
|
1406
|
-
{scheduleSection}
|
|
2039
|
+
{!isCreateMode ? contractSection : null}
|
|
2040
|
+
{!isCreateMode ? scheduleSection : null}
|
|
1407
2041
|
</div>
|
|
1408
2042
|
);
|
|
1409
2043
|
|
|
@@ -1436,7 +2070,7 @@ export function CollaboratorFormScreen({
|
|
|
1436
2070
|
<Page>
|
|
1437
2071
|
<OperationsHeader
|
|
1438
2072
|
title={t(collaboratorId ? 'editTitle' : 'newTitle')}
|
|
1439
|
-
description={t('description')}
|
|
2073
|
+
description={isCreateMode ? t('descriptionCreate') : t('description')}
|
|
1440
2074
|
current={t('breadcrumb')}
|
|
1441
2075
|
actions={
|
|
1442
2076
|
<div className="flex gap-2">
|