@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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { CardDescription, CardTitle } from '@/components/ui/card';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from '
|
|
3
|
+
Tooltip,
|
|
4
|
+
TooltipContent,
|
|
5
|
+
TooltipTrigger,
|
|
6
|
+
} from '@/components/ui/tooltip';
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
|
+
import { Info } from 'lucide-react';
|
|
8
9
|
import { ReactNode } from 'react';
|
|
9
10
|
|
|
10
11
|
interface SectionCardProps {
|
|
@@ -12,6 +13,8 @@ interface SectionCardProps {
|
|
|
12
13
|
description?: string;
|
|
13
14
|
children: ReactNode;
|
|
14
15
|
className?: string;
|
|
16
|
+
compact?: boolean;
|
|
17
|
+
descriptionMode?: 'inline' | 'tooltip';
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export function SectionCard({
|
|
@@ -19,14 +22,46 @@ export function SectionCard({
|
|
|
19
22
|
description,
|
|
20
23
|
children,
|
|
21
24
|
className,
|
|
25
|
+
compact = false,
|
|
26
|
+
descriptionMode = 'inline',
|
|
22
27
|
}: SectionCardProps) {
|
|
23
28
|
return (
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
<div className={cn(className, '')}>
|
|
30
|
+
<div className={compact ? 'space-y-1 px-0 py-2.5' : undefined}>
|
|
31
|
+
<div className="flex items-start justify-between gap-2">
|
|
32
|
+
<div className="flex items-center gap-2">
|
|
33
|
+
<CardTitle
|
|
34
|
+
className={compact ? 'text-sm font-semibold' : undefined}
|
|
35
|
+
>
|
|
36
|
+
{title}
|
|
37
|
+
</CardTitle>
|
|
38
|
+
{description && descriptionMode === 'tooltip' ? (
|
|
39
|
+
<Tooltip>
|
|
40
|
+
<TooltipTrigger asChild>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
className="cursor-help text-muted-foreground transition-colors hover:text-foreground"
|
|
44
|
+
aria-label={description}
|
|
45
|
+
>
|
|
46
|
+
<Info className="size-4" />
|
|
47
|
+
</button>
|
|
48
|
+
</TooltipTrigger>
|
|
49
|
+
<TooltipContent className="max-w-xs text-sm leading-relaxed">
|
|
50
|
+
{description}
|
|
51
|
+
</TooltipContent>
|
|
52
|
+
</Tooltip>
|
|
53
|
+
) : null}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
{description && descriptionMode !== 'tooltip' ? (
|
|
57
|
+
<CardDescription
|
|
58
|
+
className={compact ? 'text-[11px] leading-relaxed' : undefined}
|
|
59
|
+
>
|
|
60
|
+
{description}
|
|
61
|
+
</CardDescription>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
<div className={compact ? 'px-0 pb-3 pt-0' : undefined}>{children}</div>
|
|
65
|
+
</div>
|
|
31
66
|
);
|
|
32
67
|
}
|
|
@@ -2,12 +2,9 @@ type RequestFn = (input: {
|
|
|
2
2
|
url: string;
|
|
3
3
|
method: string;
|
|
4
4
|
data?: unknown;
|
|
5
|
-
}) => Promise<{ data:
|
|
5
|
+
}) => Promise<{ data: unknown }>;
|
|
6
6
|
|
|
7
|
-
export async function fetchOperations<T>(
|
|
8
|
-
request: RequestFn,
|
|
9
|
-
url: string
|
|
10
|
-
) {
|
|
7
|
+
export async function fetchOperations<T>(request: RequestFn, url: string) {
|
|
11
8
|
const response = await request({
|
|
12
9
|
url,
|
|
13
10
|
method: 'GET',
|
|
@@ -26,10 +26,14 @@ export type OperationsDashboard = {
|
|
|
26
26
|
|
|
27
27
|
export type OperationsCollaborator = {
|
|
28
28
|
id: number;
|
|
29
|
-
userId?: number;
|
|
29
|
+
userId?: number | null;
|
|
30
|
+
personId?: number | null;
|
|
31
|
+
personName?: string | null;
|
|
32
|
+
personAvatarId?: number | null;
|
|
30
33
|
code: string;
|
|
31
34
|
collaboratorType?: string;
|
|
32
35
|
displayName: string;
|
|
36
|
+
departmentId?: number | null;
|
|
33
37
|
department?: string | null;
|
|
34
38
|
title?: string | null;
|
|
35
39
|
levelLabel?: string | null;
|
|
@@ -48,6 +52,18 @@ export type OperationsCollaborator = {
|
|
|
48
52
|
pendingScheduleAdjustmentRequests?: number;
|
|
49
53
|
};
|
|
50
54
|
|
|
55
|
+
export type OperationsDepartment = {
|
|
56
|
+
id: number;
|
|
57
|
+
slug: string;
|
|
58
|
+
code?: string | null;
|
|
59
|
+
name: string;
|
|
60
|
+
description?: string | null;
|
|
61
|
+
status: 'active' | 'inactive';
|
|
62
|
+
collaboratorCount?: number;
|
|
63
|
+
createdAt?: string | null;
|
|
64
|
+
updatedAt?: string | null;
|
|
65
|
+
};
|
|
66
|
+
|
|
51
67
|
export type OperationsProject = {
|
|
52
68
|
id: number;
|
|
53
69
|
contractId?: number | null;
|
|
@@ -71,41 +87,65 @@ export type OperationsProject = {
|
|
|
71
87
|
teamSize?: number;
|
|
72
88
|
};
|
|
73
89
|
|
|
74
|
-
export type OperationsContract = {
|
|
75
|
-
id: number;
|
|
76
|
-
code: string;
|
|
77
|
-
name
|
|
78
|
-
contractCategory?: string;
|
|
79
|
-
contractType?: string;
|
|
80
|
-
clientName
|
|
81
|
-
signatureStatus?: string;
|
|
82
|
-
isActive?: boolean;
|
|
83
|
-
billingModel: string;
|
|
90
|
+
export type OperationsContract = {
|
|
91
|
+
id: number;
|
|
92
|
+
code: string;
|
|
93
|
+
name?: string | null;
|
|
94
|
+
contractCategory?: string;
|
|
95
|
+
contractType?: string;
|
|
96
|
+
clientName?: string | null;
|
|
97
|
+
signatureStatus?: string;
|
|
98
|
+
isActive?: boolean;
|
|
99
|
+
billingModel: string;
|
|
84
100
|
accountManagerCollaboratorId?: number | null;
|
|
85
101
|
accountManagerName?: string | null;
|
|
86
102
|
relatedCollaboratorId?: number | null;
|
|
87
103
|
relatedCollaboratorName?: string | null;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
contractTemplateId?: number | null;
|
|
105
|
+
contractTemplateName?: string | null;
|
|
106
|
+
contractTemplateSlug?: string | null;
|
|
107
|
+
contractTemplateCode?: string | null;
|
|
108
|
+
mainRelatedPartyName?: string | null;
|
|
109
|
+
originType?: string | null;
|
|
110
|
+
originId?: number | null;
|
|
111
|
+
startDate?: string | null;
|
|
112
|
+
endDate?: string | null;
|
|
113
|
+
signedAt?: string | null;
|
|
114
|
+
effectiveDate?: string | null;
|
|
95
115
|
budgetAmount?: number | null;
|
|
96
116
|
monthlyHourCap?: number | null;
|
|
97
117
|
valueAmount?: number | null;
|
|
98
118
|
paymentAmount?: number | null;
|
|
99
119
|
revenueAmount?: number | null;
|
|
100
|
-
fineAmount?: number | null;
|
|
101
|
-
status: string;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
fineAmount?: number | null;
|
|
121
|
+
status: string;
|
|
122
|
+
creationMode?: 'blank' | 'template' | 'upload' | 'duplicate' | null;
|
|
123
|
+
wizardStep?: number | null;
|
|
124
|
+
description?: string | null;
|
|
125
|
+
contentHtml?: string | null;
|
|
126
|
+
projectCount?: number;
|
|
105
127
|
currentPdfFileName?: string | null;
|
|
106
128
|
scheduleSummary?: OperationsWeeklyScheduleDay[];
|
|
107
129
|
};
|
|
108
130
|
|
|
131
|
+
export type OperationsContractTemplate = {
|
|
132
|
+
id: number;
|
|
133
|
+
slug: string;
|
|
134
|
+
code?: string | null;
|
|
135
|
+
name: string;
|
|
136
|
+
description?: string | null;
|
|
137
|
+
contractCategory?: string | null;
|
|
138
|
+
contractType?: string | null;
|
|
139
|
+
billingModel?: string | null;
|
|
140
|
+
signatureStatus?: string | null;
|
|
141
|
+
isActive?: boolean;
|
|
142
|
+
status: string;
|
|
143
|
+
contentHtml?: string | null;
|
|
144
|
+
usageCount?: number;
|
|
145
|
+
createdAt?: string | null;
|
|
146
|
+
updatedAt?: string | null;
|
|
147
|
+
};
|
|
148
|
+
|
|
109
149
|
export type OperationsWeeklyScheduleDay = {
|
|
110
150
|
weekday: string;
|
|
111
151
|
isWorkingDay: boolean;
|
|
@@ -331,16 +371,19 @@ export type OperationsContractDetails = OperationsContract & {
|
|
|
331
371
|
dueDay?: number | null;
|
|
332
372
|
notes?: string | null;
|
|
333
373
|
}>;
|
|
334
|
-
documents: Array<{
|
|
335
|
-
id: number;
|
|
336
|
-
documentType: string;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
374
|
+
documents: Array<{
|
|
375
|
+
id: number;
|
|
376
|
+
documentType: string;
|
|
377
|
+
fileId?: number | null;
|
|
378
|
+
fileName: string;
|
|
379
|
+
mimeType: string;
|
|
380
|
+
fileContentBase64?: string | null;
|
|
381
|
+
isCurrent: boolean;
|
|
382
|
+
extractionStatus?: string | null;
|
|
383
|
+
extractionSummary?: string | null;
|
|
384
|
+
notes?: string | null;
|
|
385
|
+
createdAt: string;
|
|
386
|
+
}>;
|
|
344
387
|
revisions: Array<{
|
|
345
388
|
id: number;
|
|
346
389
|
revisionType: string;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDateTime as formatDateTimeWithSettings,
|
|
3
|
+
formatDate as formatDateWithSettings,
|
|
4
|
+
} from '@/lib/format-date';
|
|
5
|
+
|
|
1
6
|
export const operationsCurrency = new Intl.NumberFormat('en-US', {
|
|
2
7
|
style: 'currency',
|
|
3
8
|
currency: 'USD',
|
|
@@ -8,24 +13,74 @@ export function formatCurrency(value: number) {
|
|
|
8
13
|
return operationsCurrency.format(value);
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
function resolveLocale(currentLocaleCode: string) {
|
|
17
|
+
return currentLocaleCode.startsWith('pt') ? 'pt-BR' : 'en-US';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatDate(
|
|
21
|
+
value?: string | null,
|
|
22
|
+
getSettingValue?: ((key: string) => unknown) | null,
|
|
23
|
+
currentLocaleCode = 'en-US'
|
|
24
|
+
) {
|
|
12
25
|
if (!value) {
|
|
13
26
|
return '-';
|
|
14
27
|
}
|
|
15
28
|
|
|
16
|
-
|
|
29
|
+
const parsedDate = new Date(
|
|
30
|
+
value.includes('T') ? value : `${value}T00:00:00`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
34
|
+
return '-';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (getSettingValue) {
|
|
38
|
+
try {
|
|
39
|
+
return formatDateWithSettings(
|
|
40
|
+
parsedDate,
|
|
41
|
+
getSettingValue,
|
|
42
|
+
currentLocaleCode
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
// Fall back to a locale-based format below.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return parsedDate.toLocaleDateString(resolveLocale(currentLocaleCode), {
|
|
17
50
|
month: 'short',
|
|
18
51
|
day: 'numeric',
|
|
19
52
|
year: 'numeric',
|
|
20
53
|
});
|
|
21
54
|
}
|
|
22
55
|
|
|
23
|
-
export function formatDateTime(
|
|
56
|
+
export function formatDateTime(
|
|
57
|
+
value?: string | null,
|
|
58
|
+
getSettingValue?: ((key: string) => unknown) | null,
|
|
59
|
+
currentLocaleCode = 'en-US'
|
|
60
|
+
) {
|
|
24
61
|
if (!value) {
|
|
25
62
|
return '-';
|
|
26
63
|
}
|
|
27
64
|
|
|
28
|
-
|
|
65
|
+
const parsedDate = new Date(value);
|
|
66
|
+
|
|
67
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
68
|
+
return '-';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (getSettingValue) {
|
|
72
|
+
try {
|
|
73
|
+
return formatDateTimeWithSettings(
|
|
74
|
+
parsedDate,
|
|
75
|
+
getSettingValue,
|
|
76
|
+
currentLocaleCode
|
|
77
|
+
);
|
|
78
|
+
} catch {
|
|
79
|
+
// Fall back to a locale-based format below.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return parsedDate.toLocaleString(resolveLocale(currentLocaleCode), {
|
|
29
84
|
month: 'short',
|
|
30
85
|
day: 'numeric',
|
|
31
86
|
year: 'numeric',
|
|
@@ -50,6 +105,26 @@ export function formatHours(value?: number | null) {
|
|
|
50
105
|
return `${Number(value).toFixed(1)}h`;
|
|
51
106
|
}
|
|
52
107
|
|
|
108
|
+
function formatTimeValue(value?: string | null) {
|
|
109
|
+
if (!value) {
|
|
110
|
+
return '-';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const directMatch = String(value).match(/(\d{2}:\d{2})(?::\d{2})?/);
|
|
114
|
+
|
|
115
|
+
if (directMatch?.[1]) {
|
|
116
|
+
return directMatch[1];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parsedDate = new Date(value);
|
|
120
|
+
|
|
121
|
+
if (!Number.isNaN(parsedDate.getTime())) {
|
|
122
|
+
return parsedDate.toISOString().slice(11, 16);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return String(value);
|
|
126
|
+
}
|
|
127
|
+
|
|
53
128
|
export function formatEnumLabel(value?: string | null) {
|
|
54
129
|
if (!value) {
|
|
55
130
|
return '-';
|
|
@@ -62,17 +137,19 @@ export function formatEnumLabel(value?: string | null) {
|
|
|
62
137
|
|
|
63
138
|
export function formatDateRange(
|
|
64
139
|
startDate?: string | null,
|
|
65
|
-
endDate?: string | null
|
|
140
|
+
endDate?: string | null,
|
|
141
|
+
getSettingValue?: ((key: string) => unknown) | null,
|
|
142
|
+
currentLocaleCode = 'en-US'
|
|
66
143
|
) {
|
|
67
144
|
if (!startDate && !endDate) {
|
|
68
145
|
return '-';
|
|
69
146
|
}
|
|
70
147
|
|
|
71
148
|
if (!endDate || startDate === endDate) {
|
|
72
|
-
return formatDate(startDate);
|
|
149
|
+
return formatDate(startDate, getSettingValue, currentLocaleCode);
|
|
73
150
|
}
|
|
74
151
|
|
|
75
|
-
return `${formatDate(startDate)} to ${formatDate(endDate)}`;
|
|
152
|
+
return `${formatDate(startDate, getSettingValue, currentLocaleCode)} to ${formatDate(endDate, getSettingValue, currentLocaleCode)}`;
|
|
76
153
|
}
|
|
77
154
|
|
|
78
155
|
export function getStatusBadgeClass(value?: string | null) {
|
|
@@ -120,7 +197,7 @@ export function summarizeScheduleDays(
|
|
|
120
197
|
.map((day) => {
|
|
121
198
|
const label = formatEnumLabel(day.weekday);
|
|
122
199
|
if (day.startTime && day.endTime) {
|
|
123
|
-
return `${label}: ${day.startTime}-${day.endTime}`;
|
|
200
|
+
return `${label}: ${formatTimeValue(day.startTime)}-${formatTimeValue(day.endTime)}`;
|
|
124
201
|
}
|
|
125
202
|
|
|
126
203
|
return label;
|
|
@@ -37,34 +37,12 @@ import {
|
|
|
37
37
|
getStatusBadgeClass,
|
|
38
38
|
} from '../_lib/utils/format';
|
|
39
39
|
|
|
40
|
-
type PendingDecision = {
|
|
41
|
-
approval: OperationsApproval;
|
|
42
|
-
action: 'approve' | 'reject';
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function
|
|
46
|
-
switch (approval.targetType) {
|
|
47
|
-
case 'timesheet':
|
|
48
|
-
return `Timesheet ${formatDateRange(
|
|
49
|
-
approval.timesheetWeekStartDate,
|
|
50
|
-
approval.timesheetWeekEndDate
|
|
51
|
-
)}`;
|
|
52
|
-
case 'time_off_request':
|
|
53
|
-
return `${formatEnumLabel(approval.timeOffType)} ${formatDateRange(
|
|
54
|
-
approval.timeOffStartDate,
|
|
55
|
-
approval.timeOffEndDate
|
|
56
|
-
)}`;
|
|
57
|
-
case 'schedule_adjustment_request':
|
|
58
|
-
return `${formatEnumLabel(approval.scheduleRequestScope)} ${formatDateRange(
|
|
59
|
-
approval.scheduleStartDate,
|
|
60
|
-
approval.scheduleEndDate
|
|
61
|
-
)}`;
|
|
62
|
-
default:
|
|
63
|
-
return formatEnumLabel(approval.targetType);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export default function OperationsApprovalsPage() {
|
|
40
|
+
type PendingDecision = {
|
|
41
|
+
approval: OperationsApproval;
|
|
42
|
+
action: 'approve' | 'reject';
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default function OperationsApprovalsPage() {
|
|
68
46
|
const t = useTranslations('operations.ApprovalsPage');
|
|
69
47
|
const commonT = useTranslations('operations.Common');
|
|
70
48
|
const { request, showToastHandler, currentLocaleCode } = useApp();
|
|
@@ -72,10 +50,68 @@ export default function OperationsApprovalsPage() {
|
|
|
72
50
|
const [search, setSearch] = useState('');
|
|
73
51
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
74
52
|
const [targetFilter, setTargetFilter] = useState('all');
|
|
75
|
-
const [decisionNote, setDecisionNote] = useState('');
|
|
76
|
-
const [pendingDecision, setPendingDecision] = useState<PendingDecision | null>(
|
|
77
|
-
null
|
|
78
|
-
);
|
|
53
|
+
const [decisionNote, setDecisionNote] = useState('');
|
|
54
|
+
const [pendingDecision, setPendingDecision] = useState<PendingDecision | null>(
|
|
55
|
+
null
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const getStatusLabel = (value?: string | null) => {
|
|
59
|
+
if (!value) {
|
|
60
|
+
return '-';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const key = `options.statuses.${value}`;
|
|
64
|
+
return t.has(key) ? t(key) : formatEnumLabel(value);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getTargetTypeLabel = (value?: string | null) => {
|
|
68
|
+
if (!value) {
|
|
69
|
+
return '-';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const key = `options.targetTypes.${value}`;
|
|
73
|
+
return t.has(key) ? t(key) : formatEnumLabel(value);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getTimeOffTypeLabel = (value?: string | null) => {
|
|
77
|
+
if (!value) {
|
|
78
|
+
return '-';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const key = `options.timeOffTypes.${value}`;
|
|
82
|
+
return t.has(key) ? t(key) : formatEnumLabel(value);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const getScheduleScopeLabel = (value?: string | null) => {
|
|
86
|
+
if (!value) {
|
|
87
|
+
return '-';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const key = `options.scheduleScopes.${value}`;
|
|
91
|
+
return t.has(key) ? t(key) : formatEnumLabel(value);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getTargetLabel = (approval: OperationsApproval) => {
|
|
95
|
+
switch (approval.targetType) {
|
|
96
|
+
case 'timesheet':
|
|
97
|
+
return `${getTargetTypeLabel('timesheet')} ${formatDateRange(
|
|
98
|
+
approval.timesheetWeekStartDate,
|
|
99
|
+
approval.timesheetWeekEndDate
|
|
100
|
+
)}`;
|
|
101
|
+
case 'time_off_request':
|
|
102
|
+
return `${getTimeOffTypeLabel(approval.timeOffType)} ${formatDateRange(
|
|
103
|
+
approval.timeOffStartDate,
|
|
104
|
+
approval.timeOffEndDate
|
|
105
|
+
)}`;
|
|
106
|
+
case 'schedule_adjustment_request':
|
|
107
|
+
return `${getScheduleScopeLabel(approval.scheduleRequestScope)} ${formatDateRange(
|
|
108
|
+
approval.scheduleStartDate,
|
|
109
|
+
approval.scheduleEndDate
|
|
110
|
+
)}`;
|
|
111
|
+
default:
|
|
112
|
+
return getTargetTypeLabel(approval.targetType);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
79
115
|
|
|
80
116
|
const { data: approvals = [], refetch } = useQuery<OperationsApproval[]>({
|
|
81
117
|
queryKey: ['operations-approvals', currentLocaleCode],
|
|
@@ -194,12 +230,12 @@ export default function OperationsApprovalsPage() {
|
|
|
194
230
|
value: statusFilter,
|
|
195
231
|
onChange: setStatusFilter,
|
|
196
232
|
placeholder: commonT('labels.status'),
|
|
197
|
-
options: [
|
|
198
|
-
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
199
|
-
{ value: 'pending', label:
|
|
200
|
-
{ value: 'approved', label:
|
|
201
|
-
{ value: 'rejected', label:
|
|
202
|
-
],
|
|
233
|
+
options: [
|
|
234
|
+
{ value: 'all', label: commonT('filters.allStatuses') },
|
|
235
|
+
{ value: 'pending', label: getStatusLabel('pending') },
|
|
236
|
+
{ value: 'approved', label: getStatusLabel('approved') },
|
|
237
|
+
{ value: 'rejected', label: getStatusLabel('rejected') },
|
|
238
|
+
],
|
|
203
239
|
},
|
|
204
240
|
{
|
|
205
241
|
id: 'target',
|
|
@@ -207,18 +243,18 @@ export default function OperationsApprovalsPage() {
|
|
|
207
243
|
value: targetFilter,
|
|
208
244
|
onChange: setTargetFilter,
|
|
209
245
|
placeholder: commonT('labels.requestType'),
|
|
210
|
-
options: [
|
|
211
|
-
{ value: 'all', label: commonT('filters.allTypes') },
|
|
212
|
-
{ value: 'timesheet', label:
|
|
213
|
-
{
|
|
214
|
-
value: 'time_off_request',
|
|
215
|
-
label:
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
value: 'schedule_adjustment_request',
|
|
219
|
-
label:
|
|
220
|
-
},
|
|
221
|
-
],
|
|
246
|
+
options: [
|
|
247
|
+
{ value: 'all', label: commonT('filters.allTypes') },
|
|
248
|
+
{ value: 'timesheet', label: getTargetTypeLabel('timesheet') },
|
|
249
|
+
{
|
|
250
|
+
value: 'time_off_request',
|
|
251
|
+
label: getTargetTypeLabel('time_off_request'),
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
value: 'schedule_adjustment_request',
|
|
255
|
+
label: getTargetTypeLabel('schedule_adjustment_request'),
|
|
256
|
+
},
|
|
257
|
+
],
|
|
222
258
|
},
|
|
223
259
|
]}
|
|
224
260
|
/>
|
|
@@ -272,10 +308,10 @@ export default function OperationsApprovalsPage() {
|
|
|
272
308
|
</div>
|
|
273
309
|
</TableCell>
|
|
274
310
|
<TableCell>
|
|
275
|
-
<StatusBadge
|
|
276
|
-
label={
|
|
277
|
-
className={getStatusBadgeClass(approval.status)}
|
|
278
|
-
/>
|
|
311
|
+
<StatusBadge
|
|
312
|
+
label={getStatusLabel(approval.status)}
|
|
313
|
+
className={getStatusBadgeClass(approval.status)}
|
|
314
|
+
/>
|
|
279
315
|
</TableCell>
|
|
280
316
|
<TableCell>
|
|
281
317
|
{approval.status === 'pending' ? (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
2
|
|
|
3
3
|
export default async function EditCollaboratorPage({
|
|
4
4
|
params,
|
|
@@ -7,5 +7,5 @@ export default async function EditCollaboratorPage({
|
|
|
7
7
|
}) {
|
|
8
8
|
const { id } = await params;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
redirect(`/operations/collaborators?edit=${id}`);
|
|
11
11
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { redirect } from 'next/navigation';
|
|
2
2
|
|
|
3
3
|
export default async function CollaboratorDetailsPage({
|
|
4
4
|
params,
|
|
@@ -7,5 +7,5 @@ export default async function CollaboratorDetailsPage({
|
|
|
7
7
|
}) {
|
|
8
8
|
const { id } = await params;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
redirect(`/operations/collaborators?edit=${id}`);
|
|
11
11
|
}
|