@hed-hog/operations 0.0.299 → 0.0.301

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 (97) hide show
  1. package/dist/operations.controller.d.ts +713 -31
  2. package/dist/operations.controller.d.ts.map +1 -1
  3. package/dist/operations.controller.js +157 -0
  4. package/dist/operations.controller.js.map +1 -1
  5. package/dist/operations.module.d.ts.map +1 -1
  6. package/dist/operations.module.js +5 -1
  7. package/dist/operations.module.js.map +1 -1
  8. package/dist/operations.proposal.subscriber.d.ts +11 -0
  9. package/dist/operations.proposal.subscriber.d.ts.map +1 -0
  10. package/dist/operations.proposal.subscriber.js +80 -0
  11. package/dist/operations.proposal.subscriber.js.map +1 -0
  12. package/dist/operations.proposal.subscriber.spec.d.ts +2 -0
  13. package/dist/operations.proposal.subscriber.spec.d.ts.map +1 -0
  14. package/dist/operations.proposal.subscriber.spec.js +88 -0
  15. package/dist/operations.proposal.subscriber.spec.js.map +1 -0
  16. package/dist/operations.service.d.ts +490 -46
  17. package/dist/operations.service.d.ts.map +1 -1
  18. package/dist/operations.service.js +3590 -1267
  19. package/dist/operations.service.js.map +1 -1
  20. package/dist/operations.service.spec.d.ts +2 -0
  21. package/dist/operations.service.spec.d.ts.map +1 -0
  22. package/dist/operations.service.spec.js +159 -0
  23. package/dist/operations.service.spec.js.map +1 -0
  24. package/hedhog/data/menu.yaml +232 -198
  25. package/hedhog/data/role.yaml +23 -23
  26. package/hedhog/data/role_route.yaml +39 -0
  27. package/hedhog/data/route.yaml +447 -317
  28. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +8 -6
  29. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +1163 -327
  30. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -0
  31. package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
  32. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +631 -0
  33. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +353 -27
  34. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +1926 -87
  35. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +526 -0
  36. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -0
  37. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -0
  38. package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +370 -0
  39. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +826 -0
  40. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1251 -364
  41. package/hedhog/frontend/app/_components/section-card.tsx.ejs +48 -13
  42. package/hedhog/frontend/app/_lib/api.ts.ejs +2 -5
  43. package/hedhog/frontend/app/_lib/types.ts.ejs +76 -33
  44. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +85 -8
  45. package/hedhog/frontend/app/approvals/page.tsx.ejs +90 -54
  46. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +2 -2
  47. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +2 -2
  48. package/hedhog/frontend/app/collaborators/page.tsx.ejs +597 -140
  49. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +2 -2
  50. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  51. package/hedhog/frontend/app/contracts/page.tsx.ejs +941 -262
  52. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +384 -0
  53. package/hedhog/frontend/app/departments/page.tsx.ejs +442 -0
  54. package/hedhog/frontend/app/page.tsx.ejs +36 -12
  55. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +2 -2
  56. package/hedhog/frontend/app/projects/new/page.tsx.ejs +2 -2
  57. package/hedhog/frontend/app/projects/page.tsx.ejs +264 -102
  58. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +50 -28
  59. package/hedhog/frontend/app/time-off/page.tsx.ejs +57 -31
  60. package/hedhog/frontend/app/timesheets/page.tsx.ejs +85 -42
  61. package/hedhog/frontend/messages/en.json +473 -12
  62. package/hedhog/frontend/messages/pt.json +528 -66
  63. package/hedhog/table/operations_approval.yaml +49 -49
  64. package/hedhog/table/operations_approval_history.yaml +29 -29
  65. package/hedhog/table/operations_collaborator.yaml +87 -67
  66. package/hedhog/table/operations_collaborator_schedule_day.yaml +34 -34
  67. package/hedhog/table/operations_contract.yaml +121 -100
  68. package/hedhog/table/operations_contract_document.yaml +40 -23
  69. package/hedhog/table/operations_contract_financial_term.yaml +40 -40
  70. package/hedhog/table/operations_contract_history.yaml +27 -27
  71. package/hedhog/table/operations_contract_party.yaml +46 -46
  72. package/hedhog/table/operations_contract_revision.yaml +38 -38
  73. package/hedhog/table/operations_contract_signature.yaml +38 -38
  74. package/hedhog/table/operations_contract_template.yaml +58 -0
  75. package/hedhog/table/operations_department.yaml +24 -0
  76. package/hedhog/table/operations_project.yaml +54 -54
  77. package/hedhog/table/operations_project_assignment.yaml +55 -55
  78. package/hedhog/table/operations_schedule_adjustment_day.yaml +34 -34
  79. package/hedhog/table/operations_schedule_adjustment_request.yaml +53 -53
  80. package/hedhog/table/operations_time_off_request.yaml +57 -57
  81. package/hedhog/table/operations_timesheet.yaml +41 -41
  82. package/hedhog/table/operations_timesheet_entry.yaml +40 -40
  83. package/package.json +5 -3
  84. package/src/operations.controller.ts +304 -182
  85. package/src/operations.module.ts +26 -22
  86. package/src/operations.proposal.subscriber.spec.ts +121 -0
  87. package/src/operations.proposal.subscriber.ts +86 -0
  88. package/src/operations.service.spec.ts +210 -0
  89. package/src/operations.service.ts +7317 -3595
  90. package/dist/operations-data.controller.d.ts +0 -139
  91. package/dist/operations-data.controller.d.ts.map +0 -1
  92. package/dist/operations-data.controller.js +0 -113
  93. package/dist/operations-data.controller.js.map +0 -1
  94. package/dist/operations-growth.controller.d.ts +0 -48
  95. package/dist/operations-growth.controller.d.ts.map +0 -1
  96. package/dist/operations-growth.controller.js +0 -90
  97. package/dist/operations-growth.controller.js.map +0 -1
@@ -59,15 +59,33 @@ const emptyForm: TimeOffFormState = {
59
59
  reason: '',
60
60
  };
61
61
 
62
- export default function OperationsTimeOffPage() {
63
- const t = useTranslations('operations.TimeOffPage');
64
- const commonT = useTranslations('operations.Common');
65
- const { request, showToastHandler, currentLocaleCode } = useApp();
66
- const access = useOperationsAccess();
67
- const [search, setSearch] = useState('');
68
- const [statusFilter, setStatusFilter] = useState('all');
69
- const [isSheetOpen, setIsSheetOpen] = useState(false);
70
- const [form, setForm] = useState<TimeOffFormState>(emptyForm);
62
+ export default function OperationsTimeOffPage() {
63
+ const t = useTranslations('operations.TimeOffPage');
64
+ const commonT = useTranslations('operations.Common');
65
+ const { request, showToastHandler, currentLocaleCode } = useApp();
66
+ const access = useOperationsAccess();
67
+ const [search, setSearch] = useState('');
68
+ const [statusFilter, setStatusFilter] = useState('all');
69
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
70
+ const [form, setForm] = useState<TimeOffFormState>(emptyForm);
71
+
72
+ const getRequestTypeLabel = (value?: string | null) => {
73
+ if (!value) {
74
+ return '-';
75
+ }
76
+
77
+ const key = `options.requestTypes.${value}`;
78
+ return t.has(key) ? t(key) : formatEnumLabel(value);
79
+ };
80
+
81
+ const getStatusLabel = (value?: string | null) => {
82
+ if (!value) {
83
+ return '-';
84
+ }
85
+
86
+ const key = `options.statuses.${value}`;
87
+ return t.has(key) ? t(key) : formatEnumLabel(value);
88
+ };
71
89
 
72
90
  const { data: requests = [], refetch } = useQuery<OperationsTimeOffRequest[]>({
73
91
  queryKey: ['operations-time-off', currentLocaleCode],
@@ -166,12 +184,12 @@ export default function OperationsTimeOffPage() {
166
184
  value: statusFilter,
167
185
  onChange: setStatusFilter,
168
186
  placeholder: commonT('labels.status'),
169
- options: [
170
- { value: 'all', label: commonT('filters.allStatuses') },
171
- { value: 'submitted', label: formatEnumLabel('submitted') },
172
- { value: 'approved', label: formatEnumLabel('approved') },
173
- { value: 'rejected', label: formatEnumLabel('rejected') },
174
- ],
187
+ options: [
188
+ { value: 'all', label: commonT('filters.allStatuses') },
189
+ { value: 'submitted', label: getStatusLabel('submitted') },
190
+ { value: 'approved', label: getStatusLabel('approved') },
191
+ { value: 'rejected', label: getStatusLabel('rejected') },
192
+ ],
175
193
  },
176
194
  ]}
177
195
  />
@@ -196,7 +214,7 @@ export default function OperationsTimeOffPage() {
196
214
  {filteredRows.map((requestItem) => (
197
215
  <TableRow key={requestItem.id}>
198
216
  <TableCell>{requestItem.collaboratorName}</TableCell>
199
- <TableCell>{formatEnumLabel(requestItem.requestType)}</TableCell>
217
+ <TableCell>{getRequestTypeLabel(requestItem.requestType)}</TableCell>
200
218
  <TableCell>
201
219
  <div>
202
220
  {formatDateRange(requestItem.startDate, requestItem.endDate)}
@@ -209,10 +227,10 @@ export default function OperationsTimeOffPage() {
209
227
  {requestItem.approverName || commonT('labels.notAssigned')}
210
228
  </TableCell>
211
229
  <TableCell>
212
- <StatusBadge
213
- label={formatEnumLabel(requestItem.status)}
214
- className={getStatusBadgeClass(requestItem.status)}
215
- />
230
+ <StatusBadge
231
+ label={getStatusLabel(requestItem.status)}
232
+ className={getStatusBadgeClass(requestItem.status)}
233
+ />
216
234
  </TableCell>
217
235
  <TableCell>
218
236
  {requestItem.reason || commonT('labels.noNotes')}
@@ -251,17 +269,25 @@ export default function OperationsTimeOffPage() {
251
269
  setForm((current) => ({ ...current, requestType: value }))
252
270
  }
253
271
  >
254
- <SelectTrigger>
255
- <SelectValue />
256
- </SelectTrigger>
257
- <SelectContent>
258
- <SelectItem value="vacation">Vacation</SelectItem>
259
- <SelectItem value="personal_time">Personal Time</SelectItem>
260
- <SelectItem value="sick_leave">Sick Leave</SelectItem>
261
- <SelectItem value="unpaid_leave">Unpaid Leave</SelectItem>
262
- <SelectItem value="other">Other</SelectItem>
263
- </SelectContent>
264
- </Select>
272
+ <SelectTrigger>
273
+ <SelectValue />
274
+ </SelectTrigger>
275
+ <SelectContent>
276
+ <SelectItem value="vacation">
277
+ {getRequestTypeLabel('vacation')}
278
+ </SelectItem>
279
+ <SelectItem value="personal_time">
280
+ {getRequestTypeLabel('personal_time')}
281
+ </SelectItem>
282
+ <SelectItem value="sick_leave">
283
+ {getRequestTypeLabel('sick_leave')}
284
+ </SelectItem>
285
+ <SelectItem value="unpaid_leave">
286
+ {getRequestTypeLabel('unpaid_leave')}
287
+ </SelectItem>
288
+ <SelectItem value="other">{getRequestTypeLabel('other')}</SelectItem>
289
+ </SelectContent>
290
+ </Select>
265
291
  </div>
266
292
 
267
293
  <div className="grid gap-4 md:grid-cols-2">
@@ -78,7 +78,9 @@ const emptyForm: TimesheetFormState = {
78
78
  entries: [createEmptyEntry()],
79
79
  };
80
80
 
81
- function toFormState(timesheet?: OperationsTimesheet | null): TimesheetFormState {
81
+ function toFormState(
82
+ timesheet?: OperationsTimesheet | null
83
+ ): TimesheetFormState {
82
84
  if (!timesheet) {
83
85
  return emptyForm;
84
86
  }
@@ -87,21 +89,20 @@ function toFormState(timesheet?: OperationsTimesheet | null): TimesheetFormState
87
89
  weekStartDate: timesheet.weekStartDate ?? '',
88
90
  weekEndDate: timesheet.weekEndDate ?? '',
89
91
  notes: timesheet.notes ?? '',
90
- entries:
91
- timesheet.entries?.length
92
- ? timesheet.entries.map((entry) => ({
93
- projectAssignmentId: entry.projectAssignmentId
94
- ? String(entry.projectAssignmentId)
95
- : 'none',
96
- activityLabel: entry.activityLabel ?? '',
97
- workDate: entry.workDate ?? '',
98
- hours:
99
- entry.hours !== null && entry.hours !== undefined
100
- ? String(entry.hours)
101
- : '',
102
- description: entry.description ?? '',
103
- }))
104
- : [createEmptyEntry()],
92
+ entries: timesheet.entries?.length
93
+ ? timesheet.entries.map((entry) => ({
94
+ projectAssignmentId: entry.projectAssignmentId
95
+ ? String(entry.projectAssignmentId)
96
+ : 'none',
97
+ activityLabel: entry.activityLabel ?? '',
98
+ workDate: entry.workDate ?? '',
99
+ hours:
100
+ entry.hours !== null && entry.hours !== undefined
101
+ ? String(entry.hours)
102
+ : '',
103
+ description: entry.description ?? '',
104
+ }))
105
+ : [createEmptyEntry()],
105
106
  };
106
107
  }
107
108
 
@@ -113,9 +114,8 @@ export default function OperationsTimesheetsPage() {
113
114
  const [search, setSearch] = useState('');
114
115
  const [statusFilter, setStatusFilter] = useState('all');
115
116
  const [isSheetOpen, setIsSheetOpen] = useState(false);
116
- const [editingTimesheet, setEditingTimesheet] = useState<OperationsTimesheet | null>(
117
- null
118
- );
117
+ const [editingTimesheet, setEditingTimesheet] =
118
+ useState<OperationsTimesheet | null>(null);
119
119
  const [form, setForm] = useState<TimesheetFormState>(emptyForm);
120
120
 
121
121
  const { data: timesheets = [], refetch } = useQuery<OperationsTimesheet[]>({
@@ -128,13 +128,17 @@ export default function OperationsTimesheetsPage() {
128
128
  queryKey: ['operations-timesheets-me', currentLocaleCode],
129
129
  enabled: access.isCollaborator,
130
130
  queryFn: () =>
131
- fetchOperations<OperationsCollaborator>(request, '/operations/collaborators/me'),
131
+ fetchOperations<OperationsCollaborator>(
132
+ request,
133
+ '/operations/collaborators/me'
134
+ ),
132
135
  });
133
136
 
134
137
  const { data: projects = [] } = useQuery<OperationsProject[]>({
135
138
  queryKey: ['operations-timesheet-project-options', currentLocaleCode],
136
139
  enabled: access.isCollaborator,
137
- queryFn: () => fetchOperations<OperationsProject[]>(request, '/operations/projects'),
140
+ queryFn: () =>
141
+ fetchOperations<OperationsProject[]>(request, '/operations/projects'),
138
142
  });
139
143
 
140
144
  const projectOptions = useMemo(
@@ -143,7 +147,9 @@ export default function OperationsTimesheetsPage() {
143
147
  .filter((project) => project.myAssignmentId)
144
148
  .map((project) => ({
145
149
  value: String(project.myAssignmentId),
146
- label: [project.name, project.myRoleLabel].filter(Boolean).join(' • '),
150
+ label: [project.name, project.myRoleLabel]
151
+ .filter(Boolean)
152
+ .join(' • '),
147
153
  })),
148
154
  [projects]
149
155
  );
@@ -164,7 +170,9 @@ export default function OperationsTimesheetsPage() {
164
170
  ]
165
171
  .filter(Boolean)
166
172
  .some((value) =>
167
- String(value).toLowerCase().includes(search.trim().toLowerCase())
173
+ String(value)
174
+ .toLowerCase()
175
+ .includes(search.trim().toLowerCase())
168
176
  );
169
177
  const matchesStatus =
170
178
  statusFilter === 'all' ? true : item.status === statusFilter;
@@ -211,8 +219,8 @@ export default function OperationsTimesheetsPage() {
211
219
  const canManageRow = (timesheet: OperationsTimesheet) => {
212
220
  return Boolean(
213
221
  me?.id &&
214
- timesheet.collaboratorId === me.id &&
215
- ['draft', 'rejected'].includes(timesheet.status)
222
+ timesheet.collaboratorId === me.id &&
223
+ ['draft', 'rejected'].includes(timesheet.status)
216
224
  );
217
225
  };
218
226
 
@@ -283,7 +291,12 @@ export default function OperationsTimesheetsPage() {
283
291
  payload
284
292
  );
285
293
  } else {
286
- await mutateOperations(request, '/operations/timesheets', 'POST', payload);
294
+ await mutateOperations(
295
+ request,
296
+ '/operations/timesheets',
297
+ 'POST',
298
+ payload
299
+ );
287
300
  }
288
301
 
289
302
  showToastHandler?.('success', t('messages.saveSuccess'));
@@ -369,7 +382,9 @@ export default function OperationsTimesheetsPage() {
369
382
  {filteredRows.map((timesheet) => (
370
383
  <TableRow key={timesheet.id}>
371
384
  <TableCell>
372
- <div className="font-medium">{timesheet.collaboratorName}</div>
385
+ <div className="font-medium">
386
+ {timesheet.collaboratorName}
387
+ </div>
373
388
  <div className="text-xs text-muted-foreground">
374
389
  {timesheet.notes || commonT('labels.noNotes')}
375
390
  </div>
@@ -387,10 +402,11 @@ export default function OperationsTimesheetsPage() {
387
402
  <div className="text-xs text-muted-foreground">
388
403
  {(timesheet.entries ?? [])
389
404
  .slice(0, 2)
390
- .map((entry) =>
391
- [entry.projectName, entry.activityLabel]
392
- .filter(Boolean)
393
- .join(' • ') || commonT('labels.unassigned')
405
+ .map(
406
+ (entry) =>
407
+ [entry.projectName, entry.activityLabel]
408
+ .filter(Boolean)
409
+ .join(' • ') || commonT('labels.unassigned')
394
410
  )
395
411
  .join(', ') || commonT('labels.unassigned')}
396
412
  </div>
@@ -400,7 +416,12 @@ export default function OperationsTimesheetsPage() {
400
416
  {timesheet.approverName || commonT('labels.notAssigned')}
401
417
  </TableCell>
402
418
  <TableCell>
403
- <div>{formatDateRange(timesheet.weekStartDate, timesheet.weekEndDate)}</div>
419
+ <div>
420
+ {formatDateRange(
421
+ timesheet.weekStartDate,
422
+ timesheet.weekEndDate
423
+ )}
424
+ </div>
404
425
  <div className="text-xs text-muted-foreground">
405
426
  {timesheet.decisionNote || commonT('labels.noNotes')}
406
427
  </div>
@@ -446,7 +467,11 @@ export default function OperationsTimesheetsPage() {
446
467
  icon={<ClipboardList className="size-12" />}
447
468
  title={commonT('states.emptyTitle')}
448
469
  description={t('emptyDescription')}
449
- actionLabel={access.isCollaborator ? commonT('actions.create') : commonT('actions.refresh')}
470
+ actionLabel={
471
+ access.isCollaborator
472
+ ? commonT('actions.create')
473
+ : commonT('actions.refresh')
474
+ }
450
475
  onAction={access.isCollaborator ? openCreate : () => void refetch()}
451
476
  />
452
477
  )}
@@ -469,10 +494,12 @@ export default function OperationsTimesheetsPage() {
469
494
  <SheetDescription>{t('sheet.description')}</SheetDescription>
470
495
  </SheetHeader>
471
496
 
472
- <div className="mt-6 grid gap-4">
497
+ <div className="mt-6 grid gap-4 px-4">
473
498
  <div className="grid gap-4 md:grid-cols-2">
474
499
  <div className="space-y-2">
475
- <label className="text-sm font-medium">{commonT('labels.weekStart')}</label>
500
+ <label className="text-sm font-medium">
501
+ {commonT('labels.weekStart')}
502
+ </label>
476
503
  <Input
477
504
  type="date"
478
505
  value={form.weekStartDate}
@@ -485,7 +512,9 @@ export default function OperationsTimesheetsPage() {
485
512
  />
486
513
  </div>
487
514
  <div className="space-y-2">
488
- <label className="text-sm font-medium">{commonT('labels.weekEnd')}</label>
515
+ <label className="text-sm font-medium">
516
+ {commonT('labels.weekEnd')}
517
+ </label>
489
518
  <Input
490
519
  type="date"
491
520
  value={form.weekEndDate}
@@ -500,12 +529,17 @@ export default function OperationsTimesheetsPage() {
500
529
  </div>
501
530
 
502
531
  <div className="space-y-2">
503
- <label className="text-sm font-medium">{commonT('labels.notes')}</label>
532
+ <label className="text-sm font-medium">
533
+ {commonT('labels.notes')}
534
+ </label>
504
535
  <Textarea
505
536
  rows={3}
506
537
  value={form.notes}
507
538
  onChange={(event) =>
508
- setForm((current) => ({ ...current, notes: event.target.value }))
539
+ setForm((current) => ({
540
+ ...current,
541
+ notes: event.target.value,
542
+ }))
509
543
  }
510
544
  />
511
545
  </div>
@@ -513,7 +547,9 @@ export default function OperationsTimesheetsPage() {
513
547
  <div className="space-y-3">
514
548
  <div className="flex items-center justify-between">
515
549
  <div>
516
- <div className="text-sm font-medium">{t('entries.title')}</div>
550
+ <div className="text-sm font-medium">
551
+ {t('entries.title')}
552
+ </div>
517
553
  <div className="text-xs text-muted-foreground">
518
554
  {projectOptions.length > 0
519
555
  ? t('entries.description')
@@ -551,7 +587,10 @@ export default function OperationsTimesheetsPage() {
551
587
  {commonT('labels.unassigned')}
552
588
  </SelectItem>
553
589
  {projectOptions.map((option) => (
554
- <SelectItem key={option.value} value={option.value}>
590
+ <SelectItem
591
+ key={option.value}
592
+ value={option.value}
593
+ >
555
594
  {option.label}
556
595
  </SelectItem>
557
596
  ))}
@@ -566,7 +605,9 @@ export default function OperationsTimesheetsPage() {
566
605
  <Input
567
606
  value={entry.activityLabel}
568
607
  onChange={(event) =>
569
- updateEntry(index, { activityLabel: event.target.value })
608
+ updateEntry(index, {
609
+ activityLabel: event.target.value,
610
+ })
570
611
  }
571
612
  placeholder={t('entries.activityPlaceholder')}
572
613
  />
@@ -580,7 +621,9 @@ export default function OperationsTimesheetsPage() {
580
621
  rows={2}
581
622
  value={entry.description}
582
623
  onChange={(event) =>
583
- updateEntry(index, { description: event.target.value })
624
+ updateEntry(index, {
625
+ description: event.target.value,
626
+ })
584
627
  }
585
628
  />
586
629
  </div>