@hed-hog/operations 0.0.295 → 0.0.296

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/operations.controller.d.ts +415 -0
  2. package/dist/operations.controller.d.ts.map +1 -0
  3. package/dist/operations.controller.js +333 -0
  4. package/dist/operations.controller.js.map +1 -0
  5. package/dist/operations.module.d.ts.map +1 -1
  6. package/dist/operations.module.js +4 -3
  7. package/dist/operations.module.js.map +1 -1
  8. package/dist/operations.service.d.ts +589 -153
  9. package/dist/operations.service.d.ts.map +1 -1
  10. package/dist/operations.service.js +2229 -100
  11. package/dist/operations.service.js.map +1 -1
  12. package/hedhog/data/menu.yaml +198 -251
  13. package/hedhog/data/role.yaml +23 -14
  14. package/hedhog/data/route.yaml +317 -143
  15. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +310 -0
  16. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +631 -0
  17. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +132 -0
  18. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +558 -0
  19. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +291 -0
  20. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +689 -0
  21. package/hedhog/frontend/app/_lib/api.ts.ejs +32 -0
  22. package/hedhog/frontend/app/_lib/hooks/use-operations-access.ts.ejs +44 -0
  23. package/hedhog/frontend/app/_lib/types.ts.ejs +360 -0
  24. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +129 -25
  25. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +14 -0
  26. package/hedhog/frontend/app/approvals/page.tsx.ejs +386 -147
  27. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +11 -0
  28. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +11 -0
  29. package/hedhog/frontend/app/collaborators/new/page.tsx.ejs +5 -0
  30. package/hedhog/frontend/app/collaborators/page.tsx.ejs +261 -0
  31. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +11 -0
  32. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +11 -108
  33. package/hedhog/frontend/app/contracts/new/page.tsx.ejs +17 -0
  34. package/hedhog/frontend/app/contracts/page.tsx.ejs +262 -181
  35. package/hedhog/frontend/app/page.tsx.ejs +319 -177
  36. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +11 -0
  37. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +11 -936
  38. package/hedhog/frontend/app/projects/new/page.tsx.ejs +5 -0
  39. package/hedhog/frontend/app/projects/page.tsx.ejs +236 -1074
  40. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +418 -0
  41. package/hedhog/frontend/app/team/page.tsx.ejs +339 -0
  42. package/hedhog/frontend/app/time-off/page.tsx.ejs +328 -0
  43. package/hedhog/frontend/app/timesheets/page.tsx.ejs +636 -126
  44. package/hedhog/frontend/messages/en.json +648 -454
  45. package/hedhog/frontend/messages/pt.json +647 -454
  46. package/hedhog/table/operations_approval.yaml +49 -0
  47. package/hedhog/table/operations_approval_history.yaml +29 -0
  48. package/hedhog/table/{operations_employee.yaml → operations_collaborator.yaml} +67 -64
  49. package/hedhog/table/operations_collaborator_schedule_day.yaml +34 -0
  50. package/hedhog/table/operations_contract.yaml +100 -48
  51. package/hedhog/table/operations_contract_document.yaml +39 -0
  52. package/hedhog/table/operations_contract_financial_term.yaml +40 -0
  53. package/hedhog/table/operations_contract_history.yaml +27 -0
  54. package/hedhog/table/operations_contract_party.yaml +46 -0
  55. package/hedhog/table/operations_contract_revision.yaml +38 -0
  56. package/hedhog/table/operations_contract_signature.yaml +38 -0
  57. package/hedhog/table/operations_project.yaml +54 -50
  58. package/hedhog/table/{operations_allocation.yaml → operations_project_assignment.yaml} +55 -52
  59. package/hedhog/table/operations_schedule_adjustment_day.yaml +34 -0
  60. package/hedhog/table/operations_schedule_adjustment_request.yaml +53 -0
  61. package/hedhog/table/operations_time_off_request.yaml +57 -0
  62. package/hedhog/table/operations_timesheet.yaml +41 -36
  63. package/hedhog/table/operations_timesheet_entry.yaml +40 -50
  64. package/package.json +8 -7
  65. package/src/operations.controller.ts +182 -0
  66. package/src/operations.module.ts +22 -21
  67. package/src/operations.service.ts +3595 -137
  68. package/hedhog/data/operations_career_level.yaml +0 -102
  69. package/hedhog/data/operations_career_track.yaml +0 -8
  70. package/hedhog/data/operations_certification.yaml +0 -38
  71. package/hedhog/data/operations_evaluation_cycle.yaml +0 -18
  72. package/hedhog/data/operations_performance_criterion.yaml +0 -48
  73. package/hedhog/frontend/app/_components/allocation-calendar.tsx.ejs +0 -56
  74. package/hedhog/frontend/app/_components/kanban-board.tsx.ejs +0 -626
  75. package/hedhog/frontend/app/_components/timesheet-entry-dialog.tsx.ejs +0 -142
  76. package/hedhog/frontend/app/_lib/hooks/use-operations-data.ts.ejs +0 -41
  77. package/hedhog/frontend/app/_lib/hooks/use-operations-growth-data.ts.ejs +0 -63
  78. package/hedhog/frontend/app/_lib/mocks/allocations.mock.ts.ejs +0 -74
  79. package/hedhog/frontend/app/_lib/mocks/contracts.mock.ts.ejs +0 -74
  80. package/hedhog/frontend/app/_lib/mocks/operations-growth.mock.ts.ejs +0 -824
  81. package/hedhog/frontend/app/_lib/mocks/projects.mock.ts.ejs +0 -455
  82. package/hedhog/frontend/app/_lib/mocks/tasks.mock.ts.ejs +0 -117
  83. package/hedhog/frontend/app/_lib/mocks/timesheets.mock.ts.ejs +0 -84
  84. package/hedhog/frontend/app/_lib/mocks/users.mock.ts.ejs +0 -67
  85. package/hedhog/frontend/app/_lib/services/contracts.service.ts.ejs +0 -10
  86. package/hedhog/frontend/app/_lib/services/operations-growth.service.ts.ejs +0 -31
  87. package/hedhog/frontend/app/_lib/services/projects.service.ts.ejs +0 -10
  88. package/hedhog/frontend/app/_lib/services/tasks.service.ts.ejs +0 -10
  89. package/hedhog/frontend/app/_lib/services/timesheets.service.ts.ejs +0 -10
  90. package/hedhog/frontend/app/_lib/types/operations-growth.ts.ejs +0 -209
  91. package/hedhog/frontend/app/_lib/types/operations.ts.ejs +0 -156
  92. package/hedhog/frontend/app/_lib/utils/growth.ts.ejs +0 -62
  93. package/hedhog/frontend/app/_lib/utils/metrics.ts.ejs +0 -103
  94. package/hedhog/frontend/app/_lib/utils/status.ts.ejs +0 -80
  95. package/hedhog/frontend/app/allocations/page.tsx.ejs +0 -155
  96. package/hedhog/frontend/app/career/page.tsx.ejs +0 -143
  97. package/hedhog/frontend/app/certifications/page.tsx.ejs +0 -202
  98. package/hedhog/frontend/app/evaluations/page.tsx.ejs +0 -278
  99. package/hedhog/frontend/app/goals/page.tsx.ejs +0 -171
  100. package/hedhog/frontend/app/growth/page.tsx.ejs +0 -288
  101. package/hedhog/frontend/app/manager/page.tsx.ejs +0 -175
  102. package/hedhog/frontend/app/rewards/page.tsx.ejs +0 -196
  103. package/hedhog/frontend/app/tasks/page.tsx.ejs +0 -999
  104. package/hedhog/table/operations_calibration_item.yaml +0 -61
  105. package/hedhog/table/operations_calibration_session.yaml +0 -25
  106. package/hedhog/table/operations_career_level.yaml +0 -75
  107. package/hedhog/table/operations_career_track.yaml +0 -21
  108. package/hedhog/table/operations_certification.yaml +0 -48
  109. package/hedhog/table/operations_employee_certification.yaml +0 -43
  110. package/hedhog/table/operations_employee_connect.yaml +0 -61
  111. package/hedhog/table/operations_employee_evaluation.yaml +0 -113
  112. package/hedhog/table/operations_employee_evaluation_item.yaml +0 -39
  113. package/hedhog/table/operations_employee_profile.yaml +0 -80
  114. package/hedhog/table/operations_employee_skill_matrix.yaml +0 -30
  115. package/hedhog/table/operations_evaluation_cycle.yaml +0 -31
  116. package/hedhog/table/operations_goal.yaml +0 -67
  117. package/hedhog/table/operations_goal_progress.yaml +0 -31
  118. package/hedhog/table/operations_performance_criterion.yaml +0 -29
  119. package/hedhog/table/operations_promotion_readiness.yaml +0 -49
  120. package/hedhog/table/operations_promotion_recommendation.yaml +0 -63
  121. package/hedhog/table/operations_public_recognition.yaml +0 -46
  122. package/hedhog/table/operations_reward.yaml +0 -100
  123. package/hedhog/table/operations_score_event.yaml +0 -81
  124. package/hedhog/table/operations_task.yaml +0 -60
  125. package/src/operations-data.controller.ts +0 -54
  126. package/src/operations-growth.controller.ts +0 -44
@@ -0,0 +1,631 @@
1
+ 'use client';
2
+
3
+ import { EmptyState, Page } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Input } from '@/components/ui/input';
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from '@/components/ui/select';
13
+ import { Switch } from '@/components/ui/switch';
14
+ import { Textarea } from '@/components/ui/textarea';
15
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
16
+ import { ArrowLeft, Save, UserRound } from 'lucide-react';
17
+ import Link from 'next/link';
18
+ import { useRouter } from 'next/navigation';
19
+ import { useEffect, useState } from 'react';
20
+ import { useTranslations } from 'next-intl';
21
+ import { OperationsHeader } from './operations-header';
22
+ import { SectionCard } from './section-card';
23
+ import { fetchOperations, mutateOperations } from '../_lib/api';
24
+ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
25
+ import type {
26
+ OperationsCollaborator,
27
+ OperationsCollaboratorDetails,
28
+ OperationsWeeklyScheduleDay,
29
+ } from '../_lib/types';
30
+ import { parseNumberInput, trimToNull } from '../_lib/utils/forms';
31
+ import { formatEnumLabel } from '../_lib/utils/format';
32
+
33
+ const weekdays = [
34
+ 'monday',
35
+ 'tuesday',
36
+ 'wednesday',
37
+ 'thursday',
38
+ 'friday',
39
+ 'saturday',
40
+ 'sunday',
41
+ ] as const;
42
+
43
+ type CollaboratorFormState = {
44
+ userId: string;
45
+ code: string;
46
+ displayName: string;
47
+ collaboratorType: string;
48
+ department: string;
49
+ title: string;
50
+ levelLabel: string;
51
+ weeklyCapacityHours: string;
52
+ status: string;
53
+ joinedAt: string;
54
+ leftAt: string;
55
+ supervisorCollaboratorId: string;
56
+ compensationAmount: string;
57
+ contractDescription: string;
58
+ autoGenerateContractDraft: boolean;
59
+ notes: string;
60
+ weeklySchedule: Array<{
61
+ weekday: string;
62
+ isWorkingDay: boolean;
63
+ startTime: string;
64
+ endTime: string;
65
+ breakMinutes: string;
66
+ }>;
67
+ };
68
+
69
+ function defaultSchedule(): CollaboratorFormState['weeklySchedule'] {
70
+ return weekdays.map((weekday) => ({
71
+ weekday,
72
+ isWorkingDay: !['saturday', 'sunday'].includes(weekday),
73
+ startTime: '09:00',
74
+ endTime: '18:00',
75
+ breakMinutes: '60',
76
+ }));
77
+ }
78
+
79
+ function buildEmptyForm(): CollaboratorFormState {
80
+ return {
81
+ userId: '',
82
+ code: '',
83
+ displayName: '',
84
+ collaboratorType: 'clt',
85
+ department: '',
86
+ title: '',
87
+ levelLabel: '',
88
+ weeklyCapacityHours: '40',
89
+ status: 'active',
90
+ joinedAt: '',
91
+ leftAt: '',
92
+ supervisorCollaboratorId: 'none',
93
+ compensationAmount: '',
94
+ contractDescription: '',
95
+ autoGenerateContractDraft: true,
96
+ notes: '',
97
+ weeklySchedule: defaultSchedule(),
98
+ };
99
+ }
100
+
101
+ function normalizeSchedule(
102
+ weeklySchedule?: OperationsWeeklyScheduleDay[]
103
+ ): CollaboratorFormState['weeklySchedule'] {
104
+ const byWeekday = new Map(
105
+ (weeklySchedule ?? []).map((item) => [item.weekday, item])
106
+ );
107
+
108
+ return weekdays.map((weekday) => {
109
+ const item = byWeekday.get(weekday);
110
+
111
+ return {
112
+ weekday,
113
+ isWorkingDay: item?.isWorkingDay ?? !['saturday', 'sunday'].includes(weekday),
114
+ startTime: item?.startTime ?? '09:00',
115
+ endTime: item?.endTime ?? '18:00',
116
+ breakMinutes:
117
+ item?.breakMinutes !== null && item?.breakMinutes !== undefined
118
+ ? String(item.breakMinutes)
119
+ : '60',
120
+ };
121
+ });
122
+ }
123
+
124
+ function toFormState(
125
+ collaborator: OperationsCollaboratorDetails
126
+ ): CollaboratorFormState {
127
+ return {
128
+ userId: collaborator.userId ? String(collaborator.userId) : '',
129
+ code: collaborator.code ?? '',
130
+ displayName: collaborator.displayName ?? '',
131
+ collaboratorType: collaborator.collaboratorType ?? 'other',
132
+ department: collaborator.department ?? '',
133
+ title: collaborator.title ?? '',
134
+ levelLabel: collaborator.levelLabel ?? '',
135
+ weeklyCapacityHours:
136
+ collaborator.weeklyCapacityHours !== null &&
137
+ collaborator.weeklyCapacityHours !== undefined
138
+ ? String(collaborator.weeklyCapacityHours)
139
+ : '',
140
+ status: collaborator.status ?? 'active',
141
+ joinedAt: collaborator.joinedAt ?? '',
142
+ leftAt: collaborator.leftAt ?? '',
143
+ supervisorCollaboratorId: collaborator.supervisorId
144
+ ? String(collaborator.supervisorId)
145
+ : 'none',
146
+ compensationAmount:
147
+ collaborator.relatedContracts?.[0]?.budgetAmount !== null &&
148
+ collaborator.relatedContracts?.[0]?.budgetAmount !== undefined
149
+ ? String(collaborator.relatedContracts[0].budgetAmount)
150
+ : '',
151
+ contractDescription: collaborator.relatedContracts?.[0]?.description ?? '',
152
+ autoGenerateContractDraft: true,
153
+ notes: collaborator.notes ?? '',
154
+ weeklySchedule: normalizeSchedule(collaborator.weeklySchedule),
155
+ };
156
+ }
157
+
158
+ type CollaboratorFormScreenProps = {
159
+ collaboratorId?: number;
160
+ };
161
+
162
+ export function CollaboratorFormScreen({
163
+ collaboratorId,
164
+ }: CollaboratorFormScreenProps) {
165
+ const t = useTranslations('operations.CollaboratorFormPage');
166
+ const commonT = useTranslations('operations.Common');
167
+ const { request, showToastHandler, currentLocaleCode } = useApp();
168
+ const access = useOperationsAccess();
169
+ const router = useRouter();
170
+ const [form, setForm] = useState<CollaboratorFormState>(buildEmptyForm());
171
+
172
+ const { data: collaborator, isLoading: isLoadingCollaborator } =
173
+ useQuery<OperationsCollaboratorDetails>({
174
+ queryKey: ['operations-collaborator-form', currentLocaleCode, collaboratorId],
175
+ enabled: Boolean(collaboratorId),
176
+ queryFn: () =>
177
+ fetchOperations<OperationsCollaboratorDetails>(
178
+ request,
179
+ `/operations/collaborators/${collaboratorId}`
180
+ ),
181
+ });
182
+
183
+ const { data: collaborators = [] } = useQuery<OperationsCollaborator[]>({
184
+ queryKey: ['operations-collaborator-form-supervisors', currentLocaleCode],
185
+ enabled: access.isDirector,
186
+ queryFn: () =>
187
+ fetchOperations<OperationsCollaborator[]>(request, '/operations/collaborators'),
188
+ });
189
+
190
+ useEffect(() => {
191
+ if (collaborator) {
192
+ setForm(toFormState(collaborator));
193
+ }
194
+ }, [collaborator]);
195
+
196
+ const updateScheduleDay = (
197
+ weekday: string,
198
+ patch: Partial<CollaboratorFormState['weeklySchedule'][number]>
199
+ ) => {
200
+ setForm((current) => ({
201
+ ...current,
202
+ weeklySchedule: current.weeklySchedule.map((day) =>
203
+ day.weekday === weekday ? { ...day, ...patch } : day
204
+ ),
205
+ }));
206
+ };
207
+
208
+ const onSubmit = async () => {
209
+ const userId = parseNumberInput(form.userId);
210
+ if (!collaboratorId && !userId) {
211
+ showToastHandler?.('error', t('messages.userRequired'));
212
+ return;
213
+ }
214
+
215
+ if (!form.code.trim() || !form.displayName.trim()) {
216
+ showToastHandler?.('error', t('messages.requiredFields'));
217
+ return;
218
+ }
219
+
220
+ const payload = {
221
+ userId: userId ?? undefined,
222
+ code: form.code.trim(),
223
+ displayName: form.displayName.trim(),
224
+ collaboratorType: form.collaboratorType,
225
+ department: trimToNull(form.department),
226
+ title: trimToNull(form.title),
227
+ levelLabel: trimToNull(form.levelLabel),
228
+ weeklyCapacityHours: parseNumberInput(form.weeklyCapacityHours),
229
+ status: form.status,
230
+ joinedAt: trimToNull(form.joinedAt),
231
+ leftAt: trimToNull(form.leftAt),
232
+ supervisorCollaboratorId:
233
+ form.supervisorCollaboratorId === 'none'
234
+ ? null
235
+ : parseNumberInput(form.supervisorCollaboratorId),
236
+ compensationAmount: parseNumberInput(form.compensationAmount),
237
+ contractDescription: trimToNull(form.contractDescription),
238
+ autoGenerateContractDraft: form.autoGenerateContractDraft,
239
+ notes: trimToNull(form.notes),
240
+ weeklySchedule: form.weeklySchedule.map((day) => ({
241
+ weekday: day.weekday,
242
+ isWorkingDay: day.isWorkingDay,
243
+ startTime: day.isWorkingDay ? trimToNull(day.startTime) : null,
244
+ endTime: day.isWorkingDay ? trimToNull(day.endTime) : null,
245
+ breakMinutes: day.isWorkingDay ? parseNumberInput(day.breakMinutes) : 0,
246
+ })),
247
+ };
248
+
249
+ try {
250
+ const response = collaboratorId
251
+ ? await mutateOperations<OperationsCollaboratorDetails>(
252
+ request,
253
+ `/operations/collaborators/${collaboratorId}`,
254
+ 'PATCH',
255
+ payload
256
+ )
257
+ : await mutateOperations<OperationsCollaboratorDetails>(
258
+ request,
259
+ '/operations/collaborators',
260
+ 'POST',
261
+ payload
262
+ );
263
+
264
+ showToastHandler?.(
265
+ 'success',
266
+ collaboratorId ? t('messages.updateSuccess') : t('messages.createSuccess')
267
+ );
268
+ router.push(`/operations/collaborators/${response.id}`);
269
+ } catch {
270
+ showToastHandler?.(
271
+ 'error',
272
+ collaboratorId ? t('messages.updateError') : t('messages.createError')
273
+ );
274
+ }
275
+ };
276
+
277
+ if (!access.isDirector && !access.isLoading) {
278
+ return (
279
+ <Page>
280
+ <OperationsHeader
281
+ title={t(collaboratorId ? 'editTitle' : 'newTitle')}
282
+ description={t('description')}
283
+ current={t('breadcrumb')}
284
+ />
285
+ <EmptyState
286
+ icon={<UserRound className="size-12" />}
287
+ title={commonT('states.noAccessTitle')}
288
+ description={t('noAccessDescription')}
289
+ actionLabel={commonT('actions.refresh')}
290
+ onAction={() => router.refresh()}
291
+ />
292
+ </Page>
293
+ );
294
+ }
295
+
296
+ return (
297
+ <Page>
298
+ <OperationsHeader
299
+ title={t(collaboratorId ? 'editTitle' : 'newTitle')}
300
+ description={t('description')}
301
+ current={t('breadcrumb')}
302
+ actions={
303
+ <div className="flex gap-2">
304
+ <Button variant="outline" size="sm" asChild>
305
+ <Link
306
+ href={
307
+ collaboratorId
308
+ ? `/operations/collaborators/${collaboratorId}`
309
+ : '/operations/collaborators'
310
+ }
311
+ >
312
+ <ArrowLeft className="size-4" />
313
+ {commonT('actions.back')}
314
+ </Link>
315
+ </Button>
316
+ <Button size="sm" onClick={() => void onSubmit()}>
317
+ <Save className="size-4" />
318
+ {commonT('actions.save')}
319
+ </Button>
320
+ </div>
321
+ }
322
+ />
323
+
324
+ <div className="grid gap-4 xl:grid-cols-2">
325
+ <SectionCard title={t('sections.basicInfo')} description={t('sections.basicInfoDescription')}>
326
+ <div className="grid gap-4 md:grid-cols-2">
327
+ <div className="space-y-2">
328
+ <label className="text-sm font-medium">{t('fields.userId')}</label>
329
+ <Input
330
+ value={form.userId}
331
+ disabled={Boolean(collaboratorId)}
332
+ onChange={(event) =>
333
+ setForm((current) => ({ ...current, userId: event.target.value }))
334
+ }
335
+ />
336
+ </div>
337
+ <div className="space-y-2">
338
+ <label className="text-sm font-medium">{t('fields.code')}</label>
339
+ <Input
340
+ value={form.code}
341
+ onChange={(event) =>
342
+ setForm((current) => ({ ...current, code: event.target.value }))
343
+ }
344
+ />
345
+ </div>
346
+ <div className="space-y-2">
347
+ <label className="text-sm font-medium">{t('fields.displayName')}</label>
348
+ <Input
349
+ value={form.displayName}
350
+ onChange={(event) =>
351
+ setForm((current) => ({
352
+ ...current,
353
+ displayName: event.target.value,
354
+ }))
355
+ }
356
+ />
357
+ </div>
358
+ <div className="space-y-2">
359
+ <label className="text-sm font-medium">{t('fields.department')}</label>
360
+ <Input
361
+ value={form.department}
362
+ onChange={(event) =>
363
+ setForm((current) => ({
364
+ ...current,
365
+ department: event.target.value,
366
+ }))
367
+ }
368
+ />
369
+ </div>
370
+ <div className="space-y-2">
371
+ <label className="text-sm font-medium">{t('fields.title')}</label>
372
+ <Input
373
+ value={form.title}
374
+ onChange={(event) =>
375
+ setForm((current) => ({ ...current, title: event.target.value }))
376
+ }
377
+ />
378
+ </div>
379
+ <div className="space-y-2">
380
+ <label className="text-sm font-medium">{t('fields.levelLabel')}</label>
381
+ <Input
382
+ value={form.levelLabel}
383
+ onChange={(event) =>
384
+ setForm((current) => ({
385
+ ...current,
386
+ levelLabel: event.target.value,
387
+ }))
388
+ }
389
+ />
390
+ </div>
391
+ <div className="space-y-2 md:col-span-2">
392
+ <label className="text-sm font-medium">{t('fields.notes')}</label>
393
+ <Textarea
394
+ rows={4}
395
+ value={form.notes}
396
+ onChange={(event) =>
397
+ setForm((current) => ({ ...current, notes: event.target.value }))
398
+ }
399
+ />
400
+ </div>
401
+ </div>
402
+ </SectionCard>
403
+
404
+ <SectionCard
405
+ title={t('sections.employmentInfo')}
406
+ description={t('sections.employmentInfoDescription')}
407
+ >
408
+ <div className="grid gap-4 md:grid-cols-2">
409
+ <div className="space-y-2">
410
+ <label className="text-sm font-medium">{t('fields.collaboratorType')}</label>
411
+ <Select
412
+ value={form.collaboratorType}
413
+ onValueChange={(value) =>
414
+ setForm((current) => ({ ...current, collaboratorType: value }))
415
+ }
416
+ >
417
+ <SelectTrigger>
418
+ <SelectValue />
419
+ </SelectTrigger>
420
+ <SelectContent>
421
+ <SelectItem value="clt">CLT</SelectItem>
422
+ <SelectItem value="pj">PJ</SelectItem>
423
+ <SelectItem value="freelancer">Freelancer</SelectItem>
424
+ <SelectItem value="intern">Intern</SelectItem>
425
+ <SelectItem value="other">Other</SelectItem>
426
+ </SelectContent>
427
+ </Select>
428
+ </div>
429
+ <div className="space-y-2">
430
+ <label className="text-sm font-medium">{commonT('labels.status')}</label>
431
+ <Select
432
+ value={form.status}
433
+ onValueChange={(value) =>
434
+ setForm((current) => ({ ...current, status: value }))
435
+ }
436
+ >
437
+ <SelectTrigger>
438
+ <SelectValue />
439
+ </SelectTrigger>
440
+ <SelectContent>
441
+ <SelectItem value="active">Active</SelectItem>
442
+ <SelectItem value="on_leave">On Leave</SelectItem>
443
+ <SelectItem value="inactive">Inactive</SelectItem>
444
+ </SelectContent>
445
+ </Select>
446
+ </div>
447
+ <div className="space-y-2">
448
+ <label className="text-sm font-medium">{commonT('labels.startDate')}</label>
449
+ <Input
450
+ type="date"
451
+ value={form.joinedAt}
452
+ onChange={(event) =>
453
+ setForm((current) => ({ ...current, joinedAt: event.target.value }))
454
+ }
455
+ />
456
+ </div>
457
+ <div className="space-y-2">
458
+ <label className="text-sm font-medium">{commonT('labels.endDate')}</label>
459
+ <Input
460
+ type="date"
461
+ value={form.leftAt}
462
+ onChange={(event) =>
463
+ setForm((current) => ({ ...current, leftAt: event.target.value }))
464
+ }
465
+ />
466
+ </div>
467
+ </div>
468
+ </SectionCard>
469
+
470
+ <SectionCard
471
+ title={t('sections.supervisor')}
472
+ description={t('sections.supervisorDescription')}
473
+ >
474
+ <div className="grid gap-4 md:grid-cols-2">
475
+ <div className="space-y-2">
476
+ <label className="text-sm font-medium">{commonT('labels.supervisor')}</label>
477
+ <Select
478
+ value={form.supervisorCollaboratorId}
479
+ onValueChange={(value) =>
480
+ setForm((current) => ({
481
+ ...current,
482
+ supervisorCollaboratorId: value,
483
+ }))
484
+ }
485
+ >
486
+ <SelectTrigger>
487
+ <SelectValue />
488
+ </SelectTrigger>
489
+ <SelectContent>
490
+ <SelectItem value="none">{commonT('labels.notAssigned')}</SelectItem>
491
+ {collaborators
492
+ .filter((item) => item.id !== collaboratorId)
493
+ .map((item) => (
494
+ <SelectItem key={item.id} value={String(item.id)}>
495
+ {item.displayName}
496
+ </SelectItem>
497
+ ))}
498
+ </SelectContent>
499
+ </Select>
500
+ </div>
501
+ </div>
502
+ </SectionCard>
503
+
504
+ <SectionCard
505
+ title={t('sections.contract')}
506
+ description={t('sections.contractDescription')}
507
+ >
508
+ <div className="grid gap-4 md:grid-cols-2">
509
+ <div className="space-y-2">
510
+ <label className="text-sm font-medium">{t('fields.weeklyCapacityHours')}</label>
511
+ <Input
512
+ type="number"
513
+ step="0.5"
514
+ value={form.weeklyCapacityHours}
515
+ onChange={(event) =>
516
+ setForm((current) => ({
517
+ ...current,
518
+ weeklyCapacityHours: event.target.value,
519
+ }))
520
+ }
521
+ />
522
+ </div>
523
+ <div className="space-y-2">
524
+ <label className="text-sm font-medium">{t('fields.compensationAmount')}</label>
525
+ <Input
526
+ type="number"
527
+ step="0.01"
528
+ value={form.compensationAmount}
529
+ onChange={(event) =>
530
+ setForm((current) => ({
531
+ ...current,
532
+ compensationAmount: event.target.value,
533
+ }))
534
+ }
535
+ />
536
+ </div>
537
+ <div className="space-y-2 md:col-span-2">
538
+ <label className="text-sm font-medium">{t('fields.contractDescription')}</label>
539
+ <Textarea
540
+ rows={4}
541
+ value={form.contractDescription}
542
+ onChange={(event) =>
543
+ setForm((current) => ({
544
+ ...current,
545
+ contractDescription: event.target.value,
546
+ }))
547
+ }
548
+ />
549
+ </div>
550
+ <div className="flex items-center justify-between rounded-lg border px-4 py-3 md:col-span-2">
551
+ <div>
552
+ <div className="font-medium">{t('fields.autoGenerateContractDraft')}</div>
553
+ <div className="text-sm text-muted-foreground">
554
+ {t('fields.autoGenerateContractDraftDescription')}
555
+ </div>
556
+ </div>
557
+ <Switch
558
+ checked={form.autoGenerateContractDraft}
559
+ onCheckedChange={(checked) =>
560
+ setForm((current) => ({
561
+ ...current,
562
+ autoGenerateContractDraft: checked,
563
+ }))
564
+ }
565
+ />
566
+ </div>
567
+ </div>
568
+ </SectionCard>
569
+ </div>
570
+
571
+ <SectionCard
572
+ title={t('sections.schedule')}
573
+ description={t('sections.scheduleDescription')}
574
+ >
575
+ <div className="space-y-3">
576
+ {form.weeklySchedule.map((day) => (
577
+ <div
578
+ key={day.weekday}
579
+ className="grid gap-3 rounded-lg border p-4 lg:grid-cols-[1fr_auto_1fr_1fr_1fr]"
580
+ >
581
+ <div>
582
+ <div className="font-medium">{formatEnumLabel(day.weekday)}</div>
583
+ <div className="text-xs text-muted-foreground">
584
+ {day.isWorkingDay ? commonT('labels.workingDay') : commonT('labels.dayOff')}
585
+ </div>
586
+ </div>
587
+ <div className="flex items-center gap-2">
588
+ <Switch
589
+ checked={day.isWorkingDay}
590
+ onCheckedChange={(checked) =>
591
+ updateScheduleDay(day.weekday, { isWorkingDay: checked })
592
+ }
593
+ />
594
+ </div>
595
+ <Input
596
+ type="time"
597
+ value={day.startTime}
598
+ disabled={!day.isWorkingDay}
599
+ onChange={(event) =>
600
+ updateScheduleDay(day.weekday, { startTime: event.target.value })
601
+ }
602
+ />
603
+ <Input
604
+ type="time"
605
+ value={day.endTime}
606
+ disabled={!day.isWorkingDay}
607
+ onChange={(event) =>
608
+ updateScheduleDay(day.weekday, { endTime: event.target.value })
609
+ }
610
+ />
611
+ <Input
612
+ type="number"
613
+ value={day.breakMinutes}
614
+ disabled={!day.isWorkingDay}
615
+ onChange={(event) =>
616
+ updateScheduleDay(day.weekday, {
617
+ breakMinutes: event.target.value,
618
+ })
619
+ }
620
+ />
621
+ </div>
622
+ ))}
623
+ </div>
624
+ </SectionCard>
625
+
626
+ {collaboratorId && isLoadingCollaborator ? (
627
+ <div className="text-sm text-muted-foreground">{t('loading')}</div>
628
+ ) : null}
629
+ </Page>
630
+ );
631
+ }