@hed-hog/operations 0.0.3 → 0.0.285
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/README.md +122 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/operations-data.controller.d.ts +139 -0
- package/dist/operations-data.controller.d.ts.map +1 -0
- package/dist/operations-data.controller.js +113 -0
- package/dist/operations-data.controller.js.map +1 -0
- package/dist/operations-growth.controller.d.ts +48 -0
- package/dist/operations-growth.controller.d.ts.map +1 -0
- package/dist/operations-growth.controller.js +90 -0
- package/dist/operations-growth.controller.js.map +1 -0
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +10 -4
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.service.d.ts +178 -0
- package/dist/operations.service.d.ts.map +1 -0
- package/dist/operations.service.js +134 -0
- package/dist/operations.service.js.map +1 -0
- package/hedhog/data/menu.yaml +251 -132
- package/hedhog/data/operations_career_level.yaml +102 -0
- package/hedhog/data/operations_career_track.yaml +8 -0
- package/hedhog/data/operations_certification.yaml +38 -0
- package/hedhog/data/operations_evaluation_cycle.yaml +18 -0
- package/hedhog/data/operations_performance_criterion.yaml +48 -0
- package/hedhog/data/role.yaml +14 -7
- package/hedhog/data/route.yaml +143 -80
- package/hedhog/frontend/app/_components/allocation-calendar.tsx.ejs +56 -56
- package/hedhog/frontend/app/_components/kanban-board.tsx.ejs +83 -83
- package/hedhog/frontend/app/_components/operations-header.tsx.ejs +29 -29
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +32 -32
- package/hedhog/frontend/app/_components/status-badge.tsx.ejs +15 -15
- package/hedhog/frontend/app/_components/timesheet-entry-dialog.tsx.ejs +142 -142
- package/hedhog/frontend/app/_lib/hooks/use-operations-data.ts.ejs +41 -41
- package/hedhog/frontend/app/_lib/hooks/use-operations-growth-data.ts.ejs +63 -0
- package/hedhog/frontend/app/_lib/mocks/allocations.mock.ts.ejs +74 -74
- package/hedhog/frontend/app/_lib/mocks/contracts.mock.ts.ejs +74 -74
- package/hedhog/frontend/app/_lib/mocks/operations-growth.mock.ts.ejs +824 -0
- package/hedhog/frontend/app/_lib/mocks/projects.mock.ts.ejs +60 -60
- package/hedhog/frontend/app/_lib/mocks/tasks.mock.ts.ejs +88 -88
- package/hedhog/frontend/app/_lib/mocks/timesheets.mock.ts.ejs +84 -84
- package/hedhog/frontend/app/_lib/mocks/users.mock.ts.ejs +67 -67
- package/hedhog/frontend/app/_lib/services/contracts.service.ts.ejs +10 -10
- package/hedhog/frontend/app/_lib/services/operations-growth.service.ts.ejs +31 -0
- package/hedhog/frontend/app/_lib/services/projects.service.ts.ejs +10 -10
- package/hedhog/frontend/app/_lib/services/tasks.service.ts.ejs +10 -10
- package/hedhog/frontend/app/_lib/services/timesheets.service.ts.ejs +10 -10
- package/hedhog/frontend/app/_lib/types/operations-growth.ts.ejs +209 -0
- package/hedhog/frontend/app/_lib/types/operations.ts.ejs +95 -95
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +25 -25
- package/hedhog/frontend/app/_lib/utils/growth.ts.ejs +62 -0
- package/hedhog/frontend/app/_lib/utils/metrics.ts.ejs +103 -103
- package/hedhog/frontend/app/_lib/utils/status.ts.ejs +80 -80
- package/hedhog/frontend/app/allocations/page.tsx.ejs +154 -99
- package/hedhog/frontend/app/approvals/page.tsx.ejs +147 -147
- package/hedhog/frontend/app/career/page.tsx.ejs +143 -0
- package/hedhog/frontend/app/certifications/page.tsx.ejs +201 -0
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +108 -108
- package/hedhog/frontend/app/contracts/page.tsx.ejs +180 -124
- package/hedhog/frontend/app/evaluations/page.tsx.ejs +277 -0
- package/hedhog/frontend/app/goals/page.tsx.ejs +170 -0
- package/hedhog/frontend/app/growth/page.tsx.ejs +288 -0
- package/hedhog/frontend/app/layout.tsx.ejs +9 -9
- package/hedhog/frontend/app/manager/page.tsx.ejs +175 -0
- package/hedhog/frontend/app/page.tsx.ejs +177 -177
- package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +186 -186
- package/hedhog/frontend/app/projects/page.tsx.ejs +111 -111
- package/hedhog/frontend/app/rewards/page.tsx.ejs +195 -0
- package/hedhog/frontend/app/tasks/page.tsx.ejs +47 -47
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +126 -126
- package/hedhog/frontend/messages/en.json +152 -142
- package/hedhog/frontend/messages/pt.json +152 -142
- package/hedhog/table/operations_allocation.yaml +52 -0
- package/hedhog/table/operations_calibration_item.yaml +61 -0
- package/hedhog/table/operations_calibration_session.yaml +25 -0
- package/hedhog/table/operations_career_level.yaml +75 -0
- package/hedhog/table/operations_career_track.yaml +21 -0
- package/hedhog/table/operations_certification.yaml +48 -0
- package/hedhog/table/operations_contract.yaml +57 -0
- package/hedhog/table/operations_employee.yaml +64 -0
- package/hedhog/table/operations_employee_certification.yaml +43 -0
- package/hedhog/table/operations_employee_connect.yaml +61 -0
- package/hedhog/table/operations_employee_evaluation.yaml +113 -0
- package/hedhog/table/operations_employee_evaluation_item.yaml +39 -0
- package/hedhog/table/operations_employee_profile.yaml +80 -0
- package/hedhog/table/operations_employee_skill_matrix.yaml +30 -0
- package/hedhog/table/operations_evaluation_cycle.yaml +31 -0
- package/hedhog/table/operations_goal.yaml +67 -0
- package/hedhog/table/operations_goal_progress.yaml +31 -0
- package/hedhog/table/operations_performance_criterion.yaml +29 -0
- package/hedhog/table/operations_project.yaml +66 -0
- package/hedhog/table/operations_promotion_readiness.yaml +49 -0
- package/hedhog/table/operations_promotion_recommendation.yaml +63 -0
- package/hedhog/table/operations_public_recognition.yaml +46 -0
- package/hedhog/table/operations_reward.yaml +100 -0
- package/hedhog/table/operations_score_event.yaml +81 -0
- package/hedhog/table/operations_task.yaml +60 -0
- package/hedhog/table/operations_timesheet.yaml +49 -0
- package/hedhog/table/operations_timesheet_entry.yaml +51 -0
- package/package.json +4 -4
- package/src/index.ts +2 -1
- package/src/language/en.json +8 -8
- package/src/language/pt.json +8 -8
- package/src/operations-data.controller.ts +54 -0
- package/src/operations-growth.controller.ts +44 -0
- package/src/operations.module.ts +21 -15
- package/src/operations.service.ts +137 -0
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import { formatDate, formatHours } from '../_lib/utils/format';
|
|
2
|
-
import {
|
|
3
|
-
getTaskBadgeClasses,
|
|
4
|
-
getTaskStatusLabel,
|
|
5
|
-
} from '../_lib/utils/status';
|
|
6
|
-
import type { OperationsUser, Task, TaskStatus } from '../_lib/types/operations';
|
|
7
|
-
import { StatusBadge } from './status-badge';
|
|
8
|
-
|
|
9
|
-
const columns: TaskStatus[] = [
|
|
10
|
-
'backlog',
|
|
11
|
-
'todo',
|
|
12
|
-
'in-progress',
|
|
13
|
-
'review',
|
|
14
|
-
'done',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
interface KanbanBoardProps {
|
|
18
|
-
tasks: Task[];
|
|
19
|
-
users: OperationsUser[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function KanbanBoard({ tasks, users }: KanbanBoardProps) {
|
|
23
|
-
return (
|
|
24
|
-
<div className="grid gap-4 xl:grid-cols-5">
|
|
25
|
-
{columns.map((column) => {
|
|
26
|
-
const columnTasks = tasks.filter((task) => task.status === column);
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div
|
|
30
|
-
key={column}
|
|
31
|
-
className="rounded-xl border bg-muted/30 p-3 shadow-sm"
|
|
32
|
-
>
|
|
33
|
-
<div className="mb-3 flex items-center justify-between">
|
|
34
|
-
<h3 className="text-sm font-semibold">{getTaskStatusLabel(column)}</h3>
|
|
35
|
-
<span className="text-xs text-muted-foreground">
|
|
36
|
-
{columnTasks.length}
|
|
37
|
-
</span>
|
|
38
|
-
</div>
|
|
39
|
-
<div className="space-y-3">
|
|
40
|
-
{columnTasks.map((task) => {
|
|
41
|
-
const assignedUser = users.find(
|
|
42
|
-
(user) => user.id === task.assignedUserId
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div
|
|
47
|
-
key={task.id}
|
|
48
|
-
className="rounded-lg border bg-background p-3 shadow-sm"
|
|
49
|
-
>
|
|
50
|
-
<div className="space-y-2">
|
|
51
|
-
<div className="flex items-start justify-between gap-2">
|
|
52
|
-
<p className="text-sm font-medium">{task.title}</p>
|
|
53
|
-
<StatusBadge
|
|
54
|
-
label={getTaskStatusLabel(task.status)}
|
|
55
|
-
className={getTaskBadgeClasses(task.status)}
|
|
56
|
-
/>
|
|
57
|
-
</div>
|
|
58
|
-
<div className="flex flex-wrap gap-2">
|
|
59
|
-
{task.labels.map((label) => (
|
|
60
|
-
<span
|
|
61
|
-
key={label}
|
|
62
|
-
className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] text-slate-700"
|
|
63
|
-
>
|
|
64
|
-
{label}
|
|
65
|
-
</span>
|
|
66
|
-
))}
|
|
67
|
-
</div>
|
|
68
|
-
<div className="text-xs text-muted-foreground">
|
|
69
|
-
<p>{assignedUser?.name ?? 'Unassigned'}</p>
|
|
70
|
-
<p>{formatDate(task.dueDate)}</p>
|
|
71
|
-
<p>{formatHours(task.estimatedHours)}</p>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
76
|
-
})}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
})}
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
1
|
+
import { formatDate, formatHours } from '../_lib/utils/format';
|
|
2
|
+
import {
|
|
3
|
+
getTaskBadgeClasses,
|
|
4
|
+
getTaskStatusLabel,
|
|
5
|
+
} from '../_lib/utils/status';
|
|
6
|
+
import type { OperationsUser, Task, TaskStatus } from '../_lib/types/operations';
|
|
7
|
+
import { StatusBadge } from './status-badge';
|
|
8
|
+
|
|
9
|
+
const columns: TaskStatus[] = [
|
|
10
|
+
'backlog',
|
|
11
|
+
'todo',
|
|
12
|
+
'in-progress',
|
|
13
|
+
'review',
|
|
14
|
+
'done',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
interface KanbanBoardProps {
|
|
18
|
+
tasks: Task[];
|
|
19
|
+
users: OperationsUser[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function KanbanBoard({ tasks, users }: KanbanBoardProps) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="grid gap-4 xl:grid-cols-5">
|
|
25
|
+
{columns.map((column) => {
|
|
26
|
+
const columnTasks = tasks.filter((task) => task.status === column);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
key={column}
|
|
31
|
+
className="rounded-xl border bg-muted/30 p-3 shadow-sm"
|
|
32
|
+
>
|
|
33
|
+
<div className="mb-3 flex items-center justify-between">
|
|
34
|
+
<h3 className="text-sm font-semibold">{getTaskStatusLabel(column)}</h3>
|
|
35
|
+
<span className="text-xs text-muted-foreground">
|
|
36
|
+
{columnTasks.length}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="space-y-3">
|
|
40
|
+
{columnTasks.map((task) => {
|
|
41
|
+
const assignedUser = users.find(
|
|
42
|
+
(user) => user.id === task.assignedUserId
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
key={task.id}
|
|
48
|
+
className="rounded-lg border bg-background p-3 shadow-sm"
|
|
49
|
+
>
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<div className="flex items-start justify-between gap-2">
|
|
52
|
+
<p className="text-sm font-medium">{task.title}</p>
|
|
53
|
+
<StatusBadge
|
|
54
|
+
label={getTaskStatusLabel(task.status)}
|
|
55
|
+
className={getTaskBadgeClasses(task.status)}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex flex-wrap gap-2">
|
|
59
|
+
{task.labels.map((label) => (
|
|
60
|
+
<span
|
|
61
|
+
key={label}
|
|
62
|
+
className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] text-slate-700"
|
|
63
|
+
>
|
|
64
|
+
{label}
|
|
65
|
+
</span>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
<div className="text-xs text-muted-foreground">
|
|
69
|
+
<p>{assignedUser?.name ?? 'Unassigned'}</p>
|
|
70
|
+
<p>{formatDate(task.dueDate)}</p>
|
|
71
|
+
<p>{formatHours(task.estimatedHours)}</p>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { PageHeader } from '@/components/entity-list';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
interface OperationsHeaderProps {
|
|
5
|
-
title: string;
|
|
6
|
-
description: string;
|
|
7
|
-
current: string;
|
|
8
|
-
actions?: ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function OperationsHeader({
|
|
12
|
-
title,
|
|
13
|
-
description,
|
|
14
|
-
current,
|
|
15
|
-
actions,
|
|
16
|
-
}: OperationsHeaderProps) {
|
|
17
|
-
return (
|
|
18
|
-
<PageHeader
|
|
19
|
-
title={title}
|
|
20
|
-
description={description}
|
|
21
|
-
actions={actions}
|
|
22
|
-
breadcrumbs={[
|
|
23
|
-
{ label: 'Home', href: '/' },
|
|
24
|
-
{ label: 'Operations', href: '/operations' },
|
|
25
|
-
{ label: current },
|
|
26
|
-
]}
|
|
27
|
-
/>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
1
|
+
import { PageHeader } from '@/components/entity-list';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface OperationsHeaderProps {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
current: string;
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function OperationsHeader({
|
|
12
|
+
title,
|
|
13
|
+
description,
|
|
14
|
+
current,
|
|
15
|
+
actions,
|
|
16
|
+
}: OperationsHeaderProps) {
|
|
17
|
+
return (
|
|
18
|
+
<PageHeader
|
|
19
|
+
title={title}
|
|
20
|
+
description={description}
|
|
21
|
+
actions={actions}
|
|
22
|
+
breadcrumbs={[
|
|
23
|
+
{ label: 'Home', href: '/' },
|
|
24
|
+
{ label: 'Operations', href: '/operations' },
|
|
25
|
+
{ label: current },
|
|
26
|
+
]}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Card,
|
|
3
|
-
CardContent,
|
|
4
|
-
CardDescription,
|
|
5
|
-
CardHeader,
|
|
6
|
-
CardTitle,
|
|
7
|
-
} from '@/components/ui/card';
|
|
8
|
-
import { ReactNode } from 'react';
|
|
9
|
-
|
|
10
|
-
interface SectionCardProps {
|
|
11
|
-
title: string;
|
|
12
|
-
description?: string;
|
|
13
|
-
children: ReactNode;
|
|
14
|
-
className?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function SectionCard({
|
|
18
|
-
title,
|
|
19
|
-
description,
|
|
20
|
-
children,
|
|
21
|
-
className,
|
|
22
|
-
}: SectionCardProps) {
|
|
23
|
-
return (
|
|
24
|
-
<Card className={className}>
|
|
25
|
-
<CardHeader>
|
|
26
|
-
<CardTitle>{title}</CardTitle>
|
|
27
|
-
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
28
|
-
</CardHeader>
|
|
29
|
-
<CardContent>{children}</CardContent>
|
|
30
|
-
</Card>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardDescription,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
} from '@/components/ui/card';
|
|
8
|
+
import { ReactNode } from 'react';
|
|
9
|
+
|
|
10
|
+
interface SectionCardProps {
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function SectionCard({
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
}: SectionCardProps) {
|
|
23
|
+
return (
|
|
24
|
+
<Card className={className}>
|
|
25
|
+
<CardHeader>
|
|
26
|
+
<CardTitle>{title}</CardTitle>
|
|
27
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
28
|
+
</CardHeader>
|
|
29
|
+
<CardContent>{children}</CardContent>
|
|
30
|
+
</Card>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { Badge } from '@/components/ui/badge';
|
|
2
|
-
import { cn } from '@/lib/utils';
|
|
3
|
-
|
|
4
|
-
interface StatusBadgeProps {
|
|
5
|
-
label: string;
|
|
6
|
-
className?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function StatusBadge({ label, className }: StatusBadgeProps) {
|
|
10
|
-
return (
|
|
11
|
-
<Badge variant="outline" className={cn('border-transparent', className)}>
|
|
12
|
-
{label}
|
|
13
|
-
</Badge>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
1
|
+
import { Badge } from '@/components/ui/badge';
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
|
|
4
|
+
interface StatusBadgeProps {
|
|
5
|
+
label: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function StatusBadge({ label, className }: StatusBadgeProps) {
|
|
10
|
+
return (
|
|
11
|
+
<Badge variant="outline" className={cn('border-transparent', className)}>
|
|
12
|
+
{label}
|
|
13
|
+
</Badge>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Button } from '@/components/ui/button';
|
|
4
|
-
import {
|
|
5
|
-
Dialog,
|
|
6
|
-
DialogContent,
|
|
7
|
-
DialogDescription,
|
|
8
|
-
DialogFooter,
|
|
9
|
-
DialogHeader,
|
|
10
|
-
DialogTitle,
|
|
11
|
-
DialogTrigger,
|
|
12
|
-
} from '@/components/ui/dialog';
|
|
13
|
-
import { Input } from '@/components/ui/input';
|
|
14
|
-
import { Label } from '@/components/ui/label';
|
|
15
|
-
import {
|
|
16
|
-
Select,
|
|
17
|
-
SelectContent,
|
|
18
|
-
SelectItem,
|
|
19
|
-
SelectTrigger,
|
|
20
|
-
SelectValue,
|
|
21
|
-
} from '@/components/ui/select';
|
|
22
|
-
import { Textarea } from '@/components/ui/textarea';
|
|
23
|
-
import { Plus } from 'lucide-react';
|
|
24
|
-
import { useState } from 'react';
|
|
25
|
-
import type { OperationsUser, Project, Task } from '../_lib/types/operations';
|
|
26
|
-
|
|
27
|
-
interface TimesheetEntryDialogProps {
|
|
28
|
-
users: OperationsUser[];
|
|
29
|
-
projects: Project[];
|
|
30
|
-
tasks: Task[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function TimesheetEntryDialog({
|
|
34
|
-
users,
|
|
35
|
-
projects,
|
|
36
|
-
tasks,
|
|
37
|
-
}: TimesheetEntryDialogProps) {
|
|
38
|
-
const [open, setOpen] = useState(false);
|
|
39
|
-
const [projectId, setProjectId] = useState(projects[0]?.id ?? '');
|
|
40
|
-
|
|
41
|
-
const availableTasks = tasks.filter((task) => task.projectId === projectId);
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<Dialog open={open} onOpenChange={setOpen}>
|
|
45
|
-
<DialogTrigger asChild>
|
|
46
|
-
<Button>
|
|
47
|
-
<Plus className="mr-2 h-4 w-4" />
|
|
48
|
-
Add Timesheet Entry
|
|
49
|
-
</Button>
|
|
50
|
-
</DialogTrigger>
|
|
51
|
-
<DialogContent className="sm:max-w-2xl">
|
|
52
|
-
<DialogHeader>
|
|
53
|
-
<DialogTitle>Add Timesheet Entry</DialogTitle>
|
|
54
|
-
<DialogDescription>
|
|
55
|
-
Mock-only modal wired for a future API-backed create flow.
|
|
56
|
-
</DialogDescription>
|
|
57
|
-
</DialogHeader>
|
|
58
|
-
<div className="grid gap-4 md:grid-cols-2">
|
|
59
|
-
<div className="space-y-2">
|
|
60
|
-
<Label>User</Label>
|
|
61
|
-
<Select defaultValue={users[0]?.id}>
|
|
62
|
-
<SelectTrigger>
|
|
63
|
-
<SelectValue />
|
|
64
|
-
</SelectTrigger>
|
|
65
|
-
<SelectContent>
|
|
66
|
-
{users.map((user) => (
|
|
67
|
-
<SelectItem key={user.id} value={user.id}>
|
|
68
|
-
{user.name}
|
|
69
|
-
</SelectItem>
|
|
70
|
-
))}
|
|
71
|
-
</SelectContent>
|
|
72
|
-
</Select>
|
|
73
|
-
</div>
|
|
74
|
-
<div className="space-y-2">
|
|
75
|
-
<Label>Date</Label>
|
|
76
|
-
<Input defaultValue="2026-03-16" type="date" />
|
|
77
|
-
</div>
|
|
78
|
-
<div className="space-y-2">
|
|
79
|
-
<Label>Project</Label>
|
|
80
|
-
<Select value={projectId} onValueChange={setProjectId}>
|
|
81
|
-
<SelectTrigger>
|
|
82
|
-
<SelectValue />
|
|
83
|
-
</SelectTrigger>
|
|
84
|
-
<SelectContent>
|
|
85
|
-
{projects.map((project) => (
|
|
86
|
-
<SelectItem key={project.id} value={project.id}>
|
|
87
|
-
{project.name}
|
|
88
|
-
</SelectItem>
|
|
89
|
-
))}
|
|
90
|
-
</SelectContent>
|
|
91
|
-
</Select>
|
|
92
|
-
</div>
|
|
93
|
-
<div className="space-y-2">
|
|
94
|
-
<Label>Task</Label>
|
|
95
|
-
<Select defaultValue={availableTasks[0]?.id}>
|
|
96
|
-
<SelectTrigger>
|
|
97
|
-
<SelectValue />
|
|
98
|
-
</SelectTrigger>
|
|
99
|
-
<SelectContent>
|
|
100
|
-
{availableTasks.map((task) => (
|
|
101
|
-
<SelectItem key={task.id} value={task.id}>
|
|
102
|
-
{task.title}
|
|
103
|
-
</SelectItem>
|
|
104
|
-
))}
|
|
105
|
-
</SelectContent>
|
|
106
|
-
</Select>
|
|
107
|
-
</div>
|
|
108
|
-
<div className="space-y-2">
|
|
109
|
-
<Label>Hours</Label>
|
|
110
|
-
<Input defaultValue="6" min="0" step="0.5" type="number" />
|
|
111
|
-
</div>
|
|
112
|
-
<div className="space-y-2">
|
|
113
|
-
<Label>Status</Label>
|
|
114
|
-
<Select defaultValue="pending">
|
|
115
|
-
<SelectTrigger>
|
|
116
|
-
<SelectValue />
|
|
117
|
-
</SelectTrigger>
|
|
118
|
-
<SelectContent>
|
|
119
|
-
<SelectItem value="pending">Pending</SelectItem>
|
|
120
|
-
<SelectItem value="approved">Approved</SelectItem>
|
|
121
|
-
<SelectItem value="rejected">Rejected</SelectItem>
|
|
122
|
-
</SelectContent>
|
|
123
|
-
</Select>
|
|
124
|
-
</div>
|
|
125
|
-
<div className="space-y-2 md:col-span-2">
|
|
126
|
-
<Label>Description</Label>
|
|
127
|
-
<Textarea
|
|
128
|
-
defaultValue="Mock entry for sprint execution and internal review."
|
|
129
|
-
rows={4}
|
|
130
|
-
/>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
<DialogFooter>
|
|
134
|
-
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
135
|
-
Cancel
|
|
136
|
-
</Button>
|
|
137
|
-
<Button onClick={() => setOpen(false)}>Save Mock Entry</Button>
|
|
138
|
-
</DialogFooter>
|
|
139
|
-
</DialogContent>
|
|
140
|
-
</Dialog>
|
|
141
|
-
);
|
|
142
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogFooter,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogTrigger,
|
|
12
|
+
} from '@/components/ui/dialog';
|
|
13
|
+
import { Input } from '@/components/ui/input';
|
|
14
|
+
import { Label } from '@/components/ui/label';
|
|
15
|
+
import {
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectItem,
|
|
19
|
+
SelectTrigger,
|
|
20
|
+
SelectValue,
|
|
21
|
+
} from '@/components/ui/select';
|
|
22
|
+
import { Textarea } from '@/components/ui/textarea';
|
|
23
|
+
import { Plus } from 'lucide-react';
|
|
24
|
+
import { useState } from 'react';
|
|
25
|
+
import type { OperationsUser, Project, Task } from '../_lib/types/operations';
|
|
26
|
+
|
|
27
|
+
interface TimesheetEntryDialogProps {
|
|
28
|
+
users: OperationsUser[];
|
|
29
|
+
projects: Project[];
|
|
30
|
+
tasks: Task[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function TimesheetEntryDialog({
|
|
34
|
+
users,
|
|
35
|
+
projects,
|
|
36
|
+
tasks,
|
|
37
|
+
}: TimesheetEntryDialogProps) {
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [projectId, setProjectId] = useState(projects[0]?.id ?? '');
|
|
40
|
+
|
|
41
|
+
const availableTasks = tasks.filter((task) => task.projectId === projectId);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
45
|
+
<DialogTrigger asChild>
|
|
46
|
+
<Button>
|
|
47
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
48
|
+
Add Timesheet Entry
|
|
49
|
+
</Button>
|
|
50
|
+
</DialogTrigger>
|
|
51
|
+
<DialogContent className="sm:max-w-2xl">
|
|
52
|
+
<DialogHeader>
|
|
53
|
+
<DialogTitle>Add Timesheet Entry</DialogTitle>
|
|
54
|
+
<DialogDescription>
|
|
55
|
+
Mock-only modal wired for a future API-backed create flow.
|
|
56
|
+
</DialogDescription>
|
|
57
|
+
</DialogHeader>
|
|
58
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<Label>User</Label>
|
|
61
|
+
<Select defaultValue={users[0]?.id}>
|
|
62
|
+
<SelectTrigger>
|
|
63
|
+
<SelectValue />
|
|
64
|
+
</SelectTrigger>
|
|
65
|
+
<SelectContent>
|
|
66
|
+
{users.map((user) => (
|
|
67
|
+
<SelectItem key={user.id} value={user.id}>
|
|
68
|
+
{user.name}
|
|
69
|
+
</SelectItem>
|
|
70
|
+
))}
|
|
71
|
+
</SelectContent>
|
|
72
|
+
</Select>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="space-y-2">
|
|
75
|
+
<Label>Date</Label>
|
|
76
|
+
<Input defaultValue="2026-03-16" type="date" />
|
|
77
|
+
</div>
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
<Label>Project</Label>
|
|
80
|
+
<Select value={projectId} onValueChange={setProjectId}>
|
|
81
|
+
<SelectTrigger>
|
|
82
|
+
<SelectValue />
|
|
83
|
+
</SelectTrigger>
|
|
84
|
+
<SelectContent>
|
|
85
|
+
{projects.map((project) => (
|
|
86
|
+
<SelectItem key={project.id} value={project.id}>
|
|
87
|
+
{project.name}
|
|
88
|
+
</SelectItem>
|
|
89
|
+
))}
|
|
90
|
+
</SelectContent>
|
|
91
|
+
</Select>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="space-y-2">
|
|
94
|
+
<Label>Task</Label>
|
|
95
|
+
<Select defaultValue={availableTasks[0]?.id}>
|
|
96
|
+
<SelectTrigger>
|
|
97
|
+
<SelectValue />
|
|
98
|
+
</SelectTrigger>
|
|
99
|
+
<SelectContent>
|
|
100
|
+
{availableTasks.map((task) => (
|
|
101
|
+
<SelectItem key={task.id} value={task.id}>
|
|
102
|
+
{task.title}
|
|
103
|
+
</SelectItem>
|
|
104
|
+
))}
|
|
105
|
+
</SelectContent>
|
|
106
|
+
</Select>
|
|
107
|
+
</div>
|
|
108
|
+
<div className="space-y-2">
|
|
109
|
+
<Label>Hours</Label>
|
|
110
|
+
<Input defaultValue="6" min="0" step="0.5" type="number" />
|
|
111
|
+
</div>
|
|
112
|
+
<div className="space-y-2">
|
|
113
|
+
<Label>Status</Label>
|
|
114
|
+
<Select defaultValue="pending">
|
|
115
|
+
<SelectTrigger>
|
|
116
|
+
<SelectValue />
|
|
117
|
+
</SelectTrigger>
|
|
118
|
+
<SelectContent>
|
|
119
|
+
<SelectItem value="pending">Pending</SelectItem>
|
|
120
|
+
<SelectItem value="approved">Approved</SelectItem>
|
|
121
|
+
<SelectItem value="rejected">Rejected</SelectItem>
|
|
122
|
+
</SelectContent>
|
|
123
|
+
</Select>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="space-y-2 md:col-span-2">
|
|
126
|
+
<Label>Description</Label>
|
|
127
|
+
<Textarea
|
|
128
|
+
defaultValue="Mock entry for sprint execution and internal review."
|
|
129
|
+
rows={4}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<DialogFooter>
|
|
134
|
+
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
135
|
+
Cancel
|
|
136
|
+
</Button>
|
|
137
|
+
<Button onClick={() => setOpen(false)}>Save Mock Entry</Button>
|
|
138
|
+
</DialogFooter>
|
|
139
|
+
</DialogContent>
|
|
140
|
+
</Dialog>
|
|
141
|
+
);
|
|
142
|
+
}
|