-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2531 from gordon-cs/wmr-ra-tasklist
RA TaskList
- Loading branch information
Showing
3 changed files
with
419 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
298
src/views/ResLife/components/RAView/components/TaskList/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.