Skip to content

Commit

Permalink
Merge pull request #2531 from gordon-cs/wmr-ra-tasklist
Browse files Browse the repository at this point in the history
RA TaskList
  • Loading branch information
Mrand03 authored Feb 25, 2025
2 parents 7c86804 + c0526cf commit ee58685
Show file tree
Hide file tree
Showing 3 changed files with 419 additions and 63 deletions.
54 changes: 54 additions & 0 deletions src/services/residentLife/Tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import http from '../http';

export type Task = {
Name: string;
Description: string;
HallID: string;
IsRecurring: boolean;
Frequency: string;
Interval: number;
StartDate: Date;
EndDate?: Date;
CreatedDate: Date;
CompletedDate?: Date;
CompletedBy?: string;
OccurDate?: Date;
};

export type DailyTask = {
Task_ID: number;
Name: string;
Description: string;
HallID: string;
CompletedDate?: Date;
CompletedBy?: string;
OccurDate?: Date;
};

const createTask = (newTask: Task) => http.post('Housing/halls/task', newTask);

const updateTask = (taskID: number, newTask: Task) =>
http.patch(`Housing/halls/task/${taskID}`, newTask);

const deleteTask = (taskID: number) => http.del(`Housing/halls/task/${taskID}`);

const completeTask = (taskID: number, completedBy: string) =>
http.patch(`Housing/halls/task/Complete/${taskID}`, completedBy);

const incompleteTask = (taskID: number) => http.patch(`Housing/halls/task/Incomplete/${taskID}`);

const getActiveTasksForHall = (hallID: string): Promise<Task[]> =>
http.get(`Housing/Halls/${hallID}/ActiveTasks`);

const getTasksForHall = (hallID: string): Promise<Task[]> =>
http.get(`Housing/Halls/${hallID}/DailyTasks`);

export {
createTask,
updateTask,
deleteTask,
completeTask,
incompleteTask,
getActiveTasksForHall,
getTasksForHall,
};
298 changes: 298 additions & 0 deletions src/views/ResLife/components/RAView/components/TaskList/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
import {
Card,
CardContent,
CardHeader,
Checkbox,
Container,
FormControl,
FormControlLabel,
Grid,
Typography,
IconButton,
ListItemIcon,
ListItemText,
ListItemButton,
ListSubheader,
List,
ListItem,
Link,
} from '@mui/material';
import React, { useCallback, useEffect, useState } from 'react';
import CommentIcon from '@mui/icons-material/Comment';
import { completeTask, incompleteTask, getTasksForHall } from 'services/residentLife/Tasks';
import { getRACurrentHalls } from 'services/residentLife/RA_Checkin';
import { useUser } from 'hooks';
import GordonDialogBox from 'components/GordonDialogBox';
import SimpleSnackbar from 'components/Snackbar';

const TaskList = () => {
const [taskList, setTaskList] = useState([]);
const { profile } = useUser();
const [descriptionOpen, setDescriptionOpen] = useState(false);
const [taskCheckedOpen, setTaskCheckedOpen] = useState(false);
const [incompleteTaskDialogOpen, setIncompleteTaskDialogOpen] = useState(false);
const [checkedList, setCheckedList] = useState([]);
const [disabledList, setDisabledList] = useState([]);
const [snackbar, setSnackbar] = useState({ message: '', severity: null, open: false });
const [hallList, setHallList] = useState([]);
const [confirmTask, setConfirmTask] = useState(null);
const [confirmIncompleteTask, setConfirmIncompleteTask] = useState(null);
const [selectedDescription, setSelectedDescription] = useState(null);

const createSnackbar = useCallback((message, severity) => {
setSnackbar({ message, severity, open: true });
}, []);

useEffect(() => {
const fetchCheckedInHalls = async () => {
try {
const halls = await getRACurrentHalls(profile.AD_Username);
setHallList(halls);
console.log(halls);
} catch (error) {
console.log('Error fetching halls', error);
}
};
fetchCheckedInHalls();
}, []);

useEffect(() => {
const fetchTaskList = async () => {
try {
const updatedTasks = [];
for (const hall of hallList) {
const tasks = await getTasksForHall(hall);
updatedTasks.push({ hallID: hall, tasks });
}
setTaskList(updatedTasks);
} catch (error) {
console.log('Error fetching tasks', error);
}
};

if (hallList.length > 0) {
fetchTaskList();
}
}, [hallList]);

const handleConfirm = async () => {
if (!confirmTask) {
console.error('No task selected for confirmation.');
return;
}

const { hallIndex, taskIndex, taskID } = confirmTask;

try {
await completeTask(taskID, profile.ID);

setCheckedList((prev) =>
prev.map((hall, hIndex) =>
hIndex === hallIndex
? hall.map((checked, tIndex) => (tIndex === taskIndex ? true : checked))
: hall,
),
);

setDisabledList((prev) =>
prev.map((hall, hIndex) =>
hIndex === hallIndex
? hall.map((disabled, tIndex) => (tIndex === taskIndex ? true : disabled))
: hall,
),
);

createSnackbar(`Completed task: ${taskList[hallIndex].tasks[taskIndex].Name}`, 'success');

setTaskCheckedOpen(false);
setConfirmTask(null);
} catch (error) {
console.error('Error completing task', error);
createSnackbar('Failed to complete task. Please try again.', 'error');
}
};

const handleMarkIncompleteConfirm = async () => {
if (!confirmIncompleteTask) {
console.error('No task selected for incomplete confirmation.');
return;
}

const { hallIndex, taskIndex, taskID } = confirmIncompleteTask;

try {
await incompleteTask(taskID, profile.ID);

setCheckedList((prev) =>
prev.map((hall, hIndex) =>
hIndex === hallIndex
? hall.map((checked, tIndex) => (tIndex === taskIndex ? false : checked))
: hall,
),
);

setDisabledList((prev) =>
prev.map((hall, hIndex) =>
hIndex === hallIndex
? hall.map((disabled, tIndex) => (tIndex === taskIndex ? false : disabled))
: hall,
),
);

createSnackbar(
`Marked task as incomplete: ${taskList[hallIndex].tasks[taskIndex].Name}`,
'success',
);

setIncompleteTaskDialogOpen(false);
setConfirmIncompleteTask(null);
} catch (error) {
console.error('Error marking task as incomplete', error);
createSnackbar('Failed to mark task as incomplete. Please try again.', 'error');
}
};

useEffect(() => {
if (taskList.length > 0) {
const newCheckedList = taskList.map((hall) =>
hall.tasks.map((task) => task.CompletedDate !== null),
);
setCheckedList(newCheckedList);
setDisabledList(newCheckedList);
}
}, [taskList]);

const handleTaskChecked = (hallIndex, taskIndex, taskID, taskCompleted) => {
if (taskCompleted) {
setConfirmIncompleteTask({ hallIndex, taskIndex, taskID });
setIncompleteTaskDialogOpen(true);
} else {
setConfirmTask({ hallIndex, taskIndex, taskID });
setTaskCheckedOpen(true);
}
};

const handleClickDescription = (hallIndex, taskIndex) => {
const selectedTask = taskList[hallIndex]?.tasks[taskIndex];
setSelectedDescription(selectedTask?.Description || 'No description provided');
setDescriptionOpen(true);
};

return (
<Grid item xs={12} md={12} padding={0}>
<Card>
<CardHeader title={`On-Call Tasks`} className="gc360_header" />
<CardContent>
<Typography>
<List disablePadding>
{taskList.length > 0 ? (
taskList.map((hallData, hallIndex) => (
<React.Fragment key={hallData.hallID}>
<ListSubheader>{hallData.hallID}</ListSubheader>
{hallData.tasks.length > 0 ? (
hallData.tasks.map((task, index) => (
<ListItem
key={task.TaskID}
secondaryAction={
<IconButton
edge="end"
aria-label="description"
onClick={() => handleClickDescription(hallIndex, index)}
>
<CommentIcon />
</IconButton>
}
>
<ListItemButton
onClick={() =>
handleTaskChecked(
hallIndex,
index,
task.TaskID,
checkedList[hallIndex]?.[index] || task.CompletedDate !== null,
)
}
dense
>
<ListItemIcon>
<FormControlLabel
label={task.Name}
control={
<Checkbox
id={`task-checkbox-${task.TaskID}`}
edge="start"
checked={
checkedList[hallIndex]?.[index] ?? task.CompletedDate !== null
}
disabled={
disabledList[hallIndex]?.[index] ??
task.CompletedDate !== null
}
/>
}
/>
</ListItemIcon>
</ListItemButton>
</ListItem>
))
) : (
<ListItem key={`no-tasks-${hallData.hallID}`}>
<ListItemText>No tasks available for {hallData.hallID}</ListItemText>
</ListItem>
)}
</React.Fragment>
))
) : (
<ListItem>
<ListItemText>No tasks to see</ListItemText>
</ListItem>
)}
</List>
</Typography>
<GordonDialogBox
open={taskCheckedOpen}
onClose={() => setTaskCheckedOpen(false)}
title={'Complete Task'}
buttonName="Confirm"
buttonClicked={handleConfirm}
cancelButtonName="CANCEL"
cancelButtonClicked={() => setTaskCheckedOpen(false)}
>
<br />
Are you sure you have completed this task?
</GordonDialogBox>
<GordonDialogBox
open={incompleteTaskDialogOpen}
onClose={() => setIncompleteTaskDialogOpen(false)}
title={'Mark Task as Incomplete'}
buttonName="Confirm"
buttonClicked={handleMarkIncompleteConfirm}
cancelButtonName="CANCEL"
cancelButtonClicked={() => setIncompleteTaskDialogOpen(false)}
>
<br />
Are you sure you want to mark this task as incomplete?
</GordonDialogBox>
<GordonDialogBox
open={descriptionOpen}
onClose={() => setDescriptionOpen(false)}
title="Task Description"
buttonName="Close"
buttonClicked={() => setDescriptionOpen(false)}
>
<br />
{selectedDescription}
</GordonDialogBox>
<SimpleSnackbar
open={snackbar.open}
text={snackbar.message}
severity={snackbar.severity}
onClose={() => setSnackbar((s) => ({ ...s, open: false }))}
/>
</CardContent>
</Card>
</Grid>
);
};

export default TaskList;
Loading

0 comments on commit ee58685

Please sign in to comment.