@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
@@ -2,6 +2,15 @@
2
2
 
3
3
  import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
4
  import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent } from '@/components/ui/card';
6
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
7
+ import {
8
+ Sheet,
9
+ SheetContent,
10
+ SheetDescription,
11
+ SheetHeader,
12
+ SheetTitle,
13
+ } from '@/components/ui/sheet';
5
14
  import {
6
15
  Table,
7
16
  TableBody,
@@ -11,11 +20,21 @@ import {
11
20
  TableRow,
12
21
  } from '@/components/ui/table';
13
22
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
14
- import { Eye, FileText, FolderKanban, Pencil } from 'lucide-react';
23
+ import {
24
+ CalendarDays,
25
+ Eye,
26
+ FileText,
27
+ FolderKanban,
28
+ Pencil,
29
+ PlayCircle,
30
+ ShieldAlert,
31
+ } from 'lucide-react';
32
+ import { useTranslations } from 'next-intl';
15
33
  import Link from 'next/link';
34
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
16
35
  import { useMemo, useState } from 'react';
17
- import { useTranslations } from 'next-intl';
18
36
  import { OperationsHeader } from '../_components/operations-header';
37
+ import { ProjectFormScreen } from '../_components/project-form-screen';
19
38
  import { StatusBadge } from '../_components/status-badge';
20
39
  import { fetchOperations, mutateOperations } from '../_lib/api';
21
40
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
@@ -26,17 +45,62 @@ import {
26
45
  getStatusBadgeClass,
27
46
  } from '../_lib/utils/format';
28
47
 
48
+ function parseEditProjectId(value: string | null) {
49
+ const parsed = Number(value);
50
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
51
+ }
52
+
29
53
  export default function OperationsProjectsPage() {
30
54
  const t = useTranslations('operations.ProjectsPage');
31
55
  const commonT = useTranslations('operations.Common');
32
56
  const { request, showToastHandler, currentLocaleCode } = useApp();
33
57
  const access = useOperationsAccess();
58
+ const router = useRouter();
59
+ const pathname = usePathname();
60
+ const searchParams = useSearchParams();
34
61
  const [search, setSearch] = useState('');
35
62
  const [statusFilter, setStatusFilter] = useState('all');
36
63
 
64
+ const createParam = searchParams.get('create');
65
+ const editProjectId = parseEditProjectId(searchParams.get('edit'));
66
+ const isSheetOpen = createParam === '1' || editProjectId !== null;
67
+
68
+ const updateSheetQuery = (next: { create?: boolean; editId?: number | null }) => {
69
+ const params = new URLSearchParams(searchParams.toString());
70
+
71
+ params.delete('create');
72
+ params.delete('edit');
73
+
74
+ if (next.create) {
75
+ params.set('create', '1');
76
+ }
77
+
78
+ if (next.editId) {
79
+ params.set('edit', String(next.editId));
80
+ }
81
+
82
+ const query = params.toString();
83
+ router.replace(query ? `${pathname}?${query}` : pathname, {
84
+ scroll: false,
85
+ });
86
+ };
87
+
88
+ const closeSheet = () => {
89
+ updateSheetQuery({});
90
+ };
91
+
92
+ const openCreateSheet = () => {
93
+ updateSheetQuery({ create: true });
94
+ };
95
+
96
+ const openEditSheet = (projectId: number) => {
97
+ updateSheetQuery({ editId: projectId });
98
+ };
99
+
37
100
  const { data: projects = [], refetch } = useQuery<OperationsProject[]>({
38
101
  queryKey: ['operations-projects-list', currentLocaleCode],
39
- queryFn: () => fetchOperations<OperationsProject[]>(request, '/operations/projects'),
102
+ queryFn: () =>
103
+ fetchOperations<OperationsProject[]>(request, '/operations/projects'),
40
104
  });
41
105
 
42
106
  const filteredRows = useMemo(
@@ -64,13 +128,48 @@ export default function OperationsProjectsPage() {
64
128
  [projects, search, statusFilter]
65
129
  );
66
130
 
131
+ const statsCards = useMemo(
132
+ () => [
133
+ {
134
+ key: 'total',
135
+ title: t('cards.total'),
136
+ value: projects.length,
137
+ icon: FolderKanban,
138
+ },
139
+ {
140
+ key: 'active',
141
+ title: t('cards.active'),
142
+ value: projects.filter((item) => item.status === 'active').length,
143
+ icon: PlayCircle,
144
+ },
145
+ {
146
+ key: 'atRisk',
147
+ title: t('cards.atRisk'),
148
+ value: projects.filter((item) => item.status === 'at_risk').length,
149
+ icon: ShieldAlert,
150
+ },
151
+ {
152
+ key: 'upcomingDeliveries',
153
+ title: t('cards.upcomingDeliveries'),
154
+ value: projects.filter((item) => Boolean(item.endDate)).length,
155
+ icon: CalendarDays,
156
+ },
157
+ ],
158
+ [projects, t]
159
+ );
160
+
67
161
  const toggleArchived = async (project: OperationsProject) => {
68
162
  const nextStatus = project.status === 'archived' ? 'active' : 'archived';
69
163
 
70
164
  try {
71
- await mutateOperations(request, `/operations/projects/${project.id}`, 'PATCH', {
72
- status: nextStatus,
73
- });
165
+ await mutateOperations(
166
+ request,
167
+ `/operations/projects/${project.id}`,
168
+ 'PATCH',
169
+ {
170
+ status: nextStatus,
171
+ }
172
+ );
74
173
  showToastHandler?.('success', t('messages.statusSuccess'));
75
174
  await refetch();
76
175
  } catch {
@@ -86,15 +185,15 @@ export default function OperationsProjectsPage() {
86
185
  current={t('breadcrumb')}
87
186
  actions={
88
187
  access.isDirector ? (
89
- <Button size="sm" asChild>
90
- <Link href="/operations/projects/new">
91
- {commonT('actions.create')}
92
- </Link>
188
+ <Button size="sm" onClick={openCreateSheet}>
189
+ {commonT('actions.create')}
93
190
  </Button>
94
191
  ) : undefined
95
192
  }
96
193
  />
97
194
 
195
+ <KpiCardsGrid items={statsCards} columns={4} />
196
+
98
197
  <SearchBar
99
198
  searchQuery={search}
100
199
  onSearchChange={setSearch}
@@ -121,116 +220,179 @@ export default function OperationsProjectsPage() {
121
220
  />
122
221
 
123
222
  {filteredRows.length > 0 ? (
124
- <div className="overflow-x-auto rounded-md border">
125
- <Table>
126
- <TableHeader>
127
- <TableRow>
128
- <TableHead>{commonT('labels.project')}</TableHead>
129
- <TableHead>{commonT('labels.client')}</TableHead>
130
- <TableHead>{commonT('labels.status')}</TableHead>
131
- <TableHead>{commonT('labels.manager')}</TableHead>
132
- <TableHead>{commonT('labels.teamSize')}</TableHead>
133
- <TableHead>{commonT('labels.startDate')}</TableHead>
134
- <TableHead>{commonT('labels.endDate')}</TableHead>
135
- <TableHead>{commonT('labels.contractStatus')}</TableHead>
136
- <TableHead className="w-[260px] text-right">
137
- {commonT('labels.actions')}
138
- </TableHead>
139
- </TableRow>
140
- </TableHeader>
141
- <TableBody>
142
- {filteredRows.map((project) => (
143
- <TableRow key={project.id}>
144
- <TableCell>
145
- <div className="font-medium">{project.name}</div>
146
- <div className="text-xs text-muted-foreground">
147
- {[project.code, project.myRoleLabel].filter(Boolean).join(' • ')}
148
- </div>
149
- </TableCell>
150
- <TableCell>{project.clientName || commonT('labels.notAvailable')}</TableCell>
151
- <TableCell>
152
- <StatusBadge
153
- label={formatEnumLabel(project.status)}
154
- className={getStatusBadgeClass(project.status)}
155
- />
156
- </TableCell>
157
- <TableCell>{project.managerName || commonT('labels.notAssigned')}</TableCell>
158
- <TableCell>{project.teamSize ?? 0}</TableCell>
159
- <TableCell>{formatDate(project.startDate)}</TableCell>
160
- <TableCell>{formatDate(project.endDate)}</TableCell>
161
- <TableCell>
162
- {project.contractStatus ? (
223
+ <Card className="overflow-hidden border-border/60 py-0 shadow-sm">
224
+ <CardContent className="p-0">
225
+ <Table className="table-fixed">
226
+ <TableHeader>
227
+ <TableRow>
228
+ <TableHead className="w-[30%]">
229
+ {commonT('labels.project')}
230
+ </TableHead>
231
+ <TableHead>{commonT('labels.client')}</TableHead>
232
+ <TableHead>{commonT('labels.status')}</TableHead>
233
+ <TableHead className="hidden lg:table-cell">
234
+ {commonT('labels.manager')}
235
+ </TableHead>
236
+ <TableHead className="hidden md:table-cell">
237
+ {commonT('labels.teamSize')}
238
+ </TableHead>
239
+ <TableHead className="hidden xl:table-cell">
240
+ {commonT('labels.startDate')}
241
+ </TableHead>
242
+ <TableHead className="hidden xl:table-cell">
243
+ {commonT('labels.endDate')}
244
+ </TableHead>
245
+ <TableHead className="hidden 2xl:table-cell">
246
+ {commonT('labels.contractStatus')}
247
+ </TableHead>
248
+ <TableHead className="w-30 text-right sm:w-42.5">
249
+ {commonT('labels.actions')}
250
+ </TableHead>
251
+ </TableRow>
252
+ </TableHeader>
253
+ <TableBody>
254
+ {filteredRows.map((project) => (
255
+ <TableRow key={project.id} className="hover:bg-muted/30">
256
+ <TableCell>
257
+ <div className="min-w-0">
258
+ <div className="truncate font-medium">{project.name}</div>
259
+ <div className="truncate text-xs text-muted-foreground">
260
+ {[project.code, project.myRoleLabel, project.contractName]
261
+ .filter(Boolean)
262
+ .join(' • ') || commonT('labels.notAvailable')}
263
+ </div>
264
+ </div>
265
+ </TableCell>
266
+ <TableCell>
267
+ <div className="truncate">
268
+ {project.clientName || commonT('labels.notAvailable')}
269
+ </div>
270
+ </TableCell>
271
+ <TableCell>
163
272
  <StatusBadge
164
- label={formatEnumLabel(project.contractStatus)}
165
- className={getStatusBadgeClass(project.contractStatus)}
273
+ label={formatEnumLabel(project.status)}
274
+ className={getStatusBadgeClass(project.status)}
166
275
  />
167
- ) : (
168
- commonT('labels.notAssigned')
169
- )}
170
- </TableCell>
171
- <TableCell>
172
- <div className="flex justify-end gap-2">
173
- <Button variant="outline" size="icon" asChild>
174
- <Link href={`/operations/projects/${project.id}`}>
175
- <Eye className="size-4" />
176
- </Link>
177
- </Button>
178
- {access.isDirector ? (
276
+ </TableCell>
277
+ <TableCell className="hidden lg:table-cell">
278
+ <div className="truncate">
279
+ {project.managerName || commonT('labels.notAssigned')}
280
+ </div>
281
+ </TableCell>
282
+ <TableCell className="hidden md:table-cell">
283
+ {project.teamSize ?? 0}
284
+ </TableCell>
285
+ <TableCell className="hidden xl:table-cell">
286
+ {formatDate(project.startDate)}
287
+ </TableCell>
288
+ <TableCell className="hidden xl:table-cell">
289
+ {formatDate(project.endDate)}
290
+ </TableCell>
291
+ <TableCell className="hidden 2xl:table-cell">
292
+ {project.contractStatus ? (
293
+ <StatusBadge
294
+ label={formatEnumLabel(project.contractStatus)}
295
+ className={getStatusBadgeClass(project.contractStatus)}
296
+ />
297
+ ) : (
298
+ commonT('labels.notAssigned')
299
+ )}
300
+ </TableCell>
301
+ <TableCell>
302
+ <div className="flex flex-wrap justify-end gap-1.5 sm:gap-2">
179
303
  <Button variant="outline" size="icon" asChild>
180
- <Link href={`/operations/projects/${project.id}/edit`}>
181
- <Pencil className="size-4" />
304
+ <Link href={`/operations/projects/${project.id}`}>
305
+ <Eye className="size-4" />
182
306
  </Link>
183
307
  </Button>
184
- ) : null}
185
- <Button
186
- variant="outline"
187
- size="icon"
188
- asChild={Boolean(project.contractId)}
189
- disabled={!project.contractId}
190
- >
191
- {project.contractId ? (
192
- <Link href={`/operations/contracts?edit=${project.contractId}`}>
193
- <FileText className="size-4" />
194
- </Link>
195
- ) : (
196
- <span>
197
- <FileText className="size-4" />
198
- </span>
199
- )}
200
- </Button>
201
- {access.isDirector ? (
308
+ {access.isDirector ? (
309
+ <Button
310
+ variant="outline"
311
+ size="icon"
312
+ className="cursor-pointer"
313
+ onClick={() => openEditSheet(project.id)}
314
+ >
315
+ <Pencil className="size-4" />
316
+ </Button>
317
+ ) : null}
202
318
  <Button
203
319
  variant="outline"
204
- size="sm"
205
- onClick={() => void toggleArchived(project)}
320
+ size="icon"
321
+ asChild={Boolean(project.contractId)}
322
+ disabled={!project.contractId}
206
323
  >
207
- {project.status === 'archived'
208
- ? commonT('actions.activate')
209
- : t('actions.archive')}
324
+ {project.contractId ? (
325
+ <Link
326
+ href={`/operations/contracts?edit=${project.contractId}`}
327
+ >
328
+ <FileText className="size-4" />
329
+ </Link>
330
+ ) : (
331
+ <span>
332
+ <FileText className="size-4" />
333
+ </span>
334
+ )}
210
335
  </Button>
211
- ) : null}
212
- </div>
213
- </TableCell>
214
- </TableRow>
215
- ))}
216
- </TableBody>
217
- </Table>
218
- </div>
336
+ {access.isDirector ? (
337
+ <Button
338
+ variant="outline"
339
+ size="sm"
340
+ className="cursor-pointer"
341
+ onClick={() => void toggleArchived(project)}
342
+ >
343
+ {project.status === 'archived'
344
+ ? commonT('actions.activate')
345
+ : t('actions.archive')}
346
+ </Button>
347
+ ) : null}
348
+ </div>
349
+ </TableCell>
350
+ </TableRow>
351
+ ))}
352
+ </TableBody>
353
+ </Table>
354
+ </CardContent>
355
+ </Card>
219
356
  ) : (
220
357
  <EmptyState
221
358
  icon={<FolderKanban className="size-12" />}
222
359
  title={commonT('states.emptyTitle')}
223
360
  description={t('emptyDescription')}
224
- actionLabel={access.isDirector ? commonT('actions.create') : commonT('actions.refresh')}
225
- onAction={
361
+ actionLabel={
226
362
  access.isDirector
227
- ? () => {
228
- window.location.href = '/operations/projects/new';
229
- }
230
- : () => void refetch()
363
+ ? commonT('actions.create')
364
+ : commonT('actions.refresh')
231
365
  }
366
+ onAction={access.isDirector ? openCreateSheet : () => void refetch()}
232
367
  />
233
368
  )}
369
+
370
+ <Sheet
371
+ open={isSheetOpen}
372
+ onOpenChange={(open) => {
373
+ if (!open) {
374
+ closeSheet();
375
+ }
376
+ }}
377
+ >
378
+ <SheetContent className="w-full overflow-x-hidden overflow-y-auto sm:max-w-[min(92vw,64rem)]">
379
+ <SheetHeader>
380
+ <SheetTitle>
381
+ {editProjectId ? t('sheet.editTitle') : t('sheet.createTitle')}
382
+ </SheetTitle>
383
+ <SheetDescription>{t('sheet.description')}</SheetDescription>
384
+ </SheetHeader>
385
+
386
+ <ProjectFormScreen
387
+ projectId={editProjectId ?? undefined}
388
+ onCancel={closeSheet}
389
+ onSaved={async () => {
390
+ closeSheet();
391
+ await refetch();
392
+ }}
393
+ />
394
+ </SheetContent>
395
+ </Sheet>
234
396
  </Page>
235
397
  );
236
398
  }
@@ -86,15 +86,33 @@ const emptyForm: ScheduleFormState = {
86
86
  })),
87
87
  };
88
88
 
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);
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
+ };
98
116
 
99
117
  const { data: requests = [], refetch } = useQuery<
100
118
  OperationsScheduleAdjustmentRequest[]
@@ -218,12 +236,12 @@ export default function OperationsScheduleAdjustmentsPage() {
218
236
  value: statusFilter,
219
237
  onChange: setStatusFilter,
220
238
  placeholder: commonT('labels.status'),
221
- options: [
222
- { value: 'all', label: commonT('filters.allStatuses') },
223
- { value: 'submitted', label: formatEnumLabel('submitted') },
224
- { value: 'approved', label: formatEnumLabel('approved') },
225
- { value: 'rejected', label: formatEnumLabel('rejected') },
226
- ],
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
+ ],
227
245
  },
228
246
  ]}
229
247
  />
@@ -249,7 +267,7 @@ export default function OperationsScheduleAdjustmentsPage() {
249
267
  {filteredRows.map((requestItem) => (
250
268
  <TableRow key={requestItem.id}>
251
269
  <TableCell>{requestItem.collaboratorName}</TableCell>
252
- <TableCell>{formatEnumLabel(requestItem.requestScope)}</TableCell>
270
+ <TableCell>{getRequestScopeLabel(requestItem.requestScope)}</TableCell>
253
271
  <TableCell>
254
272
  {formatDateRange(
255
273
  requestItem.effectiveStartDate,
@@ -268,10 +286,10 @@ export default function OperationsScheduleAdjustmentsPage() {
268
286
  {requestItem.approverName || commonT('labels.notAssigned')}
269
287
  </TableCell>
270
288
  <TableCell>
271
- <StatusBadge
272
- label={formatEnumLabel(requestItem.status)}
273
- className={getStatusBadgeClass(requestItem.status)}
274
- />
289
+ <StatusBadge
290
+ label={getStatusLabel(requestItem.status)}
291
+ className={getStatusBadgeClass(requestItem.status)}
292
+ />
275
293
  </TableCell>
276
294
  <TableCell>
277
295
  {requestItem.approverNote || commonT('labels.noNotes')}
@@ -308,14 +326,18 @@ export default function OperationsScheduleAdjustmentsPage() {
308
326
  setForm((current) => ({ ...current, requestScope: value }))
309
327
  }
310
328
  >
311
- <SelectTrigger>
312
- <SelectValue />
313
- </SelectTrigger>
314
- <SelectContent>
315
- <SelectItem value="temporary">Temporary</SelectItem>
316
- <SelectItem value="permanent">Permanent</SelectItem>
317
- </SelectContent>
318
- </Select>
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>
319
341
  </div>
320
342
  <div className="space-y-2">
321
343
  <label className="text-sm font-medium">{commonT('labels.startDate')}</label>