@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
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
4
  import { Button } from '@/components/ui/button';
5
+ import { FormActions } from '@/components/ui/form-actions';
5
6
  import { Input } from '@/components/ui/input';
6
7
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
8
  import {
@@ -11,7 +12,6 @@ import {
11
12
  SelectTrigger,
12
13
  SelectValue,
13
14
  } from '@/components/ui/select';
14
- import { Switch } from '@/components/ui/switch';
15
15
  import {
16
16
  Sheet,
17
17
  SheetContent,
@@ -19,6 +19,7 @@ import {
19
19
  SheetHeader,
20
20
  SheetTitle,
21
21
  } from '@/components/ui/sheet';
22
+ import { Switch } from '@/components/ui/switch';
22
23
  import {
23
24
  Table,
24
25
  TableBody,
@@ -30,8 +31,8 @@ import {
30
31
  import { Textarea } from '@/components/ui/textarea';
31
32
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
32
33
  import { CalendarRange, Plus } from 'lucide-react';
33
- import { useMemo, useState } from 'react';
34
34
  import { useTranslations } from 'next-intl';
35
+ import { useMemo, useState } from 'react';
35
36
  import { OperationsHeader } from '../_components/operations-header';
36
37
  import { StatusBadge } from '../_components/status-badge';
37
38
  import { fetchOperations, mutateOperations } from '../_lib/api';
@@ -40,13 +41,14 @@ import type {
40
41
  OperationsScheduleAdjustmentDay,
41
42
  OperationsScheduleAdjustmentRequest,
42
43
  } from '../_lib/types';
43
- import { parseNumberInput, trimToNull } from '../_lib/utils/forms';
44
44
  import {
45
45
  formatDateRange,
46
46
  formatEnumLabel,
47
+ formatWeekdayLabel,
47
48
  getStatusBadgeClass,
48
49
  summarizeScheduleDays,
49
50
  } from '../_lib/utils/format';
51
+ import { parseNumberInput, trimToNull } from '../_lib/utils/forms';
50
52
 
51
53
  const weekdays = [
52
54
  'monday',
@@ -86,33 +88,42 @@ const emptyForm: ScheduleFormState = {
86
88
  })),
87
89
  };
88
90
 
89
- export default function OperationsScheduleAdjustmentsPage() {
90
- const t = useTranslations('operations.ScheduleAdjustmentsPage');
91
- const commonT = useTranslations('operations.Common');
92
- const { request, showToastHandler, currentLocaleCode } = useApp();
93
- const access = useOperationsAccess();
94
- const [search, setSearch] = useState('');
95
- const [statusFilter, setStatusFilter] = useState('all');
96
- const [isSheetOpen, setIsSheetOpen] = useState(false);
97
- const [form, setForm] = useState<ScheduleFormState>(emptyForm);
98
-
99
- const getRequestScopeLabel = (value?: string | null) => {
100
- if (!value) {
101
- return '-';
102
- }
103
-
104
- const key = `options.requestScopes.${value}`;
105
- return t.has(key) ? t(key) : formatEnumLabel(value);
106
- };
107
-
108
- const getStatusLabel = (value?: string | null) => {
109
- if (!value) {
110
- return '-';
111
- }
112
-
113
- const key = `options.statuses.${value}`;
114
- return t.has(key) ? t(key) : formatEnumLabel(value);
115
- };
91
+ export default function OperationsScheduleAdjustmentsPage() {
92
+ const t = useTranslations('operations.ScheduleAdjustmentsPage');
93
+ const commonT = useTranslations('operations.Common');
94
+ const { request, showToastHandler, currentLocaleCode } = useApp();
95
+ const access = useOperationsAccess();
96
+ const [search, setSearch] = useState('');
97
+ const [statusFilter, setStatusFilter] = useState('all');
98
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
99
+ const [form, setForm] = useState<ScheduleFormState>(emptyForm);
100
+
101
+ const getRequestScopeLabel = (value?: string | null) => {
102
+ if (!value) {
103
+ return '-';
104
+ }
105
+
106
+ const key = `options.requestScopes.${value}`;
107
+ return t.has(key) ? t(key) : formatEnumLabel(value);
108
+ };
109
+
110
+ const getStatusLabel = (value?: string | null) => {
111
+ if (!value) {
112
+ return '-';
113
+ }
114
+
115
+ const key = `options.statuses.${value}`;
116
+ return t.has(key) ? t(key) : formatEnumLabel(value);
117
+ };
118
+
119
+ const getStatusDescription = (value?: string | null) => {
120
+ if (!value) {
121
+ return null;
122
+ }
123
+
124
+ const key = `statusHelp.${value}`;
125
+ return t.has(key) ? t(key) : null;
126
+ };
116
127
 
117
128
  const { data: requests = [], refetch } = useQuery<
118
129
  OperationsScheduleAdjustmentRequest[]
@@ -139,7 +150,9 @@ export default function OperationsScheduleAdjustmentsPage() {
139
150
  ]
140
151
  .filter(Boolean)
141
152
  .some((value) =>
142
- String(value).toLowerCase().includes(search.trim().toLowerCase())
153
+ String(value)
154
+ .toLowerCase()
155
+ .includes(search.trim().toLowerCase())
143
156
  );
144
157
  const matchesStatus =
145
158
  statusFilter === 'all' ? true : item.status === statusFilter;
@@ -152,20 +165,34 @@ export default function OperationsScheduleAdjustmentsPage() {
152
165
  {
153
166
  key: 'submitted',
154
167
  title: t('cards.submitted'),
168
+ description: t('cards.submittedDescription'),
155
169
  value: requests.filter((item) => item.status === 'submitted').length,
156
170
  },
157
171
  {
158
172
  key: 'approved',
159
173
  title: t('cards.approved'),
174
+ description: t('cards.approvedDescription'),
160
175
  value: requests.filter((item) => item.status === 'approved').length,
161
176
  },
162
177
  {
163
178
  key: 'permanent',
164
179
  title: t('cards.permanent'),
165
- value: requests.filter((item) => item.requestScope === 'permanent').length,
180
+ description: t('cards.permanentDescription'),
181
+ value: requests.filter((item) => item.requestScope === 'permanent')
182
+ .length,
166
183
  },
167
184
  ];
168
185
 
186
+ const requestedScheduleSummary = useMemo(
187
+ () => summarizeScheduleDays(form.days, currentLocaleCode),
188
+ [form.days, currentLocaleCode]
189
+ );
190
+
191
+ const workingDaysCount = useMemo(
192
+ () => form.days.filter((day) => day.isWorkingDay).length,
193
+ [form.days]
194
+ );
195
+
169
196
  const updateDay = (
170
197
  weekday: string,
171
198
  patch: Partial<ScheduleFormState['days'][number]>
@@ -185,19 +212,26 @@ export default function OperationsScheduleAdjustmentsPage() {
185
212
  }
186
213
 
187
214
  try {
188
- await mutateOperations(request, '/operations/schedule-adjustments', 'POST', {
189
- requestScope: form.requestScope,
190
- effectiveStartDate: form.effectiveStartDate,
191
- effectiveEndDate: trimToNull(form.effectiveEndDate),
192
- reason: trimToNull(form.reason),
193
- days: form.days.map((day) => ({
194
- weekday: day.weekday,
195
- isWorkingDay: day.isWorkingDay,
196
- startTime: day.isWorkingDay ? trimToNull(day.startTime) : null,
197
- endTime: day.isWorkingDay ? trimToNull(day.endTime) : null,
198
- breakMinutes: day.isWorkingDay ? parseNumberInput(day.breakMinutes) : 0,
199
- })),
200
- });
215
+ await mutateOperations(
216
+ request,
217
+ '/operations/schedule-adjustments',
218
+ 'POST',
219
+ {
220
+ requestScope: form.requestScope,
221
+ effectiveStartDate: form.effectiveStartDate,
222
+ effectiveEndDate: trimToNull(form.effectiveEndDate),
223
+ reason: trimToNull(form.reason),
224
+ days: form.days.map((day) => ({
225
+ weekday: day.weekday,
226
+ isWorkingDay: day.isWorkingDay,
227
+ startTime: day.isWorkingDay ? trimToNull(day.startTime) : null,
228
+ endTime: day.isWorkingDay ? trimToNull(day.endTime) : null,
229
+ breakMinutes: day.isWorkingDay
230
+ ? parseNumberInput(day.breakMinutes)
231
+ : 0,
232
+ })),
233
+ }
234
+ );
201
235
 
202
236
  showToastHandler?.('success', t('messages.saveSuccess'));
203
237
  setIsSheetOpen(false);
@@ -236,12 +270,12 @@ export default function OperationsScheduleAdjustmentsPage() {
236
270
  value: statusFilter,
237
271
  onChange: setStatusFilter,
238
272
  placeholder: commonT('labels.status'),
239
- options: [
240
- { value: 'all', label: commonT('filters.allStatuses') },
241
- { value: 'submitted', label: getStatusLabel('submitted') },
242
- { value: 'approved', label: getStatusLabel('approved') },
243
- { value: 'rejected', label: getStatusLabel('rejected') },
244
- ],
273
+ options: [
274
+ { value: 'all', label: commonT('filters.allStatuses') },
275
+ { value: 'submitted', label: getStatusLabel('submitted') },
276
+ { value: 'approved', label: getStatusLabel('approved') },
277
+ { value: 'rejected', label: getStatusLabel('rejected') },
278
+ ],
245
279
  },
246
280
  ]}
247
281
  />
@@ -257,7 +291,7 @@ export default function OperationsScheduleAdjustmentsPage() {
257
291
  <TableHead>{commonT('labels.requestScope')}</TableHead>
258
292
  <TableHead>{commonT('labels.timeline')}</TableHead>
259
293
  <TableHead>{t('table.currentSchedule')}</TableHead>
260
- <TableHead>{commonT('labels.schedule')}</TableHead>
294
+ <TableHead>{t('table.requestedSchedule')}</TableHead>
261
295
  <TableHead>{commonT('labels.approver')}</TableHead>
262
296
  <TableHead>{commonT('labels.status')}</TableHead>
263
297
  <TableHead>{commonT('labels.notes')}</TableHead>
@@ -267,7 +301,9 @@ export default function OperationsScheduleAdjustmentsPage() {
267
301
  {filteredRows.map((requestItem) => (
268
302
  <TableRow key={requestItem.id}>
269
303
  <TableCell>{requestItem.collaboratorName}</TableCell>
270
- <TableCell>{getRequestScopeLabel(requestItem.requestScope)}</TableCell>
304
+ <TableCell>
305
+ {getRequestScopeLabel(requestItem.requestScope)}
306
+ </TableCell>
271
307
  <TableCell>
272
308
  {formatDateRange(
273
309
  requestItem.effectiveStartDate,
@@ -275,21 +311,33 @@ export default function OperationsScheduleAdjustmentsPage() {
275
311
  )}
276
312
  </TableCell>
277
313
  <TableCell>
278
- {summarizeScheduleDays(requestItem.currentSchedule ?? [])}
314
+ {summarizeScheduleDays(
315
+ requestItem.currentSchedule ?? [],
316
+ currentLocaleCode
317
+ )}
279
318
  </TableCell>
280
319
  <TableCell>
281
320
  {summarizeScheduleDays(
282
- (requestItem.days ?? []) as OperationsScheduleAdjustmentDay[]
321
+ (requestItem.days ??
322
+ []) as OperationsScheduleAdjustmentDay[],
323
+ currentLocaleCode
283
324
  )}
284
325
  </TableCell>
285
326
  <TableCell>
286
327
  {requestItem.approverName || commonT('labels.notAssigned')}
287
328
  </TableCell>
288
329
  <TableCell>
289
- <StatusBadge
290
- label={getStatusLabel(requestItem.status)}
291
- className={getStatusBadgeClass(requestItem.status)}
292
- />
330
+ <div className="space-y-1">
331
+ <StatusBadge
332
+ label={getStatusLabel(requestItem.status)}
333
+ className={getStatusBadgeClass(requestItem.status)}
334
+ />
335
+ {getStatusDescription(requestItem.status) ? (
336
+ <p className="max-w-40 text-xs text-muted-foreground">
337
+ {getStatusDescription(requestItem.status)}
338
+ </p>
339
+ ) : null}
340
+ </div>
293
341
  </TableCell>
294
342
  <TableCell>
295
343
  {requestItem.approverNote || commonT('labels.noNotes')}
@@ -304,8 +352,16 @@ export default function OperationsScheduleAdjustmentsPage() {
304
352
  icon={<CalendarRange className="size-12" />}
305
353
  title={commonT('states.emptyTitle')}
306
354
  description={t('emptyDescription')}
307
- actionLabel={access.isCollaborator ? commonT('actions.create') : commonT('actions.refresh')}
308
- onAction={access.isCollaborator ? () => setIsSheetOpen(true) : () => void refetch()}
355
+ actionLabel={
356
+ access.isCollaborator
357
+ ? commonT('actions.create')
358
+ : commonT('actions.refresh')
359
+ }
360
+ onAction={
361
+ access.isCollaborator
362
+ ? () => setIsSheetOpen(true)
363
+ : () => void refetch()
364
+ }
309
365
  />
310
366
  )}
311
367
 
@@ -316,31 +372,35 @@ export default function OperationsScheduleAdjustmentsPage() {
316
372
  <SheetDescription>{t('sheet.description')}</SheetDescription>
317
373
  </SheetHeader>
318
374
 
319
- <div className="mt-6 grid gap-4">
375
+ <div className="mt-6 grid gap-4 px-4 pb-20">
320
376
  <div className="grid gap-4 md:grid-cols-3">
321
377
  <div className="space-y-2">
322
- <label className="text-sm font-medium">{commonT('labels.requestScope')}</label>
378
+ <label className="text-sm font-medium">
379
+ {commonT('labels.requestScope')}
380
+ </label>
323
381
  <Select
324
382
  value={form.requestScope}
325
383
  onValueChange={(value) =>
326
384
  setForm((current) => ({ ...current, requestScope: value }))
327
385
  }
328
386
  >
329
- <SelectTrigger>
330
- <SelectValue />
331
- </SelectTrigger>
332
- <SelectContent>
333
- <SelectItem value="temporary">
334
- {getRequestScopeLabel('temporary')}
335
- </SelectItem>
336
- <SelectItem value="permanent">
337
- {getRequestScopeLabel('permanent')}
338
- </SelectItem>
339
- </SelectContent>
340
- </Select>
387
+ <SelectTrigger className="w-full">
388
+ <SelectValue />
389
+ </SelectTrigger>
390
+ <SelectContent>
391
+ <SelectItem value="temporary">
392
+ {getRequestScopeLabel('temporary')}
393
+ </SelectItem>
394
+ <SelectItem value="permanent">
395
+ {getRequestScopeLabel('permanent')}
396
+ </SelectItem>
397
+ </SelectContent>
398
+ </Select>
341
399
  </div>
342
400
  <div className="space-y-2">
343
- <label className="text-sm font-medium">{commonT('labels.startDate')}</label>
401
+ <label className="text-sm font-medium">
402
+ {commonT('labels.startDate')}
403
+ </label>
344
404
  <Input
345
405
  type="date"
346
406
  value={form.effectiveStartDate}
@@ -353,7 +413,9 @@ export default function OperationsScheduleAdjustmentsPage() {
353
413
  />
354
414
  </div>
355
415
  <div className="space-y-2">
356
- <label className="text-sm font-medium">{commonT('labels.endDate')}</label>
416
+ <label className="text-sm font-medium">
417
+ {commonT('labels.endDate')}
418
+ </label>
357
419
  <Input
358
420
  type="date"
359
421
  value={form.effectiveEndDate}
@@ -368,71 +430,133 @@ export default function OperationsScheduleAdjustmentsPage() {
368
430
  </div>
369
431
 
370
432
  <div className="space-y-2">
371
- <label className="text-sm font-medium">{commonT('labels.reason')}</label>
433
+ <label className="text-sm font-medium">
434
+ {commonT('labels.reason')}
435
+ </label>
372
436
  <Textarea
373
437
  rows={3}
438
+ placeholder={t('sheet.reasonPlaceholder')}
374
439
  value={form.reason}
375
440
  onChange={(event) =>
376
- setForm((current) => ({ ...current, reason: event.target.value }))
441
+ setForm((current) => ({
442
+ ...current,
443
+ reason: event.target.value,
444
+ }))
377
445
  }
378
446
  />
379
447
  </div>
380
448
 
381
449
  <div className="space-y-3">
382
- <div className="text-sm font-medium">{commonT('labels.weeklySchedule')}</div>
450
+ <div className="text-sm font-medium">
451
+ {commonT('labels.weeklySchedule')}
452
+ </div>
453
+ <div className="rounded-lg border bg-muted/30 p-3 text-sm">
454
+ <div className="font-medium text-foreground">
455
+ {t('scheduleEditor.summaryLabel')}
456
+ </div>
457
+ <p className="mt-1 text-muted-foreground">
458
+ {requestedScheduleSummary}
459
+ </p>
460
+ <p className="mt-2 text-xs text-muted-foreground">
461
+ {t('scheduleEditor.daysPerWeek', {
462
+ count: workingDaysCount,
463
+ })}
464
+ </p>
465
+ </div>
466
+ <p className="text-sm text-muted-foreground">
467
+ {t('scheduleEditor.helper')}
468
+ </p>
383
469
  <div className="space-y-3">
384
470
  {form.days.map((day) => (
385
471
  <div
386
472
  key={day.weekday}
387
- className="grid gap-3 rounded-lg border p-4 lg:grid-cols-[1fr_auto_1fr_1fr_1fr]"
473
+ className="grid gap-3 rounded-lg border p-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]"
388
474
  >
389
- <div>
390
- <div className="font-medium">{formatEnumLabel(day.weekday)}</div>
391
- <div className="text-xs text-muted-foreground">
392
- {day.isWorkingDay
393
- ? commonT('labels.workingDay')
394
- : commonT('labels.dayOff')}
475
+ <div className="space-y-2">
476
+ <div>
477
+ <div className="font-medium">
478
+ {formatWeekdayLabel(day.weekday, currentLocaleCode)}
479
+ </div>
480
+ <div className="text-xs text-muted-foreground">
481
+ {day.isWorkingDay
482
+ ? `${commonT('labels.workingDay')} • ${day.startTime || '--:--'}-${day.endTime || '--:--'}`
483
+ : commonT('labels.dayOff')}
484
+ </div>
395
485
  </div>
486
+ <label className="flex items-center gap-2 text-sm text-muted-foreground">
487
+ <Switch
488
+ checked={day.isWorkingDay}
489
+ onCheckedChange={(checked) =>
490
+ updateDay(day.weekday, { isWorkingDay: checked })
491
+ }
492
+ />
493
+ <span>
494
+ {day.isWorkingDay
495
+ ? commonT('labels.workingDay')
496
+ : commonT('labels.dayOff')}
497
+ </span>
498
+ </label>
396
499
  </div>
397
- <div className="flex items-center gap-2">
398
- <Switch
399
- checked={day.isWorkingDay}
400
- onCheckedChange={(checked) =>
401
- updateDay(day.weekday, { isWorkingDay: checked })
402
- }
403
- />
500
+ <div className="grid gap-3 sm:grid-cols-3">
501
+ <div className="space-y-1">
502
+ <div className="text-xs text-muted-foreground">
503
+ {t('scheduleEditor.startTime')}
504
+ </div>
505
+ <Input
506
+ type="time"
507
+ value={day.startTime}
508
+ disabled={!day.isWorkingDay}
509
+ onChange={(event) =>
510
+ updateDay(day.weekday, {
511
+ startTime: event.target.value,
512
+ })
513
+ }
514
+ />
515
+ </div>
516
+ <div className="space-y-1">
517
+ <div className="text-xs text-muted-foreground">
518
+ {t('scheduleEditor.endTime')}
519
+ </div>
520
+ <Input
521
+ type="time"
522
+ value={day.endTime}
523
+ disabled={!day.isWorkingDay}
524
+ onChange={(event) =>
525
+ updateDay(day.weekday, {
526
+ endTime: event.target.value,
527
+ })
528
+ }
529
+ />
530
+ </div>
531
+ <div className="space-y-1">
532
+ <div className="text-xs text-muted-foreground">
533
+ {t('scheduleEditor.breakMinutes')}
534
+ </div>
535
+ <Input
536
+ type="number"
537
+ value={day.breakMinutes}
538
+ disabled={!day.isWorkingDay}
539
+ onChange={(event) =>
540
+ updateDay(day.weekday, {
541
+ breakMinutes: event.target.value,
542
+ })
543
+ }
544
+ />
545
+ </div>
404
546
  </div>
405
- <Input
406
- type="time"
407
- value={day.startTime}
408
- disabled={!day.isWorkingDay}
409
- onChange={(event) =>
410
- updateDay(day.weekday, { startTime: event.target.value })
411
- }
412
- />
413
- <Input
414
- type="time"
415
- value={day.endTime}
416
- disabled={!day.isWorkingDay}
417
- onChange={(event) =>
418
- updateDay(day.weekday, { endTime: event.target.value })
419
- }
420
- />
421
- <Input
422
- type="number"
423
- value={day.breakMinutes}
424
- disabled={!day.isWorkingDay}
425
- onChange={(event) =>
426
- updateDay(day.weekday, { breakMinutes: event.target.value })
427
- }
428
- />
429
547
  </div>
430
548
  ))}
431
549
  </div>
432
550
  </div>
433
-
434
- <Button onClick={() => void onSubmit()}>{commonT('actions.save')}</Button>
435
551
  </div>
552
+
553
+ <FormActions
554
+ sheet
555
+ cancelLabel={commonT('actions.cancel')}
556
+ onCancel={() => setIsSheetOpen(false)}
557
+ onSubmit={() => void onSubmit()}
558
+ submitLabel={commonT('actions.save')}
559
+ />
436
560
  </SheetContent>
437
561
  </Sheet>
438
562
  </Page>
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { EmptyState, Page } from '@/components/entity-list';
4
- import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
5
4
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
6
6
  import {
7
7
  Table,
8
8
  TableBody,
@@ -61,31 +61,37 @@ export default function OperationsTeamPage() {
61
61
  {
62
62
  key: 'members',
63
63
  title: t('cards.members'),
64
+ description: t('cards.membersDescription'),
64
65
  value: team?.teamMembers.length ?? 0,
65
66
  },
66
67
  {
67
68
  key: 'projects',
68
69
  title: t('cards.projects'),
70
+ description: t('cards.projectsDescription'),
69
71
  value: team?.projectCount ?? 0,
70
72
  },
71
73
  {
72
74
  key: 'approvals',
73
75
  title: t('cards.pendingApprovals'),
76
+ description: t('cards.pendingApprovalsDescription'),
74
77
  value: team?.pendingApprovals ?? 0,
75
78
  },
76
79
  {
77
80
  key: 'timeOff',
78
81
  title: t('cards.timeOff'),
82
+ description: t('cards.timeOffDescription'),
79
83
  value: team?.pendingItems?.timeOffRequests ?? 0,
80
84
  },
81
85
  {
82
86
  key: 'schedule',
83
87
  title: t('cards.scheduleAdjustments'),
88
+ description: t('cards.scheduleAdjustmentsDescription'),
84
89
  value: team?.pendingItems?.scheduleAdjustmentRequests ?? 0,
85
90
  },
86
91
  {
87
92
  key: 'timesheets',
88
93
  title: t('cards.timesheets'),
94
+ description: t('cards.timesheetsDescription'),
89
95
  value: team?.pendingItems?.timesheets ?? 0,
90
96
  },
91
97
  ];
@@ -120,7 +126,14 @@ export default function OperationsTeamPage() {
120
126
  <TableCell>
121
127
  <div className="font-medium">{member.displayName}</div>
122
128
  <div className="text-xs text-muted-foreground">
123
- {[member.code, formatEnumLabel(member.collaboratorType)]
129
+ {[
130
+ member.code,
131
+ member.collaboratorType ||
132
+ formatEnumLabel(
133
+ member.collaboratorTypeSlug ??
134
+ member.collaboratorType
135
+ ),
136
+ ]
124
137
  .filter(Boolean)
125
138
  .join(' • ')}
126
139
  </div>