Skip to content

Commit 8761d3a

Browse files
Add Alert while plan is in progress
1 parent 27382f5 commit 8761d3a

File tree

9 files changed

+394
-84
lines changed

9 files changed

+394
-84
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react';
2+
import {
3+
Dialog,
4+
DialogTrigger,
5+
DialogSurface,
6+
DialogTitle,
7+
DialogContent,
8+
DialogBody,
9+
DialogActions,
10+
Button,
11+
} from '@fluentui/react-components';
12+
import { Warning20Regular } from '@fluentui/react-icons';
13+
import "../../styles/Panel.css";
14+
15+
interface PlanCancellationDialogProps {
16+
isOpen: boolean;
17+
onConfirm: () => void;
18+
onCancel: () => void;
19+
loading?: boolean;
20+
}
21+
22+
/**
23+
* Confirmation dialog for plan cancellation when navigating during active plans
24+
*/
25+
const PlanCancellationDialog: React.FC<PlanCancellationDialogProps> = ({
26+
isOpen,
27+
onConfirm,
28+
onCancel,
29+
loading = false
30+
}) => {
31+
return (
32+
<Dialog open={isOpen} onOpenChange={(_, data) => !data.open && onCancel()}>
33+
<DialogSurface>
34+
<DialogBody>
35+
<DialogTitle>
36+
<div className="plan-cancellation-dialog-title">
37+
<Warning20Regular className="plan-cancellation-warning-icon" />
38+
Confirm Plan Cancellation
39+
</div>
40+
</DialogTitle>
41+
<DialogContent>
42+
If you continue, the plan process will be stopped and the plan will be cancelled.
43+
</DialogContent>
44+
<DialogActions>
45+
<DialogTrigger disableButtonEnhancement>
46+
<Button
47+
appearance="secondary"
48+
onClick={onCancel}
49+
disabled={loading}
50+
>
51+
Cancel
52+
</Button>
53+
</DialogTrigger>
54+
<Button
55+
appearance="primary"
56+
onClick={onConfirm}
57+
disabled={loading}
58+
>
59+
{loading ? 'Cancelling...' : 'Yes'}
60+
</Button>
61+
</DialogActions>
62+
</DialogBody>
63+
</DialogSurface>
64+
</Dialog>
65+
);
66+
};
67+
68+
export default PlanCancellationDialog;

src/frontend/src/components/content/PlanPanelLeft.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ import TeamService from "@/services/TeamService";
3030

3131
const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
3232
reloadTasks,
33+
onNewTaskButton,
3334
restReload,
3435
onTeamSelect,
3536
onTeamUpload,
3637
isHomePage,
37-
selectedTeam: parentSelectedTeam
38+
selectedTeam: parentSelectedTeam,
39+
onNavigationWithAlert
3840
}) => {
3941
const { dispatchToast } = useToastController("toast");
4042
const navigate = useNavigate();
@@ -116,16 +118,36 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
116118

117119
const handleTaskSelect = useCallback(
118120
(taskId: string) => {
119-
const selectedPlan = plans?.find(
120-
(plan: Plan) => plan.session_id === taskId
121-
);
122-
if (selectedPlan) {
123-
navigate(`/plan/${selectedPlan.id}`);
121+
const performNavigation = () => {
122+
const selectedPlan = plans?.find(
123+
(plan: Plan) => plan.session_id === taskId
124+
);
125+
if (selectedPlan) {
126+
navigate(`/plan/${selectedPlan.id}`);
127+
}
128+
};
129+
130+
if (onNavigationWithAlert) {
131+
onNavigationWithAlert(performNavigation);
132+
} else {
133+
performNavigation();
124134
}
125135
},
126-
[plans, navigate]
136+
[plans, navigate, onNavigationWithAlert]
127137
);
128138

139+
const handleLogoClick = useCallback(() => {
140+
const performNavigation = () => {
141+
navigate("/");
142+
};
143+
144+
if (onNavigationWithAlert) {
145+
onNavigationWithAlert(performNavigation);
146+
} else {
147+
performNavigation();
148+
}
149+
}, [navigate, onNavigationWithAlert]);
150+
129151
const handleTeamSelect = useCallback(
130152
(team: TeamConfig | null) => {
131153
// Use parent's team select handler if provided, otherwise use local state
@@ -163,10 +185,11 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
163185
);
164186

165187
return (
166-
<div style={{ flexShrink: 0, display: "flex", overflow: "hidden" }}>
188+
<div className="panel-left-container">
167189
<PanelLeft panelWidth={280} panelResize={true}>
168190
<PanelLeftToolbar
169-
linkTo="/"
191+
linkTo={onNavigationWithAlert ? undefined : "/"}
192+
onTitleClick={onNavigationWithAlert ? handleLogoClick : undefined}
170193
panelTitle="Contoso"
171194
panelIcon={<ContosoLogo />}
172195
>
@@ -175,7 +198,7 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
175198

176199
{/* Team Selector right under the toolbar */}
177200

178-
<div style={{ marginTop: '8px', marginBottom: '8px' }}>
201+
<div className="team-selector-container">
179202
{isHomePage && (
180203
<TeamSelector
181204
onTeamSelect={handleTeamSelect}
@@ -194,13 +217,13 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
194217
</div>
195218
<div
196219
className="tab tab-new-task"
197-
onClick={() => navigate("/", { state: { focusInput: true } })}
220+
onClick={onNewTaskButton}
198221
tabIndex={0} // ✅ allows tab focus
199222
role="button" // ✅ announces as button
200223
onKeyDown={(e) => {
201224
if (e.key === "Enter" || e.key === " ") {
202225
e.preventDefault();
203-
navigate("/", { state: { focusInput: true } });
226+
onNewTaskButton();
204227
}
205228
}}
206229
>
@@ -219,7 +242,7 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
219242
/>
220243

221244
<PanelFooter>
222-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', width: '100%' }}>
245+
<div className="panel-footer-content">
223246
{/* User Card */}
224247
<PanelUserCard
225248
name={userInfo?.user_first_last_name || "Guest"}

src/frontend/src/coral/components/Panels/PanelLeftToolbar.tsx

Lines changed: 25 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,67 @@
11
import React, { ReactNode } from "react";
22
import { Subtitle2 } from "@fluentui/react-components";
33
import { Link } from "react-router-dom";
4+
import "../../../styles/Panel.css";
45

56
interface PanelLeftToolbarProps {
67
panelIcon?: ReactNode;
78
panelTitle?: string | null;
89
linkTo?: string;
10+
onTitleClick?: () => void; // Custom click handler for protected navigation
911
children?: ReactNode;
1012
}
1113

1214
const PanelLeftToolbar: React.FC<PanelLeftToolbarProps> = ({
1315
panelIcon,
1416
panelTitle,
1517
linkTo,
18+
onTitleClick,
1619
children,
1720
}) => {
1821
const TitleContent = (
19-
<div
20-
className="panelTitle"
21-
style={{
22-
display: "flex",
23-
alignItems: "center",
24-
gap: "6px",
25-
flexShrink: 1,
26-
overflow: "hidden",
27-
minWidth: 0,
28-
}}
29-
>
22+
<div className="panel-title">
3023
{panelIcon && (
31-
<div
32-
style={{
33-
flexShrink: 0,
34-
display: "flex",
35-
alignItems: "center",
36-
}}
37-
>
24+
<div className="panel-title-icon">
3825
{panelIcon}
3926
</div>
4027
)}
4128
{panelTitle && (
42-
<Subtitle2
43-
style={{
44-
whiteSpace: "nowrap",
45-
overflow: "hidden",
46-
textOverflow: "ellipsis",
47-
}}
48-
>
29+
<Subtitle2 className="panel-title-text">
4930
{panelTitle}
5031
</Subtitle2>
5132
)}
5233
</div>
5334
);
5435

5536
return (
56-
<div
57-
className="panelToolbar"
58-
style={{
59-
display: "flex",
60-
alignItems: "center",
61-
gap: "8px",
62-
padding: "16px",
63-
boxSizing: "border-box",
64-
height: "56px",
65-
}}
66-
>
37+
<div className="panel-toolbar">
6738
{(panelIcon || panelTitle) &&
68-
(linkTo ? (
39+
(onTitleClick ? (
40+
<div
41+
onClick={onTitleClick}
42+
className="panel-title-clickable clickable-element"
43+
role="button"
44+
tabIndex={0}
45+
onKeyDown={(e) => {
46+
if (e.key === "Enter" || e.key === " ") {
47+
e.preventDefault();
48+
onTitleClick();
49+
}
50+
}}
51+
>
52+
{TitleContent}
53+
</div>
54+
) : linkTo ? (
6955
<Link
7056
to={linkTo}
71-
style={{
72-
textDecoration: "none",
73-
color: "inherit",
74-
display: "flex",
75-
alignItems: "center",
76-
minWidth: 0,
77-
flexShrink: 1,
78-
}}
57+
className="panel-title-clickable"
7958
>
8059
{TitleContent}
8160
</Link>
8261
) : (
8362
TitleContent
8463
))}
85-
<div
86-
className="panelTools"
87-
style={{
88-
display: "flex",
89-
alignItems: "center",
90-
flexGrow: 1,
91-
justifyContent: "flex-end",
92-
minWidth: 0,
93-
}}
94-
>
64+
<div className="panel-tools">
9565
{children}
9666
</div>
9767
</div>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useCallback } from 'react';
2+
import { PlanStatus } from '../models';
3+
import { APIService } from '../api/apiService';
4+
5+
interface UsePlanCancellationAlertProps {
6+
planData: any;
7+
planApprovalRequest: any;
8+
onNavigate: () => void;
9+
}
10+
11+
/**
12+
* Custom hook to handle plan cancellation alerts when navigating during active plans
13+
*/
14+
export const usePlanCancellationAlert = ({
15+
planData,
16+
planApprovalRequest,
17+
onNavigate
18+
}: UsePlanCancellationAlertProps) => {
19+
const apiService = new APIService();
20+
21+
/**
22+
* Check if a plan is currently active/running
23+
*/
24+
const isPlanActive = useCallback(() => {
25+
return planData?.plan?.overall_status === PlanStatus.IN_PROGRESS;
26+
}, [planData]);
27+
28+
/**
29+
* Handle the confirmation dialog and plan cancellation
30+
*/
31+
const handleNavigationWithConfirmation = useCallback(async () => {
32+
if (!isPlanActive()) {
33+
// Plan is not active, proceed with navigation
34+
onNavigate();
35+
return;
36+
}
37+
38+
// Show confirmation dialog
39+
const userConfirmed = window.confirm(
40+
"If you continue, the plan process will be stopped and the plan will be cancelled."
41+
);
42+
43+
if (!userConfirmed) {
44+
// User cancelled, do nothing
45+
return;
46+
}
47+
48+
try {
49+
// User confirmed, cancel the plan
50+
if (planApprovalRequest?.id) {
51+
await apiService.approvePlan({
52+
m_plan_id: planApprovalRequest.id,
53+
plan_id: planData?.plan?.id,
54+
approved: false,
55+
feedback: 'Plan cancelled by user navigation'
56+
});
57+
}
58+
59+
// Navigate after successful cancellation
60+
onNavigate();
61+
} catch (error) {
62+
console.error('❌ Failed to cancel plan:', error);
63+
// Show error but still allow navigation
64+
alert('Failed to cancel the plan properly, but navigation will continue.');
65+
onNavigate();
66+
}
67+
}, [isPlanActive, onNavigate, planApprovalRequest, planData, apiService]);
68+
69+
return {
70+
isPlanActive,
71+
handleNavigationWithConfirmation
72+
};
73+
};
74+
75+
export default usePlanCancellationAlert;

src/frontend/src/models/planPanelLeft.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export interface PlanPanelLefProps {
88
onTeamUpload?: () => Promise<void>;
99
isHomePage: boolean;
1010
selectedTeam?: TeamConfig | null;
11+
onNavigationWithAlert?: (navigationFn: () => void) => void; // Function to handle protected navigation
1112
}

0 commit comments

Comments
 (0)