@hed-hog/operations 0.0.300 → 0.0.302
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.
- package/dist/operations.controller.d.ts +713 -31
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.controller.js +157 -0
- package/dist/operations.controller.js.map +1 -1
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +5 -1
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.proposal.subscriber.d.ts +11 -0
- package/dist/operations.proposal.subscriber.d.ts.map +1 -0
- package/dist/operations.proposal.subscriber.js +80 -0
- package/dist/operations.proposal.subscriber.js.map +1 -0
- package/dist/operations.proposal.subscriber.spec.d.ts +2 -0
- package/dist/operations.proposal.subscriber.spec.d.ts.map +1 -0
- package/dist/operations.proposal.subscriber.spec.js +88 -0
- package/dist/operations.proposal.subscriber.spec.js.map +1 -0
- package/dist/operations.service.d.ts +491 -46
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +2484 -121
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.d.ts +2 -0
- package/dist/operations.service.spec.d.ts.map +1 -0
- package/dist/operations.service.spec.js +159 -0
- package/dist/operations.service.spec.js.map +1 -0
- package/hedhog/data/menu.yaml +35 -22
- package/hedhog/data/role_route.yaml +39 -0
- package/hedhog/data/route.yaml +130 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +8 -6
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +1163 -327
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -0
- package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +631 -0
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +353 -27
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +1926 -87
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +526 -0
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -0
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -0
- package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +370 -0
- package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +826 -0
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1251 -364
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +48 -13
- package/hedhog/frontend/app/_lib/api.ts.ejs +2 -5
- package/hedhog/frontend/app/_lib/types.ts.ejs +76 -33
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +85 -8
- package/hedhog/frontend/app/approvals/page.tsx.ejs +90 -54
- package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +597 -140
- package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +941 -262
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +384 -0
- package/hedhog/frontend/app/departments/page.tsx.ejs +442 -0
- package/hedhog/frontend/app/page.tsx.ejs +3 -317
- package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/projects/new/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/projects/page.tsx.ejs +264 -102
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +50 -28
- package/hedhog/frontend/app/time-off/page.tsx.ejs +57 -31
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +85 -42
- package/hedhog/frontend/messages/en.json +473 -12
- package/hedhog/frontend/messages/pt.json +528 -66
- package/hedhog/table/operations_collaborator.yaml +20 -0
- package/hedhog/table/operations_contract.yaml +22 -1
- package/hedhog/table/operations_contract_document.yaml +33 -16
- package/hedhog/table/operations_contract_template.yaml +58 -0
- package/hedhog/table/operations_department.yaml +24 -0
- package/package.json +7 -5
- package/src/operations.controller.ts +122 -0
- package/src/operations.module.ts +6 -2
- package/src/operations.proposal.subscriber.spec.ts +121 -0
- package/src/operations.proposal.subscriber.ts +86 -0
- package/src/operations.service.spec.ts +210 -0
- package/src/operations.service.ts +4026 -241
|
@@ -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 {
|
|
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: () =>
|
|
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(
|
|
72
|
-
|
|
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"
|
|
90
|
-
|
|
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
|
-
<
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
165
|
-
className={getStatusBadgeClass(project.
|
|
273
|
+
label={formatEnumLabel(project.status)}
|
|
274
|
+
className={getStatusBadgeClass(project.status)}
|
|
166
275
|
/>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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}
|
|
181
|
-
<
|
|
304
|
+
<Link href={`/operations/projects/${project.id}`}>
|
|
305
|
+
<Eye className="size-4" />
|
|
182
306
|
</Link>
|
|
183
307
|
</Button>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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="
|
|
205
|
-
|
|
320
|
+
size="icon"
|
|
321
|
+
asChild={Boolean(project.contractId)}
|
|
322
|
+
disabled={!project.contractId}
|
|
206
323
|
>
|
|
207
|
-
{project.
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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={
|
|
225
|
-
onAction={
|
|
361
|
+
actionLabel={
|
|
226
362
|
access.isDirector
|
|
227
|
-
? ()
|
|
228
|
-
|
|
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:
|
|
224
|
-
{ value: 'approved', label:
|
|
225
|
-
{ value: 'rejected', label:
|
|
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>{
|
|
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={
|
|
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">
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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>
|