Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/180 student request from application #119

Merged
merged 8 commits into from
Jan 15, 2024
5 changes: 2 additions & 3 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ function Main() {
const [applications, setApplications] = useState([]);
const [notifications, setNotifications] = useState([]);
const [requests, setRequests] = useState([]);
const [requestSent, setRequestSent] = useState(false);

// Message to be shown to the user after an API has been called
const [alert, setAlert] = useState({
Expand Down Expand Up @@ -183,9 +182,9 @@ function Main() {
<Routes>
{/* prettier-ignore */}
<Route path="/" element={user ? <RootPage loading={loading} setAlert={setAlert} setDirty={setDirty} currentDate={currentDate} fetchProposals={fetchProposals} fetchApplications={fetchApplications} fetchNotifications={fetchNotifications} fetchRequests={fetchRequests} /> : <LoginPage />}>
<Route path="proposals" element={user ? <ProposalsPage requestSent={requestSent} setAlert={setAlert} setDirty={setDirty} currentDate={currentDate} proposals={proposals} applications={applications} teachers={teachers} groups={groups} getTeacherById={getTeacherById} /> : <Navigate replace to="/" />} />
<Route path="proposals" element={user ? <ProposalsPage setAlert={setAlert} setDirty={setDirty} currentDate={currentDate} proposals={proposals} applications={applications} teachers={teachers} groups={groups} getTeacherById={getTeacherById} /> : <Navigate replace to="/" />} />
<Route path="proposals/:proposalId" element={user ? <ViewProposalPage setDirty={setDirty} setAlert={setAlert} getTeacherById={getTeacherById} getDegreeById={getDegreeById} applications={applications} /> : <Navigate replace to="/" />} />
<Route path="add-request" element={user ? <CreateRequestPage teachers={teachers} setAlert={setAlert} setRequestSent={setRequestSent} /> : <Navigate replace to="/" />} />
<Route path="add-request" element={user ? <CreateRequestPage fetchRequests={fetchRequests} teachers={teachers} getTeacherById={getTeacherById} setAlert={setAlert} /> : <Navigate replace to="/" />} />
<Route path="edit-proposal/:proposalId" element={user ? <EditProposalPage currentDate={currentDate} fetchProposals={fetchProposals} teachers={teachers} degrees={degrees} setAlert={setAlert} /> : <Navigate replace to="/" />} />
<Route path="applications" element={user ? <ApplicationsPage applications={applications} /> : <Navigate replace to="/" /> } />
<Route path="applications/:applicationId" element={user ? <ViewApplicationPage fetchApplications={fetchApplications} fetchNotifications={fetchNotifications} setAlert={setAlert} applications={applications} /> : <Navigate replace to="/" />} />
Expand Down
20 changes: 17 additions & 3 deletions client/src/components/ApplicationDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import ConfirmationDialog from "./ConfirmationDialog";
import { useThemeContext } from "../theme/ThemeContextProvider";
import API from "../utils/API";
import StudentCareerTable from "./StudentCareerTable";
import { NavLink } from "react-router-dom";

function ApplicationDetails(props) {
const user = useContext(UserContext);
const handleErrors = useContext(ErrorContext);
const { theme } = useThemeContext();
const { application, evaluateApplication, applications } = props;
const { application, evaluateApplication, applications, proposal } = props;

const [decision, setDecision] = useState(null);
const [openDialog, setOpenDialog] = useState(false);
Expand Down Expand Up @@ -227,7 +228,19 @@ function ApplicationDetails(props) {
<Divider variant="middle" />
<Typography variant="body1" gutterBottom paddingTop={2}>
<span style={{ fontWeight: "bold" }}>Title: </span>
{application.title}
{isApplicationAccepted() && user.role === "student" ? (
<Link
color="primary"
underline="always"
component={NavLink}
to={`/proposals/${proposal?.id}`}
state={{ proposal: proposal }}
>
{application.title}
</Link>
) : (
application.title
)}
</Typography>
<Box paddingTop={4} sx={{ display: "flex", justifyContent: "center" }}>
{renderActionButton()}
Expand All @@ -239,7 +252,8 @@ function ApplicationDetails(props) {
ApplicationDetails.propTypes = {
application: PropTypes.object,
evaluateApplication: PropTypes.func,
applications: PropTypes.array
applications: PropTypes.array,
proposal: PropTypes.object
};

export default ApplicationDetails;
2 changes: 1 addition & 1 deletion client/src/components/ApplicationTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ApplicationRow from "./ApplicationRow";
import UserContext from "../contexts/UserContext";

const TEACHER_HEADERS = ["Student", "Proposal", "Status", "Open"];
const STUDENT_HEADERS = ["Teacher", "Proposal", "Status", "Open"];
const STUDENT_HEADERS = ["Supervisor", "Proposal", "Status", "Open"];

function generateTableHeaders(headers, align) {
return headers.map((headCell) => (
Expand Down
173 changes: 114 additions & 59 deletions client/src/components/RequestDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import EditNotificationsIcon from "@mui/icons-material/EditNotifications";
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
import ModeEditIcon from "@mui/icons-material/ModeEdit";
import ScheduleSendIcon from "@mui/icons-material/ScheduleSend";
import ConfirmationDialog from "./ConfirmationDialog";
import UserContext from "../contexts/UserContext";
import { useThemeContext } from "../theme/ThemeContextProvider";
Expand Down Expand Up @@ -62,7 +65,7 @@ function RequestDetails(props) {
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<AccessTimeIcon color="info" />
<Typography variant="h6" fontWeight={700} style={{ color: theme.palette.info.main }}>
<Typography variant="h6" textAlign="center" fontWeight={700} style={{ color: theme.palette.info.main }}>
REQUEST APPROVED
</Typography>
</Box>
Expand Down Expand Up @@ -95,78 +98,118 @@ function RequestDetails(props) {
</Button>
</Stack>
);
} else if (user.role === "student") {
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<ScheduleSendIcon color="info" />
<Typography variant="h6" textAlign="center" fontWeight={700} style={{ color: theme.palette.info.main }}>
WAITING FOR SUPERVISOR APPROVAL
</Typography>
</Box>
);
}
break;
case "requested":
return (
<Stack direction="row" spacing={3} sx={{ width: "100%" }}>
<Button
variant="outlined"
color="error"
fullWidth
sx={{ mt: 3, mb: 2 }}
onClick={() => {
setDecision("rejected");
handleOpenDialog();
}}
>
Reject
</Button>
<Button
variant="contained"
fullWidth
sx={{ mt: 3, mb: 2 }}
onClick={() => {
setDecision("approved");
handleOpenDialog();
}}
>
Approve
</Button>
</Stack>
);
if (user.role === "student") {
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<ScheduleSendIcon color="info" />
<Typography variant="h6" textAlign="center" fontWeight={700} style={{ color: theme.palette.info.main }}>
WAITING FOR SECRETARY APPROVAL
</Typography>
</Box>
);
} else {
return (
<Stack direction="row" spacing={3} sx={{ width: "100%" }}>
<Button
variant="outlined"
color="error"
fullWidth
sx={{ mt: 3, mb: 2 }}
onClick={() => {
setDecision("rejected");
handleOpenDialog();
}}
>
Reject
</Button>
<Button
variant="contained"
fullWidth
sx={{ mt: 3, mb: 2 }}
onClick={() => {
setDecision("approved");
handleOpenDialog();
}}
>
Approve
</Button>
</Stack>
);
}
case "rejected":
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<HighlightOffIcon color="error" />
<Typography variant="h6" fontWeight={700} style={{ color: theme.palette.error.main }}>
REQUEST REJECTED
<Typography variant="h6" textAlign="center" fontWeight={700} style={{ color: theme.palette.error.main }}>
{user.role === "student" ? "REQUEST REJECTED. YOU CAN NOW SUBMIT A NEW REQUEST." : "REQUEST REJECTED"}
</Typography>
</Box>
);
case "started":
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<CheckCircleOutlineIcon color="success" />
<Typography variant="h6" fontWeight={700} style={{ color: theme.palette.success.main }}>
REQUEST STARTED
<Typography variant="h6" textAlign="center" fontWeight={700} style={{ color: theme.palette.success.main }}>
THESIS STARTED
</Typography>
</Box>
);
case "changes_requested":
return (
user.role === "student" && (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<EditNotificationsIcon color="warning" />
<Typography
variant="h6"
textAlign="center"
fontWeight={700}
style={{ color: theme.palette.warning.main }}
>
CHANGES REQUESTED BY SUPERVISOR
</Typography>
</Box>
)
);
default:
break;
}
};

return (
<>
<ConfirmationDialog
title="Confirm Decision"
message={dialogMessage}
primaryButtonLabel="Submit"
secondaryButtonLabel="Cancel"
open={openDialog}
handleClose={handleCloseDialog}
handleSubmit={handleDecisionSubmit}
/>
<Typography variant="h5" gutterBottom paddingTop={2}>
Student information
</Typography>
<Divider variant="middle" />
<Typography variant="body1" gutterBottom paddingTop={2}>
<span style={{ fontWeight: "bold" }}>Student ID: </span>
{request.student_id}
</Typography>
{user.role !== "student" && (
<>
<ConfirmationDialog
title="Confirm Decision"
message={dialogMessage}
primaryButtonLabel="Submit"
secondaryButtonLabel="Cancel"
open={openDialog}
handleClose={handleCloseDialog}
handleSubmit={handleDecisionSubmit}
/>
<Typography variant="h5" gutterBottom paddingTop={2}>
Student information
</Typography>
<Divider variant="middle" />
<Typography variant="body1" gutterBottom paddingTop={2}>
<span style={{ fontWeight: "bold" }}>Student ID: </span>
{request.student_id}
</Typography>
</>
)}

<Typography variant="h5" gutterBottom paddingTop={2}>
Supervisors
Expand All @@ -184,24 +227,36 @@ function RequestDetails(props) {
)}

<Typography variant="h5" gutterBottom sx={{ mt: 3 }}>
Thesis Request
Thesis Details
</Typography>
<Divider variant="middle" />
<Typography variant="body1" gutterBottom paddingTop={2}>
<span style={{ fontWeight: "bold" }}>Title: </span>
{request.title}
</Typography>
<Typography variant="body1" gutterBottom>
<span style={{ fontWeight: "bold" }}>Description: </span>
{showMore ? `${request.description} ` : `${request.description.substring(0, 250)}... `}
<Link component="button" variant="body2" onClick={() => setShowMore(!showMore)}>
{showMore ? "Show less" : "Show more"}
</Link>
</Typography>

<Box paddingTop={4} sx={{ display: "flex", justifyContent: "center" }}>
{request.description.length > 250 ? (
<Typography variant="body1" gutterBottom>
<span style={{ fontWeight: "bold" }}>Description: </span>
{showMore ? `${request.description} ` : `${request.description.substring(0, 250)}... `}
<Link component="button" variant="body2" onClick={() => setShowMore(!showMore)}>
{showMore ? "Show less" : "Show more"}
</Link>
</Typography>
) : (
<Typography variant="body1" gutterBottom>
<span style={{ fontWeight: "bold" }}>Description: </span>
{request.description}
</Typography>
)}
<Box paddingTop={1} sx={{ display: "flex", justifyContent: "center" }}>
{renderActions()}
</Box>
{user.role === "student" && request.status === "changes_requested" && (
<Typography variant="body1" gutterBottom paddingTop={2}>
<span style={{ fontWeight: "bold" }}>Message from supervisor: </span>
{request.changes_requested}
</Typography>
)}
</>
);
}
Expand Down
16 changes: 10 additions & 6 deletions client/src/components/RequestForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import TextField from "@mui/material/TextField";
import CustomPaper from "./CustomPaper";

function RequestForm(props) {
const { createRequest, teachers } = props;
const { createRequest, teachers, getTeacherById, proposal } = props;

const supervisor = proposal ? getTeacherById(proposal.supervisor) : null;

const [formData, setFormData] = useState({
title: "",
description: "",
supervisor: null,
coSupervisors: []
title: proposal ? proposal.title : "",
description: proposal ? proposal.description : "",
supervisor: supervisor ? supervisor.email : null,
coSupervisors: proposal ? proposal.co_supervisors.split(", ") : []
});

const [formErrors, setFormErrors] = useState({
Expand Down Expand Up @@ -191,7 +193,9 @@ function RequestForm(props) {

RequestForm.propTypes = {
createRequest: PropTypes.func,
teachers: PropTypes.array
teachers: PropTypes.array,
getTeacherById: PropTypes.func,
proposal: PropTypes.object
};

export default RequestForm;
4 changes: 2 additions & 2 deletions client/src/components/RequestRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ function RequestRow(props) {

return (
<TableRow>
<TableCell>{request.student_id}</TableCell>
{user.role === "secretary_clerk" && <TableCell>{renderSupervisor()}</TableCell>}
{user.role !== "student" && <TableCell>{request.student_id}</TableCell>}
{user.role !== "teacher" && <TableCell>{renderSupervisor()}</TableCell>}
<TableCell
sx={{
maxWidth: "30vw",
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/RequestTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function RequestTable(props) {
const renderHeaders = () => {
if (user.role === "teacher") {
return HEADERS.filter((header) => header !== "Supervisor");
} else if (user.role === "student") {
return HEADERS.filter((header) => header !== "Student");
} else {
return HEADERS;
}
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const sidebarTabs = [
},
{
id: "requests",
label: "Requests",
label: "Start Requests",
icon: <ApprovalIcon color="primary" />,
path: "/requests"
},
Expand Down Expand Up @@ -63,7 +63,7 @@ function Sidebar(props) {
let tabs = [];
switch (user.role) {
case "student":
tabs = sidebarTabs.filter((tab) => tab.id !== "requests");
tabs = sidebarTabs.map((tab) => (tab.id === "requests" ? { ...tab, label: "Start Request" } : tab));
break;
case "teacher":
tabs = sidebarTabs;
Expand Down
Loading