@hed-hog/operations 0.0.306 → 0.0.310
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/controllers/operations-approvals.controller.d.ts +114 -1
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
- package/dist/controllers/operations-approvals.controller.js +16 -3
- package/dist/controllers/operations-approvals.controller.js.map +1 -1
- package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +16 -3
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +14 -453
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.js +11 -112
- package/dist/controllers/operations-contracts.controller.js.map +1 -1
- package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
- package/dist/controllers/operations-org-structure.controller.js +18 -5
- package/dist/controllers/operations-org-structure.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +28 -4
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +17 -5
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +31 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +16 -11
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/list-approvals.dto.d.ts +6 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -0
- package/dist/dto/list-approvals.dto.js +28 -0
- package/dist/dto/list-approvals.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborator-types.dto.js +7 -1
- package/dist/dto/list-collaborator-types.dto.js.map +1 -1
- package/dist/dto/list-collaborators.dto.d.ts +1 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborators.dto.js +5 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -1
- package/dist/dto/list-contracts.dto.d.ts +8 -0
- package/dist/dto/list-contracts.dto.d.ts.map +1 -0
- package/dist/dto/list-contracts.dto.js +38 -0
- package/dist/dto/list-contracts.dto.js.map +1 -0
- package/dist/dto/list-departments.dto.d.ts +5 -0
- package/dist/dto/list-departments.dto.d.ts.map +1 -0
- package/dist/dto/list-departments.dto.js +23 -0
- package/dist/dto/list-departments.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +5 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-projects.dto.js +23 -0
- package/dist/dto/list-projects.dto.js.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.js +23 -0
- package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
- package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
- package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
- package/dist/dto/list-time-off-requests.dto.js +23 -0
- package/dist/dto/list-time-off-requests.dto.js.map +1 -0
- package/dist/dto/list-timesheets.dto.d.ts +5 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheets.dto.js +23 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.js +25 -0
- package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
- package/dist/operations.service.d.ts +340 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1007 -1043
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +0 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +0 -36
- package/hedhog/data/route.yaml +42 -73
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
- package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
- package/hedhog/frontend/app/approvals/page.tsx.ejs +842 -150
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
- package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
- package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +412 -147
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
- package/hedhog/frontend/messages/en.json +143 -14
- package/hedhog/frontend/messages/pt.json +192 -54
- package/hedhog/table/operations_contract.yaml +0 -9
- package/package.json +4 -4
- package/src/controllers/operations-approvals.controller.ts +9 -3
- package/src/controllers/operations-collaborators.controller.ts +15 -2
- package/src/controllers/operations-contracts.controller.ts +8 -92
- package/src/controllers/operations-org-structure.controller.ts +17 -4
- package/src/controllers/operations-projects.controller.ts +10 -4
- package/src/controllers/operations-timesheets.controller.ts +17 -8
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +7 -2
- package/src/dto/list-collaborators.dto.ts +4 -0
- package/src/dto/list-contracts.dto.ts +20 -0
- package/src/dto/list-departments.dto.ts +8 -0
- package/src/dto/list-projects.dto.ts +8 -0
- package/src/dto/list-schedule-adjustments.dto.ts +8 -0
- package/src/dto/list-time-off-requests.dto.ts +8 -0
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/reorder-collaborator-types.dto.ts +10 -0
- package/src/operations.service.spec.ts +0 -30
- package/src/operations.service.ts +1557 -1806
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
- package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
- package/hedhog/table/operations_contract_financial_term.yaml +0 -40
- package/hedhog/table/operations_contract_revision.yaml +0 -38
- package/hedhog/table/operations_contract_signature.yaml +0 -38
- package/hedhog/table/operations_contract_template.yaml +0 -58
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
6
|
+
PaginationFooter,
|
|
7
|
+
SearchBar,
|
|
8
|
+
} from '@/components/entity-list';
|
|
4
9
|
import { Badge } from '@/components/ui/badge';
|
|
5
10
|
import { Button } from '@/components/ui/button';
|
|
6
11
|
import { FormActions } from '@/components/ui/form-actions';
|
|
@@ -21,9 +26,26 @@ import {
|
|
|
21
26
|
SheetTitle,
|
|
22
27
|
} from '@/components/ui/sheet';
|
|
23
28
|
import { Switch } from '@/components/ui/switch';
|
|
29
|
+
import {
|
|
30
|
+
Table,
|
|
31
|
+
TableBody,
|
|
32
|
+
TableCell,
|
|
33
|
+
TableHead,
|
|
34
|
+
TableHeader,
|
|
35
|
+
TableRow,
|
|
36
|
+
} from '@/components/ui/table';
|
|
24
37
|
import { Textarea } from '@/components/ui/textarea';
|
|
38
|
+
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
25
39
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
26
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
CalendarRange,
|
|
42
|
+
Clock,
|
|
43
|
+
Eye,
|
|
44
|
+
LayoutGrid,
|
|
45
|
+
List,
|
|
46
|
+
Plus,
|
|
47
|
+
User,
|
|
48
|
+
} from 'lucide-react';
|
|
27
49
|
import { useTranslations } from 'next-intl';
|
|
28
50
|
import { useMemo, useState } from 'react';
|
|
29
51
|
import { OperationsHeader } from '../_components/operations-header';
|
|
@@ -33,6 +55,7 @@ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
|
33
55
|
import type {
|
|
34
56
|
OperationsScheduleAdjustmentDay,
|
|
35
57
|
OperationsScheduleAdjustmentRequest,
|
|
58
|
+
PaginatedResponse,
|
|
36
59
|
} from '../_lib/types';
|
|
37
60
|
import {
|
|
38
61
|
formatDateRange,
|
|
@@ -211,7 +234,19 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
211
234
|
const access = useOperationsAccess();
|
|
212
235
|
const [search, setSearch] = useState('');
|
|
213
236
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
237
|
+
const [page, setPage] = useState(1);
|
|
238
|
+
const [pageSize, setPageSize] = useState(12);
|
|
239
|
+
const [viewMode, setViewMode] = useState<'table' | 'cards'>(() => {
|
|
240
|
+
if (typeof window === 'undefined') return 'cards';
|
|
241
|
+
const saved = window.localStorage.getItem(
|
|
242
|
+
'operations-schedule-adjustments-view-mode'
|
|
243
|
+
);
|
|
244
|
+
return saved === 'table' ? 'table' : 'cards';
|
|
245
|
+
});
|
|
214
246
|
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
247
|
+
const [isDetailsOpen, setIsDetailsOpen] = useState(false);
|
|
248
|
+
const [selectedRequest, setSelectedRequest] =
|
|
249
|
+
useState<OperationsScheduleAdjustmentRequest | null>(null);
|
|
215
250
|
const [form, setForm] = useState<ScheduleFormState>(emptyForm);
|
|
216
251
|
|
|
217
252
|
const getRequestScopeLabel = (value?: string | null) => {
|
|
@@ -241,41 +276,44 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
241
276
|
return t.has(key) ? t(key) : null;
|
|
242
277
|
};
|
|
243
278
|
|
|
244
|
-
const { data:
|
|
245
|
-
OperationsScheduleAdjustmentRequest
|
|
279
|
+
const { data: requestsResponse, refetch } = useQuery<
|
|
280
|
+
PaginatedResponse<OperationsScheduleAdjustmentRequest>
|
|
246
281
|
>({
|
|
247
|
-
queryKey: [
|
|
282
|
+
queryKey: [
|
|
283
|
+
'operations-schedule-adjustments',
|
|
284
|
+
currentLocaleCode,
|
|
285
|
+
search,
|
|
286
|
+
statusFilter,
|
|
287
|
+
page,
|
|
288
|
+
pageSize,
|
|
289
|
+
],
|
|
248
290
|
enabled: access.isCollaborator,
|
|
249
|
-
queryFn: () =>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
291
|
+
queryFn: () => {
|
|
292
|
+
const params = new URLSearchParams({
|
|
293
|
+
page: String(page),
|
|
294
|
+
pageSize: String(pageSize),
|
|
295
|
+
});
|
|
296
|
+
if (search.trim()) params.set('search', search.trim());
|
|
297
|
+
if (statusFilter !== 'all') params.set('status', statusFilter);
|
|
298
|
+
return fetchOperations<
|
|
299
|
+
PaginatedResponse<OperationsScheduleAdjustmentRequest>
|
|
300
|
+
>(request, `/operations/schedule-adjustments?${params.toString()}`);
|
|
301
|
+
},
|
|
302
|
+
placeholderData: (previous) => previous,
|
|
254
303
|
});
|
|
255
304
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
.some((value) =>
|
|
269
|
-
String(value)
|
|
270
|
-
.toLowerCase()
|
|
271
|
-
.includes(search.trim().toLowerCase())
|
|
272
|
-
);
|
|
273
|
-
const matchesStatus =
|
|
274
|
-
statusFilter === 'all' ? true : item.status === statusFilter;
|
|
275
|
-
return matchesSearch && matchesStatus;
|
|
276
|
-
}),
|
|
277
|
-
[requests, search, statusFilter]
|
|
278
|
-
);
|
|
305
|
+
const requests = requestsResponse?.data ?? [];
|
|
306
|
+
|
|
307
|
+
const handleViewModeChange = (value: string) => {
|
|
308
|
+
if (value !== 'table' && value !== 'cards') return;
|
|
309
|
+
setViewMode(value as 'table' | 'cards');
|
|
310
|
+
if (typeof window !== 'undefined') {
|
|
311
|
+
window.localStorage.setItem(
|
|
312
|
+
'operations-schedule-adjustments-view-mode',
|
|
313
|
+
value
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
279
317
|
|
|
280
318
|
const cards = [
|
|
281
319
|
{
|
|
@@ -370,6 +408,11 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
370
408
|
}
|
|
371
409
|
};
|
|
372
410
|
|
|
411
|
+
const openDetails = (requestItem: OperationsScheduleAdjustmentRequest) => {
|
|
412
|
+
setSelectedRequest(requestItem);
|
|
413
|
+
setIsDetailsOpen(true);
|
|
414
|
+
};
|
|
415
|
+
|
|
373
416
|
return (
|
|
374
417
|
<Page>
|
|
375
418
|
<OperationsHeader
|
|
@@ -386,130 +429,239 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
386
429
|
}
|
|
387
430
|
/>
|
|
388
431
|
|
|
389
|
-
<SearchBar
|
|
390
|
-
searchQuery={search}
|
|
391
|
-
onSearchChange={setSearch}
|
|
392
|
-
onSearch={() => undefined}
|
|
393
|
-
placeholder={t('searchPlaceholder')}
|
|
394
|
-
controls={[
|
|
395
|
-
{
|
|
396
|
-
id: 'status',
|
|
397
|
-
type: 'select',
|
|
398
|
-
value: statusFilter,
|
|
399
|
-
onChange: setStatusFilter,
|
|
400
|
-
placeholder: commonT('labels.status'),
|
|
401
|
-
options: [
|
|
402
|
-
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
403
|
-
{ value: 'submitted', label: getStatusLabel('submitted') },
|
|
404
|
-
{ value: 'approved', label: getStatusLabel('approved') },
|
|
405
|
-
{ value: 'rejected', label: getStatusLabel('rejected') },
|
|
406
|
-
],
|
|
407
|
-
},
|
|
408
|
-
]}
|
|
409
|
-
/>
|
|
410
|
-
|
|
411
432
|
<KpiCardsGrid items={cards} columns={3} />
|
|
412
433
|
|
|
413
|
-
|
|
414
|
-
<div className="
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
434
|
+
<div className="flex min-w-0 flex-col gap-4 xl:flex-row xl:items-center">
|
|
435
|
+
<div className="flex-1">
|
|
436
|
+
<SearchBar
|
|
437
|
+
searchQuery={search}
|
|
438
|
+
onSearchChange={(value) => {
|
|
439
|
+
setSearch(value);
|
|
440
|
+
setPage(1);
|
|
441
|
+
}}
|
|
442
|
+
showSearchButton={false}
|
|
443
|
+
debounceMs={500}
|
|
444
|
+
placeholder={t('searchPlaceholder')}
|
|
445
|
+
controls={[
|
|
446
|
+
{
|
|
447
|
+
id: 'status',
|
|
448
|
+
type: 'select',
|
|
449
|
+
value: statusFilter,
|
|
450
|
+
onChange: (value) => {
|
|
451
|
+
setStatusFilter(value);
|
|
452
|
+
setPage(1);
|
|
453
|
+
},
|
|
454
|
+
placeholder: commonT('labels.status'),
|
|
455
|
+
options: [
|
|
456
|
+
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
457
|
+
{ value: 'submitted', label: getStatusLabel('submitted') },
|
|
458
|
+
{ value: 'approved', label: getStatusLabel('approved') },
|
|
459
|
+
{ value: 'rejected', label: getStatusLabel('rejected') },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
]}
|
|
463
|
+
/>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<div className="flex items-center justify-between gap-3 xl:justify-end">
|
|
467
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
468
|
+
{t('viewMode')}
|
|
469
|
+
</span>
|
|
470
|
+
<ToggleGroup
|
|
471
|
+
type="single"
|
|
472
|
+
value={viewMode}
|
|
473
|
+
onValueChange={handleViewModeChange}
|
|
474
|
+
variant="outline"
|
|
475
|
+
size="sm"
|
|
476
|
+
>
|
|
477
|
+
<ToggleGroupItem value="table" className="gap-1.5 px-2.5">
|
|
478
|
+
<List className="h-4 w-4" />
|
|
479
|
+
<span className="hidden sm:inline">{t('viewModeTable')}</span>
|
|
480
|
+
</ToggleGroupItem>
|
|
481
|
+
<ToggleGroupItem value="cards" className="gap-1.5 px-2.5">
|
|
482
|
+
<LayoutGrid className="h-4 w-4" />
|
|
483
|
+
<span className="hidden sm:inline">{t('viewModeCards')}</span>
|
|
484
|
+
</ToggleGroupItem>
|
|
485
|
+
</ToggleGroup>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
{requests.length > 0 ? (
|
|
490
|
+
viewMode === 'cards' ? (
|
|
491
|
+
<div className="space-y-3">
|
|
492
|
+
{requests.map((requestItem) => (
|
|
493
|
+
<div
|
|
494
|
+
key={requestItem.id}
|
|
495
|
+
className="rounded-lg border bg-card shadow-sm"
|
|
496
|
+
>
|
|
497
|
+
{/* Card header */}
|
|
498
|
+
<div className="flex flex-wrap items-start justify-between gap-3 border-b px-4 py-3">
|
|
499
|
+
<div className="space-y-1">
|
|
500
|
+
<div className="flex items-center gap-2">
|
|
501
|
+
<User className="size-4 shrink-0 text-muted-foreground" />
|
|
502
|
+
<span className="font-semibold">
|
|
503
|
+
{requestItem.collaboratorName}
|
|
504
|
+
</span>
|
|
505
|
+
<Badge variant="outline" className="text-xs">
|
|
506
|
+
{getRequestScopeLabel(requestItem.requestScope)}
|
|
507
|
+
</Badge>
|
|
508
|
+
</div>
|
|
509
|
+
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
510
|
+
<CalendarRange className="size-3.5 shrink-0" />
|
|
511
|
+
<span>
|
|
512
|
+
{formatDateRange(
|
|
513
|
+
requestItem.effectiveStartDate,
|
|
514
|
+
requestItem.effectiveEndDate
|
|
515
|
+
)}
|
|
516
|
+
</span>
|
|
517
|
+
</div>
|
|
431
518
|
</div>
|
|
432
|
-
<div className="flex items-
|
|
433
|
-
<
|
|
434
|
-
|
|
435
|
-
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
519
|
+
<div className="flex flex-col items-end gap-1">
|
|
520
|
+
<StatusBadge
|
|
521
|
+
label={getStatusLabel(requestItem.status)}
|
|
522
|
+
className={getStatusBadgeClass(requestItem.status)}
|
|
523
|
+
/>
|
|
524
|
+
{getStatusDescription(requestItem.status) ? (
|
|
525
|
+
<p className="text-xs text-muted-foreground">
|
|
526
|
+
{getStatusDescription(requestItem.status)}
|
|
527
|
+
</p>
|
|
528
|
+
) : null}
|
|
440
529
|
</div>
|
|
441
530
|
</div>
|
|
442
|
-
<div className="flex flex-col items-end gap-1">
|
|
443
|
-
<StatusBadge
|
|
444
|
-
label={getStatusLabel(requestItem.status)}
|
|
445
|
-
className={getStatusBadgeClass(requestItem.status)}
|
|
446
|
-
/>
|
|
447
|
-
{getStatusDescription(requestItem.status) ? (
|
|
448
|
-
<p className="text-xs text-muted-foreground">
|
|
449
|
-
{getStatusDescription(requestItem.status)}
|
|
450
|
-
</p>
|
|
451
|
-
) : null}
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
531
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
532
|
+
{/* Schedule comparison */}
|
|
533
|
+
<div className="grid gap-px bg-border sm:grid-cols-2">
|
|
534
|
+
<div className="bg-card px-4 py-3">
|
|
535
|
+
<p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
536
|
+
{t('table.currentSchedule')}
|
|
537
|
+
</p>
|
|
538
|
+
<SchedulePanel
|
|
539
|
+
days={requestItem.currentSchedule ?? []}
|
|
540
|
+
locale={currentLocaleCode}
|
|
541
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
542
|
+
/>
|
|
543
|
+
</div>
|
|
544
|
+
<div className="bg-card px-4 py-3">
|
|
545
|
+
<p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
546
|
+
{t('table.requestedSchedule')}
|
|
547
|
+
</p>
|
|
548
|
+
<SchedulePanel
|
|
549
|
+
days={
|
|
550
|
+
(requestItem.days ??
|
|
551
|
+
[]) as OperationsScheduleAdjustmentDay[]
|
|
552
|
+
}
|
|
553
|
+
locale={currentLocaleCode}
|
|
554
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
555
|
+
/>
|
|
556
|
+
</div>
|
|
479
557
|
</div>
|
|
480
|
-
</div>
|
|
481
558
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
</
|
|
506
|
-
{requestItem.reason}
|
|
559
|
+
{/* Card footer */}
|
|
560
|
+
<div className="flex flex-wrap items-start justify-between gap-3 border-t bg-muted/20 px-4 py-2.5 text-sm">
|
|
561
|
+
<div className="space-y-2">
|
|
562
|
+
<div className="flex items-center gap-1.5 text-muted-foreground">
|
|
563
|
+
<Clock className="size-3.5 shrink-0" />
|
|
564
|
+
<span className="font-medium">
|
|
565
|
+
{commonT('labels.approver')}:
|
|
566
|
+
</span>
|
|
567
|
+
<span>
|
|
568
|
+
{requestItem.approverName ||
|
|
569
|
+
commonT('labels.notAssigned')}
|
|
570
|
+
</span>
|
|
571
|
+
</div>
|
|
572
|
+
<div>
|
|
573
|
+
<Button
|
|
574
|
+
variant="outline"
|
|
575
|
+
size="sm"
|
|
576
|
+
className="cursor-pointer"
|
|
577
|
+
onClick={() => openDetails(requestItem)}
|
|
578
|
+
>
|
|
579
|
+
<Eye className="size-4" />
|
|
580
|
+
{t('actions.viewDetails')}
|
|
581
|
+
</Button>
|
|
582
|
+
</div>
|
|
507
583
|
</div>
|
|
508
|
-
|
|
584
|
+
{requestItem.approverNote ? (
|
|
585
|
+
<div className="text-xs text-muted-foreground">
|
|
586
|
+
<span className="font-medium">
|
|
587
|
+
{commonT('labels.notes')}:
|
|
588
|
+
</span>{' '}
|
|
589
|
+
{requestItem.approverNote}
|
|
590
|
+
</div>
|
|
591
|
+
) : null}
|
|
592
|
+
{requestItem.reason ? (
|
|
593
|
+
<div className="text-xs text-muted-foreground">
|
|
594
|
+
<span className="font-medium">
|
|
595
|
+
{commonT('labels.reason')}:
|
|
596
|
+
</span>{' '}
|
|
597
|
+
{requestItem.reason}
|
|
598
|
+
</div>
|
|
599
|
+
) : null}
|
|
600
|
+
</div>
|
|
509
601
|
</div>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
602
|
+
))}
|
|
603
|
+
</div>
|
|
604
|
+
) : (
|
|
605
|
+
<div className="overflow-x-auto rounded-md border">
|
|
606
|
+
<Table>
|
|
607
|
+
<TableHeader>
|
|
608
|
+
<TableRow>
|
|
609
|
+
<TableHead>{commonT('labels.collaborator')}</TableHead>
|
|
610
|
+
<TableHead>{t('table.scope')}</TableHead>
|
|
611
|
+
<TableHead>{commonT('labels.timeline')}</TableHead>
|
|
612
|
+
<TableHead>{commonT('labels.approver')}</TableHead>
|
|
613
|
+
<TableHead>{commonT('labels.status')}</TableHead>
|
|
614
|
+
<TableHead className="text-right">
|
|
615
|
+
{commonT('labels.actions')}
|
|
616
|
+
</TableHead>
|
|
617
|
+
</TableRow>
|
|
618
|
+
</TableHeader>
|
|
619
|
+
<TableBody>
|
|
620
|
+
{requests.map((requestItem) => (
|
|
621
|
+
<TableRow key={requestItem.id} className="hover:bg-muted/30">
|
|
622
|
+
<TableCell className="font-medium">
|
|
623
|
+
{requestItem.collaboratorName}
|
|
624
|
+
</TableCell>
|
|
625
|
+
<TableCell>
|
|
626
|
+
<Badge variant="outline" className="text-xs">
|
|
627
|
+
{getRequestScopeLabel(requestItem.requestScope)}
|
|
628
|
+
</Badge>
|
|
629
|
+
</TableCell>
|
|
630
|
+
<TableCell>
|
|
631
|
+
{formatDateRange(
|
|
632
|
+
requestItem.effectiveStartDate,
|
|
633
|
+
requestItem.effectiveEndDate
|
|
634
|
+
)}
|
|
635
|
+
</TableCell>
|
|
636
|
+
<TableCell>
|
|
637
|
+
{requestItem.approverName ||
|
|
638
|
+
commonT('labels.notAssigned')}
|
|
639
|
+
</TableCell>
|
|
640
|
+
<TableCell>
|
|
641
|
+
<StatusBadge
|
|
642
|
+
label={getStatusLabel(requestItem.status)}
|
|
643
|
+
className={getStatusBadgeClass(requestItem.status)}
|
|
644
|
+
/>
|
|
645
|
+
</TableCell>
|
|
646
|
+
<TableCell>
|
|
647
|
+
<div className="flex justify-end">
|
|
648
|
+
<Button
|
|
649
|
+
variant="outline"
|
|
650
|
+
size="sm"
|
|
651
|
+
className="cursor-pointer"
|
|
652
|
+
onClick={() => openDetails(requestItem)}
|
|
653
|
+
>
|
|
654
|
+
<Eye className="size-4" />
|
|
655
|
+
{t('actions.viewDetails')}
|
|
656
|
+
</Button>
|
|
657
|
+
</div>
|
|
658
|
+
</TableCell>
|
|
659
|
+
</TableRow>
|
|
660
|
+
))}
|
|
661
|
+
</TableBody>
|
|
662
|
+
</Table>
|
|
663
|
+
</div>
|
|
664
|
+
)
|
|
513
665
|
) : (
|
|
514
666
|
<EmptyState
|
|
515
667
|
icon={<CalendarRange className="size-12" />}
|
|
@@ -528,6 +680,18 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
528
680
|
/>
|
|
529
681
|
)}
|
|
530
682
|
|
|
683
|
+
<PaginationFooter
|
|
684
|
+
currentPage={page}
|
|
685
|
+
pageSize={pageSize}
|
|
686
|
+
totalItems={requestsResponse?.total ?? 0}
|
|
687
|
+
pageSizeOptions={[12, 24, 48]}
|
|
688
|
+
onPageChange={setPage}
|
|
689
|
+
onPageSizeChange={(size) => {
|
|
690
|
+
setPageSize(size);
|
|
691
|
+
setPage(1);
|
|
692
|
+
}}
|
|
693
|
+
/>
|
|
694
|
+
|
|
531
695
|
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
|
|
532
696
|
<SheetContent className="w-full overflow-y-auto sm:max-w-3xl">
|
|
533
697
|
<SheetHeader>
|
|
@@ -722,6 +886,107 @@ export default function OperationsScheduleAdjustmentsPage() {
|
|
|
722
886
|
/>
|
|
723
887
|
</SheetContent>
|
|
724
888
|
</Sheet>
|
|
889
|
+
|
|
890
|
+
<Sheet open={isDetailsOpen} onOpenChange={setIsDetailsOpen}>
|
|
891
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-3xl">
|
|
892
|
+
<SheetHeader>
|
|
893
|
+
<SheetTitle>{t('details.title')}</SheetTitle>
|
|
894
|
+
<SheetDescription>{t('details.description')}</SheetDescription>
|
|
895
|
+
</SheetHeader>
|
|
896
|
+
|
|
897
|
+
{selectedRequest ? (
|
|
898
|
+
<div className="mt-6 space-y-5 px-4 pb-8">
|
|
899
|
+
<div className="rounded-lg border bg-muted/20 p-4">
|
|
900
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
901
|
+
<div>
|
|
902
|
+
<div className="text-lg font-semibold">
|
|
903
|
+
{selectedRequest.collaboratorName}
|
|
904
|
+
</div>
|
|
905
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
906
|
+
{getRequestScopeLabel(selectedRequest.requestScope)}
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
<StatusBadge
|
|
910
|
+
label={getStatusLabel(selectedRequest.status)}
|
|
911
|
+
className={getStatusBadgeClass(selectedRequest.status)}
|
|
912
|
+
/>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
917
|
+
<div>
|
|
918
|
+
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
919
|
+
{commonT('labels.timeline')}
|
|
920
|
+
</div>
|
|
921
|
+
<div className="mt-1 text-sm">
|
|
922
|
+
{formatDateRange(
|
|
923
|
+
selectedRequest.effectiveStartDate,
|
|
924
|
+
selectedRequest.effectiveEndDate
|
|
925
|
+
)}
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
928
|
+
<div>
|
|
929
|
+
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
930
|
+
{commonT('labels.approver')}
|
|
931
|
+
</div>
|
|
932
|
+
<div className="mt-1 text-sm">
|
|
933
|
+
{selectedRequest.approverName ||
|
|
934
|
+
commonT('labels.notAssigned')}
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
|
|
939
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
940
|
+
<div>
|
|
941
|
+
<div className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
942
|
+
{t('table.currentSchedule')}
|
|
943
|
+
</div>
|
|
944
|
+
<div className="rounded-lg border p-3">
|
|
945
|
+
<SchedulePanel
|
|
946
|
+
days={selectedRequest.currentSchedule ?? []}
|
|
947
|
+
locale={currentLocaleCode}
|
|
948
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
949
|
+
/>
|
|
950
|
+
</div>
|
|
951
|
+
</div>
|
|
952
|
+
<div>
|
|
953
|
+
<div className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
954
|
+
{t('table.requestedSchedule')}
|
|
955
|
+
</div>
|
|
956
|
+
<div className="rounded-lg border p-3">
|
|
957
|
+
<SchedulePanel
|
|
958
|
+
days={
|
|
959
|
+
(selectedRequest.days ??
|
|
960
|
+
[]) as OperationsScheduleAdjustmentDay[]
|
|
961
|
+
}
|
|
962
|
+
locale={currentLocaleCode}
|
|
963
|
+
emptyLabel={commonT('labels.notAssigned')}
|
|
964
|
+
/>
|
|
965
|
+
</div>
|
|
966
|
+
</div>
|
|
967
|
+
</div>
|
|
968
|
+
|
|
969
|
+
<div>
|
|
970
|
+
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
971
|
+
{commonT('labels.reason')}
|
|
972
|
+
</div>
|
|
973
|
+
<div className="mt-1 rounded-lg border p-3 text-sm">
|
|
974
|
+
{selectedRequest.reason || commonT('labels.noNotes')}
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<div>
|
|
979
|
+
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
980
|
+
{commonT('labels.notes')}
|
|
981
|
+
</div>
|
|
982
|
+
<div className="mt-1 rounded-lg border p-3 text-sm">
|
|
983
|
+
{selectedRequest.approverNote || commonT('labels.noNotes')}
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
</div>
|
|
987
|
+
) : null}
|
|
988
|
+
</SheetContent>
|
|
989
|
+
</Sheet>
|
|
725
990
|
</Page>
|
|
726
991
|
);
|
|
727
992
|
}
|