diff --git a/src/pages/teacherDashboard/TeacherDashboard.tsx b/src/pages/teacherDashboard/TeacherDashboard.tsx index eb2472d..d430b39 100644 --- a/src/pages/teacherDashboard/TeacherDashboard.tsx +++ b/src/pages/teacherDashboard/TeacherDashboard.tsx @@ -1,5 +1,6 @@ import * as page from "codeforlife/components/page" import { type FC, useEffect } from "react" +import { CircularProgress } from "@mui/material" import { type Parameters } from "codeforlife/utils/router" import { type SchoolTeacherUser } from "codeforlife/api" import { useNavigate } from "codeforlife/hooks" @@ -17,10 +18,10 @@ import { paths } from "../../router" export interface TeacherDashboardProps {} const TeacherDashboard: FC = () => { - const [retrieveUser, { data: user, isError }] = useLazyRetrieveUserQuery() + let [retrieveUser, { data: authUser, isError }] = useLazyRetrieveUserQuery() const navigate = useNavigate() - const isNonSchoolTeacher = user && !user.teacher?.school + const isNonSchoolTeacher = authUser && !authUser.teacher?.school useEffect(() => { if (isNonSchoolTeacher) navigate(paths.teacher.onboarding._) @@ -34,21 +35,22 @@ const TeacherDashboard: FC = () => { return ( {({ user_id }) => { - if (!user) { + if (!authUser) { retrieveUser(user_id) - return <>Loading... + return } - const schoolTeacherUser = user as SchoolTeacherUser + const authSchoolTeacherUser = + authUser as SchoolTeacherUser return ( , + children: , path: (paths.teacher.dashboard.tab.school.__ as Parameters).tab, }, // { diff --git a/src/pages/teacherDashboard/school/School.tsx b/src/pages/teacherDashboard/school/School.tsx index 1b19193..b4ab62e 100644 --- a/src/pages/teacherDashboard/school/School.tsx +++ b/src/pages/teacherDashboard/school/School.tsx @@ -1,52 +1,50 @@ import * as page from "codeforlife/components/page" -import { Button, Stack, Typography } from "@mui/material" +import { + Button, + CircularProgress, + Unstable_Grid2 as Grid, + Stack, + Typography, +} from "@mui/material" import { useLocation, useNavigate } from "codeforlife/hooks" import { type FC } from "react" import { type SchoolTeacherUser } from "codeforlife/api" +import TransferClasses, { type TransferClassesProps } from "./TransferClasses" import InviteTeacherForm from "./InviteTeacherForm" import { type RetrieveUserResult } from "../../../api/user" -import TransferClasses from "./TransferClasses" -import { paths } from "../../../router" -import { useRemoveTeacherFromSchoolMutation } from "../../../api/teacher" +import TeacherInvitationTable from "./TeacherInvitationTable" +import TeacherTable from "./TeacherTable" +import UpdateSchoolForm from "./UpdateSchoolForm" import { useRetrieveSchoolQuery } from "../../../api/school" export interface SchoolProps { - user: SchoolTeacherUser + authUser: SchoolTeacherUser } -const School: FC = ({ user }) => { - const { data: school } = useRetrieveSchoolQuery(user.teacher.school) - const { state } = useLocation<{ transferClasses: boolean }>() - const [removeTeacherFromSchool] = useRemoveTeacherFromSchoolMutation() - const navigate = useNavigate() - - const { transferClasses } = state || {} +const School: FC = ({ authUser }) => { + const { data: school, isError } = useRetrieveSchoolQuery( + authUser.teacher.school, + ) + const { state } = useLocation<{ + transferClasses: TransferClassesProps["user"] + }>() + const navigate = useNavigate<{ + transferClasses?: TransferClassesProps["user"] + }>() - const onLeaveOrganisation = (): void => { - removeTeacherFromSchool(user.id) - .unwrap() - .then(() => { - // if (moveClassData?.classes) { - // navigate(paths.teacher.dashboard.school.leave._, { - // state: moveClassData, - // }) - // } else { - // navigate(paths.teacher.onboarding._, { - // state: { leftOrganisation: true }, - // }) - // } - }) - .catch(error => { - console.error(error) - }) + if (state?.transferClasses) { + return ( + + ) } - if (!school) return <>TODO + // TODO: handle this better + if (isError) return <>There was an error! + + if (!school) return - return true ? ( - - ) : ( + return ( <> @@ -54,7 +52,7 @@ const School: FC = ({ user }) => { - {user.teacher.is_admin ? ( + {authUser.teacher.is_admin ? ( <> As an administrator of your school or club, you can select other @@ -77,15 +75,7 @@ const School: FC = ({ user }) => { + ) : ( + + )} + + + + )} + + ), + )} + + )} + + ) +} + +export default TeacherInvitationTable diff --git a/src/pages/teacherDashboard/school/TeacherTable.tsx b/src/pages/teacherDashboard/school/TeacherTable.tsx index bf44a36..6c8297a 100644 --- a/src/pages/teacherDashboard/school/TeacherTable.tsx +++ b/src/pages/teacherDashboard/school/TeacherTable.tsx @@ -1,382 +1,154 @@ -import { type FC } from "react" import { - Button, - Dialog, - Grid, - InputAdornment, - Stack, - Typography, - useTheme, -} from "@mui/material" - -const TeachersTableActions: React.FC<{ - isInvite: boolean - teacherEmail: string - userEmail: string - isTeacherAdmin: boolean - id: string - token?: string - twoFactorAuthentication?: boolean - setDialog: SetDialogType -}> = ({ - isInvite, - teacherEmail, - userEmail, - isTeacherAdmin, - id, - token, - twoFactorAuthentication, - setDialog, -}) => { - const navigate = useNavigate() - const [toggleAdmin] = useToggleAdminMutation() - const [organisationKick] = useOrganisationKickMutation() - const [inviteToggleAdmin] = useInviteToggleAdminMutation() - const [resendInvite] = useResendInviteMutation() - const [deleteInvite] = useDeleteInviteMutation() + Add as AddIcon, + Create as CreateIcon, + DeleteOutline as DeleteOutlineIcon, + DoDisturbOnOutlined as DoDisturbOnOutlinedIcon, + DoNotDisturb as DoNotDisturbIcon, +} from "@mui/icons-material" +import { Button, Typography } from "@mui/material" +import { type FC } from "react" +import { type SchoolTeacherUser } from "codeforlife/api" +import { TablePagination } from "codeforlife/components" +import { useNavigate } from "codeforlife/hooks" - const onToggleAdmin = (id: string): void => { - toggleAdmin({ id }) - .unwrap() - .then(res => { - navigate(".", { - state: { - message: res.isAdminNow - ? "Administrator status has been given successfully." - : "Administrator status has been revoked successfully.", - }, - }) - }) - .catch(err => { - console.error("ToggleAdmin error: ", err) - }) - } +import * as table from "../../../components/table" +import { + type ListUsersResult, + type RetrieveUserResult, + useLazyListUsersQuery, +} from "../../../api/user" +import { type TransferClassesProps } from "./TransferClasses" +import { paths } from "../../../router" +import { useSetTeacherAdminAccessMutation } from "../../../api/teacher" - const onOrganisationKick = (id: string): void => { - organisationKick({ id }) - .unwrap() - .then(moveClassData => { - if (moveClassData?.classes) { - navigate(paths.teacher.dashboard.school.leave._, { - state: moveClassData, - }) - } else { - navigate(".", { - state: { - message: - "The teacher has been successfully removed from your school or club.", - }, - }) - } - }) - .catch(err => { - console.error("OrganisationKick error: ", err) - }) - } +export interface TeacherTableProps { + authUser: SchoolTeacherUser +} - const onInviteToggleAdmin = (id: string): void => { - inviteToggleAdmin({ id }) - .unwrap() - .then(res => { - navigate(".", { - state: { - message: res.isAdminNow - ? "Administrator invite status has been given successfully." - : "Administrator invite status has been revoked successfully.", - }, - }) - }) - .catch(err => { - console.error("InviteToggleAdmin error: ", err) - }) - } +const TeacherTable: FC = ({ authUser }) => { + const navigate = useNavigate<{ + transferClasses?: TransferClassesProps["user"] + }>() + const [setTeacherAdminAccess] = useSetTeacherAdminAccessMutation() - const onResendInvite = (token: string): void => { - resendInvite({ token }) - .unwrap() - .then(() => { - navigate(".", { - state: { - message: "Teacher re-invited!", - }, - }) - }) - .catch(err => { - console.error("ResendInvite error: ", err) - }) + function showNotification(children: string, error: boolean = false) { + navigate(".", { + state: { notifications: [{ props: { children, error } }] }, + }) } - const onDeleteInvite = (token: string): void => { - deleteInvite({ token }) + function handleSetTeacherAdminAccess(id: number, isAdmin: boolean) { + setTeacherAdminAccess({ id, is_admin: isAdmin }) .unwrap() .then(() => { - navigate(".", { - state: { - message: "Invitation successfully deleted.", - }, - }) + showNotification( + isAdmin + ? "Administrator status has been given successfully." + : "Administrator status has been revoked successfully.", + ) }) - .catch(err => { - console.error("DeleteInvite error: ", err) + .catch(() => { + showNotification( + isAdmin + ? "Failed to give administrator status." + : "Failed to revoke administrator status.", + true, + ) }) } - const onInviteMakeAdmin = (id: string): void => { - setDialog({ - open: true, - onConfirm: () => { - onInviteToggleAdmin(id) - setDialog({ open: false }) - }, - }) - } - - const onMakeAdmin = (id: string): void => { - setDialog({ - open: true, - onConfirm: () => { - onToggleAdmin(id) - setDialog({ open: false }) - }, - }) - } - - if (isInvite) { - return ( - <> - {isTeacherAdmin ? ( - - ) : ( - - )} - - - - ) - } else { - if (teacherEmail === userEmail) { - return ( - <> - - {/* This button below will be used for pending invites */} - - - ) - } else if (isTeacherAdmin) { - return ( - <> - - - - ) - } else { - return ( - <> - - - {twoFactorAuthentication ? ( - - ) : ( - <> - )} - - ) - } - } -} - -export interface TeacherTableProps { - teacherData: TeacherDashboardProps["teacher"] - coworkersData: TeacherDashboardProps["coworkers"] - sentInvites: TeacherDashboardProps["sentInvites"] - setDialog: SetDialogType -} - -const TeacherTable: FC = ({ - teacherData, - coworkersData, - sentInvites, - setDialog, -}) => { - const isUserAdmin = teacherData.isAdmin - const email = teacherData.teacherEmail - const boldText: React.FC = (str: string) => ( - - ({str}) - - ) - return ( - - {coworkersData.map( - ({ - teacherFirstName, - teacherLastName, - teacherEmail, - isTeacherAdmin, - id, - }) => ( - - - - {teacherFirstName} {teacherLastName}{" "} - {teacherEmail === email ? boldText("you") : ""}{" "} - - - - - {isTeacherAdmin ? "Teacher Administrator" : "Standard Teacher"} - - ({teacherEmail}) - - {isUserAdmin && ( - - - - )} - - ), - )} - {sentInvites.map( - ({ - invitedTeacherFirstName, - invitedTeacherLastName, - invitedTeacherEmail, - invitedTeacherIsAdmin, - isExpired, - id, - token, - }) => ( - - - - {invitedTeacherFirstName} {invitedTeacherLastName}{" "} - {isExpired ? boldText("expired") : boldText("pending")}{" "} - - - - - {invitedTeacherIsAdmin - ? "Teacher Administrator" - : "Standard Teacher"} - - - ({invitedTeacherEmail}) - - - {isUserAdmin && ( - - - - )} - - ), + {users => ( + + {( + users as Array> + ).map(user => ( + + + + {user.first_name} {user.last_name} + {user.id === authUser.id ? (you) : ""} + + + + + {user.teacher.is_admin + ? "Teacher Administrator" + : "Standard Teacher"} + + ({user.email}) + + {authUser.teacher.is_admin && ( + + {authUser.id === user.id && ( + + )} + {user.teacher.is_admin ? ( + + ) : ( + + )} + + {true && ( // TODO check if user has otp enabled + + )} + + )} + + ))} + )} - + ) } diff --git a/src/pages/teacherDashboard/school/TransferClasses.tsx b/src/pages/teacherDashboard/school/TransferClasses.tsx index b82650e..b6755dd 100644 --- a/src/pages/teacherDashboard/school/TransferClasses.tsx +++ b/src/pages/teacherDashboard/school/TransferClasses.tsx @@ -3,8 +3,9 @@ import * as page from "codeforlife/components/page" import { Link, LinkButton } from "codeforlife/components/router" import { type SchoolTeacher, type User } from "codeforlife/api" import { Stack, Typography } from "@mui/material" -import { useFullList, useNavigate } from "codeforlife/hooks" import { type FC } from "react" +import { TablePagination } from "codeforlife/components" +import { useNavigate } from "codeforlife/hooks" import * as table from "../../../components/table" import { @@ -17,39 +18,21 @@ import { useLazyListUsersQuery } from "../../../api/user" import { useRemoveTeacherFromSchoolMutation } from "../../../api/teacher" export interface TransferClassesProps { - loggedInTeacherId: number - teacherUser: Pick & { + authUserId: User["id"] + user: Pick & { teacher: Pick } } -const TransferClasses: FC = ({ - loggedInTeacherId, - teacherUser, -}) => { +const TransferClasses: FC = ({ authUserId, user }) => { const [updateClasses] = useUpdateClassesMutation() const [removeTeacherFromSchool] = useRemoveTeacherFromSchoolMutation() const navigate = useNavigate() - const { data: teacherUsers } = useFullList(useLazyListUsersQuery, { - teachers_in_school: teacherUser.teacher.school, - _id: teacherUser.id, - }) - const { data: classes } = useFullList(useLazyListClassesQuery, { - teacher: teacherUser.teacher.id, - }) - - if (!teacherUsers) return <>TODO - if (!classes) return <>TODO - - const isSelf = teacherUser.teacher.id === loggedInTeacherId - - const usersByTeacherId = Object.fromEntries( - teacherUsers.map(user => [user.teacher!.id, user]), - ) + const isSelf = user.id === authUserId function handleRemoveTeacherFromSchool() { - removeTeacherFromSchool(teacherUser.teacher.id) + removeTeacherFromSchool(user.teacher.id) .unwrap() .then(() => { if (isSelf) { @@ -84,8 +67,7 @@ const TransferClasses: FC = ({ - Move all classes for teacher {teacherUser.first_name}{" "} - {teacherUser.last_name} + Move all classes for teacher {user.first_name} {user.last_name} dashboard @@ -94,54 +76,78 @@ const TransferClasses: FC = ({ Please specify which teacher you would like the classes below to be moved to. - ({ - ...values, - [klass.id]: { teacher: undefined }, - }), - {}, - )} - onSubmit={submitForm(updateClasses, { - then: handleRemoveTeacherFromSchool, - })} + - - {classes.map(klass => ( - - - {klass.name} - - - { - const user = usersByTeacherId[teacherId] - return `${user.first_name} ${user.last_name}` - }} - textFieldProps={{ - required: true, - name: `${klass.id}.teacher`, - }} - /> - - - ))} - - - - Cancel - - - {isSelf - ? "Move classes and leave" - : "Move classes and remove teacher"} - - - + {classes => { + if (!classes.length) { + handleRemoveTeacherFromSchool() + return <> + } + + return ( + ({ + ...values, + [klass.id]: { teacher: undefined }, + }), + {}, + )} + onSubmit={submitForm(updateClasses)} + > + + {classes.map(klass => ( + + + + {klass.name} + + + + + `${first_name} ${last_name}` + } + getOptionKey={({ teacher }) => + (teacher as SchoolTeacher).id + } + textFieldProps={{ + required: true, + name: `${klass.id}.teacher`, + }} + searchKey="name" + /> + + + ))} + + + + Cancel + + + {isSelf + ? "Move classes and leave" + : "Move classes and remove teacher"} + + + + ) + }} + ) diff --git a/src/pages/teacherDashboard/school/UpdateSchoolForm.tsx b/src/pages/teacherDashboard/school/UpdateSchoolForm.tsx index 6de21b6..e455d78 100644 --- a/src/pages/teacherDashboard/school/UpdateSchoolForm.tsx +++ b/src/pages/teacherDashboard/school/UpdateSchoolForm.tsx @@ -1,48 +1,74 @@ -import * as form from "codeforlife/components/form" +import * as forms from "codeforlife/components/form" +import { Stack, Typography } from "@mui/material" import { type FC } from "react" +import { submitForm } from "codeforlife/utils/form" +import { useNavigate } from "codeforlife/hooks" + +import { + type RetrieveSchoolResult, + useUpdateSchoolMutation, +} from "../../../api/school" +import { SchoolNameField } from "../../../components/form" export interface UpdateSchoolFormProps { - schoolData: TeacherDashboardProps["school"] + school: RetrieveSchoolResult } -const UpdateSchoolForm: FC = ({ schoolData }) => { +const UpdateSchoolForm: FC = ({ school }) => { const navigate = useNavigate() - const schoolName = schoolData.name - const schoolPostcode = schoolData.postcode - const schoolCountry = schoolData.country - const [updateSchool] = useOldUpdateSchoolMutation() + const [updateSchool] = useUpdateSchoolMutation() return ( - { - updateSchool(values) - .unwrap() - .then(() => { + <> + + Update details of your school or club + + { + navigate(".", { + state: { + notifications: [ + { + props: { + children: + "You have updated the details for your school or club successfully.", + }, + }, + ], + }, + }) + }, + catch: () => { navigate(".", { state: { - message: - "You have updated the details for your school or club successfully.", + notifications: [ + { + props: { + children: + "Failed to updated the details for your school or club.", + error: true, + }, + }, + ], }, }) - }) - .catch(err => { - console.error("UpdateSchool error: ", err) - }) - }} - submitButton={Update details} - > - - - - + }, + })} + > + {form => ( + <> + + + + {form.values.country === "GB" && } + + Update details + + )} + + ) }