Skip to content

Commit de8aefb

Browse files
authored
Merge pull request bcgov#139 from tom0827/ENGAGE-106
[ENGAGE-106] Ability to delete engagements
2 parents cba181e + 0ab76d2 commit de8aefb

File tree

8 files changed

+184
-17
lines changed

8 files changed

+184
-17
lines changed

met-api/src/met_api/models/engagement.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,14 @@ def get_assigned_engagements(user_id: int) -> List[Engagement]:
336336
)) \
337337
.all()
338338
return engagements
339+
340+
@classmethod
341+
def delete_engagement(cls, engagement_id: int) -> bool:
342+
"""Delete engagement."""
343+
query = Engagement.query.filter_by(id=engagement_id)
344+
record = query.first()
345+
if not record:
346+
return False
347+
query.delete()
348+
db.session.commit()
349+
return True

met-api/src/met_api/resources/engagement.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ def get(engagement_id):
5757
except ValueError as err:
5858
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR
5959

60+
@staticmethod
61+
@cross_origin(origins=allowedorigins())
62+
@_jwt.requires_auth
63+
def delete(engagement_id):
64+
"""Delete the engagement associated with the provided id."""
65+
try:
66+
EngagementService().delete(engagement_id)
67+
return {'message': 'Engagement deleted successfully'}, HTTPStatus.OK
68+
except KeyError:
69+
return 'Engagement was not found', HTTPStatus.NOT_FOUND
70+
except ValueError as err:
71+
return str(err), HTTPStatus.BAD_REQUEST
72+
6073

6174
@cors_preflight('GET, POST, PATCH, OPTIONS')
6275
@API.route('/')

met-api/src/met_api/services/engagement_service.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,20 @@ def _get_dashboard_path(engagement: EngagementModel):
326326
format(slug=engagement_slug.slug)
327327
return current_app.config.get('ENGAGEMENT_DASHBOARD_PATH'). \
328328
format(engagement_id=engagement.id)
329+
330+
@staticmethod
331+
def delete(engagement_id: int) -> None:
332+
"""Delete engagement by id."""
333+
authorization.check_auth(
334+
one_of_roles=(
335+
MembershipType.TEAM_MEMBER.name,
336+
Role.CREATE_ADMIN_USER.value
337+
),
338+
engagement_id=engagement_id
339+
)
340+
engagement = EngagementModel.find_by_id(engagement_id)
341+
342+
if not engagement:
343+
raise ValueError('Engagement to delete was not found')
344+
345+
EngagementModel.delete_engagement(engagement_id)

met-web/src/apiManager/endpoints/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const Endpoints = {
66
CREATE: `${AppConfig.apiUrl}/engagements/`,
77
UPDATE: `${AppConfig.apiUrl}/engagements/`,
88
GET: `${AppConfig.apiUrl}/engagements/engagement_id`,
9+
DELETE: `${AppConfig.apiUrl}/engagements/engagement_id`,
910
},
1011
EngagementMetadata: {
1112
CREATE: `${AppConfig.apiUrl}/engagementsmetadata/`,

met-web/src/components/admin/engagement/listing/ActionsDropDown.tsx

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,25 @@ import { getFormsSheet } from 'services/FormCAC';
1010
import { openNotification } from 'services/notificationService/notificationSlice';
1111
import { formatToUTC } from 'utils/helpers/dateHelper';
1212
import { downloadFile } from 'utils';
13+
import { DeleteEngagementModal } from './DeleteEngagementModal';
1314

1415
interface ActionDropDownItem {
1516
value: number;
1617
label: string;
1718
action?: () => void;
1819
condition?: boolean;
1920
}
20-
export const ActionsDropDown = ({ engagement }: { engagement: Engagement }) => {
21+
export const ActionsDropDown = ({
22+
engagement,
23+
onEngagementDeleted,
24+
}: {
25+
engagement: Engagement;
26+
onEngagementDeleted: (id: number) => void;
27+
}) => {
2128
const navigate = useNavigate();
2229
const dispatch = useAppDispatch();
2330
const [isExportingCacForms, setIsExportingCacForms] = useState(false);
31+
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
2432
const { roles, assignedEngagements } = useAppSelector((state) => state.user);
2533
const submissionHasBeenOpened = [SubmissionStatus.Open, SubmissionStatus.Closed].includes(
2634
engagement.submission_status,
@@ -89,6 +97,23 @@ export const ActionsDropDown = ({ engagement }: { engagement: Engagement }) => {
8997
}
9098
};
9199

100+
const canDeleteEngagement: boolean = useMemo(() => {
101+
if (!roles.includes(USER_ROLES.CREATE_ADMIN_USER)) {
102+
return false;
103+
}
104+
105+
const today = new Date().toISOString().slice(0, 10);
106+
if (engagement.engagement_status.id === EngagementStatus.Unpublished && today < engagement.start_date) {
107+
return true;
108+
}
109+
110+
return false;
111+
}, [engagement.engagement_status.id, engagement.start_date, roles]);
112+
113+
const handleEngagementDeleted = () => {
114+
onEngagementDeleted(engagement.id);
115+
};
116+
92117
const ITEMS: ActionDropDownItem[] = useMemo(
93118
() => [
94119
{
@@ -151,6 +176,14 @@ export const ActionsDropDown = ({ engagement }: { engagement: Engagement }) => {
151176
(roles.includes(USER_ROLES.EXPORT_CAC_FORM_TO_SHEET) &&
152177
assignedEngagements.includes(engagement.id)),
153178
},
179+
{
180+
value: 7,
181+
label: 'Delete Engagement',
182+
action: () => {
183+
setDeleteModalOpen(true);
184+
},
185+
condition: canDeleteEngagement,
186+
},
154187
],
155188
[engagement.id],
156189
);
@@ -160,21 +193,29 @@ export const ActionsDropDown = ({ engagement }: { engagement: Engagement }) => {
160193
}
161194

162195
return (
163-
<Select
164-
id={`action-drop-down-${engagement.id}`}
165-
value={0}
166-
fullWidth
167-
size="small"
168-
sx={{ backgroundColor: 'white', color: Palette.info.main }}
169-
>
170-
<MenuItem value={0} sx={{ fontStyle: 'italic', height: '2em' }} color="info" disabled>
171-
{'(Select One)'}
172-
</MenuItem>
173-
{ITEMS.filter((item) => item.condition).map((item) => (
174-
<MenuItem key={item.value} value={item.value} onClick={item.action}>
175-
{item.label}
196+
<>
197+
<Select
198+
id={`action-drop-down-${engagement.id}`}
199+
value={0}
200+
fullWidth
201+
size="small"
202+
sx={{ backgroundColor: 'white', color: Palette.info.main }}
203+
>
204+
<MenuItem value={0} sx={{ fontStyle: 'italic', height: '2em' }} color="info" disabled>
205+
{'(Select One)'}
176206
</MenuItem>
177-
))}
178-
</Select>
207+
{ITEMS.filter((item) => item.condition).map((item) => (
208+
<MenuItem key={item.value} value={item.value} onClick={item.action}>
209+
{item.label}
210+
</MenuItem>
211+
))}
212+
</Select>
213+
<DeleteEngagementModal
214+
open={deleteModalOpen}
215+
onClose={() => setDeleteModalOpen(false)}
216+
onDelete={handleEngagementDeleted}
217+
engagement={engagement}
218+
/>
219+
</>
179220
);
180221
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react';
2+
import { Modal, Paper, Grid, Stack } from '@mui/material';
3+
import { PrimaryButton, SecondaryButton } from 'components/shared/common';
4+
import { Engagement } from 'models/engagement';
5+
import { deleteEngagement } from 'services/engagementService';
6+
7+
interface DeleteEngagementModalProps {
8+
open: boolean;
9+
onClose: () => void;
10+
onDelete: () => void;
11+
engagement: Engagement;
12+
}
13+
14+
export const DeleteEngagementModal: React.FC<DeleteEngagementModalProps> = ({
15+
open,
16+
onClose,
17+
onDelete,
18+
engagement,
19+
}) => {
20+
const [isDeleting, setIsDeleting] = React.useState(false);
21+
22+
const handleClose = () => {
23+
onClose();
24+
};
25+
26+
const onDeleteHandler = async () => {
27+
try {
28+
setIsDeleting(true);
29+
await deleteEngagement(engagement.id);
30+
setIsDeleting(false);
31+
onDelete();
32+
handleClose();
33+
} catch (error) {
34+
setIsDeleting(false);
35+
console.error('Failed to delete engagement:', error);
36+
}
37+
};
38+
39+
return (
40+
<Modal open={open} onClose={handleClose}>
41+
<Paper
42+
sx={{
43+
position: 'absolute',
44+
top: '50%',
45+
left: '50%',
46+
transform: 'translate(-50%, -50%)',
47+
p: 4,
48+
minWidth: 400,
49+
}}
50+
>
51+
<Grid container spacing={2}>
52+
<Grid item xs={12}>
53+
<h2>Delete Engagement</h2>
54+
</Grid>
55+
56+
<Grid item xs={12}>
57+
This will permanently delete <strong>{engagement.name}</strong> and cannot be undone. Do you
58+
want to continue?
59+
</Grid>
60+
61+
<Grid item xs={12}>
62+
<Stack direction="row" spacing={1} justifyContent="flex-end">
63+
<SecondaryButton type="button" onClick={handleClose}>
64+
Cancel
65+
</SecondaryButton>
66+
<PrimaryButton type="button" onClick={onDeleteHandler} disabled={isDeleting}>
67+
{isDeleting ? 'Deleting...' : 'Delete'}
68+
</PrimaryButton>
69+
</Stack>
70+
</Grid>
71+
</Grid>
72+
</Paper>
73+
</Modal>
74+
);
75+
};

met-web/src/components/admin/engagement/listing/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ const EngagementListing = () => {
132132
});
133133
};
134134

135+
const handleEngagementDeleted = (engagementId: number) => {
136+
setEngagements((prev) => prev.filter((e) => e.id !== engagementId));
137+
};
138+
135139
const headCells: HeadCell<Engagement>[] = [
136140
{
137141
key: 'name',
@@ -384,7 +388,7 @@ const EngagementListing = () => {
384388
label: 'Actions',
385389
allowSort: false,
386390
renderCell: (row: Engagement) => {
387-
return <ActionsDropDown engagement={row} />;
391+
return <ActionsDropDown engagement={row} onEngagementDeleted={handleEngagementDeleted} />;
388392
},
389393
customStyle: {
390394
minWidth: '200px',

met-web/src/services/engagementService/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,8 @@ export const patchEngagement = async (data: PatchEngagementRequest): Promise<Eng
7878
}
7979
return Promise.reject('Failed to update engagement');
8080
};
81+
82+
export const deleteEngagement = async (engagementId: number): Promise<void> => {
83+
const url = replaceUrl(Endpoints.Engagement.DELETE, 'engagement_id', String(engagementId));
84+
await http.DeleteRequest<void>(url);
85+
};

0 commit comments

Comments
 (0)