diff --git a/src/App.tsx b/src/App.tsx index 3fdadf4..504e09d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import EquipmentInstanceLogsPage from "./Pages/EquipmentInstanceLogsPage/Equipme import EquipmentInstancesFilteredListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesFilteredListPage"; import RestrictedPage from "./Components/RestrictedPage/RestrictedPage"; import TransactionsListPage from "./Pages/TransactionsListPage/TransactionsListPage"; +import AddTransactionPage from "./Pages/AddTransactionPage/AddTransactionPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -125,6 +126,17 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/new/transaction", + element: ( + <> + + + + + ), + errorElement: , + }, ]); export default function App() { diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 59131c7..e2dee30 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -19,6 +19,8 @@ import { TransactionListType, TransactionUpdateType, TransactionType, + ClearanceType, + TransactionCreateType, } from "../Types/Types"; const debug = true; @@ -184,6 +186,29 @@ export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) { }); } +export async function ClearanceAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/accounts/clearance/", config) + .then((response) => { + return response.data as ClearanceType; + }) + .catch(() => { + console.log("Error retrieving clearance status for user"); + }); +} + +export async function TeachersAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/accounts/teachers/", config) + .then((response) => { + return response.data as Array; + }) + .catch(() => { + console.log("Error retrieving teachers"); + }); +} // Equipment APIs export async function EquipmentAPI(id: number) { @@ -330,6 +355,18 @@ export async function EquipmentInstancesAPI() { }); } +export async function AvailableEquipmentInstancesAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/equipments/equipment_instances/available", config) + .then((response) => { + return response.data as EquipmentInstanceListType; + }) + .catch(() => { + console.log("Error retrieving available equipments"); + }); +} + export async function EquipmentInstanceCreateAPI( equipment_instance: AddEquipmentInstanceType ) { @@ -371,6 +408,19 @@ export async function TransactionAPI(id: number) { }); } +export async function TransactionCreateAPI(transaction: TransactionCreateType) { + const config = await GetConfig(); + return instance + .post(`api/v1/transactions/`, transaction, config) + .then((response) => { + return [true, response.data as TransactionType]; + }) + .catch((error) => { + console.log("Error creating transaction"); + return [false, ParseError(error)]; + }); +} + export async function TransactionUpdateAPI( transaction: TransactionUpdateType, id: number @@ -386,3 +436,27 @@ export async function TransactionUpdateAPI( return [false, ParseError(error)]; }); } + +export async function TransactionsByStudentAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/transactions/student/", config) + .then((response) => { + return response.data as TransactionListType; + }) + .catch(() => { + console.log("Error retrieving transactions for current student"); + }); +} + +export async function TransactionsByTeacherAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/transactions/teacher/", config) + .then((response) => { + return response.data as TransactionListType; + }) + .catch(() => { + console.log("Error retrieving transactions for current teacher"); + }); +} diff --git a/src/Components/DashboardPage/Student/StudentDashboard.tsx b/src/Components/DashboardPage/Student/StudentDashboard.tsx new file mode 100644 index 0000000..702e93f --- /dev/null +++ b/src/Components/DashboardPage/Student/StudentDashboard.tsx @@ -0,0 +1,62 @@ +import styles from "../../../styles"; +import { Button } from "@mui/material"; +import AddBoxIcon from "@mui/icons-material/AddBox"; +import { colors } from "../../../styles"; +import { useNavigate } from "react-router-dom"; +export default function StudentDashboard() { + const navigate = useNavigate(); + return ( +
+

+ Student Actions +

+
+ +
+
+ ); +} diff --git a/src/Components/DashboardPage/Student/StudentTransactionFilterMenu.tsx b/src/Components/DashboardPage/Student/StudentTransactionFilterMenu.tsx new file mode 100644 index 0000000..be58c39 --- /dev/null +++ b/src/Components/DashboardPage/Student/StudentTransactionFilterMenu.tsx @@ -0,0 +1,388 @@ +import styles from "../../../styles"; +import { + Button, + FormControl, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, +} from "@mui/material"; +import HourglassBottomIcon from "@mui/icons-material/HourglassBottom"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import FlashOffIcon from "@mui/icons-material/FlashOff"; +import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; +import ShoppingCartCheckoutIcon from "@mui/icons-material/ShoppingCartCheckout"; +import AssignmentReturnedIcon from "@mui/icons-material/AssignmentReturned"; +import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined"; +import CancelIcon from "@mui/icons-material/Cancel"; +import ClearAllIcon from "@mui/icons-material/ClearAll"; + +type props = { + filter: string; + setFilter: React.Dispatch>; +}; + +export default function StudentTransactionFilterMenu(props: props) { + return ( + <> +

+ Personal Transactions +

+ +
+ + + Filter Transactions + + + +
+ + ); +} diff --git a/src/Components/DashboardPage/Student/StudentTransactionListView.tsx b/src/Components/DashboardPage/Student/StudentTransactionListView.tsx new file mode 100644 index 0000000..ddc7d62 --- /dev/null +++ b/src/Components/DashboardPage/Student/StudentTransactionListView.tsx @@ -0,0 +1,67 @@ +import { useQuery } from "@tanstack/react-query"; +import { TransactionsByStudentAPI } from "../../API/API"; +import styles from "../../../styles"; +import CircularProgress from "@mui/material/CircularProgress/CircularProgress"; +import React, { useState } from "react"; +import TransactionEntry from "../../TransactionEntry/TransactionEntry"; +import StudentTransactionFilterMenu from "./StudentTransactionFilterMenu"; + +export default function StudentTransactionListView() { + const transactions = useQuery({ + queryKey: ["transactions_student"], + queryFn: TransactionsByStudentAPI, + }); + const [filter, setFilter] = useState(""); + if (transactions.isLoading) { + return ( +
+ +

+ Loading +

+
+ ); + } + return ( +
+
+ +
+
+ {transactions.data ? ( + transactions.data + .filter((transaction) => + filter !== "" ? transaction.transaction_status == filter : true + ) + .map((transaction) => ( + + + + )) + ) : ( + <> + )} +
+
+
+ ); +} diff --git a/src/Components/DashboardPage/StudentTransactionButtons.tsx b/src/Components/DashboardPage/StudentTransactionButtons.tsx deleted file mode 100644 index 16151ba..0000000 --- a/src/Components/DashboardPage/StudentTransactionButtons.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import styles from "../../styles"; -import { useNavigate } from "react-router-dom"; -import { Button } from "@mui/material"; -import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; -import ThumbUpIcon from '@mui/icons-material/ThumbUp'; -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import FlashOffIcon from '@mui/icons-material/FlashOff'; -import ThumbDownIcon from '@mui/icons-material/ThumbDown'; -import { colors } from "../../styles"; -import Popup from "reactjs-popup"; -import AddItemModal from "../AddItemModal/AddItemModal"; -import AddSKUModal from "../AddSKUModal/AddSKUModal"; -import { useState } from "react"; - -export default function StudentTransactionButtons() { - const [addSKUmodalOpen, SetAddSKUModalOpen] = useState(false); - const [additemmodalOpen, SetAddItemModalOpen] = useState(false); - const navigate = useNavigate(); - return ( - <> - -

- Equipments -

- -
- - - - - - - - - - -
-
- -
- SetAddSKUModalOpen(false)} - modal - position={"top center"} - contentStyle={{ - width: "32rem", - borderRadius: 16, - borderColor: "grey", - borderStyle: "solid", - borderWidth: 1, - padding: 16, - alignContent: "center", - justifyContent: "center", - textAlign: "center", - }} - > - - - SetAddItemModalOpen(false)} - modal - position={"top center"} - contentStyle={{ - width: "32rem", - borderRadius: 16, - borderColor: "grey", - borderStyle: "solid", - borderWidth: 1, - padding: 16, - alignContent: "center", - justifyContent: "center", - textAlign: "center", - }} - > - - - - ); -} diff --git a/src/Components/DashboardPage/TechnicianEquipmentButtons.tsx b/src/Components/DashboardPage/Technician/TechnicianEquipmentButtons.tsx similarity index 97% rename from src/Components/DashboardPage/TechnicianEquipmentButtons.tsx rename to src/Components/DashboardPage/Technician/TechnicianEquipmentButtons.tsx index 9831970..094289d 100644 --- a/src/Components/DashboardPage/TechnicianEquipmentButtons.tsx +++ b/src/Components/DashboardPage/Technician/TechnicianEquipmentButtons.tsx @@ -1,17 +1,17 @@ -import styles from "../../styles"; +import styles from "../../../styles"; 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 { colors } from "../../../styles"; import ScienceIcon from "@mui/icons-material/Science"; import ColorizeIcon from "@mui/icons-material/Colorize"; import ArticleIcon from '@mui/icons-material/Article'; import Popup from "reactjs-popup"; -import AddItemModal from "../AddItemModal/AddItemModal"; -import AddSKUModal from "../AddSKUModal/AddSKUModal"; +import AddItemModal from "../../AddItemModal/AddItemModal"; +import AddSKUModal from "../../AddSKUModal/AddSKUModal"; import { useState } from "react"; export default function TechnicianEquipmentButtons() { const [addSKUmodalOpen, SetAddSKUModalOpen] = useState(false); diff --git a/src/Components/DashboardPage/TechnicianLogButtons.tsx b/src/Components/DashboardPage/Technician/TechnicianLogButtons.tsx similarity index 96% rename from src/Components/DashboardPage/TechnicianLogButtons.tsx rename to src/Components/DashboardPage/Technician/TechnicianLogButtons.tsx index 74fffe1..22a3b5e 100644 --- a/src/Components/DashboardPage/TechnicianLogButtons.tsx +++ b/src/Components/DashboardPage/Technician/TechnicianLogButtons.tsx @@ -1,8 +1,8 @@ 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"; +import styles from "../../../styles"; +import { colors } from "../../../styles"; export default function TechnicianLogButtons() { const navigate = useNavigate(); diff --git a/src/Components/DashboardPage/TechnicianWidgets.tsx b/src/Components/DashboardPage/Technician/TechnicianWidgets.tsx similarity index 98% rename from src/Components/DashboardPage/TechnicianWidgets.tsx rename to src/Components/DashboardPage/Technician/TechnicianWidgets.tsx index 8c3c239..23bfe42 100644 --- a/src/Components/DashboardPage/TechnicianWidgets.tsx +++ b/src/Components/DashboardPage/Technician/TechnicianWidgets.tsx @@ -1,6 +1,6 @@ import { useQueries } from "@tanstack/react-query"; -import styles from "../../styles"; -import { EquipmentsAPI, EquipmentInstancesAPI, UserAPI } from "../API/API"; +import styles from "../../../styles"; +import { EquipmentsAPI, EquipmentInstancesAPI, UserAPI } from "../../API/API"; import CircularProgress from "@mui/material/CircularProgress"; export default function TechnicianWidgets() { diff --git a/src/Components/Drawer/Drawer.tsx b/src/Components/Drawer/Drawer.tsx index c84e4e0..34ea5ff 100644 --- a/src/Components/Drawer/Drawer.tsx +++ b/src/Components/Drawer/Drawer.tsx @@ -3,7 +3,12 @@ import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import HomeIcon from "@mui/icons-material/Home"; import LogoutIcon from "@mui/icons-material/Logout"; import { useQuery } from "@tanstack/react-query"; -import { UserAPI, setAccessToken, setRefreshToken } from "../API/API"; +import { + ClearanceAPI, + UserAPI, + setAccessToken, + setRefreshToken, +} from "../API/API"; import DrawerButton from "../DrawerButton/DrawerButton"; import { useDispatch } from "react-redux"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; @@ -11,6 +16,12 @@ import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; export default function Drawer() { const user = useQuery({ queryKey: ["user"], queryFn: UserAPI }); + const clearance = useQuery({ + enabled: + user.isFetched && !user?.data?.is_teacher && !user?.data?.is_technician, + queryKey: ["clearance"], + queryFn: ClearanceAPI, + }); const dispatch = useDispatch(); const navigate = useNavigate(); return ( @@ -62,6 +73,72 @@ export default function Drawer() {

+ {user.isFetched && + user.data && + !user.data.is_teacher && + !user?.data?.is_technician ? ( + <> +
+
+

+ Status: +

+

+ {clearance.data?.cleared} +

+

+ {`(${clearance.data?.uncleared_transactions} pending)`} +

+
+ + ) : ( + <> + )}
+ {props.status} +

+ ); +} +export default function TransactionEntry(props: props) { + return ( +
+
+

+ ID: {props.transaction.id} +

+

+ {props.transaction.timestamp} +

+
+
+
+

+ Borrower: {props.transaction.borrower.name}{" "} + {`(ID:${props.transaction.borrower.id})`} +

+

+ Teacher: {props.transaction.teacher.name}{" "} + {`(ID:${props.transaction.teacher.id})`} +

+

+ {props.transaction.remarks} +

+
+
+

+ Equipments: +

+
+ {props.transaction.equipments.map((equipment) => ( +

+ {` - ${equipment.name} (ID:${equipment.id})`} +

+ ))} +
+
+
+ +
+ ); +} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index b8afb35..8d2abcd 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -98,6 +98,7 @@ export type EquipmentInstanceLogType = { export type EquipmentInstanceLogListType = Array; export type UserType = { + id: number; username: string; email: string; avatar: string; @@ -122,10 +123,25 @@ export type TransactionType = { name: string; }>; transaction_status: string; + timestamp: string; + remarks: string; }; export type TransactionListType = Array; +export type TransactionCreateType = { + equipments: number[]; + remarks: string; + teacher: number; + borrower: number; + transaction_status: "Pending Approval"; +}; + export type TransactionUpdateType = { transaction_status: string; }; + +export type ClearanceType = { + cleared: string; + uncleared_transactions: number; +}; diff --git a/src/Pages/AddTransactionPage/AddTransactionPage.tsx b/src/Pages/AddTransactionPage/AddTransactionPage.tsx new file mode 100644 index 0000000..741e9fa --- /dev/null +++ b/src/Pages/AddTransactionPage/AddTransactionPage.tsx @@ -0,0 +1,205 @@ +import { useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import TextField from "@mui/material/TextField"; +import AddToQueueIcon from "@mui/icons-material/AddToQueue"; +import Button from "../../Components/Button/Button"; +import { toast } from "react-toastify"; +import { + AvailableEquipmentInstancesAPI, + TransactionCreateAPI, + TeachersAPI, + UserAPI, +} from "../../Components/API/API"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import { useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; +import { + Select, + CircularProgress, + MenuItem, + OutlinedInput, +} from "@mui/material"; +import React from "react"; +import Header from "../../Components/Header/Header"; +import { useNavigate } from "react-router-dom"; + +export default function AddTransactionPage() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [selecteditems, SetSelectedItems] = useState([]); + const [selectedteacher, SetSelectedTeacher] = useState(0); + const [remarks, SetRemarks] = useState(""); + const [error, setError] = useState(""); + + const equipments = useQuery({ + queryKey: ["equipment_instances_available"], + queryFn: AvailableEquipmentInstancesAPI, + }); + + const teachers = useQuery({ + queryKey: ["teachers"], + queryFn: TeachersAPI, + }); + + const user = useQuery({ + queryKey: ["user"], + queryFn: UserAPI, + }); + const isLoading = + equipments.isLoading || teachers.isLoading || user.isLoading; + + if (isLoading) { + return ( +
+
+
+ +

+ Loading +

+
+
+ ); + } + return ( +
+
+
+ +

New Transaction

+
+ +
+ + Items Requested + + + + Assigned Teacher + + + ) => { + SetRemarks(e.target.value); + setError(""); + }} + value={remarks} + placeholder={"Optionally add a brief description of the request"} + /> +
+

{error}

+
+
+ ); +} diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 6ef2fa7..6e33cda 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -1,10 +1,11 @@ import Header from "../../Components/Header/Header"; import styles from "../../styles"; import RestrictedComponent from "../../Components/RestrictedComponent/RestrictedComponent"; -import TechnicianWidgets from "../../Components/DashboardPage/TechnicianWidgets"; -import TechnicianEquipmentButtons from "../../Components/DashboardPage/TechnicianEquipmentButtons"; -import TechnicianLogButtons from "../../Components/DashboardPage/TechnicianLogButtons"; -import StudentTransactionButtons from "../../Components/DashboardPage/StudentTransactionButtons"; +import TechnicianWidgets from "../../Components/DashboardPage/Technician/TechnicianWidgets"; +import TechnicianEquipmentButtons from "../../Components/DashboardPage/Technician/TechnicianEquipmentButtons"; +import TechnicianLogButtons from "../../Components/DashboardPage/Technician/TechnicianLogButtons"; +import StudentTransactionListView from "../../Components/DashboardPage/Student/StudentTransactionListView"; +import StudentDashboard from "../../Components/DashboardPage/Student/StudentDashboard"; export default function Dashboard() { return (
@@ -15,7 +16,20 @@ export default function Dashboard() { - +
+ + +

Welcome teacher!

diff --git a/src/styles.tsx b/src/styles.tsx index 2f5cfbf..2e4c3ba 100644 --- a/src/styles.tsx +++ b/src/styles.tsx @@ -25,7 +25,6 @@ const styles: { [key: string]: React.CSSProperties } = { text_dark: { color: colors.font_dark, fontWeight: "bold", - }, text_light: { color: colors.font_light, @@ -88,6 +87,13 @@ const styles: { [key: string]: React.CSSProperties } = { textAlign: "center", overflowY: "scroll", }, + student_filter_item: { + height: 32, + width: 32, + fill: colors.font_dark, + marginLeft: "1rem", + marginRight: "1rem", + }, }; export default styles;