@hed-hog/operations 0.0.303 → 0.0.304
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 +169 -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 +54 -0
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -0
- package/dist/controllers/operations-tasks.controller.js +79 -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 +8 -0
- package/dist/dto/create-task.dto.d.ts.map +1 -0
- package/dist/dto/create-task.dto.js +50 -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 +8 -0
- package/dist/dto/update-task.dto.d.ts.map +1 -0
- package/dist/dto/update-task.dto.js +51 -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 +373 -8
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1598 -111
- 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 +183 -0
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +231 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +134 -49
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +772 -93
- package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +38 -16
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +875 -632
- 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 +142 -39
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +33 -2
- 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 +109 -68
- 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/page.tsx.ejs +5 -1
- 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 +243 -51
- package/hedhog/frontend/messages/pt.json +458 -268
- 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_assignment.yaml +9 -0
- package/hedhog/table/operations_project_role.yaml +39 -0
- package/hedhog/table/operations_task.yaml +30 -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 +52 -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 +35 -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 +36 -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 +4641 -2163
- 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,6 +66,7 @@ import {
|
|
|
60
66
|
formatDateRange,
|
|
61
67
|
formatEnumLabel,
|
|
62
68
|
formatHours,
|
|
69
|
+
formatWeekdayLabel,
|
|
63
70
|
getStatusBadgeClass,
|
|
64
71
|
} from '../_lib/utils/format';
|
|
65
72
|
import { parseNumberInput, trimToNull } from '../_lib/utils/forms';
|
|
@@ -79,6 +86,50 @@ const weekdays = [
|
|
|
79
86
|
] as const;
|
|
80
87
|
|
|
81
88
|
const SUPERVISOR_PAGE_SIZE = 10;
|
|
89
|
+
const COLLABORATOR_LEVEL_OPTIONS = [
|
|
90
|
+
'junior',
|
|
91
|
+
'mid',
|
|
92
|
+
'senior',
|
|
93
|
+
'coordinator',
|
|
94
|
+
'manager',
|
|
95
|
+
] as const;
|
|
96
|
+
|
|
97
|
+
type CollaboratorLevel = (typeof COLLABORATOR_LEVEL_OPTIONS)[number];
|
|
98
|
+
|
|
99
|
+
type SystemUserOption = {
|
|
100
|
+
id: number;
|
|
101
|
+
name: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function normalizeCollaboratorLevel(
|
|
105
|
+
value?: string | null
|
|
106
|
+
): CollaboratorLevel | '' {
|
|
107
|
+
const normalizedValue = String(value ?? '')
|
|
108
|
+
.trim()
|
|
109
|
+
.toLowerCase()
|
|
110
|
+
.normalize('NFD')
|
|
111
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
112
|
+
|
|
113
|
+
switch (normalizedValue) {
|
|
114
|
+
case 'junior':
|
|
115
|
+
case 'junior level':
|
|
116
|
+
return 'junior';
|
|
117
|
+
case 'mid':
|
|
118
|
+
case 'middle':
|
|
119
|
+
case 'pleno':
|
|
120
|
+
return 'mid';
|
|
121
|
+
case 'senior':
|
|
122
|
+
return 'senior';
|
|
123
|
+
case 'coordinator':
|
|
124
|
+
case 'coordenador':
|
|
125
|
+
return 'coordinator';
|
|
126
|
+
case 'manager':
|
|
127
|
+
case 'gerente':
|
|
128
|
+
return 'manager';
|
|
129
|
+
default:
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
82
133
|
|
|
83
134
|
function getPersonAvatarUrl(avatarId?: number | null) {
|
|
84
135
|
return typeof avatarId === 'number' && avatarId > 0
|
|
@@ -120,14 +171,24 @@ function normalizeTimeValue(value?: string | null, fallback = '') {
|
|
|
120
171
|
return fallback || String(value);
|
|
121
172
|
}
|
|
122
173
|
|
|
174
|
+
const EQUITY_ENABLED_TYPE_SLUGS = new Set([
|
|
175
|
+
'socio',
|
|
176
|
+
'acionista',
|
|
177
|
+
'administrador',
|
|
178
|
+
'diretor',
|
|
179
|
+
'conselheiro',
|
|
180
|
+
'parceiro',
|
|
181
|
+
]);
|
|
182
|
+
|
|
123
183
|
type CollaboratorFormState = {
|
|
124
184
|
userId: string;
|
|
125
185
|
personId: string;
|
|
126
186
|
code: string;
|
|
127
187
|
displayName: string;
|
|
128
|
-
|
|
188
|
+
collaboratorTypeId: string;
|
|
129
189
|
departmentId: string;
|
|
130
190
|
department: string;
|
|
191
|
+
jobTitleId: string;
|
|
131
192
|
title: string;
|
|
132
193
|
levelLabel: string;
|
|
133
194
|
weeklyCapacityHours: string;
|
|
@@ -139,6 +200,14 @@ type CollaboratorFormState = {
|
|
|
139
200
|
contractDescription: string;
|
|
140
201
|
autoGenerateContractDraft: boolean;
|
|
141
202
|
notes: string;
|
|
203
|
+
equityParticipation: {
|
|
204
|
+
participationType: string;
|
|
205
|
+
percentage: string;
|
|
206
|
+
votingPower: string;
|
|
207
|
+
startDate: string;
|
|
208
|
+
endDate: string;
|
|
209
|
+
notes: string;
|
|
210
|
+
};
|
|
142
211
|
weeklySchedule: Array<{
|
|
143
212
|
weekday: string;
|
|
144
213
|
isWorkingDay: boolean;
|
|
@@ -164,9 +233,10 @@ function buildEmptyForm(): CollaboratorFormState {
|
|
|
164
233
|
personId: '',
|
|
165
234
|
code: '',
|
|
166
235
|
displayName: '',
|
|
167
|
-
|
|
236
|
+
collaboratorTypeId: '',
|
|
168
237
|
departmentId: '',
|
|
169
238
|
department: '',
|
|
239
|
+
jobTitleId: '',
|
|
170
240
|
title: '',
|
|
171
241
|
levelLabel: '',
|
|
172
242
|
weeklyCapacityHours: '40',
|
|
@@ -178,6 +248,14 @@ function buildEmptyForm(): CollaboratorFormState {
|
|
|
178
248
|
contractDescription: '',
|
|
179
249
|
autoGenerateContractDraft: true,
|
|
180
250
|
notes: '',
|
|
251
|
+
equityParticipation: {
|
|
252
|
+
participationType: 'partner_quota_holder',
|
|
253
|
+
percentage: '',
|
|
254
|
+
votingPower: '',
|
|
255
|
+
startDate: '',
|
|
256
|
+
endDate: '',
|
|
257
|
+
notes: '',
|
|
258
|
+
},
|
|
181
259
|
weeklySchedule: defaultSchedule(),
|
|
182
260
|
};
|
|
183
261
|
}
|
|
@@ -214,13 +292,16 @@ function toFormState(
|
|
|
214
292
|
personId: collaborator.personId ? String(collaborator.personId) : '',
|
|
215
293
|
code: collaborator.code ?? '',
|
|
216
294
|
displayName: collaborator.displayName ?? '',
|
|
217
|
-
|
|
295
|
+
collaboratorTypeId: collaborator.collaboratorTypeId
|
|
296
|
+
? String(collaborator.collaboratorTypeId)
|
|
297
|
+
: '',
|
|
218
298
|
departmentId: collaborator.departmentId
|
|
219
299
|
? String(collaborator.departmentId)
|
|
220
300
|
: '',
|
|
221
301
|
department: collaborator.department ?? '',
|
|
302
|
+
jobTitleId: collaborator.jobTitleId ? String(collaborator.jobTitleId) : '',
|
|
222
303
|
title: collaborator.title ?? '',
|
|
223
|
-
levelLabel: collaborator.levelLabel
|
|
304
|
+
levelLabel: normalizeCollaboratorLevel(collaborator.levelLabel),
|
|
224
305
|
weeklyCapacityHours:
|
|
225
306
|
collaborator.weeklyCapacityHours !== null &&
|
|
226
307
|
collaborator.weeklyCapacityHours !== undefined
|
|
@@ -240,6 +321,24 @@ function toFormState(
|
|
|
240
321
|
contractDescription: collaborator.relatedContracts?.[0]?.description ?? '',
|
|
241
322
|
autoGenerateContractDraft: true,
|
|
242
323
|
notes: collaborator.notes ?? '',
|
|
324
|
+
equityParticipation: {
|
|
325
|
+
participationType:
|
|
326
|
+
collaborator.equityParticipation?.participationType ??
|
|
327
|
+
'partner_quota_holder',
|
|
328
|
+
percentage:
|
|
329
|
+
collaborator.equityParticipation?.percentage !== null &&
|
|
330
|
+
collaborator.equityParticipation?.percentage !== undefined
|
|
331
|
+
? String(collaborator.equityParticipation.percentage)
|
|
332
|
+
: '',
|
|
333
|
+
votingPower:
|
|
334
|
+
collaborator.equityParticipation?.votingPower !== null &&
|
|
335
|
+
collaborator.equityParticipation?.votingPower !== undefined
|
|
336
|
+
? String(collaborator.equityParticipation.votingPower)
|
|
337
|
+
: '',
|
|
338
|
+
startDate: collaborator.equityParticipation?.startDate ?? '',
|
|
339
|
+
endDate: collaborator.equityParticipation?.endDate ?? '',
|
|
340
|
+
notes: collaborator.equityParticipation?.notes ?? '',
|
|
341
|
+
},
|
|
243
342
|
weeklySchedule: normalizeSchedule(collaborator.weeklySchedule),
|
|
244
343
|
};
|
|
245
344
|
}
|
|
@@ -397,6 +496,166 @@ function SupervisorAutocomplete({
|
|
|
397
496
|
);
|
|
398
497
|
}
|
|
399
498
|
|
|
499
|
+
type SystemUserAutocompleteProps = {
|
|
500
|
+
label: string;
|
|
501
|
+
value: string;
|
|
502
|
+
options: SystemUserOption[];
|
|
503
|
+
placeholder: string;
|
|
504
|
+
emptyLabel: string;
|
|
505
|
+
onChange: (value: string) => void;
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
function SystemUserAutocomplete({
|
|
509
|
+
label,
|
|
510
|
+
value,
|
|
511
|
+
options,
|
|
512
|
+
placeholder,
|
|
513
|
+
emptyLabel,
|
|
514
|
+
onChange,
|
|
515
|
+
}: SystemUserAutocompleteProps) {
|
|
516
|
+
const commonT = useTranslations('operations.Common');
|
|
517
|
+
const [open, setOpen] = useState(false);
|
|
518
|
+
const [search, setSearch] = useState('');
|
|
519
|
+
const [visibleCount, setVisibleCount] = useState(SUPERVISOR_PAGE_SIZE);
|
|
520
|
+
|
|
521
|
+
const filteredOptions = useMemo(() => {
|
|
522
|
+
const normalizedSearch = search.trim().toLowerCase();
|
|
523
|
+
|
|
524
|
+
if (!normalizedSearch) {
|
|
525
|
+
return options;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return options.filter((option) =>
|
|
529
|
+
[option.name, `#${option.id}`]
|
|
530
|
+
.filter(Boolean)
|
|
531
|
+
.some((field) => String(field).toLowerCase().includes(normalizedSearch))
|
|
532
|
+
);
|
|
533
|
+
}, [options, search]);
|
|
534
|
+
|
|
535
|
+
const selectedOption =
|
|
536
|
+
options.find((option) => String(option.id) === value) ??
|
|
537
|
+
(value
|
|
538
|
+
? {
|
|
539
|
+
id: Number(value),
|
|
540
|
+
name: `#${value}`,
|
|
541
|
+
}
|
|
542
|
+
: null);
|
|
543
|
+
const visibleOptions = filteredOptions.slice(0, visibleCount);
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
<div className="grid gap-2">
|
|
547
|
+
<Label>{label}</Label>
|
|
548
|
+
|
|
549
|
+
<Popover
|
|
550
|
+
open={open}
|
|
551
|
+
onOpenChange={(nextOpen) => {
|
|
552
|
+
setOpen(nextOpen);
|
|
553
|
+
setVisibleCount(SUPERVISOR_PAGE_SIZE);
|
|
554
|
+
|
|
555
|
+
if (!nextOpen) {
|
|
556
|
+
setSearch('');
|
|
557
|
+
}
|
|
558
|
+
}}
|
|
559
|
+
>
|
|
560
|
+
<PopoverTrigger asChild>
|
|
561
|
+
<Button
|
|
562
|
+
type="button"
|
|
563
|
+
variant="outline"
|
|
564
|
+
role="combobox"
|
|
565
|
+
className="w-full justify-between overflow-hidden"
|
|
566
|
+
>
|
|
567
|
+
<span className="truncate text-left">
|
|
568
|
+
{selectedOption
|
|
569
|
+
? `${selectedOption.name} (#${selectedOption.id})`
|
|
570
|
+
: emptyLabel}
|
|
571
|
+
</span>
|
|
572
|
+
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
573
|
+
</Button>
|
|
574
|
+
</PopoverTrigger>
|
|
575
|
+
<PopoverContent
|
|
576
|
+
className="p-0"
|
|
577
|
+
style={{ width: 'var(--radix-popover-trigger-width)' }}
|
|
578
|
+
>
|
|
579
|
+
<Command shouldFilter={false}>
|
|
580
|
+
<CommandInput
|
|
581
|
+
placeholder={placeholder}
|
|
582
|
+
value={search}
|
|
583
|
+
onValueChange={(nextValue) => {
|
|
584
|
+
setSearch(nextValue);
|
|
585
|
+
setVisibleCount(SUPERVISOR_PAGE_SIZE);
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
<CommandList>
|
|
589
|
+
<CommandEmpty>
|
|
590
|
+
<div className="p-2 text-sm text-muted-foreground">
|
|
591
|
+
{commonT('states.emptyDescription')}
|
|
592
|
+
</div>
|
|
593
|
+
</CommandEmpty>
|
|
594
|
+
|
|
595
|
+
<CommandGroup>
|
|
596
|
+
<CommandItem
|
|
597
|
+
value="none"
|
|
598
|
+
onSelect={() => {
|
|
599
|
+
onChange('');
|
|
600
|
+
setOpen(false);
|
|
601
|
+
}}
|
|
602
|
+
>
|
|
603
|
+
{!value ? (
|
|
604
|
+
<Check className="mr-2 h-4 w-4" />
|
|
605
|
+
) : (
|
|
606
|
+
<span className="mr-2 h-4 w-4" />
|
|
607
|
+
)}
|
|
608
|
+
{emptyLabel}
|
|
609
|
+
</CommandItem>
|
|
610
|
+
|
|
611
|
+
{visibleOptions.map((option) => (
|
|
612
|
+
<CommandItem
|
|
613
|
+
key={option.id}
|
|
614
|
+
value={`${option.name} #${option.id}`}
|
|
615
|
+
onSelect={() => {
|
|
616
|
+
onChange(String(option.id));
|
|
617
|
+
setOpen(false);
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
{String(option.id) === value ? (
|
|
621
|
+
<Check className="mr-2 h-4 w-4" />
|
|
622
|
+
) : (
|
|
623
|
+
<span className="mr-2 h-4 w-4" />
|
|
624
|
+
)}
|
|
625
|
+
<div className="min-w-0">
|
|
626
|
+
<div className="truncate">{option.name}</div>
|
|
627
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
628
|
+
#{option.id}
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
</CommandItem>
|
|
632
|
+
))}
|
|
633
|
+
</CommandGroup>
|
|
634
|
+
|
|
635
|
+
{filteredOptions.length > visibleCount ? (
|
|
636
|
+
<div className="border-t p-2">
|
|
637
|
+
<Button
|
|
638
|
+
type="button"
|
|
639
|
+
variant="ghost"
|
|
640
|
+
className="w-full"
|
|
641
|
+
onClick={() =>
|
|
642
|
+
setVisibleCount(
|
|
643
|
+
(current) => current + SUPERVISOR_PAGE_SIZE
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
>
|
|
647
|
+
{commonT('actions.loadMore')}
|
|
648
|
+
</Button>
|
|
649
|
+
</div>
|
|
650
|
+
) : null}
|
|
651
|
+
</CommandList>
|
|
652
|
+
</Command>
|
|
653
|
+
</PopoverContent>
|
|
654
|
+
</Popover>
|
|
655
|
+
</div>
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
400
659
|
type CollaboratorFormScreenProps = {
|
|
401
660
|
collaboratorId?: number;
|
|
402
661
|
onSaved?: (
|
|
@@ -418,23 +677,7 @@ export function CollaboratorFormScreen({
|
|
|
418
677
|
const router = useRouter();
|
|
419
678
|
const [form, setForm] = useState<CollaboratorFormState>(buildEmptyForm());
|
|
420
679
|
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
|
-
};
|
|
680
|
+
const isCreateMode = !collaboratorId;
|
|
438
681
|
|
|
439
682
|
const getStatusLabel = (value?: string | null) => {
|
|
440
683
|
switch (value) {
|
|
@@ -451,24 +694,72 @@ export function CollaboratorFormScreen({
|
|
|
451
694
|
}
|
|
452
695
|
};
|
|
453
696
|
|
|
454
|
-
const {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
697
|
+
const {
|
|
698
|
+
data: collaborator,
|
|
699
|
+
isLoading: isLoadingCollaborator,
|
|
700
|
+
refetch: refetchCollaborator,
|
|
701
|
+
} = useQuery<OperationsCollaboratorDetails>({
|
|
702
|
+
queryKey: [
|
|
703
|
+
'operations-collaborator-form',
|
|
704
|
+
currentLocaleCode,
|
|
705
|
+
collaboratorId,
|
|
706
|
+
],
|
|
707
|
+
enabled: Boolean(collaboratorId),
|
|
708
|
+
staleTime: 0,
|
|
709
|
+
refetchOnMount: 'always',
|
|
710
|
+
queryFn: () =>
|
|
711
|
+
fetchOperations<OperationsCollaboratorDetails>(
|
|
712
|
+
request,
|
|
713
|
+
`/operations/collaborators/${collaboratorId}`
|
|
714
|
+
),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
const { data: systemUsers = [] } = useQuery<SystemUserOption[]>({
|
|
718
|
+
queryKey: ['operations-collaborator-form-system-users', currentLocaleCode],
|
|
719
|
+
enabled: access.isDirector,
|
|
720
|
+
staleTime: 0,
|
|
721
|
+
refetchOnMount: 'always',
|
|
722
|
+
queryFn: async () => {
|
|
723
|
+
const response = await request<
|
|
724
|
+
SystemUserOption[] | { data?: SystemUserOption[] }
|
|
725
|
+
>({
|
|
726
|
+
url: '/person/owner-options',
|
|
727
|
+
method: 'GET',
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const options = Array.isArray(response.data)
|
|
731
|
+
? response.data
|
|
732
|
+
: response.data?.data || [];
|
|
733
|
+
|
|
734
|
+
return [...options].sort((left, right) =>
|
|
735
|
+
left.name.localeCompare(right.name)
|
|
736
|
+
);
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const {
|
|
741
|
+
data: collaboratorTypes = [],
|
|
742
|
+
refetch: refetchCollaboratorTypes,
|
|
743
|
+
isLoading: isLoadingCollaboratorTypes,
|
|
744
|
+
} = useQuery<OperationsCollaboratorType[]>({
|
|
745
|
+
queryKey: ['operations-collaborator-types', currentLocaleCode],
|
|
746
|
+
enabled: access.isDirector,
|
|
747
|
+
staleTime: 0,
|
|
748
|
+
refetchOnMount: 'always',
|
|
749
|
+
queryFn: () =>
|
|
750
|
+
fetchOperations<OperationsCollaboratorType[]>(
|
|
751
|
+
request,
|
|
752
|
+
'/operations/collaborator-types?active=true'
|
|
753
|
+
),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const { data: collaborators = [], refetch: refetchCollaborators } = useQuery<
|
|
757
|
+
OperationsCollaborator[]
|
|
758
|
+
>({
|
|
470
759
|
queryKey: ['operations-collaborator-form-supervisors', currentLocaleCode],
|
|
471
760
|
enabled: access.isDirector,
|
|
761
|
+
staleTime: 0,
|
|
762
|
+
refetchOnMount: 'always',
|
|
472
763
|
queryFn: () =>
|
|
473
764
|
fetchOperations<OperationsCollaborator[]>(
|
|
474
765
|
request,
|
|
@@ -476,9 +767,13 @@ export function CollaboratorFormScreen({
|
|
|
476
767
|
),
|
|
477
768
|
});
|
|
478
769
|
|
|
479
|
-
const { data: departments = [] } = useQuery<
|
|
770
|
+
const { data: departments = [], refetch: refetchDepartments } = useQuery<
|
|
771
|
+
OperationsDepartment[]
|
|
772
|
+
>({
|
|
480
773
|
queryKey: ['operations-collaborator-form-departments', currentLocaleCode],
|
|
481
774
|
enabled: access.isDirector,
|
|
775
|
+
staleTime: 0,
|
|
776
|
+
refetchOnMount: 'always',
|
|
482
777
|
queryFn: () =>
|
|
483
778
|
fetchOperations<OperationsDepartment[]>(
|
|
484
779
|
request,
|
|
@@ -486,6 +781,17 @@ export function CollaboratorFormScreen({
|
|
|
486
781
|
),
|
|
487
782
|
});
|
|
488
783
|
|
|
784
|
+
const { data: jobTitles = [], refetch: refetchJobTitles } = useQuery<
|
|
785
|
+
OperationsJobTitle[]
|
|
786
|
+
>({
|
|
787
|
+
queryKey: ['operations-collaborator-form-job-titles', currentLocaleCode],
|
|
788
|
+
enabled: access.isDirector,
|
|
789
|
+
staleTime: 0,
|
|
790
|
+
refetchOnMount: 'always',
|
|
791
|
+
queryFn: () =>
|
|
792
|
+
fetchOperations<OperationsJobTitle[]>(request, '/operations/job-titles'),
|
|
793
|
+
});
|
|
794
|
+
|
|
489
795
|
useEffect(() => {
|
|
490
796
|
if (collaborator) {
|
|
491
797
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
@@ -493,6 +799,67 @@ export function CollaboratorFormScreen({
|
|
|
493
799
|
}
|
|
494
800
|
}, [collaborator]);
|
|
495
801
|
|
|
802
|
+
useEffect(() => {
|
|
803
|
+
if (
|
|
804
|
+
collaboratorId ||
|
|
805
|
+
form.collaboratorTypeId ||
|
|
806
|
+
!collaboratorTypes.length
|
|
807
|
+
) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const defaultType =
|
|
812
|
+
collaboratorTypes.find((item) => item.slug === 'clt') ??
|
|
813
|
+
collaboratorTypes[0];
|
|
814
|
+
|
|
815
|
+
if (!defaultType) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
820
|
+
setForm((current) => ({
|
|
821
|
+
...current,
|
|
822
|
+
collaboratorTypeId: String(defaultType.id),
|
|
823
|
+
}));
|
|
824
|
+
}, [collaboratorId, collaboratorTypes, form.collaboratorTypeId]);
|
|
825
|
+
|
|
826
|
+
const selectedCollaboratorType = useMemo(
|
|
827
|
+
() =>
|
|
828
|
+
collaboratorTypes.find(
|
|
829
|
+
(item) => String(item.id) === String(form.collaboratorTypeId)
|
|
830
|
+
) ?? null,
|
|
831
|
+
[collaboratorTypes, form.collaboratorTypeId]
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
const shouldShowEquitySection =
|
|
835
|
+
!isCreateMode &&
|
|
836
|
+
Boolean(
|
|
837
|
+
selectedCollaboratorType?.slug &&
|
|
838
|
+
EQUITY_ENABLED_TYPE_SLUGS.has(selectedCollaboratorType.slug)
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
const getCollaboratorTypeLabel = (
|
|
842
|
+
value?: string | null,
|
|
843
|
+
fallbackName?: string | null
|
|
844
|
+
) => {
|
|
845
|
+
const normalizedValue = String(value ?? '').trim();
|
|
846
|
+
|
|
847
|
+
if (fallbackName) {
|
|
848
|
+
return fallbackName;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const matchedType = collaboratorTypes.find(
|
|
852
|
+
(item) =>
|
|
853
|
+
item.slug === normalizedValue || String(item.id) === normalizedValue
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
if (matchedType?.name) {
|
|
857
|
+
return matchedType.name;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return formatEnumLabel(normalizedValue);
|
|
861
|
+
};
|
|
862
|
+
|
|
496
863
|
const departmentOptions = useMemo(() => {
|
|
497
864
|
const selectedDepartment = trimToNull(form.department);
|
|
498
865
|
const options: Array<{
|
|
@@ -529,6 +896,68 @@ export function CollaboratorFormScreen({
|
|
|
529
896
|
return options.sort((left, right) => left.name.localeCompare(right.name));
|
|
530
897
|
}, [departments, form.department, form.departmentId]);
|
|
531
898
|
|
|
899
|
+
const titleOptions = useMemo(() => {
|
|
900
|
+
const selectedTitle = trimToNull(form.title);
|
|
901
|
+
const options: Array<{
|
|
902
|
+
id?: number | null;
|
|
903
|
+
name: string;
|
|
904
|
+
code?: string | null;
|
|
905
|
+
description?: string | null;
|
|
906
|
+
}> = jobTitles
|
|
907
|
+
.filter((item) => item.status === 'active' || item.name === selectedTitle)
|
|
908
|
+
.map((item) => ({
|
|
909
|
+
id: item.id,
|
|
910
|
+
name: item.name,
|
|
911
|
+
code: item.code ?? null,
|
|
912
|
+
description: item.description ?? null,
|
|
913
|
+
}));
|
|
914
|
+
|
|
915
|
+
if (selectedTitle) {
|
|
916
|
+
const alreadyIncluded = options.some(
|
|
917
|
+
(item) => item.name === selectedTitle
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
if (!alreadyIncluded) {
|
|
921
|
+
options.push({
|
|
922
|
+
id: form.jobTitleId ? Number(form.jobTitleId) : undefined,
|
|
923
|
+
name: selectedTitle,
|
|
924
|
+
code: null,
|
|
925
|
+
description: null,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return options.sort((left, right) => left.name.localeCompare(right.name));
|
|
931
|
+
}, [jobTitles, form.title, form.jobTitleId]);
|
|
932
|
+
|
|
933
|
+
const createJobTitle = async (jobTitleName: string) => {
|
|
934
|
+
try {
|
|
935
|
+
const createdJobTitle = await mutateOperations<OperationsJobTitle>(
|
|
936
|
+
request,
|
|
937
|
+
'/operations/job-titles',
|
|
938
|
+
'POST',
|
|
939
|
+
{
|
|
940
|
+
name: jobTitleName,
|
|
941
|
+
}
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
await refetchJobTitles();
|
|
945
|
+
|
|
946
|
+
return {
|
|
947
|
+
id: createdJobTitle.id,
|
|
948
|
+
name: createdJobTitle.name,
|
|
949
|
+
code: createdJobTitle.code ?? null,
|
|
950
|
+
description: createdJobTitle.description ?? null,
|
|
951
|
+
};
|
|
952
|
+
} catch (error) {
|
|
953
|
+
showToastHandler?.(
|
|
954
|
+
'error',
|
|
955
|
+
getOperationsErrorMessage(error, 'Unable to create the job title.')
|
|
956
|
+
);
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
532
961
|
const updateScheduleDay = (
|
|
533
962
|
weekday: string,
|
|
534
963
|
patch: Partial<CollaboratorFormState['weeklySchedule'][number]>
|
|
@@ -572,15 +1001,23 @@ export function CollaboratorFormScreen({
|
|
|
572
1001
|
}
|
|
573
1002
|
|
|
574
1003
|
const departmentId = parseNumberInput(form.departmentId);
|
|
1004
|
+
const jobTitleId = parseNumberInput(form.jobTitleId);
|
|
1005
|
+
const collaboratorTypeId = parseNumberInput(form.collaboratorTypeId);
|
|
1006
|
+
|
|
1007
|
+
if (!collaboratorTypeId) {
|
|
1008
|
+
showToastHandler?.('error', t('messages.collaboratorTypeRequired'));
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
575
1011
|
|
|
576
1012
|
const payload = {
|
|
577
1013
|
userId: userId ?? undefined,
|
|
578
1014
|
personId: personId ?? undefined,
|
|
579
1015
|
code: trimToNull(form.code) ?? undefined,
|
|
580
1016
|
displayName: trimToNull(form.displayName),
|
|
581
|
-
|
|
1017
|
+
collaboratorTypeId,
|
|
582
1018
|
departmentId: departmentId ?? undefined,
|
|
583
1019
|
department: departmentId ? undefined : trimToNull(form.department),
|
|
1020
|
+
jobTitleId: jobTitleId ?? undefined,
|
|
584
1021
|
title: trimToNull(form.title),
|
|
585
1022
|
levelLabel: trimToNull(form.levelLabel),
|
|
586
1023
|
weeklyCapacityHours: parseNumberInput(form.weeklyCapacityHours),
|
|
@@ -595,6 +1032,16 @@ export function CollaboratorFormScreen({
|
|
|
595
1032
|
contractDescription: trimToNull(form.contractDescription),
|
|
596
1033
|
autoGenerateContractDraft: form.autoGenerateContractDraft,
|
|
597
1034
|
notes: trimToNull(form.notes),
|
|
1035
|
+
equityParticipation: shouldShowEquitySection
|
|
1036
|
+
? {
|
|
1037
|
+
participationType: form.equityParticipation.participationType,
|
|
1038
|
+
percentage: parseNumberInput(form.equityParticipation.percentage),
|
|
1039
|
+
votingPower: parseNumberInput(form.equityParticipation.votingPower),
|
|
1040
|
+
startDate: trimToNull(form.equityParticipation.startDate),
|
|
1041
|
+
endDate: trimToNull(form.equityParticipation.endDate),
|
|
1042
|
+
notes: trimToNull(form.equityParticipation.notes),
|
|
1043
|
+
}
|
|
1044
|
+
: null,
|
|
598
1045
|
weeklySchedule: form.weeklySchedule.map((day) => ({
|
|
599
1046
|
weekday: day.weekday,
|
|
600
1047
|
isWorkingDay: day.isWorkingDay,
|
|
@@ -623,6 +1070,16 @@ export function CollaboratorFormScreen({
|
|
|
623
1070
|
payload
|
|
624
1071
|
);
|
|
625
1072
|
|
|
1073
|
+
setForm(toFormState(response));
|
|
1074
|
+
|
|
1075
|
+
await Promise.all([
|
|
1076
|
+
refetchCollaborator(),
|
|
1077
|
+
refetchCollaboratorTypes(),
|
|
1078
|
+
refetchDepartments(),
|
|
1079
|
+
refetchCollaborators(),
|
|
1080
|
+
refetchJobTitles(),
|
|
1081
|
+
]);
|
|
1082
|
+
|
|
626
1083
|
showToastHandler?.(
|
|
627
1084
|
'success',
|
|
628
1085
|
collaboratorId
|
|
@@ -636,10 +1093,13 @@ export function CollaboratorFormScreen({
|
|
|
636
1093
|
}
|
|
637
1094
|
|
|
638
1095
|
router.push(`/operations/collaborators/${response.id}`);
|
|
639
|
-
} catch {
|
|
1096
|
+
} catch (error) {
|
|
640
1097
|
showToastHandler?.(
|
|
641
1098
|
'error',
|
|
642
|
-
|
|
1099
|
+
getOperationsErrorMessage(
|
|
1100
|
+
error,
|
|
1101
|
+
collaboratorId ? t('messages.updateError') : t('messages.createError')
|
|
1102
|
+
)
|
|
643
1103
|
);
|
|
644
1104
|
}
|
|
645
1105
|
};
|
|
@@ -690,7 +1150,11 @@ export function CollaboratorFormScreen({
|
|
|
690
1150
|
className={getStatusBadgeClass(collaborator.status)}
|
|
691
1151
|
/>
|
|
692
1152
|
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">
|
|
693
|
-
{getCollaboratorTypeLabel(
|
|
1153
|
+
{getCollaboratorTypeLabel(
|
|
1154
|
+
collaborator.collaboratorTypeSlug ??
|
|
1155
|
+
collaborator.collaboratorType,
|
|
1156
|
+
collaborator.collaboratorTypeName
|
|
1157
|
+
)}
|
|
694
1158
|
</span>
|
|
695
1159
|
</div>
|
|
696
1160
|
</div>
|
|
@@ -760,7 +1224,7 @@ export function CollaboratorFormScreen({
|
|
|
760
1224
|
</p>
|
|
761
1225
|
</div>
|
|
762
1226
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
763
|
-
<div className="space-y-2 md:col-span-2
|
|
1227
|
+
<div className="space-y-2 md:col-span-2">
|
|
764
1228
|
<PersonSelectWithCreate
|
|
765
1229
|
label={t('fields.person')}
|
|
766
1230
|
entityLabel={t('fields.personEntityLabel')}
|
|
@@ -776,33 +1240,63 @@ export function CollaboratorFormScreen({
|
|
|
776
1240
|
}
|
|
777
1241
|
/>
|
|
778
1242
|
</div>
|
|
779
|
-
<div className="space-y-2">
|
|
780
|
-
<
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
placeholder=
|
|
785
|
-
|
|
1243
|
+
<div className="space-y-2 md:col-span-2 xl:col-span-2">
|
|
1244
|
+
<SystemUserAutocomplete
|
|
1245
|
+
label={t('fields.userIdOptional')}
|
|
1246
|
+
value={form.userId}
|
|
1247
|
+
options={systemUsers}
|
|
1248
|
+
placeholder={t('placeholders.userIdOptional')}
|
|
1249
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
1250
|
+
onChange={(value) =>
|
|
786
1251
|
setForm((current) => ({
|
|
787
1252
|
...current,
|
|
788
|
-
|
|
1253
|
+
userId: value,
|
|
789
1254
|
}))
|
|
790
1255
|
}
|
|
791
1256
|
/>
|
|
1257
|
+
<p className="text-xs text-muted-foreground">
|
|
1258
|
+
{t('fields.userIdDescription')}
|
|
1259
|
+
</p>
|
|
792
1260
|
</div>
|
|
793
1261
|
<div className="space-y-2">
|
|
794
|
-
<Label>{t('fields.
|
|
1262
|
+
<Label>{t('fields.code')}</Label>
|
|
795
1263
|
<Input
|
|
796
1264
|
className="h-10"
|
|
797
|
-
value={form.
|
|
1265
|
+
value={form.code}
|
|
1266
|
+
placeholder="COL-001"
|
|
798
1267
|
onChange={(event) =>
|
|
799
1268
|
setForm((current) => ({
|
|
800
1269
|
...current,
|
|
801
|
-
|
|
1270
|
+
code: event.target.value,
|
|
802
1271
|
}))
|
|
803
1272
|
}
|
|
804
1273
|
/>
|
|
805
1274
|
</div>
|
|
1275
|
+
{!isCreateMode ? (
|
|
1276
|
+
<div className="space-y-2">
|
|
1277
|
+
<Label>{t('fields.levelLabel')}</Label>
|
|
1278
|
+
<Select
|
|
1279
|
+
value={form.levelLabel}
|
|
1280
|
+
onValueChange={(value) =>
|
|
1281
|
+
setForm((current) => ({
|
|
1282
|
+
...current,
|
|
1283
|
+
levelLabel: value,
|
|
1284
|
+
}))
|
|
1285
|
+
}
|
|
1286
|
+
>
|
|
1287
|
+
<SelectTrigger className="w-full">
|
|
1288
|
+
<SelectValue placeholder={t('placeholders.levelLabel')} />
|
|
1289
|
+
</SelectTrigger>
|
|
1290
|
+
<SelectContent>
|
|
1291
|
+
{COLLABORATOR_LEVEL_OPTIONS.map((level) => (
|
|
1292
|
+
<SelectItem key={level} value={level}>
|
|
1293
|
+
{t(`options.levels.${level}`)}
|
|
1294
|
+
</SelectItem>
|
|
1295
|
+
))}
|
|
1296
|
+
</SelectContent>
|
|
1297
|
+
</Select>
|
|
1298
|
+
</div>
|
|
1299
|
+
) : null}
|
|
806
1300
|
<div className="space-y-2 xl:col-span-1">
|
|
807
1301
|
<DepartmentSelectWithCreate
|
|
808
1302
|
label={t('fields.department')}
|
|
@@ -821,14 +1315,19 @@ export function CollaboratorFormScreen({
|
|
|
821
1315
|
/>
|
|
822
1316
|
</div>
|
|
823
1317
|
<div className="space-y-2 xl:col-span-1">
|
|
824
|
-
<
|
|
825
|
-
|
|
826
|
-
className="h-10"
|
|
1318
|
+
<DepartmentSelectWithCreate
|
|
1319
|
+
label={t('fields.title')}
|
|
827
1320
|
value={form.title}
|
|
828
|
-
|
|
1321
|
+
options={titleOptions}
|
|
1322
|
+
onCreate={createJobTitle}
|
|
1323
|
+
selectPlaceholder={t('placeholders.title')}
|
|
1324
|
+
createDescription={t('fields.titleDescription')}
|
|
1325
|
+
createPlaceholder={t('placeholders.titleCreate')}
|
|
1326
|
+
onChange={(titleOption) =>
|
|
829
1327
|
setForm((current) => ({
|
|
830
1328
|
...current,
|
|
831
|
-
|
|
1329
|
+
jobTitleId: titleOption.id ? String(titleOption.id) : '',
|
|
1330
|
+
title: titleOption.name,
|
|
832
1331
|
}))
|
|
833
1332
|
}
|
|
834
1333
|
/>
|
|
@@ -844,42 +1343,44 @@ export function CollaboratorFormScreen({
|
|
|
844
1343
|
{t('sections.employmentInfo')}
|
|
845
1344
|
</h3>
|
|
846
1345
|
<p className="text-[11px] text-muted-foreground/80">
|
|
847
|
-
{
|
|
1346
|
+
{isCreateMode
|
|
1347
|
+
? t('sections.employmentInfoCreateDescription')
|
|
1348
|
+
: t('sections.employmentInfoDescription')}
|
|
848
1349
|
</p>
|
|
849
1350
|
</div>
|
|
850
1351
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
851
|
-
<div className="space-y-2">
|
|
1352
|
+
<div className="space-y-2 xl:col-span-1">
|
|
852
1353
|
<label className="text-sm font-medium">
|
|
853
1354
|
{t('fields.collaboratorType')}
|
|
854
1355
|
</label>
|
|
1356
|
+
|
|
855
1357
|
<Select
|
|
856
|
-
value={form.
|
|
1358
|
+
value={form.collaboratorTypeId}
|
|
857
1359
|
onValueChange={(value) =>
|
|
858
1360
|
setForm((current) => ({
|
|
859
1361
|
...current,
|
|
860
|
-
|
|
1362
|
+
collaboratorTypeId: value,
|
|
861
1363
|
}))
|
|
862
1364
|
}
|
|
863
1365
|
>
|
|
864
1366
|
<SelectTrigger className="w-full">
|
|
865
|
-
<SelectValue
|
|
1367
|
+
<SelectValue
|
|
1368
|
+
placeholder={
|
|
1369
|
+
isLoadingCollaboratorTypes
|
|
1370
|
+
? t('states.loadingCollaboratorTypes')
|
|
1371
|
+
: t('placeholders.collaboratorType')
|
|
1372
|
+
}
|
|
1373
|
+
/>
|
|
866
1374
|
</SelectTrigger>
|
|
867
1375
|
<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>
|
|
1376
|
+
{collaboratorTypes.map((collaboratorType) => (
|
|
1377
|
+
<SelectItem
|
|
1378
|
+
key={collaboratorType.id}
|
|
1379
|
+
value={String(collaboratorType.id)}
|
|
1380
|
+
>
|
|
1381
|
+
{collaboratorType.name}
|
|
1382
|
+
</SelectItem>
|
|
1383
|
+
))}
|
|
883
1384
|
</SelectContent>
|
|
884
1385
|
</Select>
|
|
885
1386
|
</div>
|
|
@@ -942,10 +1443,187 @@ export function CollaboratorFormScreen({
|
|
|
942
1443
|
}
|
|
943
1444
|
/>
|
|
944
1445
|
</div>
|
|
1446
|
+
{isCreateMode ? (
|
|
1447
|
+
<>
|
|
1448
|
+
<div className="space-y-2">
|
|
1449
|
+
<label className="text-sm font-medium">
|
|
1450
|
+
{t('fields.weeklyCapacityHours')}
|
|
1451
|
+
</label>
|
|
1452
|
+
<Input
|
|
1453
|
+
type="number"
|
|
1454
|
+
step="0.5"
|
|
1455
|
+
value={form.weeklyCapacityHours}
|
|
1456
|
+
onChange={(event) =>
|
|
1457
|
+
setForm((current) => ({
|
|
1458
|
+
...current,
|
|
1459
|
+
weeklyCapacityHours: event.target.value,
|
|
1460
|
+
}))
|
|
1461
|
+
}
|
|
1462
|
+
/>
|
|
1463
|
+
</div>
|
|
1464
|
+
<div className="space-y-2">
|
|
1465
|
+
<label className="text-sm font-medium">
|
|
1466
|
+
{t('fields.compensationAmount')}
|
|
1467
|
+
</label>
|
|
1468
|
+
<InputMoney
|
|
1469
|
+
step="0.01"
|
|
1470
|
+
value={form.compensationAmount}
|
|
1471
|
+
onChange={(event) =>
|
|
1472
|
+
setForm((current) => ({
|
|
1473
|
+
...current,
|
|
1474
|
+
compensationAmount: event.target.value,
|
|
1475
|
+
}))
|
|
1476
|
+
}
|
|
1477
|
+
/>
|
|
1478
|
+
</div>
|
|
1479
|
+
</>
|
|
1480
|
+
) : null}
|
|
945
1481
|
</div>
|
|
946
1482
|
</div>
|
|
947
1483
|
);
|
|
948
1484
|
|
|
1485
|
+
const equitySection = shouldShowEquitySection ? (
|
|
1486
|
+
<div className="space-y-2">
|
|
1487
|
+
<div className="space-y-0.5">
|
|
1488
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1489
|
+
{t('sections.equity')}
|
|
1490
|
+
</h3>
|
|
1491
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1492
|
+
{t('sections.equityDescription')}
|
|
1493
|
+
</p>
|
|
1494
|
+
</div>
|
|
1495
|
+
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
1496
|
+
<div className="space-y-2 xl:col-span-2">
|
|
1497
|
+
<label className="text-sm font-medium">
|
|
1498
|
+
{t('fields.equityParticipationType')}
|
|
1499
|
+
</label>
|
|
1500
|
+
<Select
|
|
1501
|
+
value={form.equityParticipation.participationType}
|
|
1502
|
+
onValueChange={(value) =>
|
|
1503
|
+
setForm((current) => ({
|
|
1504
|
+
...current,
|
|
1505
|
+
equityParticipation: {
|
|
1506
|
+
...current.equityParticipation,
|
|
1507
|
+
participationType: value,
|
|
1508
|
+
},
|
|
1509
|
+
}))
|
|
1510
|
+
}
|
|
1511
|
+
>
|
|
1512
|
+
<SelectTrigger className="w-full">
|
|
1513
|
+
<SelectValue />
|
|
1514
|
+
</SelectTrigger>
|
|
1515
|
+
<SelectContent>
|
|
1516
|
+
<SelectItem value="partner_quota_holder">
|
|
1517
|
+
{t('options.equityParticipationTypes.partner_quota_holder')}
|
|
1518
|
+
</SelectItem>
|
|
1519
|
+
<SelectItem value="common_shareholder">
|
|
1520
|
+
{t('options.equityParticipationTypes.common_shareholder')}
|
|
1521
|
+
</SelectItem>
|
|
1522
|
+
<SelectItem value="preferred_shareholder">
|
|
1523
|
+
{t('options.equityParticipationTypes.preferred_shareholder')}
|
|
1524
|
+
</SelectItem>
|
|
1525
|
+
<SelectItem value="administrator">
|
|
1526
|
+
{t('options.equityParticipationTypes.administrator')}
|
|
1527
|
+
</SelectItem>
|
|
1528
|
+
<SelectItem value="other">
|
|
1529
|
+
{t('options.equityParticipationTypes.other')}
|
|
1530
|
+
</SelectItem>
|
|
1531
|
+
</SelectContent>
|
|
1532
|
+
</Select>
|
|
1533
|
+
</div>
|
|
1534
|
+
<div className="space-y-2">
|
|
1535
|
+
<Label>{t('fields.equityPercentage')}</Label>
|
|
1536
|
+
<Input
|
|
1537
|
+
type="number"
|
|
1538
|
+
min="0"
|
|
1539
|
+
max="100"
|
|
1540
|
+
step="0.0001"
|
|
1541
|
+
placeholder={t('placeholders.equityPercentage')}
|
|
1542
|
+
value={form.equityParticipation.percentage}
|
|
1543
|
+
onChange={(event) =>
|
|
1544
|
+
setForm((current) => ({
|
|
1545
|
+
...current,
|
|
1546
|
+
equityParticipation: {
|
|
1547
|
+
...current.equityParticipation,
|
|
1548
|
+
percentage: event.target.value,
|
|
1549
|
+
},
|
|
1550
|
+
}))
|
|
1551
|
+
}
|
|
1552
|
+
/>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div className="space-y-2">
|
|
1555
|
+
<Label>{t('fields.votingPower')}</Label>
|
|
1556
|
+
<Input
|
|
1557
|
+
type="number"
|
|
1558
|
+
min="0"
|
|
1559
|
+
max="100"
|
|
1560
|
+
step="0.0001"
|
|
1561
|
+
placeholder={t('placeholders.votingPower')}
|
|
1562
|
+
value={form.equityParticipation.votingPower}
|
|
1563
|
+
onChange={(event) =>
|
|
1564
|
+
setForm((current) => ({
|
|
1565
|
+
...current,
|
|
1566
|
+
equityParticipation: {
|
|
1567
|
+
...current.equityParticipation,
|
|
1568
|
+
votingPower: event.target.value,
|
|
1569
|
+
},
|
|
1570
|
+
}))
|
|
1571
|
+
}
|
|
1572
|
+
/>
|
|
1573
|
+
</div>
|
|
1574
|
+
<div className="space-y-2">
|
|
1575
|
+
<Label>{commonT('labels.startDate')}</Label>
|
|
1576
|
+
<Input
|
|
1577
|
+
type="date"
|
|
1578
|
+
value={form.equityParticipation.startDate}
|
|
1579
|
+
onChange={(event) =>
|
|
1580
|
+
setForm((current) => ({
|
|
1581
|
+
...current,
|
|
1582
|
+
equityParticipation: {
|
|
1583
|
+
...current.equityParticipation,
|
|
1584
|
+
startDate: event.target.value,
|
|
1585
|
+
},
|
|
1586
|
+
}))
|
|
1587
|
+
}
|
|
1588
|
+
/>
|
|
1589
|
+
</div>
|
|
1590
|
+
<div className="space-y-2">
|
|
1591
|
+
<Label>{commonT('labels.endDate')}</Label>
|
|
1592
|
+
<Input
|
|
1593
|
+
type="date"
|
|
1594
|
+
value={form.equityParticipation.endDate}
|
|
1595
|
+
onChange={(event) =>
|
|
1596
|
+
setForm((current) => ({
|
|
1597
|
+
...current,
|
|
1598
|
+
equityParticipation: {
|
|
1599
|
+
...current.equityParticipation,
|
|
1600
|
+
endDate: event.target.value,
|
|
1601
|
+
},
|
|
1602
|
+
}))
|
|
1603
|
+
}
|
|
1604
|
+
/>
|
|
1605
|
+
</div>
|
|
1606
|
+
<div className="space-y-2 md:col-span-2 xl:col-span-4">
|
|
1607
|
+
<Label>{t('fields.equityNotes')}</Label>
|
|
1608
|
+
<Textarea
|
|
1609
|
+
rows={4}
|
|
1610
|
+
placeholder={t('placeholders.equityNotes')}
|
|
1611
|
+
value={form.equityParticipation.notes}
|
|
1612
|
+
onChange={(event) =>
|
|
1613
|
+
setForm((current) => ({
|
|
1614
|
+
...current,
|
|
1615
|
+
equityParticipation: {
|
|
1616
|
+
...current.equityParticipation,
|
|
1617
|
+
notes: event.target.value,
|
|
1618
|
+
},
|
|
1619
|
+
}))
|
|
1620
|
+
}
|
|
1621
|
+
/>
|
|
1622
|
+
</div>
|
|
1623
|
+
</div>
|
|
1624
|
+
</div>
|
|
1625
|
+
) : null;
|
|
1626
|
+
|
|
949
1627
|
const supervisorSection = (
|
|
950
1628
|
<div className="space-y-2">
|
|
951
1629
|
<div className="space-y-0.5">
|
|
@@ -1070,7 +1748,7 @@ export function CollaboratorFormScreen({
|
|
|
1070
1748
|
>
|
|
1071
1749
|
<div className="space-y-0.5 md:pr-1">
|
|
1072
1750
|
<div className="text-sm font-medium leading-none">
|
|
1073
|
-
{
|
|
1751
|
+
{formatWeekdayLabel(day.weekday, currentLocaleCode)}
|
|
1074
1752
|
</div>
|
|
1075
1753
|
<div className="text-[10px] leading-none text-muted-foreground">
|
|
1076
1754
|
{day.isWorkingDay
|
|
@@ -1309,13 +1987,13 @@ export function CollaboratorFormScreen({
|
|
|
1309
1987
|
href={`/operations/contracts?edit=${contract.id}`}
|
|
1310
1988
|
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
1989
|
>
|
|
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,
|
|
1990
|
+
<div className="min-w-0">
|
|
1991
|
+
<div className="truncate font-medium">
|
|
1992
|
+
{contract.name || contract.code}
|
|
1993
|
+
</div>
|
|
1994
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1995
|
+
{[
|
|
1996
|
+
contract.code,
|
|
1319
1997
|
formatEnumLabel(contract.contractCategory),
|
|
1320
1998
|
]
|
|
1321
1999
|
.filter(Boolean)
|
|
@@ -1388,6 +2066,7 @@ export function CollaboratorFormScreen({
|
|
|
1388
2066
|
<div className="space-y-4">
|
|
1389
2067
|
{basicInfoSection}
|
|
1390
2068
|
{employmentInfoSection}
|
|
2069
|
+
{!isCreateMode ? equitySection : null}
|
|
1391
2070
|
{supervisorSection}
|
|
1392
2071
|
</div>
|
|
1393
2072
|
);
|
|
@@ -1395,15 +2074,15 @@ export function CollaboratorFormScreen({
|
|
|
1395
2074
|
const formContent = isSheetMode ? (
|
|
1396
2075
|
<div className="space-y-4 px-4">
|
|
1397
2076
|
{profileContent}
|
|
1398
|
-
{contractSection}
|
|
1399
|
-
{scheduleSection}
|
|
2077
|
+
{!isCreateMode ? contractSection : null}
|
|
2078
|
+
{!isCreateMode ? scheduleSection : null}
|
|
1400
2079
|
{activitySection}
|
|
1401
2080
|
</div>
|
|
1402
2081
|
) : (
|
|
1403
2082
|
<div className="space-y-4 px-4">
|
|
1404
2083
|
{profileContent}
|
|
1405
|
-
{contractSection}
|
|
1406
|
-
{scheduleSection}
|
|
2084
|
+
{!isCreateMode ? contractSection : null}
|
|
2085
|
+
{!isCreateMode ? scheduleSection : null}
|
|
1407
2086
|
</div>
|
|
1408
2087
|
);
|
|
1409
2088
|
|
|
@@ -1436,7 +2115,7 @@ export function CollaboratorFormScreen({
|
|
|
1436
2115
|
<Page>
|
|
1437
2116
|
<OperationsHeader
|
|
1438
2117
|
title={t(collaboratorId ? 'editTitle' : 'newTitle')}
|
|
1439
|
-
description={t('description')}
|
|
2118
|
+
description={isCreateMode ? t('descriptionCreate') : t('description')}
|
|
1440
2119
|
current={t('breadcrumb')}
|
|
1441
2120
|
actions={
|
|
1442
2121
|
<div className="flex gap-2">
|