From 6e03ecb6547cb4934747dfabef1f0b9f19e4eeec Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Fri, 17 Jan 2025 18:20:30 -0800 Subject: [PATCH 1/8] feat: create delete endpoint --- server/routes/clients.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/server/routes/clients.js b/server/routes/clients.js index d114f68..b33e85f 100644 --- a/server/routes/clients.js +++ b/server/routes/clients.js @@ -4,13 +4,31 @@ import { db } from "../db/db-pgp"; // TODO: replace this db with export const clientsRouter = Router(); -// Get all comments +// Get all clients clientsRouter.get("/", async (req, res) => { try { const users = await db.query(`SELECT * FROM clients ORDER BY id ASC`); res.status(200).json(keysToCamel(users)); } catch (err) { - res.status(400).send(err.message); + res.status(500).send(err.message); } }); + +// Delete client by id +clientsRouter.delete("/:id", async (req, res) => { + try { + const { id } = req.params; + const deletedClient = await db.query('DELETE FROM clients WHERE id = $1 RETURNING *', [id]); + + if (deletedClient.length === 0) { + return res.status(404).json(keysToCamel({result: "error", message: "Client not found"})); + } + + res.status(200).json(keysToCamel({result: "success", deletedClient: deletedClient[0]})); + + } catch (err) { + res.status(500).json(keysToCamel({result: "error", message: err.message})); + } +}); + From 4ed4e252e7b0ce9bc5675bb23131c796ce9f0186 Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Fri, 17 Jan 2025 18:48:40 -0800 Subject: [PATCH 2/8] feat: create assignment for a specific event endpoint --- server/routes/assignments.js | 29 +++++++++++++++++++++++++++++ server/src/app.js | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 server/routes/assignments.js diff --git a/server/routes/assignments.js b/server/routes/assignments.js new file mode 100644 index 0000000..6abd490 --- /dev/null +++ b/server/routes/assignments.js @@ -0,0 +1,29 @@ +import { Router } from "express"; +import { keysToCamel } from "../common/utils"; +import { db } from "../db/db-pgp"; + +export const assignmentsRouter = Router(); + +// Get assignments for a specific event +assignmentsRouter.get("/event/:event_id", async (req, res) => { + try { + const { event_id } = req.params; + + const assignments = await db.query( + `SELECT a.id, a.event_id, a.client_id, a.role, c.name as client_name, e.name as event_name + FROM assignments a, clients c, events e + WHERE a.client_id = c.id AND a.event_id = e.id AND a.event_id = $1 + ORDER BY a.id ASC`, + [event_id] + ); + + // Check if assignments exist + if (assignments.length === 0) { + return res.status(404).json(keysToCamel({error: "No assignments found for this event"})); + } + + res.status(200).json(keysToCamel(assignments)); + } catch (err) { + res.status(500).send(err.message); + } +}); \ No newline at end of file diff --git a/server/src/app.js b/server/src/app.js index 0115b95..a7ec4cf 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -10,6 +10,7 @@ import { eventsRouter } from "../routes/events"; import { bookingsRouter } from "../routes/bookings"; import { verifyToken } from "./middleware"; import { clientsRouter } from "../routes/clients"; +import { assignmentsRouter } from "../routes/assignments"; dotenv.config(); @@ -46,6 +47,7 @@ app.use("/events", eventsRouter); app.use("/bookings", bookingsRouter); app.use("/comments", commentsRouter); app.use("/clients", clientsRouter); +app.use("/assignments", assignmentsRouter); app.listen(SERVER_PORT, () => { console.info(`Server listening on ${SERVER_PORT}`); From f461a17126ef09c4d9fb7041fe4cb050bd76d861 Mon Sep 17 00:00:00 2001 From: Yihong Yu Date: Fri, 17 Jan 2025 22:09:02 -0800 Subject: [PATCH 3/8] feat: create GET and PUT for client --- server/routes/clients.js | 45 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/server/routes/clients.js b/server/routes/clients.js index b33e85f..5f639c3 100644 --- a/server/routes/clients.js +++ b/server/routes/clients.js @@ -15,6 +15,49 @@ clientsRouter.get("/", async (req, res) => { } }); +// Get client by id +clientsRouter.get("/:id", async (req, res) => { + try { + const { id } = req.params; + const user = await db.query(`SELECT * FROM clients WHERE id = $1`, [id]); + + // Case when client not found + if (user.length === 0) { + return res.status(404).json(keysToCamel("Client not found")); + } + + res.status(200).json(keysToCamel(user[0])); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// Put client by id +clientsRouter.put("/:id", async (req, res) => { + try { + const { id } = req.params; + const { name, email } = req.body; + const updatedClient = await db.query( + ` + UPDATE clients + SET + name = $1, + email = $2 + WHERE id = $3 + RETURNING * + `, + [name, email, id]); + + if (updatedClient.length === 0) { + return res.status(404).json(keysToCamel("Client not found")); + } + + res.status(200).json(keysToCamel(updatedClient)); + } catch (err) { + res.status(500).send(err.message); + } +}); + // Delete client by id clientsRouter.delete("/:id", async (req, res) => { try { @@ -28,7 +71,7 @@ clientsRouter.delete("/:id", async (req, res) => { res.status(200).json(keysToCamel({result: "success", deletedClient: deletedClient[0]})); } catch (err) { - res.status(500).json(keysToCamel({result: "error", message: err.message})); + res.status(500).send(err.message); } }); From a76ac5b8508cecf8f4fd68d937eccc151619e0c2 Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Sun, 19 Jan 2025 13:14:10 -0800 Subject: [PATCH 4/8] feat: create table to list all event information and allow assignments endpoint to return no events Co-Authored-By: Yihong Yu <116992300+HazelYuAhiru@users.noreply.github.com> --- client/src/components/EventsTable.jsx | 114 ++++++++++++++++++ .../src/components/playground/Playground.jsx | 2 + package.json | 3 + server/routes/assignments.js | 6 +- 4 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 client/src/components/EventsTable.jsx diff --git a/client/src/components/EventsTable.jsx b/client/src/components/EventsTable.jsx new file mode 100644 index 0000000..b9f786c --- /dev/null +++ b/client/src/components/EventsTable.jsx @@ -0,0 +1,114 @@ +import { + Table, + Thead, + Tbody, + Tr, + Th, + Td, + Menu, + MenuButton, + MenuList, + MenuItem, + Button, +} from '@chakra-ui/react'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import React, { useState, useEffect } from 'react'; +import { useBackendContext } from "../contexts/hooks/useBackendContext"; + +export const EventsTable = () => { + const { backend } = useBackendContext(); + const [ events, setEvents ] = useState([]); + + const fetchEvents = async () => { + try { + const eventsResponse = await backend.get('/events'); + const eventsData = eventsResponse.data; + const eventsWithAssignments = []; + + for (const event of eventsData) { + const instructors = []; + const payees = []; + + try { + // Attempt to fetch assignment information for each event + const assignmentsResponse = await backend.get('/assignments/event/' + event.id); + const assignmentsData = assignmentsResponse.data; + + // Process assignment information if available + if (Array.isArray(assignmentsData) && assignmentsData.length > 0) { + for (const assignment of assignmentsData) { + if (assignment.role.toLowerCase() === 'instructor') { + instructors.push(assignment.clientName); + } else if (assignment.role.toLowerCase() === 'payee') { + payees.push(assignment.clientName); + } + } + } + } catch { + console.error('Failed to fetch assignments for event', event.id); + } + + // Add event with assignments to list + eventsWithAssignments.push({ + id: event.id, + name: event.name, + description: event.description, + instructors: instructors.join(', '), + payees: payees.join(', ') + }); + } + + setEvents(eventsWithAssignments); + } catch { + console.error('Failed to fetch events'); + } + } + + + useEffect(() => { + // call the fetch event here to fetch all events + fetchEvents(); + }, []); + + + return ( + + + + + + + + + + + + {events.map((event) => ( + + + + + + + + ))} + +
ClassDescriptionInstructorPayeeActions
{event.name}{event.description}{event.instructors}{event.payees} + + } + size="sm" + variant="outline" + > + Actions + + + {}}>Edit + {}}>Delete + + +
+ ); + +}; \ No newline at end of file diff --git a/client/src/components/playground/Playground.jsx b/client/src/components/playground/Playground.jsx index b9c39ce..31f3b40 100644 --- a/client/src/components/playground/Playground.jsx +++ b/client/src/components/playground/Playground.jsx @@ -2,6 +2,7 @@ import { Heading, VStack, } from "@chakra-ui/react"; +import { EventsTable } from "../EventsTable"; export const Playground = () => { return ( @@ -10,6 +11,7 @@ export const Playground = () => { sx={{ width: 300, marginX: "auto" }} > Playground + ); }; diff --git a/package.json b/package.json index b262c07..7419476 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,8 @@ "packageManager": "yarn@1.22.22", "engines": { "node": ">=18" + }, + "dependencies": { + "@chakra-ui/icons": "^2.2.4" } } diff --git a/server/routes/assignments.js b/server/routes/assignments.js index 6abd490..6831e11 100644 --- a/server/routes/assignments.js +++ b/server/routes/assignments.js @@ -18,9 +18,9 @@ assignmentsRouter.get("/event/:event_id", async (req, res) => { ); // Check if assignments exist - if (assignments.length === 0) { - return res.status(404).json(keysToCamel({error: "No assignments found for this event"})); - } + // if (assignments.length === 0) { + // return res.status(404).json(keysToCamel({error: "No assignments found for this event"})); + // } res.status(200).json(keysToCamel(assignments)); } catch (err) { From f47b22b997167263dfc271d98f0c18e1e09d7cf2 Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Mon, 20 Jan 2025 12:20:46 -0800 Subject: [PATCH 5/8] fix: delete package.json (oopsies) --- package.json | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 package.json diff --git a/package.json b/package.json deleted file mode 100644 index 7419476..0000000 --- a/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "npo-template-merged", - "version": "1.0.0", - "private": true, - "type": "module", - "repository": "git@github.com:ctc-uci/npo-template-merged.git", - "author": "CTC ", - "license": "MIT", - "scripts": { - "dev": "yarn tsx concurrently.ts", - "start": "yarn tsx concurrently.ts", - "prepare": "husky install", - "lint": "eslint", - "format": "npx prettier --write .", - "postinstall": "cd client && npx patch-package" - }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx}": "yarn run eslint" - }, - "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.3.1", - "@types/lint-staged": "^13.3.0", - "concurrently": "^8.2.2", - "eslint": "^9.9.0", - "globals": "^15.9.0", - "husky": "^9.0.11", - "prettier": "3.3.3", - "typescript-eslint": "^8.1.0" - }, - "workspaces": { - "packages": [ - "client", - "server" - ], - "nohoist": [ - "**/firebase", - "**/firebase/**" - ] - }, - "packageManager": "yarn@1.22.22", - "engines": { - "node": ">=18" - }, - "dependencies": { - "@chakra-ui/icons": "^2.2.4" - } -} From e806641bd57c68195dd4daa6c6faba5c73c6a86c Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Mon, 20 Jan 2025 12:24:44 -0800 Subject: [PATCH 6/8] fix: add old package.json that I accidentally changed --- package.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..b262c07 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "npo-template-merged", + "version": "1.0.0", + "private": true, + "type": "module", + "repository": "git@github.com:ctc-uci/npo-template-merged.git", + "author": "CTC ", + "license": "MIT", + "scripts": { + "dev": "yarn tsx concurrently.ts", + "start": "yarn tsx concurrently.ts", + "prepare": "husky install", + "lint": "eslint", + "format": "npx prettier --write .", + "postinstall": "cd client && npx patch-package" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": "yarn run eslint" + }, + "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.3.1", + "@types/lint-staged": "^13.3.0", + "concurrently": "^8.2.2", + "eslint": "^9.9.0", + "globals": "^15.9.0", + "husky": "^9.0.11", + "prettier": "3.3.3", + "typescript-eslint": "^8.1.0" + }, + "workspaces": { + "packages": [ + "client", + "server" + ], + "nohoist": [ + "**/firebase", + "**/firebase/**" + ] + }, + "packageManager": "yarn@1.22.22", + "engines": { + "node": ">=18" + } +} From 2cf7a2e69a65adff43ac7af07f3d4446fed3f137 Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Mon, 20 Jan 2025 12:33:47 -0800 Subject: [PATCH 7/8] fix: remove chakra-ui/icons to pass checks (hopefully) --- client/src/components/EventsTable.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/src/components/EventsTable.jsx b/client/src/components/EventsTable.jsx index b9f786c..909270b 100644 --- a/client/src/components/EventsTable.jsx +++ b/client/src/components/EventsTable.jsx @@ -11,7 +11,6 @@ import { MenuItem, Button, } from '@chakra-ui/react'; -import { ChevronDownIcon } from '@chakra-ui/icons'; import React, { useState, useEffect } from 'react'; import { useBackendContext } from "../contexts/hooks/useBackendContext"; @@ -64,13 +63,11 @@ export const EventsTable = () => { } } - useEffect(() => { // call the fetch event here to fetch all events fetchEvents(); }, []); - return ( @@ -93,7 +90,6 @@ export const EventsTable = () => { } size="sm" variant="outline" > From 8377649a3ffcd5677616d33e0f65588a3489cd1e Mon Sep 17 00:00:00 2001 From: Cole Thompson Date: Thu, 23 Jan 2025 08:29:24 -0800 Subject: [PATCH 8/8] fix: add null checks to name and email --- server/routes/clients.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/routes/clients.js b/server/routes/clients.js index 5f639c3..0f5c6c3 100644 --- a/server/routes/clients.js +++ b/server/routes/clients.js @@ -37,15 +37,17 @@ clientsRouter.put("/:id", async (req, res) => { try { const { id } = req.params; const { name, email } = req.body; + + // Update just the name or just the email, without having to provide both fields const updatedClient = await db.query( ` UPDATE clients SET - name = $1, - email = $2 + name = COALESCE($1, name), + email = COALESCE($2, email) WHERE id = $3 RETURNING * - `, + `, [name, email, id]); if (updatedClient.length === 0) {