Added conditional rendering to dashboard page and restrict certain pages select user types

This commit is contained in:
Keannu Bernasol 2023-12-14 14:52:50 +08:00
parent 0e4c1b9f31
commit 3d20af24c9
9 changed files with 646 additions and 468 deletions

View file

@ -16,6 +16,7 @@ import EquipmentListPage from "./Pages/EquipmentListPage/EquipmentListPage";
import EquipmentLogsPage from "./Pages/EquipmentLogsPage/EquipmentLogsPage";
import EquipmentInstanceLogsPage from "./Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage";
import EquipmentInstancesFilteredListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesFilteredListPage";
import RestrictedPage from "./Components/RestrictedPage/RestrictedPage";
const queryClient = new QueryClient();
const router = createHashRouter([
@ -44,6 +45,7 @@ const router = createHashRouter([
element: (
<>
<Revalidator />
<RestrictedPage allow_only="Technician" />
<EquipmentInstancesListPage />
</>
),
@ -54,6 +56,7 @@ const router = createHashRouter([
element: (
<>
<Revalidator />
<RestrictedPage allow_only="Technician" />
<EquipmentInstancesFilteredListPage />
</>
),
@ -64,6 +67,7 @@ const router = createHashRouter([
element: (
<>
<Revalidator />
<RestrictedPage allow_only="Technician" />
<EquipmentInstanceLogsPage />
</>
),
@ -74,6 +78,7 @@ const router = createHashRouter([
element: (
<>
<Revalidator />
<RestrictedPage allow_only="Technician" />
<EquipmentListPage />
</>
),
@ -84,6 +89,7 @@ const router = createHashRouter([
element: (
<>
<Revalidator />
<RestrictedPage allow_only="Technician" />
<EquipmentLogsPage />
</>
),

View file

@ -15,6 +15,7 @@ import {
PatchEquipmentType,
EquipmentLogListType,
EquipmentInstanceLogListType,
UserType,
} from "../Types/Types";
const debug = true;
@ -135,7 +136,7 @@ export async function UserAPI() {
return instance
.get("api/v1/accounts/users/me/", config)
.then((response) => {
return response.data;
return response.data as UserType;
})
.catch(() => {
console.log("Error retrieving user data");

View file

@ -0,0 +1,261 @@
import { useQueries } from "@tanstack/react-query";
import styles from "../../styles";
import { EquipmentsAPI, EquipmentInstancesAPI, UserAPI } from "../API/API";
import { useNavigate } from "react-router-dom";
import { Button } from "@mui/material";
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
import AddToQueueIcon from "@mui/icons-material/AddToQueue";
import NoteAddIcon from "@mui/icons-material/NoteAdd";
import NoteIcon from "@mui/icons-material/Note";
import { colors } from "../../styles";
import ScienceIcon from "@mui/icons-material/Science";
import ColorizeIcon from "@mui/icons-material/Colorize";
import { Dispatch, SetStateAction } from "react";
type props = {
SetAddSKUModalOpen: Dispatch<SetStateAction<boolean>>;
SetAddItemModalOpen: Dispatch<SetStateAction<boolean>>;
};
export default function TechnicianButtons(props: props) {
const navigate = useNavigate();
const queries = useQueries({
queries: [
{
queryKey: ["equipments"],
queryFn: EquipmentsAPI,
},
{
queryKey: ["equipment_instances"],
queryFn: EquipmentInstancesAPI,
},
{
queryKey: ["user"],
queryFn: UserAPI,
},
],
});
return (
<>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
Equipments
</p>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
display:
queries[2].data && queries[2].data.is_technician
? "initial"
: "none",
},
}}
onClick={() => {
navigate("/view/equipment_instances");
}}
>
<FormatListBulletedIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
View All
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
props.SetAddItemModalOpen(true);
}}
>
<AddToQueueIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Add Item
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
props.SetAddSKUModalOpen(true);
}}
>
<NoteAddIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Add SKU
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipments");
}}
>
<NoteIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
View SKUs
</p>
</Button>
</div>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<ScienceIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
onClick={() => {
navigate("/view/equipment_instances/filter/Glassware");
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Glassware
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<ColorizeIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
onClick={() => {
navigate("/view/equipment_instances/filter/Miscellaneous");
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Miscellaneous
</p>
</Button>
</div>
</>
);
}

View file

@ -0,0 +1,94 @@
import { Button } from "@mui/material";
import { useNavigate } from "react-router-dom";
import ManageSearchIcon from "@mui/icons-material/ManageSearch";
import styles from "../../styles";
import { colors } from "../../styles";
export default function TechnicianLogs() {
const navigate = useNavigate();
return (
<>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
Logs
</p>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipments/logs");
}}
>
<ManageSearchIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
SKU Logs
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipment_instances/logs");
}}
>
<ManageSearchIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Item Logs
</p>
</Button>
</div>
</>
);
}

View file

@ -0,0 +1,180 @@
import { useQueries } from "@tanstack/react-query";
import styles from "../../styles";
import { EquipmentsAPI, EquipmentInstancesAPI, UserAPI } from "../API/API";
export default function TechnicianWidgets() {
const queries = useQueries({
queries: [
{
queryKey: ["equipments"],
queryFn: EquipmentsAPI,
},
{
queryKey: ["equipment_instances"],
queryFn: EquipmentInstancesAPI,
},
{
queryKey: ["user"],
queryFn: UserAPI,
},
],
});
return (
<div style={styles.flex_column}>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
SKUs in Database
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[0].data ? queries[0].data.length : 0}
</p>
</div>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Items in Database
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data ? queries[1].data.length : 0}
</p>
</div>
</div>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Functional Items
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data
? queries[1].data.filter(
(equipment) => equipment.status == "WORKING"
).length
: 0}
</p>
</div>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Broken Items
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data
? queries[1].data.filter(
(equipment) => equipment.status == "BROKEN"
).length
: 0}
</p>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,21 @@
import React from "react";
import { UserAPI } from "../API/API";
import { useQuery } from "@tanstack/react-query";
type props = {
allow_only: string;
children: React.ReactNode;
};
export default function RestrictedComponent(props: props) {
const user = useQuery({ queryKey: ["user"], queryFn: UserAPI });
if (props.allow_only === "Teacher") {
if (user.data && user.data.is_teacher) {
return <>{props.children}</>;
}
} else if (props.allow_only === "Technician") {
if (user.data && user.data.is_technician) {
return <>{props.children}</>;
}
}
return <></>;
}

View file

@ -0,0 +1,46 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { UserAPI } from "../API/API";
import { toast } from "react-toastify";
import { useQuery } from "@tanstack/react-query";
type props = {
allow_only: string;
};
export default function RestrictedPage(props: props) {
const navigate = useNavigate();
const user = useQuery({ queryKey: ["user"], queryFn: UserAPI });
useEffect(() => {
if (props.allow_only == "Teacher") {
if (user.data && !user.data.is_teacher) {
navigate("/");
toast("You are not a teacher!", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
});
}
} else if (props.allow_only == "Technician") {
if (user.data && !user.data.is_technician) {
navigate("/");
toast("You are not a technician!", {
position: "bottom-center",
autoClose: 2000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
});
}
}
}, [navigate, props.allow_only, user.data]);
return <></>;
}

View file

@ -96,3 +96,13 @@ export type EquipmentInstanceLogType = {
};
export type EquipmentInstanceLogListType = Array<EquipmentInstanceLogType>;
export type UserType = {
username: string;
email: string;
avatar: string;
first_name: string;
last_name: string;
is_teacher: boolean;
is_technician: boolean;
};

View file

@ -1,24 +1,21 @@
import Header from "../../Components/Header/Header";
import styles from "../../styles";
import { useQueries } from "@tanstack/react-query";
import { EquipmentsAPI, EquipmentInstancesAPI } from "../../Components/API/API";
import { Button, CircularProgress } from "@mui/material";
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
import AddToQueueIcon from "@mui/icons-material/AddToQueue";
import NoteAddIcon from "@mui/icons-material/NoteAdd";
import NoteIcon from "@mui/icons-material/Note";
import ManageSearchIcon from "@mui/icons-material/ManageSearch";
import { colors } from "../../styles";
import { useNavigate } from "react-router-dom";
import {
EquipmentsAPI,
EquipmentInstancesAPI,
UserAPI,
} from "../../Components/API/API";
import { useState } from "react";
import AddSKUModal from "../../Components/AddSKUModal/AddSKUModal";
import Popup from "reactjs-popup";
import AddItemModal from "../../Components/AddItemModal/AddItemModal";
import ScienceIcon from "@mui/icons-material/Science";
import ColorizeIcon from "@mui/icons-material/Colorize";
import RestrictedComponent from "../../Components/RestrictedComponent/RestrictedComponent";
import TechnicianWidgets from "../../Components/DashboardPage/TechnicianWidgets";
import TechnicianButtons from "../../Components/DashboardPage/TechnicianButtons";
import TechnicianLogs from "../../Components/DashboardPage/TechnicianLogs";
import CircularProgress from "@mui/material/CircularProgress/CircularProgress";
export default function Dashboard() {
const navigate = useNavigate();
const queries = useQueries({
queries: [
{
@ -29,6 +26,10 @@ export default function Dashboard() {
queryKey: ["equipment_instances"],
queryFn: EquipmentInstancesAPI,
},
{
queryKey: ["user"],
queryFn: UserAPI,
},
],
});
const isLoading = queries.some((result) => result.isLoading);
@ -66,460 +67,18 @@ export default function Dashboard() {
return (
<div style={styles.background}>
<Header label={"Dashboard"} />
<div style={styles.flex_column}>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
SKUs in Database
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[0].data ? queries[0].data.length : 0}
</p>
</div>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Items in Database
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data ? queries[1].data.length : 0}
</p>
</div>
</div>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Functional Items
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data
? queries[1].data.filter(
(equipment) => equipment.status == "WORKING"
).length
: 0}
</p>
</div>
<div
style={{
paddingLeft: "16px",
paddingRight: "16px",
margin: "16px",
borderRadius: 16,
backgroundColor: "#a6a6a6",
alignSelf: "center",
justifyContent: "center",
width: "16rem",
}}
>
<p
style={{
...styles.text_dark,
...styles.text_M,
...{ float: "left", position: "absolute" },
}}
>
Broken Items
</p>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
{queries[1].data
? queries[1].data.filter(
(equipment) => equipment.status == "BROKEN"
).length
: 0}
</p>
</div>
</div>
</div>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
Equipments
</p>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipment_instances");
}}
>
<FormatListBulletedIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
View All
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
SetAddItemModalOpen(true);
}}
>
<AddToQueueIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Add Item
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
SetAddSKUModalOpen(true);
}}
>
<NoteAddIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Add SKU
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipments");
}}
>
<NoteIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
View SKUs
</p>
</Button>
</div>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<ScienceIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
onClick={() => {
navigate("/view/equipment_instances/filter/Glassware");
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Glassware
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<ColorizeIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
onClick={() => {
navigate("/view/equipment_instances/filter/Miscellaneous");
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Miscellaneous
</p>
</Button>
</div>
<p
style={{
...styles.text_dark,
...styles.text_L,
}}
>
Logs
</p>
<div
style={{
...styles.flex_row,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipments/logs");
}}
>
<ManageSearchIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
SKU Logs
</p>
</Button>
<Button
style={{
...styles.flex_column,
...{
alignSelf: "center",
justifyContent: "center",
flexWrap: "wrap",
},
}}
onClick={() => {
navigate("/view/equipment_instances/logs");
}}
>
<ManageSearchIcon
style={{
height: 64,
width: 64,
fill: colors.font_dark,
marginLeft: "1rem",
marginRight: "1rem",
}}
/>
<p
style={{
...styles.text_dark,
...styles.text_M,
}}
>
Item Logs
</p>
</Button>
</div>
<RestrictedComponent allow_only={"Technician"}>
<TechnicianWidgets />
</RestrictedComponent>
<RestrictedComponent allow_only={"Technician"}>
<TechnicianButtons
SetAddItemModalOpen={SetAddItemModalOpen}
SetAddSKUModalOpen={SetAddSKUModalOpen}
/>
</RestrictedComponent>
<RestrictedComponent allow_only={"Technician"}>
<TechnicianLogs />
</RestrictedComponent>
<Popup
open={addSKUmodalOpen}
onClose={() => SetAddSKUModalOpen(false)}