diff --git a/client/src/AppRoutes.tsx b/client/src/AppRoutes.tsx
index 2664860ae..223398efe 100644
--- a/client/src/AppRoutes.tsx
+++ b/client/src/AppRoutes.tsx
@@ -48,6 +48,7 @@ import NewEquipmentPage from "./pages/makerspace_page/equipment_pages/NewEquipme
import EquipmentRedirector from "./pages/makerspace_page/equipment_pages/EquipmentRedirector";
import HelpPage from "./pages/maker/signup/HelpPage";
import UserPage from "./pages/lab_management/users/UserPage";
+import GlobalSearchPage from "./pages/both/globalsearch/GlobalSearchPage";
// This is where we map the browser's URL to a
// React component with the help of React Router.
@@ -126,6 +127,7 @@ export default function AppRoutes() {
} />
} />
} />
+ } />
{/* Routes that need to be protected by auth */}
}>
diff --git a/client/src/pages/both/globalsearch/GlobalSearchPage.tsx b/client/src/pages/both/globalsearch/GlobalSearchPage.tsx
new file mode 100644
index 000000000..cd1d358f3
--- /dev/null
+++ b/client/src/pages/both/globalsearch/GlobalSearchPage.tsx
@@ -0,0 +1,93 @@
+import { Divider, Grid, Stack, Typography } from "@mui/material";
+import { useNavigate, useParams } from "react-router-dom";
+import Equipment from "../../../types/Equipment";
+import { GET_EQUIPMENTS } from "../../../queries/equipmentQueries";
+import { useQuery } from "@apollo/client";
+import EquipmentCard from "../../../common/EquipmentCard";
+import { useIsMobile } from "../../../common/IsMobileProvider";
+import { useCurrentUser } from "../../../common/CurrentUserProvider";
+import RequestWrapper from "../../../common/RequestWrapper";
+import { GET_PUBLISHED_TRAINING_MODULES } from "../../../queries/trainingQueries";
+import { ModuleStatus, moduleStatusMapper, TrainingModule } from "../../../common/TrainingModuleUtils";
+import ModuleStatusRow from "../../../common/ModuleStatusRow";
+import { GET_MAKERSPACES_WITH_HOURS, MakerspaceWithHours } from "../../../queries/makerspaceQueries";
+import MakerspaceCard from "../homepage/MakerspaceCard";
+
+export default function GlobalSearchPage (){
+ const {query} = useParams();
+ const user = useCurrentUser();
+ const isMobile = useIsMobile();
+ const navigate = useNavigate();
+
+ const getEquipment = useQuery(GET_EQUIPMENTS);
+ const filteredEquipment = getEquipment.data?.equipments.filter((equipment: Equipment) => equipment.name.toLowerCase().includes(query!.toLowerCase()));
+ const foundEquipments = filteredEquipment?.map((equipment: Equipment) => (
+
+
+
+ ));
+
+
+ const getTrainings = useQuery(GET_PUBLISHED_TRAINING_MODULES);
+ const filteredTrainings = getTrainings.data?.publishedModules.filter((training: TrainingModule) => training.name.toLowerCase().includes(query!.toLowerCase()));
+ const moduleStatuses = filteredTrainings?.map(
+ moduleStatusMapper(user.passedModules, user.trainingHolds)
+ );
+ const foundTrainings = moduleStatuses?.map((moduleStatus: ModuleStatus) => (
+
+
+
+ ));
+
+ const getMakerspaces = useQuery(GET_MAKERSPACES_WITH_HOURS)
+ const filteredMakerspaces = getMakerspaces.data?.makerspaces.filter((makerspace: MakerspaceWithHours) => makerspace.name.toLowerCase().includes(query!.toLowerCase()))
+ const foundMakerspaces = filteredMakerspaces?.map((makerspace: MakerspaceWithHours) =>
+
+
+
+ )
+
+ return(
+
+ }>
+ {`${query} - Search Results`}
+ Equipment
+ {foundEquipments?.length > 0 ?
+
+ {foundEquipments}
+
+ : No Equipment Found
+ }
+
+ Trainings
+ {foundTrainings?.length > 0 ?
+
+ {foundTrainings}
+
+ : No Trainings Found
+ }
+
+ Makerspaces
+ {foundMakerspaces?.length > 0 ?
+
+ {foundMakerspaces}
+
+ : No Makerspaces Found
+ }
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/queries/equipmentQueries.ts b/client/src/queries/equipmentQueries.ts
index a42271c51..757444b57 100644
--- a/client/src/queries/equipmentQueries.ts
+++ b/client/src/queries/equipmentQueries.ts
@@ -35,6 +35,21 @@ export const GET_ALL_EQUIPMENTS = gql`
}
`;
+export const GET_ALL_PUBLISHED_EQUIPMENTS = gql`
+ query GetAllPublishedEquipment {
+ equipments {
+ id
+ name
+ archived
+ room {
+ makerspace {
+ id
+ }
+ }
+ }
+ }
+`
+
export const GET_EQUIPMENT_BY_ID = gql`
query GetEquipmentByID($id: ID!) {
equipment(id: $id) {
diff --git a/client/src/queries/trainingQueries.ts b/client/src/queries/trainingQueries.ts
index fa5f5be51..370e53812 100644
--- a/client/src/queries/trainingQueries.ts
+++ b/client/src/queries/trainingQueries.ts
@@ -62,6 +62,17 @@ export const GET_ARCHIVED_TRAINING_MODULES = gql`
}
`;
+export const GET_PUBLISHED_TRAINING_MODULES = gql`
+ query GetPublishedTrainingModules {
+ publishedModules {
+ id
+ name
+ archived
+ makerspaceID
+ }
+ }
+`
+
export const CREATE_TRAINING_MODULE = gql`
mutation CreateTrainingModule($name: String!, $quiz: JSON!, $makerspaceID: ID) {
createModule(name: $name, quiz: $quiz, makerspaceID: $makerspaceID) {
diff --git a/client/src/top_nav/GlobalSearchBar.tsx b/client/src/top_nav/GlobalSearchBar.tsx
new file mode 100644
index 000000000..5b622851a
--- /dev/null
+++ b/client/src/top_nav/GlobalSearchBar.tsx
@@ -0,0 +1,78 @@
+import { Autocomplete, TextField } from "@mui/material";
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { GET_ALL_PUBLISHED_EQUIPMENTS } from "../queries/equipmentQueries";
+import { useQuery } from "@apollo/client";
+import { GET_PUBLISHED_TRAINING_MODULES } from "../queries/trainingQueries";
+import GET_ROOMS from "../queries/roomQueries";
+import { FullMakerspace, GET_FULL_MAKERSPACES, GET_MAKERSPACES } from "../queries/makerspaceQueries";
+import Equipment from "../types/Equipment";
+import { TrainingModule } from "../common/TrainingModuleUtils";
+import Room from "../types/Room";
+
+export default function GlobalSearchBar() {
+ const navigate = useNavigate();
+ const [searchQuery, setSearchQuery] = useState("");
+
+ const getEquipment = useQuery(GET_ALL_PUBLISHED_EQUIPMENTS)
+ const getTrainings = useQuery(GET_PUBLISHED_TRAINING_MODULES)
+ const getRooms = useQuery(GET_ROOMS)
+ const getMakerspaces = useQuery(GET_FULL_MAKERSPACES)
+
+ const options: any[] = [];
+ getEquipment.data?.equipments.forEach((equipment:Equipment) => {
+ options.push({label:equipment.name, category:"Equipments", item:equipment})
+ });
+ getTrainings.data?.publishedModules.forEach((module:TrainingModule) => {
+ options.push({label:module.name, category:"Trainings", item:module})
+ });
+ getRooms.data?.rooms.forEach((room:Room) => {
+ options.push({label:room.name, category:"Rooms", item:room})
+ });
+ getMakerspaces.data?.makerspaces.forEach((makerspace:FullMakerspace) => {
+ options.push({label:makerspace.name, category:"Makerspaces", item:makerspace})
+ });
+
+ const handleRedirect = (reason:string, value:any) => {
+ switch(reason){
+ case 'input':
+ setSearchQuery(value)
+ break
+ case 'createOption':
+ const encodedQuery = searchQuery.replace('/', '%2F')
+ navigate(`/search/` + encodedQuery)
+ break
+ case 'selectOption':
+ const category = value.category
+ const encodedLabel = value.label.replace('/', '%2F')
+ if(category === "Equipments"){
+ navigate(`/makerspace/` + value.item.room.makerspace.id + `?a=${encodedLabel}`)
+ }
+ else if(category === "Trainings"){
+ navigate(`/maker/training/${value.item.id}`)
+ }
+ else if(category === "Makerspaces"){
+ navigate(`/makerspace/${value.item.id}`)
+ }
+ else{
+ navigate(`/search/` + encodedLabel)
+ break
+ }
+ }
+ };
+
+ return (
+ option.category}
+ sx={{ width: 300 }}
+ freeSolo={true}
+ autoHighlight={false}
+ blurOnSelect={true}
+ onChange={(e, v, r) => {r === 'selectOption' || r === 'createOption' ? handleRedirect(r, v === null ? "" : v) : {}}}
+ onInputChange={(e, v: string, r) => r === 'input' ? handleRedirect(r, v) : {}}
+ renderInput={(params) => {event.target.select()}}/>}
+ />
+ );
+}
\ No newline at end of file
diff --git a/client/src/top_nav/TopNav.tsx b/client/src/top_nav/TopNav.tsx
index cad7da369..188abad39 100644
--- a/client/src/top_nav/TopNav.tsx
+++ b/client/src/top_nav/TopNav.tsx
@@ -18,6 +18,7 @@ import StorefrontIcon from '@mui/icons-material/Storefront';
import ArticleIcon from '@mui/icons-material/Article';
import TuneIcon from '@mui/icons-material/Tune';
import LogoutIcon from '@mui/icons-material/Logout';
+import GlobalSearchBar from "./GlobalSearchBar";
const StyledLogo = styled.img`
padding: 12px;
@@ -182,8 +183,10 @@ export default function TopNav() {
{navlinks}
+
{userProfileButton}
{userMenu}
+
diff --git a/server/src/resolvers/trainingModuleResolver.ts b/server/src/resolvers/trainingModuleResolver.ts
index c8ac2c71b..eb05c7a74 100644
--- a/server/src/resolvers/trainingModuleResolver.ts
+++ b/server/src/resolvers/trainingModuleResolver.ts
@@ -252,6 +252,14 @@ const TrainingModuleResolvers = {
return module;
}),
+ publishedModules: async (
+ _parent: any,
+ _args: any,
+ ) => {
+ return await ModuleRepo.getModulesWhereArchived(false);
+ },
+
+
/**
* Fetch an array of AccessProgress items representing progress on gaining access to all equipment relating to the noted TrainingModule
* @argument sourceTrainingModuleID ID of TrainingModule to source from
diff --git a/server/src/schemas/trainingModuleSchema.ts b/server/src/schemas/trainingModuleSchema.ts
index 421f4224e..e7cfe650b 100644
--- a/server/src/schemas/trainingModuleSchema.ts
+++ b/server/src/schemas/trainingModuleSchema.ts
@@ -56,6 +56,7 @@ export const TrainingModuleTypeDefs = gql`
module(id: ID!): TrainingModule
moduleWithAnswers(id: ID!): TrainingModule
archivedModules: [TrainingModule]
+ publishedModules: [TrainingModule]
archivedModule(id: ID!): TrainingModule
relatedAccessProgress(sourceTrainingModuleID: ID!): [AccessProgress]
}