Skip to content

Commit a86dae5

Browse files
Refactor decision layout and enhance workflow progress visualization
- Removed the previous workflow progress logic from `DecisionLayout`, simplifying the component structure. - Integrated `HorizontalWorkflowProgress` into `EditDecisionClient` for improved step navigation. - Updated `HorizontalWorkflowProgress` component to streamline step rendering and interaction. This commit aims to make the decision-making process feel less like a tangled web and more like a clear pathway—complete with helpful signposts.
1 parent dfba611 commit a86dae5

File tree

3 files changed

+103
-140
lines changed

3 files changed

+103
-140
lines changed

app/organisation/[organisationId]/decision/[id]/edit/EditDecisionClient.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import WorkflowAccordion from '@/components/workflow/WorkflowAccordion'
4+
import HorizontalWorkflowProgress from '@/components/workflow/horizontal-workflow-progress'
45
import { notFound } from 'next/navigation'
56
import { useState } from 'react'
67
import { DecisionWorkflowStep, DecisionWorkflowSteps } from '@/lib/domain/Decision'
@@ -23,6 +24,12 @@ export default function EditDecisionClient({ organisationId, id }: EditDecisionC
2324

2425
return (
2526
<div className="container mx-auto py-8">
27+
<div className="mb-8">
28+
<HorizontalWorkflowProgress
29+
currentStep={currentStep}
30+
onStepChange={handleStepChange}
31+
/>
32+
</div>
2633
<WorkflowAccordion
2734
organisationId={organisationId}
2835
decisionId={id}
Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,17 @@
11
'use client'
22

33
import { ReactNode } from 'react'
4-
import { usePathname } from 'next/navigation'
5-
import WorkflowProgress from '@/components/workflow-progress'
64

75
export default function DecisionLayout({
86
children
97
}: {
108
children: ReactNode
119
}) {
12-
const pathname = usePathname()
13-
14-
const getCurrentStep = () => {
15-
if (pathname.endsWith('/identify')) return 1
16-
if (pathname.endsWith('/process')) return 2
17-
if (pathname.endsWith('/decide')) return 3
18-
if (pathname.endsWith('/view')) return 4
19-
return 1
20-
}
21-
22-
const showWorkflowProgress = !pathname.endsWith('/view')
23-
2410
return (
25-
<div className={`container ${showWorkflowProgress ? 'grid grid-cols-1 md:grid-cols-[1fr,300px]' : ''} gap-6 py-8`}>
11+
<div className="container py-8">
2612
<div className="space-y-8">
2713
{children}
2814
</div>
29-
30-
{showWorkflowProgress && (
31-
<div className="hidden md:block">
32-
<WorkflowProgress currentStep={getCurrentStep()} />
33-
</div>
34-
)}
3515
</div>
3616
)
3717
}
Lines changed: 95 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,119 @@
1-
'use client'
1+
"use client";
22

3-
import React, { useMemo, useCallback } from 'react'
4-
import { Card } from "@/components/ui/card"
5-
import { ChevronRight } from 'lucide-react'
6-
import {
7-
DecisionWorkflowSteps,
8-
DecisionWorkflowStep,
3+
import React from "react";
4+
import { Card } from "@/components/ui/card";
5+
import { ChevronRight } from "lucide-react";
6+
import {
7+
DecisionWorkflowSteps,
8+
DecisionWorkflowStep,
99
DecisionWorkflowStepKey,
1010
DecisionWorkflowStepsSequence,
1111
WorkflowNavigator,
1212
StepRoles,
13-
} from '@/lib/domain/Decision'
14-
import { cn } from "@/lib/utils"
15-
16-
const WORKFLOW_CONFIG = {
17-
STEP_SIZE: {
18-
ICON: 'h-12 w-12',
19-
ARROW: 'w-5 h-5',
20-
},
21-
COLORS: {
22-
ACTIVE: 'bg-primary text-primary-foreground',
23-
INACTIVE: 'bg-muted text-muted-foreground',
24-
COMPLETED: 'bg-primary text-primary-foreground',
25-
DISABLED: 'bg-muted text-muted-foreground cursor-not-allowed',
26-
},
27-
} as const;
13+
} from "@/lib/domain/Decision";
14+
import { cn } from "@/lib/utils";
2815

2916
interface HorizontalWorkflowProgressProps {
3017
currentStep?: DecisionWorkflowStep;
31-
onStepClick?: (step: DecisionWorkflowStep) => void;
18+
onStepChange?: (step: DecisionWorkflowStep) => void;
3219
allowFutureSteps?: boolean;
3320
showRoles?: boolean;
3421
className?: string;
35-
size?: 'sm' | 'md' | 'lg';
3622
}
3723

38-
export default function HorizontalWorkflowProgress({
24+
export default function HorizontalWorkflowProgress({
3925
currentStep = DecisionWorkflowSteps.IDENTIFY,
40-
onStepClick,
26+
onStepChange,
4127
allowFutureSteps = false,
4228
showRoles = true,
4329
className,
44-
size = 'md',
4530
}: HorizontalWorkflowProgressProps) {
46-
const currentStepIndex = useMemo(() =>
47-
WorkflowNavigator.getStepIndex(currentStep),
48-
[currentStep]
49-
);
31+
const currentStepIndex = WorkflowNavigator.getStepIndex(currentStep);
5032

51-
const getStepSize = useCallback((size: 'sm' | 'md' | 'lg') => {
52-
switch (size) {
53-
case 'sm':
54-
return 'h-8 w-8';
55-
case 'lg':
56-
return 'h-16 w-16';
57-
default:
58-
return WORKFLOW_CONFIG.STEP_SIZE.ICON;
59-
}
60-
}, []);
33+
return (
34+
<Card className={cn("p-6", className)}>
35+
<div
36+
className="flex items-center justify-between"
37+
role="progressbar"
38+
aria-valuemin={0}
39+
aria-valuemax={DecisionWorkflowStepsSequence.length}
40+
aria-valuenow={currentStepIndex + 1}
41+
>
42+
{DecisionWorkflowStepsSequence.map((step, index) => {
43+
const isActive = step.key === currentStep.key;
44+
const isCompleted = index < currentStepIndex;
45+
const isClickable =
46+
onStepChange && (allowFutureSteps || isCompleted || isActive);
47+
const role =
48+
StepRoles[step.key.toUpperCase() as DecisionWorkflowStepKey];
6149

62-
const renderStep = useCallback((step: DecisionWorkflowStep, index: number) => {
63-
const isActive = step.key === currentStep.key;
64-
const isCompleted = index < currentStepIndex;
65-
const isClickable = onStepClick && (allowFutureSteps || isCompleted || isActive);
66-
const role = StepRoles[step.key.toUpperCase() as DecisionWorkflowStepKey];
67-
const stepSize = getStepSize(size);
50+
return (
51+
<React.Fragment key={step.key}>
52+
{/* Step with icon and label */}
53+
<div className="flex flex-col items-center gap-2">
54+
{showRoles && (
55+
<div
56+
className={cn(
57+
"inline-flex justify-center rounded-full px-3 py-1 text-xs font-semibold",
58+
isCompleted || isActive
59+
? "bg-primary/10 text-primary"
60+
: "bg-muted text-muted-foreground",
61+
)}
62+
>
63+
{role}
64+
</div>
65+
)}
6866

69-
return (
70-
<React.Fragment key={step.key}>
71-
{/* Step circle with icon and role */}
72-
<div className="flex flex-col items-center gap-2">
73-
{showRoles && (
74-
<div
75-
className={cn(
76-
"inline-flex justify-center rounded-full px-3 py-1 text-xs font-semibold tracking-wide",
77-
isCompleted || isActive
78-
? 'bg-primary/10 text-primary'
79-
: 'bg-muted text-muted-foreground'
80-
)}
81-
>
82-
{role}
83-
</div>
84-
)}
85-
<button
86-
onClick={() => isClickable && onStepClick?.(step)}
87-
className={cn(
88-
"relative flex items-center justify-center rounded-full border-2 transition-colors",
89-
stepSize,
90-
isClickable && "cursor-pointer hover:bg-primary/90",
91-
isCompleted || isActive
92-
? WORKFLOW_CONFIG.COLORS.COMPLETED
93-
: WORKFLOW_CONFIG.COLORS.INACTIVE,
94-
!isClickable && WORKFLOW_CONFIG.COLORS.DISABLED
95-
)}
96-
disabled={!isClickable}
97-
aria-current={isActive ? 'step' : undefined}
98-
aria-label={`${step.label} step, ${isCompleted ? 'completed' : isActive ? 'current' : 'upcoming'}`}
99-
>
100-
<step.icon className="w-5 h-5" aria-hidden="true" />
101-
</button>
102-
<span
103-
className={cn(
104-
"text-sm font-medium",
105-
isCompleted || isActive ? "text-foreground" : "text-muted-foreground"
106-
)}
107-
>
108-
{step.label}
109-
</span>
110-
</div>
111-
112-
{/* Connector line with arrow */}
113-
{!WorkflowNavigator.isLastStep(step) && (
114-
<div className="flex-1 flex items-center">
115-
<div
116-
className={cn(
117-
"h-0.5 flex-1",
118-
isCompleted ? "bg-primary" : "bg-muted"
119-
)}
120-
role="presentation"
121-
/>
122-
<ChevronRight
123-
className={cn(
124-
WORKFLOW_CONFIG.STEP_SIZE.ARROW,
125-
"shrink-0",
126-
isCompleted ? "text-primary" : "text-muted"
127-
)}
128-
aria-hidden="true"
129-
/>
130-
</div>
131-
)}
132-
</React.Fragment>
133-
);
134-
}, [currentStep, currentStepIndex, onStepClick, allowFutureSteps, showRoles, size, getStepSize]);
67+
<button
68+
onClick={() => isClickable && onStepChange?.(step)}
69+
className={cn(
70+
"flex h-12 w-12 items-center justify-center rounded-full border-2 transition-colors",
71+
isClickable && "cursor-pointer hover:bg-primary/90",
72+
isCompleted || isActive
73+
? "bg-primary text-primary-foreground"
74+
: "bg-muted text-muted-foreground",
75+
!isClickable && "cursor-not-allowed",
76+
)}
77+
disabled={!isClickable}
78+
aria-current={isActive ? "step" : undefined}
79+
>
80+
<step.icon className="h-5 w-5" aria-hidden="true" />
81+
</button>
13582

136-
return (
137-
<Card className={cn("p-8", className)}>
138-
<div className="flex items-center justify-between" role="progressbar" aria-valuemin={0} aria-valuemax={DecisionWorkflowStepsSequence.length} aria-valuenow={currentStepIndex + 1}>
139-
{DecisionWorkflowStepsSequence.map(renderStep)}
83+
<span
84+
className={cn(
85+
"text-sm font-medium",
86+
isCompleted || isActive
87+
? "text-foreground"
88+
: "text-muted-foreground",
89+
)}
90+
>
91+
{step.label}
92+
</span>
93+
</div>
94+
95+
{/* Connector line */}
96+
{index < DecisionWorkflowStepsSequence.length - 1 && (
97+
<div className="flex-1 flex items-center">
98+
<div
99+
className={cn(
100+
"h-0.5 flex-1",
101+
isCompleted ? "bg-primary" : "bg-muted",
102+
)}
103+
/>
104+
<ChevronRight
105+
className={cn(
106+
"h-5 w-5 shrink-0",
107+
isCompleted ? "text-primary" : "text-muted",
108+
)}
109+
aria-hidden="true"
110+
/>
111+
</div>
112+
)}
113+
</React.Fragment>
114+
);
115+
})}
140116
</div>
141117
</Card>
142-
)
143-
}
118+
);
119+
}

0 commit comments

Comments
 (0)