@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.
Files changed (178) hide show
  1. package/README.md +200 -43
  2. package/dist/controllers/operations-approvals.controller.d.ts +9 -0
  3. package/dist/controllers/operations-approvals.controller.d.ts.map +1 -0
  4. package/dist/controllers/operations-approvals.controller.js +64 -0
  5. package/dist/controllers/operations-approvals.controller.js.map +1 -0
  6. package/dist/controllers/operations-collaborators.controller.d.ts +223 -0
  7. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -0
  8. package/dist/controllers/operations-collaborators.controller.js +96 -0
  9. package/dist/controllers/operations-collaborators.controller.js.map +1 -0
  10. package/dist/controllers/operations-contracts.controller.d.ts +683 -0
  11. package/dist/controllers/operations-contracts.controller.d.ts.map +1 -0
  12. package/dist/controllers/operations-contracts.controller.js +198 -0
  13. package/dist/controllers/operations-contracts.controller.js.map +1 -0
  14. package/dist/controllers/operations-org-structure.controller.d.ts +108 -0
  15. package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -0
  16. package/dist/controllers/operations-org-structure.controller.js +143 -0
  17. package/dist/controllers/operations-org-structure.controller.js.map +1 -0
  18. package/dist/controllers/operations-projects.controller.d.ts +184 -0
  19. package/dist/controllers/operations-projects.controller.d.ts.map +1 -0
  20. package/dist/controllers/operations-projects.controller.js +87 -0
  21. package/dist/controllers/operations-projects.controller.js.map +1 -0
  22. package/dist/controllers/operations-tasks.controller.d.ts +85 -0
  23. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -0
  24. package/dist/controllers/operations-tasks.controller.js +90 -0
  25. package/dist/controllers/operations-tasks.controller.js.map +1 -0
  26. package/dist/controllers/operations-timesheets.controller.d.ts +99 -0
  27. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -0
  28. package/dist/controllers/operations-timesheets.controller.js +154 -0
  29. package/dist/controllers/operations-timesheets.controller.js.map +1 -0
  30. package/dist/dto/create-collaborator-type.dto.d.ts +10 -0
  31. package/dist/dto/create-collaborator-type.dto.d.ts.map +1 -0
  32. package/dist/dto/create-collaborator-type.dto.js +56 -0
  33. package/dist/dto/create-collaborator-type.dto.js.map +1 -0
  34. package/dist/dto/create-collaborator.dto.d.ts +42 -0
  35. package/dist/dto/create-collaborator.dto.d.ts.map +1 -0
  36. package/dist/dto/create-collaborator.dto.js +228 -0
  37. package/dist/dto/create-collaborator.dto.js.map +1 -0
  38. package/dist/dto/create-schedule-adjustment-request.dto.d.ts +17 -0
  39. package/dist/dto/create-schedule-adjustment-request.dto.d.ts.map +1 -0
  40. package/dist/dto/create-schedule-adjustment-request.dto.js +89 -0
  41. package/dist/dto/create-schedule-adjustment-request.dto.js.map +1 -0
  42. package/dist/dto/create-task.dto.d.ts +14 -0
  43. package/dist/dto/create-task.dto.d.ts.map +1 -0
  44. package/dist/dto/create-task.dto.js +83 -0
  45. package/dist/dto/create-task.dto.js.map +1 -0
  46. package/dist/dto/create-time-off-request.dto.d.ts +9 -0
  47. package/dist/dto/create-time-off-request.dto.d.ts.map +1 -0
  48. package/dist/dto/create-time-off-request.dto.js +54 -0
  49. package/dist/dto/create-time-off-request.dto.js.map +1 -0
  50. package/dist/dto/create-timesheet-entry.dto.d.ts +12 -0
  51. package/dist/dto/create-timesheet-entry.dto.d.ts.map +1 -0
  52. package/dist/dto/create-timesheet-entry.dto.js +75 -0
  53. package/dist/dto/create-timesheet-entry.dto.js.map +1 -0
  54. package/dist/dto/list-collaborator-types.dto.d.ts +4 -0
  55. package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -0
  56. package/dist/dto/list-collaborator-types.dto.js +29 -0
  57. package/dist/dto/list-collaborator-types.dto.js.map +1 -0
  58. package/dist/dto/list-collaborators.dto.d.ts +8 -0
  59. package/dist/dto/list-collaborators.dto.d.ts.map +1 -0
  60. package/dist/dto/list-collaborators.dto.js +42 -0
  61. package/dist/dto/list-collaborators.dto.js.map +1 -0
  62. package/dist/dto/list-project-options.dto.d.ts +4 -0
  63. package/dist/dto/list-project-options.dto.d.ts.map +1 -0
  64. package/dist/dto/list-project-options.dto.js +8 -0
  65. package/dist/dto/list-project-options.dto.js.map +1 -0
  66. package/dist/dto/list-tasks.dto.d.ts +7 -0
  67. package/dist/dto/list-tasks.dto.d.ts.map +1 -0
  68. package/dist/dto/list-tasks.dto.js +38 -0
  69. package/dist/dto/list-tasks.dto.js.map +1 -0
  70. package/dist/dto/list-timesheet-entries.dto.d.ts +10 -0
  71. package/dist/dto/list-timesheet-entries.dto.d.ts.map +1 -0
  72. package/dist/dto/list-timesheet-entries.dto.js +54 -0
  73. package/dist/dto/list-timesheet-entries.dto.js.map +1 -0
  74. package/dist/dto/update-collaborator-type.dto.d.ts +4 -0
  75. package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -0
  76. package/dist/dto/update-collaborator-type.dto.js +8 -0
  77. package/dist/dto/update-collaborator-type.dto.js.map +1 -0
  78. package/dist/dto/update-collaborator.dto.d.ts +4 -0
  79. package/dist/dto/update-collaborator.dto.d.ts.map +1 -0
  80. package/dist/dto/update-collaborator.dto.js +8 -0
  81. package/dist/dto/update-collaborator.dto.js.map +1 -0
  82. package/dist/dto/update-task.dto.d.ts +14 -0
  83. package/dist/dto/update-task.dto.d.ts.map +1 -0
  84. package/dist/dto/update-task.dto.js +84 -0
  85. package/dist/dto/update-task.dto.js.map +1 -0
  86. package/dist/operations.controller.d.ts +0 -1045
  87. package/dist/operations.controller.d.ts.map +1 -1
  88. package/dist/operations.controller.js +0 -429
  89. package/dist/operations.controller.js.map +1 -1
  90. package/dist/operations.module.d.ts.map +1 -1
  91. package/dist/operations.module.js +23 -2
  92. package/dist/operations.module.js.map +1 -1
  93. package/dist/operations.service.d.ts +429 -8
  94. package/dist/operations.service.d.ts.map +1 -1
  95. package/dist/operations.service.js +1931 -165
  96. package/dist/operations.service.js.map +1 -1
  97. package/dist/operations.service.spec.js +315 -1
  98. package/dist/operations.service.spec.js.map +1 -1
  99. package/dist/services/shared/operations-access.service.d.ts +16 -0
  100. package/dist/services/shared/operations-access.service.d.ts.map +1 -0
  101. package/dist/services/shared/operations-access.service.js +48 -0
  102. package/dist/services/shared/operations-access.service.js.map +1 -0
  103. package/hedhog/data/dashboard.yaml +20 -0
  104. package/hedhog/data/dashboard_component.yaml +274 -0
  105. package/hedhog/data/dashboard_component_role.yaml +174 -0
  106. package/hedhog/data/dashboard_item.yaml +299 -0
  107. package/hedhog/data/dashboard_role.yaml +20 -0
  108. package/hedhog/data/menu.yaml +30 -13
  109. package/hedhog/data/operations_collaborator_type.yaml +76 -0
  110. package/hedhog/data/route.yaml +196 -0
  111. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +231 -0
  112. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +125 -40
  113. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +740 -106
  114. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
  115. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
  116. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
  117. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
  118. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
  119. package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +38 -16
  120. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
  121. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1017 -649
  122. package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
  123. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
  124. package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +213 -0
  125. package/hedhog/frontend/app/_lib/api.ts.ejs +30 -1
  126. package/hedhog/frontend/app/_lib/types.ts.ejs +147 -39
  127. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +40 -9
  128. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
  129. package/hedhog/frontend/app/approvals/page.tsx.ejs +116 -98
  130. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +502 -0
  131. package/hedhog/frontend/app/collaborators/page.tsx.ejs +116 -72
  132. package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
  133. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +11 -9
  134. package/hedhog/frontend/app/departments/page.tsx.ejs +1 -1
  135. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
  136. package/hedhog/frontend/app/projects/page.tsx.ejs +364 -133
  137. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +244 -120
  138. package/hedhog/frontend/app/team/page.tsx.ejs +15 -2
  139. package/hedhog/frontend/app/time-off/page.tsx.ejs +158 -82
  140. package/hedhog/frontend/app/timesheets/page.tsx.ejs +814 -357
  141. package/hedhog/frontend/messages/en.json +268 -53
  142. package/hedhog/frontend/messages/pt.json +484 -271
  143. package/hedhog/table/operations_collaborator.yaml +26 -13
  144. package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -0
  145. package/hedhog/table/operations_collaborator_type.yaml +33 -0
  146. package/hedhog/table/operations_job_title.yaml +24 -0
  147. package/hedhog/table/operations_project.yaml +9 -0
  148. package/hedhog/table/operations_project_assignment.yaml +9 -0
  149. package/hedhog/table/operations_project_role.yaml +39 -0
  150. package/hedhog/table/operations_task.yaml +69 -0
  151. package/hedhog/table/operations_timesheet_entry.yaml +12 -0
  152. package/package.json +6 -6
  153. package/src/controllers/operations-approvals.controller.ts +24 -0
  154. package/src/controllers/operations-collaborators.controller.ts +60 -0
  155. package/src/controllers/operations-contracts.controller.ts +138 -0
  156. package/src/controllers/operations-org-structure.controller.ts +92 -0
  157. package/src/controllers/operations-projects.controller.ts +50 -0
  158. package/src/controllers/operations-tasks.controller.ts +63 -0
  159. package/src/controllers/operations-timesheets.controller.ts +100 -0
  160. package/src/dto/create-collaborator-type.dto.ts +43 -0
  161. package/src/dto/create-collaborator.dto.ts +223 -0
  162. package/src/dto/create-schedule-adjustment-request.dto.ts +91 -0
  163. package/src/dto/create-task.dto.ts +75 -0
  164. package/src/dto/create-time-off-request.dto.ts +53 -0
  165. package/src/dto/create-timesheet-entry.dto.ts +67 -0
  166. package/src/dto/list-collaborator-types.dto.ts +15 -0
  167. package/src/dto/list-collaborators.dto.ts +30 -0
  168. package/src/dto/list-project-options.dto.ts +3 -0
  169. package/src/dto/list-tasks.dto.ts +25 -0
  170. package/src/dto/list-timesheet-entries.dto.ts +40 -0
  171. package/src/dto/update-collaborator-type.dto.ts +3 -0
  172. package/src/dto/update-collaborator.dto.ts +3 -0
  173. package/src/dto/update-task.dto.ts +76 -0
  174. package/src/operations.controller.ts +1 -278
  175. package/src/operations.module.ts +23 -2
  176. package/src/operations.service.spec.ts +450 -0
  177. package/src/operations.service.ts +4507 -1561
  178. 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 { fetchOperations, mutateOperations } from '../_lib/api';
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 { parseNumberInput, trimToNull } from '../_lib/utils/forms';
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
- collaboratorType: string;
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
- collaboratorType: 'clt',
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
- collaboratorType: collaborator.collaboratorType ?? 'other',
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.relatedContracts?.[0]?.budgetAmount !== null &&
237
- collaborator.relatedContracts?.[0]?.budgetAmount !== undefined
238
- ? String(collaborator.relatedContracts[0].budgetAmount)
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 { data: collaborator, isLoading: isLoadingCollaborator } =
455
- useQuery<OperationsCollaboratorDetails>({
456
- queryKey: [
457
- 'operations-collaborator-form',
458
- currentLocaleCode,
459
- collaboratorId,
460
- ],
461
- enabled: Boolean(collaboratorId),
462
- queryFn: () =>
463
- fetchOperations<OperationsCollaboratorDetails>(
464
- request,
465
- `/operations/collaborators/${collaboratorId}`
466
- ),
467
- });
468
-
469
- const { data: collaborators = [] } = useQuery<OperationsCollaborator[]>({
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<OperationsDepartment[]>({
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
- collaboratorType: form.collaboratorType,
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: parseNumberInput(form.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
- collaboratorId ? t('messages.updateError') : t('messages.createError')
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(collaborator.collaboratorType)}
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 xl:col-span-4">
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
- <Label>{t('fields.code')}</Label>
781
- <Input
782
- className="h-10"
783
- value={form.code}
784
- placeholder="COL-001"
785
- onChange={(event) =>
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
- code: event.target.value,
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.levelLabel')}</Label>
1213
+ <Label>{t('fields.code')}</Label>
795
1214
  <Input
796
1215
  className="h-10"
797
- value={form.levelLabel}
1216
+ value={form.code}
1217
+ placeholder="COL-001"
798
1218
  onChange={(event) =>
799
1219
  setForm((current) => ({
800
1220
  ...current,
801
- levelLabel: event.target.value,
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
- <Label>{t('fields.title')}</Label>
825
- <Input
826
- className="h-10"
1269
+ <DepartmentSelectWithCreate
1270
+ label={t('fields.title')}
827
1271
  value={form.title}
828
- onChange={(event) =>
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
- title: event.target.value,
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
- {t('sections.employmentInfoDescription')}
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.collaboratorType}
1309
+ value={form.collaboratorTypeId}
857
1310
  onValueChange={(value) =>
858
1311
  setForm((current) => ({
859
1312
  ...current,
860
- collaboratorType: value,
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
- <SelectItem value="clt">
869
- {t('options.collaboratorTypes.clt')}
870
- </SelectItem>
871
- <SelectItem value="pj">
872
- {t('options.collaboratorTypes.pj')}
873
- </SelectItem>
874
- <SelectItem value="freelancer">
875
- {t('options.collaboratorTypes.freelancer')}
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={collaborators.filter((item) => item.id !== collaboratorId)}
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={form.compensationAmount}
1009
- onChange={(event) =>
1637
+ value={
1638
+ form.compensationAmount === ''
1639
+ ? ''
1640
+ : Number(form.compensationAmount)
1641
+ }
1642
+ onValueChange={(value) =>
1010
1643
  setForm((current) => ({
1011
1644
  ...current,
1012
- compensationAmount: event.target.value,
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
- {formatEnumLabel(day.weekday)}
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">