diff --git a/client/src/pages/both/homepage/Dashboard.tsx b/client/src/pages/both/homepage/Dashboard.tsx index cf6314dd8..23546bf03 100644 --- a/client/src/pages/both/homepage/Dashboard.tsx +++ b/client/src/pages/both/homepage/Dashboard.tsx @@ -32,7 +32,7 @@ export function Dashboard() { result={getMakerspacesResult} render={(data) => { const makerspaces: MakerspaceWithHours[] = data.makerspaces; - const filteredSpaces: MakerspaceWithHours[] = makerspaces.filter((_makerspace: MakerspaceWithHours) => true); // TODO: grab the 'archieved' field from the db and check it (more logic than that required) + const filteredSpaces: MakerspaceWithHours[] = makerspaces.filter((_makerspace: MakerspaceWithHours) => true); const sortedSpaces = filteredSpaces.sort((a: MakerspaceWithHours, b: MakerspaceWithHours) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) ); diff --git a/client/src/pages/makerspace_page/ManageMakerspacePage.tsx b/client/src/pages/makerspace_page/ManageMakerspacePage.tsx index e1f33418f..8a71314a7 100644 --- a/client/src/pages/makerspace_page/ManageMakerspacePage.tsx +++ b/client/src/pages/makerspace_page/ManageMakerspacePage.tsx @@ -1,10 +1,13 @@ import { useMutation, useQuery } from "@apollo/client"; -import { useParams } from "react-router-dom"; -import { FullMakerspace, GET_MAKERSPACE_BY_ID } from "../../queries/makerspaceQueries"; +import { useNavigate, useParams } from "react-router-dom"; +import { ARCHIVE_MAKERSPACE, DELETE_MAKERSPACE, FullMakerspace, GET_MAKERSPACE_BY_ID, UNARCHIVE_MAKERSPACE } from "../../queries/makerspaceQueries"; import { Box, Button, Divider, Stack, TextField, Typography } from "@mui/material"; import RequestWrapper2 from "../../common/RequestWrapper2"; import { useState } from "react"; -import AddIcon from '@mui/icons-material/Add'; +import AddIcon from "@mui/icons-material/Add"; +import ArchiveIcon from "@mui/icons-material/Archive"; +import PublishIcon from "@mui/icons-material/Publish"; +import DeleteIcon from "@mui/icons-material/Delete"; import { CREATE_ROOM } from "../../queries/roomQueries"; import Room from "../../types/Room"; import RoomCard from "./RoomCard"; @@ -14,15 +17,21 @@ import ManageWelcomReadersCard from "./ManageWelcomeReadersCard"; import ManageMakerspaceTrainings from "./ManageMakerspaceTrainings"; import ManageMakerspaceHours from "./ManageMakerspaceHours"; import ManageMakerspaceInformation from "./ManageMakerspaceInformation"; +import { toast } from "react-toastify"; export default function ManageMakerspacePage() { const { makerspaceID } = useParams<{ makerspaceID: string }>(); const isMobile = useIsMobile(); + const navigate = useNavigate(); const getMakerspace = useQuery(GET_MAKERSPACE_BY_ID, { variables: { id: makerspaceID } }); + const [archiveMakerspace] = useMutation(ARCHIVE_MAKERSPACE); + const [unarchiveMakerspace] = useMutation(UNARCHIVE_MAKERSPACE); + const [deleteMakerspace] = useMutation(DELETE_MAKERSPACE); + const [createRoom] = useMutation(CREATE_ROOM); const [newRoomName, setNewRoomName] = useState(""); @@ -31,75 +40,116 @@ export default function ManageMakerspacePage() { return ( { - const space: FullMakerspace = data.makerspaceByID; + const space: FullMakerspace = data.makerspaceByID; - const handleCreateRoom = async () => { - await createRoom({ - variables: { name: newRoomName, makerspaceID: makerspaceID }, - //refetchQueries: [{ }], - }); - window.location.reload(); - }; + const handleCreateRoom = async () => { + await createRoom({ + variables: { name: newRoomName, makerspaceID: makerspaceID }, + //refetchQueries: [{ }], + }); + window.location.reload(); + }; - return ( - - {`Manage ${space.name} | Make @ RIT`} - - {`Manage ${space.name} [ID: ${space.id}]`} - - + const handleArchiveMakerspace = async () => { + try { + await archiveMakerspace({ variables: { id: makerspaceID } }); + toast.success("Room archived"); + getMakerspace.refetch(); + } catch (error: any) { + toast.error(error.message); + } + } + + const handleUnarchiveMakerspace = async () => { + try { + await unarchiveMakerspace({ variables: { id: makerspaceID } }); + toast.success("Room unarchived"); + getMakerspace.refetch(); + } catch (error: any) { + toast.error(error.message); + } + } + + const handleDeleteMakerspace = async () => { + if (window.confirm("Are you sure you want to delete this makerspace? This cannot be undone.")) { + await deleteMakerspace({ variables: { id: makerspaceID } }); + + navigate(`/`); + } + }; + + return ( + + {`Manage ${space.name} | Make @ RIT`} + + {`Manage ${space.name} [ID: ${space.id}]`} + + {space.archived + ? + : + } + + + + } justifyContent={"space-between"} width={"100%"}> - } width={"48%"}> - - - + } width={"48%"}> + + + Rooms { setNewRoomModal(false) }}> - - Creating a new room in {space.name} Makerspace + + Creating a new room in {space.name} Makerspace (setNewRoomName(e.target.value))} /> - + + - - - + + { space.rooms.map((room: Room) => ( )) } + + + } width={"48%"}> + + - - } width={"48%"}> - - - - ); + ); }} /> ); diff --git a/client/src/queries/makerspaceQueries.ts b/client/src/queries/makerspaceQueries.ts index 1c3b05784..3fdb223d1 100644 --- a/client/src/queries/makerspaceQueries.ts +++ b/client/src/queries/makerspaceQueries.ts @@ -5,18 +5,19 @@ import { TrainingModule } from "../common/TrainingModuleUtils"; import MakerspaceHours from "../types/MakerspaceHours"; export const GET_MAKERSPACES = gql` - query GetMakerspaces { - makerspaces { - id - name - imageUrl + query GetMakerspaces { + makerspaces { + id + name + imageUrl + } } - } `; export interface MakerspaceWithHours { id: number; name: string; + archived: boolean; subtitle: string | null; location: string | null; description: string; @@ -28,12 +29,13 @@ export interface MakerspaceWithHours { export interface FullMakerspace { id: number; name: string; + archived: boolean; subtitle: string | null; location: string | null; description: string; docsLink: string; hours: MakerspaceHours[]; - rooms: Room[] + rooms: Room[]; imageUrl: string; trainingModules: TrainingModule[]; } @@ -49,24 +51,25 @@ export interface MakerspaceWithItems { } export const GET_MAKERSPACES_WITH_HOURS = gql` - query GetMakerspacesWithHours { - makerspaces { - id - name - subtitle - location - description - docsLink - hours { + query GetMakerspacesWithHours { + makerspaces { + id + name + archived + subtitle + location + description + docsLink + hours { day makerspaceID open close closed } - imageUrl + imageUrl + } } - } `; export const GET_FULL_MAKERSPACES = gql` @@ -74,6 +77,7 @@ export const GET_FULL_MAKERSPACES = gql` makerspaces { id name + archived subtitle location description @@ -110,42 +114,42 @@ export const GET_FULL_MAKERSPACES = gql` `; export const GET_MAKERSPACES_WITH_ITEMS = gql` - query GetMakerspacesWithItems($storefrontVisible: Boolean) { - makerspaces(storefrontVisible: $storefrontVisible) { - id - name - name - subtitle - location - description - docsLink - items { + query GetMakerspacesWithItems($storefrontVisible: Boolean) { + makerspaces(storefrontVisible: $storefrontVisible) { id - image name - labels - unit - pluralUnit - count - pricePerUnit - threshold - staffOnly - storefrontVisible - notes + name + subtitle + location description - makerspaceID - makerspace { + docsLink + items { id + image name - } - tags { - id - label - color + labels + unit + pluralUnit + count + pricePerUnit + threshold + staffOnly + storefrontVisible + notes + description + makerspaceID + makerspace { + id + name + } + tags { + id + label + color + } } } } - } `; export const GET_MAKERSPACE_BY_ID = gql` @@ -153,6 +157,7 @@ export const GET_MAKERSPACE_BY_ID = gql` makerspaceByID(id: $id) { id name + archived subtitle location description @@ -212,7 +217,14 @@ export const UPDATE_MAKERSPACE = gql` ) { updateMakerspace( id: $id - newMakerspace: { name: $name, subtitle: $subtitle, location: $location, description: $description, docsLink: $docsLink, imageUrl: $imageUrl } + newMakerspace: { + name: $name + subtitle: $subtitle + location: $location + description: $description + docsLink: $docsLink + imageUrl: $imageUrl + } ) { id } @@ -253,4 +265,20 @@ export const DELETE_SPECIAL_HOURS = gql` mutation DeleteSpecialHours($day: DateTime!, $makerspaceID: ID!) { deleteSpecialHours(day: $day, makerspaceID: $makerspaceID) } -`; \ No newline at end of file +`; + +export const ARCHIVE_MAKERSPACE = gql` + mutation ArchiveMakerspace($id: ID!) { + archiveMakerspace(id: $id) { + id + } + } +`; + +export const UNARCHIVE_MAKERSPACE = gql` + mutation UnarchiveMakerspace($id: ID!) { + unarchiveMakerspace(id: $id) { + id + } + } +`; diff --git a/server/src/repositories/Makerspaces/MakerspaceRespository.ts b/server/src/repositories/Makerspaces/MakerspaceRespository.ts index f5478687a..fc734134e 100644 --- a/server/src/repositories/Makerspaces/MakerspaceRespository.ts +++ b/server/src/repositories/Makerspaces/MakerspaceRespository.ts @@ -13,8 +13,8 @@ import * as ModuleRepo from '../Training/ModuleRepository.js'; * Fetch all Makerspaces * @returns all Makerspaces */ -export async function getMakerspaces(archived = false): Promise { - return await knex('Makerspaces').select().where({ archived: archived }); +export async function getMakerspaces(): Promise { + return await knex('Makerspaces').select(); } /** @@ -65,7 +65,7 @@ export async function updateMakerspace( * @returns 1 */ export async function deleteMakerspace(id: number): Promise { - await knex('OpenHours').update({ makerspaceID: null }).where({ makerspaceID: id }) + await knex('DefaultHours').delete().where({ makerspaceID: id }); await knex('Rooms').update({ makerspaceID: null }).where({ makerspaceID: id }) return await knex('Makerspaces').delete().where({ id }); @@ -126,4 +126,8 @@ export async function archiveMakerspace(id: number, archive = true): Promise { + return await archiveMakerspace(id, false); } \ No newline at end of file diff --git a/server/src/resolvers/makerspaceResolver.ts b/server/src/resolvers/makerspaceResolver.ts index 98e3d305c..4622f5341 100644 --- a/server/src/resolvers/makerspaceResolver.ts +++ b/server/src/resolvers/makerspaceResolver.ts @@ -1,5 +1,5 @@ import { ApolloContext } from "../context.js"; -import { addTrainingToMakerspace, archiveMakerspace, createMakerspace, deleteMakerspace, getMakerspaceByID, getMakerspaces, getTrainingsByMakerspace, removeTrainingFromMakerspace, updateMakerspace } from "../repositories/Makerspaces/MakerspaceRespository.js"; +import { addTrainingToMakerspace, archiveMakerspace, createMakerspace, deleteMakerspace, getMakerspaceByID, getMakerspaces, getTrainingsByMakerspace, removeTrainingFromMakerspace, unarchiveMakerspace, updateMakerspace } from "../repositories/Makerspaces/MakerspaceRespository.js"; import { MakerspaceRow } from "../db/tables.js"; import { getRoomsByMakerspace } from "../repositories/Rooms/RoomRepository.js"; import { MakerspaceInput } from "../schemas/makerspacesSchema.js"; @@ -51,8 +51,21 @@ const MakerspacesResolver = { makerspaces: async ( _parent: any, _args: any, + context: any ) => { - return await getMakerspaces(); + const makerspaces = await getMakerspaces(); + + if (context.user) { + // All makerspaces if admin + if(context.user.admin) { + return makerspaces; + } + // All published makerspaces and any archived ones the user manages + const managedIDs = context.user.manager || []; + return makerspaces.filter((makerspace) => !makerspace.archived || managedIDs.includes(makerspace.id)); + } + // Only published makerspaces for other users + return makerspaces.filter((makerspace) => !makerspace.archived); }, /** @@ -63,8 +76,9 @@ const MakerspacesResolver = { makerspaceByID: async ( _parent: any, args: { id: number }, + context: any, ) => { - return await getMakerspaceByID(args.id); + return getMakerspaceByID(args.id);; }, }, @@ -81,7 +95,7 @@ const MakerspacesResolver = { { isAdmin }: ApolloContext) => isAdmin(async () => { const res = await createMakerspace(args.name); - return res + return res; }), updateMakerspace: async ( @@ -90,7 +104,7 @@ const MakerspacesResolver = { { isManagerFor }: ApolloContext) => isManagerFor(args.id, async () => { const res = await updateMakerspace(args.id, args.newMakerspace); - return res + return res; }), /** @@ -116,8 +130,8 @@ const MakerspacesResolver = { }, { isManagerFor }: ApolloContext ) => isManagerFor(args.makerspaceID, async () => { - return await addTrainingToMakerspace(args.makerspaceID, args.moduleID); - }), + return await addTrainingToMakerspace(args.makerspaceID, args.moduleID); + }), removeTrainingFromMakerspace: async ( _parent: any, @@ -127,8 +141,8 @@ const MakerspacesResolver = { }, { isManagerFor }: ApolloContext ) => isManagerFor(args.makerspaceID, async () => { - return await removeTrainingFromMakerspace(args.makerspaceID, args.moduleID); - }), + return await removeTrainingFromMakerspace(args.makerspaceID, args.moduleID); + }), archiveMakerspace: async ( _parent: any, @@ -140,7 +154,14 @@ const MakerspacesResolver = { createLog(`{user} archived makerspace ${args.id}`, "admin", { id: user.id, label: getUsersFullName(user) } ) - return await archiveMakerspace(args.id); + return await archiveMakerspace(args.id); + }), + + unarchiveMakerspace: async (_parent: any, args: { id: number }, { isAdmin }: ApolloContext) => isAdmin(async (user) => { + createLog(`{user} unarchived makerspace ${args.id}`, "admin", + { id: user.id, label: getUsersFullName(user) } + ) + return await unarchiveMakerspace(args.id); }) } diff --git a/server/src/schemas/makerspacesSchema.ts b/server/src/schemas/makerspacesSchema.ts index aadd484eb..ff5550310 100644 --- a/server/src/schemas/makerspacesSchema.ts +++ b/server/src/schemas/makerspacesSchema.ts @@ -18,6 +18,7 @@ export const MakerspacesTypeDefs = gql` type Makerspace { id: ID! name: String! + archived: Boolean! subtitle: String location: String description: String @@ -47,6 +48,7 @@ export const MakerspacesTypeDefs = gql` deleteMakerspace(id: ID!): Makerspace addMakerspace(name: String!): Makerspace archiveMakerspace(id: ID!): Makerspace + unarchiveMakerspace(id: ID!): Makerspace updateMakerspace(id: ID!, newMakerspace: MakerspaceInput): Makerspace addTrainingToMakerspace(makerspaceID: ID!, moduleID: ID!): [TrainingModule] removeTrainingFromMakerspace(makerspaceID: ID!, moduleID: ID!): [TrainingModule]