diff --git a/booking-app/app/equipment/page.tsx b/booking-app/app/equipment/page.tsx
new file mode 100644
index 0000000..9700467
--- /dev/null
+++ b/booking-app/app/equipment/page.tsx
@@ -0,0 +1,16 @@
+// app/liaison/page.tsx
+
+"use client";
+
+import Equipment from "@/components/src/client/routes/equipment/Equipment";
+import { Suspense } from "react";
+
+const EquipmentPage: React.FC = () => {
+ return (
+ Loading...}>
+
+
+ );
+};
+
+export default EquipmentPage;
diff --git a/booking-app/clientInfo b/booking-app/clientInfo
new file mode 100644
index 0000000..368be10
--- /dev/null
+++ b/booking-app/clientInfo
@@ -0,0 +1 @@
+o4Qd0YDOKtCR95EUyQ2Rbp5UbNUa:XkfbKenefg7ntnRR1D3r3nTo_lQa
diff --git a/booking-app/components/src/client/routes/admin/components/Approvers.tsx b/booking-app/components/src/client/routes/admin/components/Approvers.tsx
new file mode 100644
index 0000000..4162ea8
--- /dev/null
+++ b/booking-app/components/src/client/routes/admin/components/Approvers.tsx
@@ -0,0 +1,16 @@
+import { Typography } from "@mui/material";
+import { EquipmentUsers } from "./EquipmentUsers";
+import { Liaisons } from "./Liaisons";
+
+export const Approvers = () => (
+
+
+ Liaison Users
+
+
+
+ Equipment Users
+
+
+
+);
diff --git a/booking-app/components/src/client/routes/admin/components/EquipmentUsers.tsx b/booking-app/components/src/client/routes/admin/components/EquipmentUsers.tsx
new file mode 100644
index 0000000..1d0fa3f
--- /dev/null
+++ b/booking-app/components/src/client/routes/admin/components/EquipmentUsers.tsx
@@ -0,0 +1,60 @@
+import { useContext, useMemo } from "react";
+
+import { ApproverLevel, TableNames } from "../../../../policy";
+import { formatDate } from "../../../utils/date";
+import AddRow from "../../components/AddRow";
+import ListTable from "../../components/ListTable";
+import { DatabaseContext } from "../../components/Provider";
+
+const AddEquipmentForm = ({ equipmentEmails, reloadEquipmentEmails }) => {
+ return (
+
+ );
+};
+
+export const EquipmentUsers = () => {
+ const { equipmentUsers, reloadApproverUsers } = useContext(DatabaseContext);
+ const equipmentEmails = useMemo(
+ () => equipmentUsers.map((user) => user.email),
+ [equipmentUsers]
+ );
+
+ const rows = useMemo(() => {
+ const filtered = equipmentUsers.map((liaison) => {
+ const { level, ...other } = liaison;
+ return other;
+ });
+ const sorted = filtered.sort((a, b) =>
+ a.department.localeCompare(b.department)
+ );
+ return sorted as unknown as { [key: string]: string }[];
+ }, [equipmentUsers]);
+
+ return (
+
+ }
+ columnFormatters={{ createdAt: formatDate }}
+ />
+ );
+};
diff --git a/booking-app/components/src/client/routes/admin/components/Settings.tsx b/booking-app/components/src/client/routes/admin/components/Settings.tsx
index 4f96ca9..30525ed 100644
--- a/booking-app/components/src/client/routes/admin/components/Settings.tsx
+++ b/booking-app/components/src/client/routes/admin/components/Settings.tsx
@@ -1,23 +1,23 @@
import { Divider, ListItemButton, ListItemText, Stack } from "@mui/material";
+import Grid from "@mui/material/Unstable_Grid2";
+import { useState } from "react";
import { AdminUsers } from "./AdminUsers";
+import { Approvers } from "./Approvers";
import { BannedUsers } from "./Ban";
import BookingTypes from "./BookingTypes";
import { Departments } from "./Departments";
import ExportDatabase from "./ExportDatabase";
-import Grid from "@mui/material/Unstable_Grid2";
-import { Liaisons } from "./Liaisons";
import { PAUsers } from "./PAUsers";
import PolicySettings from "./PolicySettings";
import SafetyTrainedUsers from "./SafetyTraining";
import SyncCalendars from "./SyncCalendars";
-import { useState } from "react";
const tabs = [
{ label: "Safety Training", id: "safetyTraining" },
{ label: "PA Users", id: "pa" },
{ label: "Admin Users", id: "admin" },
- { label: "Liaisons", id: "liaisons" },
+ { label: "Approvers", id: "approvers" },
{ label: "Departments", id: "departments" },
{ label: "Ban", id: "ban" },
{ label: "Booking Types", id: "bookingTypes" },
@@ -51,7 +51,7 @@ export default function Settings() {
{tab === "safetyTraining" && }
{tab === "pa" && }
{tab === "admin" && }
- {tab === "liaisons" && }
+ {tab === "approvers" && }
{tab === "ban" && }
{tab === "departments" && }
{tab === "bookingTypes" && }
diff --git a/booking-app/components/src/client/routes/admin/hooks/useBookingActions.tsx b/booking-app/components/src/client/routes/admin/hooks/useBookingActions.tsx
index 88f76bb..59aa07d 100644
--- a/booking-app/components/src/client/routes/admin/hooks/useBookingActions.tsx
+++ b/booking-app/components/src/client/routes/admin/hooks/useBookingActions.tsx
@@ -1,4 +1,3 @@
-import { BookingStatusLabel, PageContextLevel } from "@/components/src/types";
import {
cancel,
checkOut,
@@ -7,13 +6,14 @@ import {
decline,
noShow,
} from "@/components/src/server/db";
+import { BookingStatusLabel, PageContextLevel } from "@/components/src/types";
import { useContext, useMemo, useState } from "react";
+import { Timestamp } from "@firebase/firestore";
+import { useRouter } from "next/navigation";
import { BookingContext } from "../../booking/bookingProvider";
import { DatabaseContext } from "../../components/Provider";
-import { Timestamp } from "@firebase/firestore";
import useExistingBooking from "./useExistingBooking";
-import { useRouter } from "next/navigation";
export enum Actions {
CANCEL = "Cancel",
@@ -185,6 +185,7 @@ export default function useBookingActions({
}, [status]);
const liaisonOptions = [Actions.FIRST_APPROVE, Actions.DECLINE];
+ const equipmentOptions = [Actions.MODIFICATION, Actions.DECLINE];
const adminOptions = useMemo(() => {
if (
@@ -216,6 +217,8 @@ export default function useBookingActions({
return paOptions;
case PageContextLevel.LIAISON:
return liaisonOptions;
+ case PageContextLevel.EQUIPMENT:
+ return equipmentOptions;
default:
return adminOptions;
}
diff --git a/booking-app/components/src/client/routes/components/AddRow.tsx b/booking-app/components/src/client/routes/components/AddRow.tsx
index 697779e..7fb22f2 100644
--- a/booking-app/components/src/client/routes/components/AddRow.tsx
+++ b/booking-app/components/src/client/routes/components/AddRow.tsx
@@ -1,12 +1,12 @@
-import { Box, IconButton, TextField } from "@mui/material";
+import { IconButton, TextField } from "@mui/material";
import React, { useMemo, useState } from "react";
+import { Timestamp } from "@firebase/firestore";
import { AddCircleOutline } from "@mui/icons-material";
import Grid from "@mui/material/Unstable_Grid2/Grid2";
-import Loading from "./Loading";
-import { TableNames } from "../../../policy";
-import { Timestamp } from "@firebase/firestore";
import { clientSaveDataToFirestore } from "../../../../../lib/firebase/firebase";
+import { TableNames } from "../../../policy";
+import Loading from "./Loading";
interface Props {
addDuplicateErrorMessage?: string;
@@ -18,9 +18,9 @@ interface Props {
rowsRefresh: () => Promise;
title: string;
extra?: {
- components: React.ReactNode[];
+ components?: React.ReactNode[];
values: { [key: string]: any };
- updates: ((x: string) => void)[];
+ updates?: ((x: string) => void)[];
};
}
diff --git a/booking-app/components/src/client/routes/components/Provider.tsx b/booking-app/components/src/client/routes/components/Provider.tsx
index d99cda9..d348345 100644
--- a/booking-app/components/src/client/routes/components/Provider.tsx
+++ b/booking-app/components/src/client/routes/components/Provider.tsx
@@ -1,3 +1,5 @@
+import { ApproverLevel, TableNames } from "@/components/src/policy";
+import React, { createContext, useEffect, useMemo, useState } from "react";
import {
AdminUser,
Approver,
@@ -14,15 +16,15 @@ import {
Settings,
UserApiData,
} from "../../../types";
-import { ApproverLevel, TableNames } from "@/components/src/policy";
-import React, { createContext, useEffect, useMemo, useState } from "react";
import { useAuth } from "@/components/src/client/routes/components/AuthProvider";
-import { fetchAllBookings, fetchAllFutureBooking } from "@/components/src/server/db";
+import {
+ fetchAllBookings,
+ fetchAllFutureBooking,
+} from "@/components/src/server/db";
import { clientFetchAllDataFromCollection } from "@/lib/firebase/firebase";
import { Timestamp } from "firebase-admin/firestore";
-
export interface DatabaseContextType {
adminUsers: AdminUser[];
bannedUsers: Ban[];
@@ -30,6 +32,7 @@ export interface DatabaseContextType {
allBookings: Booking[];
bookingsLoading: boolean;
liaisonUsers: Approver[];
+ equipmentUsers: Approver[];
departmentNames: DepartmentType[];
operationHours: OperationHours[];
pagePermission: PagePermission;
@@ -61,6 +64,7 @@ export const DatabaseContext = createContext({
allBookings: [],
bookingsLoading: true,
liaisonUsers: [],
+ equipmentUsers: [],
departmentNames: [],
operationHours: [],
pagePermission: PagePermission.BOOKING,
@@ -96,6 +100,7 @@ export const DatabaseProvider = ({
const [allBookings, setAllBookings] = useState([]);
const [adminUsers, setAdminUsers] = useState([]);
const [liaisonUsers, setLiaisonUsers] = useState([]);
+ const [equipmentUsers, setEquipmentUsers] = useState([]);
const [departmentNames, setDepartmentName] = useState([]);
const [operationHours, setOperationHours] = useState([]);
const [paUsers, setPaUsers] = useState([]);
@@ -137,24 +142,27 @@ export const DatabaseProvider = ({
const pagePermission = useMemo(() => {
// Early return if no email
if (!userEmail) return PagePermission.BOOKING;
-
+
// Pre-compute email lists once
- const adminEmails = adminUsers.map(admin => admin.email);
- const liaisonEmails = liaisonUsers.map(liaison => liaison.email);
- const paEmails = paUsers.map(pa => pa.email);
-
+ const adminEmails = adminUsers.map((admin) => admin.email);
+ const liaisonEmails = liaisonUsers.map((liaison) => liaison.email);
+ const paEmails = paUsers.map((pa) => pa.email);
+ const equipmentEmails = equipmentUsers.map((e) => e.email);
+
// Check permissions
if (adminEmails.includes(userEmail)) return PagePermission.ADMIN;
+ if (equipmentEmails.includes(userEmail)) return PagePermission.EQUIPMENT;
if (liaisonEmails.includes(userEmail)) return PagePermission.LIAISON;
if (paEmails.includes(userEmail)) return PagePermission.PA;
-
+
return PagePermission.BOOKING;
}, [
userEmail,
// Make sure we're using the actual arrays in dependencies
JSON.stringify(adminUsers),
JSON.stringify(liaisonUsers),
- JSON.stringify(paUsers)
+ JSON.stringify(paUsers),
+ JSON.stringify(equipmentUsers),
]);
useEffect(() => {
@@ -176,9 +184,13 @@ export const DatabaseProvider = ({
fetchRoomSettings();
}, [user]);
- useEffect(()=>{
- if(pagePermission === PagePermission.ADMIN || pagePermission === PagePermission.LIAISON || pagePermission === PagePermission.PA)
- fetchBookings();
+ useEffect(() => {
+ if (
+ pagePermission === PagePermission.ADMIN ||
+ pagePermission === PagePermission.LIAISON ||
+ pagePermission === PagePermission.PA
+ )
+ fetchBookings();
}, [pagePermission]);
const fetchActiveUserEmail = () => {
@@ -196,8 +208,11 @@ export const DatabaseProvider = ({
};
const fetchBookings = async () => {
- try{
- const bookingsResponse : Booking[] = await fetchAllBookings(LIMIT, lastItem);
+ try {
+ const bookingsResponse: Booking[] = await fetchAllBookings(
+ LIMIT,
+ lastItem
+ );
setLastItem(bookingsResponse[bookingsResponse.length - 1].requestedAt);
setAllBookings((oldBookings) => [...oldBookings, ...bookingsResponse]);
} catch (error) {
@@ -312,14 +327,20 @@ export const DatabaseProvider = ({
level: Number(item.level),
}));
const liaisons = all.filter((x) => x.level === ApproverLevel.FIRST);
+ const equipmentUsers = all.filter(
+ (x) => x.level === ApproverLevel.EQUIPMENT
+ );
+
const finalApprover = all.filter(
(x) => x.level === ApproverLevel.FINAL
)[0];
setLiaisonUsers(liaisons);
+ setEquipmentUsers(equipmentUsers);
setPolicySettings({ finalApproverEmail: finalApprover.email });
})
.catch((error) => console.error("Error fetching data:", error));
};
+
const fetchDepartmentNames = async () => {
clientFetchAllDataFromCollection(TableNames.DEPARTMENTS)
.then((fetchedData) => {
@@ -400,6 +421,7 @@ export const DatabaseProvider = ({
futureBookings,
allBookings,
liaisonUsers,
+ equipmentUsers,
departmentNames,
operationHours,
paUsers,
diff --git a/booking-app/components/src/client/routes/components/navBar.tsx b/booking-app/components/src/client/routes/components/navBar.tsx
index f74108d..43cf45a 100644
--- a/booking-app/components/src/client/routes/components/navBar.tsx
+++ b/booking-app/components/src/client/routes/components/navBar.tsx
@@ -77,6 +77,9 @@ export default function NavBar() {
case PagePermission.LIAISON:
router.push("/liaison");
break;
+ case PagePermission.EQUIPMENT:
+ router.push("/equipment");
+ break;
}
};
@@ -103,6 +106,8 @@ export default function NavBar() {
setSelectedView(PagePermission.ADMIN);
} else if (pathname.includes("/liaison")) {
setSelectedView(PagePermission.LIAISON);
+ } else if (pathname.includes("/equipment")) {
+ setSelectedView(PagePermission.EQUIPMENT);
}
}, [pathname]);
@@ -133,6 +138,8 @@ export default function NavBar() {
pagePermission === PagePermission.LIAISON ||
pagePermission === PagePermission.ADMIN;
const showAdmin = pagePermission === PagePermission.ADMIN;
+ const showEquipment =
+ pagePermission === PagePermission.ADMIN || PagePermission.EQUIPMENT;
return (
);
}, [pagePermission, selectedView]);
diff --git a/booking-app/components/src/client/routes/equipment/Equipment.tsx b/booking-app/components/src/client/routes/equipment/Equipment.tsx
new file mode 100644
index 0000000..81ce9a7
--- /dev/null
+++ b/booking-app/components/src/client/routes/equipment/Equipment.tsx
@@ -0,0 +1,40 @@
+import { Box, Tab, Tabs } from "@mui/material";
+import { useContext, useState } from "react";
+import { PageContextLevel, PagePermission } from "../../../types";
+
+import { Bookings } from "../components/bookingTable/Bookings";
+import { DatabaseContext } from "../components/Provider";
+
+const Equipment = () => {
+ const { pagePermission } = useContext(DatabaseContext);
+
+ const [tab, setTab] = useState("bookings");
+
+ const userHasPermission =
+ pagePermission === PagePermission.ADMIN ||
+ pagePermission === PagePermission.EQUIPMENT;
+
+ return (
+
+ {!userHasPermission ? (
+ You do not have permission to view this page.
+ ) : (
+
+ setTab(newVal)}
+ textColor="primary"
+ indicatorColor="primary"
+ >
+
+
+ {tab === "bookings" && (
+
+ )}
+
+ )}
+
+ );
+};
+
+export default Equipment;
diff --git a/booking-app/components/src/policy.ts b/booking-app/components/src/policy.ts
index 9fc7c8a..d82c2b3 100644
--- a/booking-app/components/src/policy.ts
+++ b/booking-app/components/src/policy.ts
@@ -3,8 +3,8 @@ import {
MEDIA_COMMONS_OPERATION_EMAIL,
} from "./mediaCommonsPolicy";
-import { BookingStatusLabel } from "./types";
import { clientGetFinalApproverEmailFromDatabase } from "@/lib/firebase/firebase";
+import { BookingStatusLabel } from "./types";
export enum TableNames {
ADMINS = "usersAdmin",
@@ -35,6 +35,7 @@ export const BOOKING_TABLE_HIDE_STATUS_TIME_ELAPSED = [
export enum ApproverLevel {
FIRST = 1,
FINAL = 2,
+ EQUIPMENT = 3,
}
/********** CONTACTS ************/
diff --git a/booking-app/components/src/types.ts b/booking-app/components/src/types.ts
index 7901a28..258ca76 100644
--- a/booking-app/components/src/types.ts
+++ b/booking-app/components/src/types.ts
@@ -194,6 +194,7 @@ export enum PagePermission {
BOOKING = 0,
PA,
LIAISON,
+ EQUIPMENT,
ADMIN,
}
@@ -201,6 +202,7 @@ export enum PageContextLevel {
USER = 0,
LIAISON,
PA,
+ EQUIPMENT,
ADMIN,
}