Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
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.
Expand All @@ -63,7 +64,7 @@
}
}

function TrainerRoute() {

Check warning on line 67 in client/src/AppRoutes.tsx

View workflow job for this annotation

GitHub Actions / build

'TrainerRoute' is defined but never used. Allowed unused vars must match /^_/u
const { makerspaceID } = useParams<{ makerspaceID: string }>();
const user = useCurrentUser();
if (isOnlyTrainer(user) || isStaffFor(user, Number(makerspaceID))) {
Expand Down Expand Up @@ -126,6 +127,7 @@
<Route path="/terms" element={<TermsPage />} />
<Route path="/help" element={<HelpPage />} />
<Route path="/storefront" element={<StorefrontPage />} />
<Route path= "/search/:query" element={<GlobalSearchPage />} />

{/* Routes that need to be protected by auth */}
<Route element={<AuthedRoute />}>
Expand Down
93 changes: 93 additions & 0 deletions client/src/pages/both/globalsearch/GlobalSearchPage.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<Grid key={equipment.id}>
<EquipmentCard equipment={equipment} isMobile={isMobile} staffMode={false} />
</Grid>
));


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) => (
<Grid key={moduleStatus.moduleID} size={isMobile ? 12 : 3}>
<ModuleStatusRow ms={moduleStatus} />
</Grid>
));

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) =>
<Grid gap={2}>
<MakerspaceCard
id={makerspace.id}
name={makerspace.name}
subtitle={makerspace.subtitle}
location={makerspace.location}
hours={makerspace.hours}
imageUrl={
makerspace.imageUrl === undefined || makerspace.imageUrl == null || makerspace.imageUrl === ""
? import.meta.env.BASE_URL + "/shed_acronym_vert.jpg"
: makerspace.imageUrl
}
isMobile={isMobile}
/>
</Grid>
)

return(
<RequestWrapper loading={getEquipment.loading} error={getEquipment.error} >
<Stack spacing={"2"} padding={"0 20px 20px"} divider={<Divider orientation="horizontal" flexItem />}>
<title>{`${query} - Search Results`}</title>
<Stack padding={"10px 0"} spacing={1}><Typography variant="h4" pl={"10px"}>Equipment</Typography>
{foundEquipments?.length > 0 ?
<Grid container spacing={3} justifyContent="center">
{foundEquipments}
</Grid>
: <Typography alignSelf="center" variant="h6" pl={"10px"}>No Equipment Found</Typography>
}
</Stack>
<Stack padding={"10px 0"} spacing={1}><Typography variant="h4" pl={"10px"}>Trainings</Typography>
{foundTrainings?.length > 0 ?
<Grid container spacing={3} justifyContent="left">
{foundTrainings}
</Grid>
: <Typography alignSelf="center" variant="h6" pl={"10px"}>No Trainings Found</Typography>
}
</Stack>
<Stack padding={"10px 0"} spacing={1}><Typography variant="h4" pl={"10px"}>Makerspaces</Typography>
{foundMakerspaces?.length > 0 ?
<Grid container spacing={3} justifyContent="center">
{foundMakerspaces}
</Grid>
: <Typography alignSelf="center" variant="h6" pl={"10px"}>No Makerspaces Found</Typography>
}
</Stack>
</Stack>
</RequestWrapper>
);
}
15 changes: 15 additions & 0 deletions client/src/queries/equipmentQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 11 additions & 0 deletions client/src/queries/trainingQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
78 changes: 78 additions & 0 deletions client/src/top_nav/GlobalSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -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<string>("");

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')

Check failure on line 42 in client/src/top_nav/GlobalSearchBar.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected lexical declaration in case block
navigate(`/search/` + encodedQuery)
break
case 'selectOption':
const category = value.category

Check failure on line 46 in client/src/top_nav/GlobalSearchBar.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected lexical declaration in case block
const encodedLabel = value.label.replace('/', '%2F')

Check failure on line 47 in client/src/top_nav/GlobalSearchBar.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected lexical declaration in case block
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 (
<Autocomplete
disablePortal
options={options}
groupBy={(option) => 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) => <TextField {...params} label="Search Item" onFocus={event => {event.target.select()}}/>}
/>
);
}
3 changes: 3 additions & 0 deletions client/src/top_nav/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -182,8 +183,10 @@ export default function TopNav() {
<StyledLogo width="100%" src={localStorage.getItem("themeMode") === "dark" ? LogoSvgOrange : LogoSvgWhite} alt="SHED logo" />
</ButtonBase>
{navlinks}
<GlobalSearchBar></GlobalSearchBar>
{userProfileButton}
{userMenu}

</Stack>
</AppBar>
</Box>
Expand Down
8 changes: 8 additions & 0 deletions server/src/resolvers/trainingModuleResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions server/src/schemas/trainingModuleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
Expand Down
Loading