diff --git a/backend/pkg/db/availabilities/userAvailabilities.go b/backend/pkg/db/availabilities/userAvailabilities.go index 3ce3c01..91cf145 100644 --- a/backend/pkg/db/availabilities/userAvailabilities.go +++ b/backend/pkg/db/availabilities/userAvailabilities.go @@ -9,7 +9,9 @@ type eventAvailabilities struct { AvailabilityID int `db:"availabilityID"` } -func Event(eventID int) (map[string]string, error) { +type AvailabilityMap map[int][]string + +func Event(eventID int) (AvailabilityMap, error) { // get the availabilities for the event var availabilitiesRows []eventAvailabilities @@ -17,17 +19,18 @@ func Event(eventID int) (map[string]string, error) { return nil, err } else { // transform the result into a map - eventAvailabilities := map[string]string{} + eventAvailabilities := AvailabilityMap{} // get the availabilities - if availabilitiesMap, err := Keys(); err != nil { - return nil, err - } else { - for _, a := range availabilitiesRows { - eventAvailabilities[a.UserName] = availabilitiesMap[a.AvailabilityID].AvailabilityName + for _, a := range availabilitiesRows { + // if there is no slice for this availability, create it + if _, exists := eventAvailabilities[a.AvailabilityID]; !exists { + eventAvailabilities[a.AvailabilityID] = make([]string, 0) } - return eventAvailabilities, nil + eventAvailabilities[a.AvailabilityID] = append(eventAvailabilities[a.AvailabilityID], a.UserName) } + + return eventAvailabilities, nil } } diff --git a/backend/pkg/db/events/events.go b/backend/pkg/db/events/events.go index 28d6a54..49b71d9 100644 --- a/backend/pkg/db/events/events.go +++ b/backend/pkg/db/events/events.go @@ -32,7 +32,7 @@ type EventWithAssignment struct { type EventWithAvailabilities struct { EventWithAssignment - Availabilities map[string]string `json:"availabilities"` + Availabilities availabilities.AvailabilityMap `json:"availabilities"` } type EventCreate struct { @@ -268,8 +268,22 @@ func User(userName string) ([]EventWithAssignment, error) { } // set the availability of an user for a specific event -func UserAvailability(userName string, eventID, availabilityID int) error { +func UserAvailability(eventID, availabilityID int, userName string) error { _, err := db.DB.Exec("INSERT INTO USER_AVAILABILITIES (userName, eventID, availabilityID) VALUES ($1, $2, $3) ON CONFLICT (userName, eventID) DO UPDATE SET availabilityID = $3", userName, eventID, availabilityID) return err } + +// set the assignment of an user to a task for a specific event +func SetAssignment(eventID, taskID int, userName string) error { + _, err := db.DB.Exec("UPDATE USER_ASSIGNMENTS SET userName = $1 WHERE eventID = $2 AND taskID = $3", userName, eventID, taskID) + + return err +} + +// remove the assignment of an user +func DeleteAssignment(eventID, taskID int) error { + _, err := db.DB.Exec("UPDATE USER_ASSIGNMENTS SET userName = null WHERE eventID = $1 AND taskID = $2", eventID, taskID) + + return err +} diff --git a/backend/pkg/router/events.go b/backend/pkg/router/events.go index 51c9912..906259f 100644 --- a/backend/pkg/router/events.go +++ b/backend/pkg/router/events.go @@ -137,14 +137,74 @@ func (a *Handler) putEventUserAvailability() { logger.Log().Msgf("setting user-event-availability failed: can't get parse: %v", err) // insert the availability into the database - } else if err := events.UserAvailability(a.UserName, eventID, availabilityID); err != nil { + } else if err := events.UserAvailability(eventID, availabilityID, a.UserName); err != nil { a.Status = fiber.StatusInternalServerError logger.Error().Msgf("setting user-event-availability failed: can't write availability to database: %v", err) } } +} + +func (a *Handler) putEventAssignment() { + // check admin + if !a.Admin { + a.Status = fiber.StatusUnauthorized + + logger.Warn().Msg("setting event-assignment failed: user is no admin") + + // retrieve the eventID from the query + } else if eventID := a.C.QueryInt("eventID", -1); eventID == -1 { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msg("setting event-assignment failed: query is missing \"eventID\"") + + // retrieve the taskID from the query + } else if taskID := a.C.QueryInt("taskID", -1); taskID == -1 { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msg("setting event-assignment failed: query is missing \"taskID\"") + + // parse the body + } else if userName := string(a.C.Body()); userName == "" { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msg("setting event-assignment failed: body is missing") + + // set the availability in the database + } else if err := events.SetAssignment(eventID, taskID, userName); err != nil { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msgf("setting event-assignment failed: can't write to database: %v", err) + } + +} + +func (a *Handler) deleteEventAssignment() { + // check admin + if !a.Admin { + a.Status = fiber.StatusUnauthorized + + logger.Warn().Msg("deleting event-assignment failed: user is no admin") + + // retrieve the eventID from the query + } else if eventID := a.C.QueryInt("eventID", -1); eventID == -1 { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msg("deleting event-assignment failed: query is missing \"eventID\"") + + // retrieve the taskID from the query + } else if taskID := a.C.QueryInt("taskID", -1); taskID == -1 { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msg("deleting event-assignment failed: query is missing \"taskID\"") + + // set the availability in the database + } else if err := events.DeleteAssignment(eventID, taskID); err != nil { + a.Status = fiber.StatusBadRequest + + logger.Warn().Msgf("deleting event-assignment failed: can't write to database: %v", err) + } - // parse the body } func (a *Handler) deleteEvent() { diff --git a/backend/pkg/router/router.go b/backend/pkg/router/router.go index 00c8820..6f15c33 100644 --- a/backend/pkg/router/router.go +++ b/backend/pkg/router/router.go @@ -107,14 +107,16 @@ func init() { "tasks": (*Handler).patchTask, // modify a task }, "PUT": { - "users/password": (*Handler).putPassword, // change the password "events/user/availability": (*Handler).putEventUserAvailability, // set or change the users availability for a specific event + "events/assignments": (*Handler).putEventAssignment, + "users/password": (*Handler).putPassword, // change the password }, "DELETE": { - "event": (*Handler).deleteEvent, // remove an event - "tasks": (*Handler).deleteTask, // remove a task - "availabilities": (*Handler).deleteAvailability, // remove an availability - "users": (*Handler).deleteUser, // remove an user + "event": (*Handler).deleteEvent, // remove an event + "events/assignments": (*Handler).deleteEventAssignment, + "tasks": (*Handler).deleteTask, // remove a task + "availabilities": (*Handler).deleteAvailability, // remove an availability + "users": (*Handler).deleteUser, // remove an user }, } diff --git a/client/src/app/MyEvents.tsx b/client/src/app/MyEvents.tsx index 13c6e48..08824f5 100644 --- a/client/src/app/MyEvents.tsx +++ b/client/src/app/MyEvents.tsx @@ -10,8 +10,12 @@ export default function MyEvents() { const result = await apiCall("GET", "events/user/assigned"); if (result.ok) { + const data = await result.json(); + + console.debug(data); + return { - items: await result.json(), + items: data, }; } else { return { diff --git a/client/src/app/assignments/page.tsx b/client/src/app/assignments/page.tsx index 408dd5c..c7387a2 100644 --- a/client/src/app/assignments/page.tsx +++ b/client/src/app/assignments/page.tsx @@ -3,7 +3,7 @@ import AddEvent from "@/components/Event/AddEvent"; import EditEvent from "@/components/Event/EditEvent"; import LocalDate from "@/components/LocalDate"; -import { apiCall, getTasks } from "@/lib"; +import { apiCall, getAvailabilities, getTasks } from "@/lib"; import { EventData } from "@/Zustand"; import { Add, @@ -21,6 +21,7 @@ import { Dropdown, DropdownItem, DropdownMenu, + DropdownSection, DropdownTrigger, Modal, ModalBody, @@ -37,14 +38,21 @@ import { Tooltip, } from "@heroui/react"; import { useAsyncList } from "@react-stately/data"; -import React, { Key, useState } from "react"; +import React, { Key, ReactElement, useEffect, useState } from "react"; +import { Availability } from "../admin/(availabilities)/AvailabilityEditor"; +import AvailabilityChip from "@/components/AvailabilityChip"; -type EventWithAvailabilities = EventData & { availabilities: string[] }; +type EventWithAvailabilities = EventData & { + availabilities: Record; +}; export default function AdminPanel() { const [showAddEvent, setShowAddEvent] = useState(false); const [editEvent, setEditEvent] = useState(); const [deleteEvent, setDeleteEvent] = useState(); + const [availabilityMap, setAvailabilityMap] = useState< + Record + >({}); // get the available tasks and craft them into the headers const headers = useAsyncList<{ @@ -113,6 +121,51 @@ export default function AdminPanel() { }, }); + // retrieve the availabilites and store them in a map + useEffect(() => { + (async () => { + setAvailabilityMap( + Object.fromEntries( + (await getAvailabilities()).map((a) => [a.availabilityID, a]), + ), + ); + })(); + }, []); + + function getAvailabilityById(availabilityID: number): Availability { + return availabilityMap[availabilityID]; + } + + // send a command to the backend to assign a volunteer to a task + async function sendVolunteerAssignment( + eventID: number, + taskID: number, + userName: string, + ) { + const result = await apiCall( + "PUT", + "events/assignments", + { eventID, taskID }, + userName, + ); + + if (result.ok) { + events.reload(); + } + } + + // sends a command to the backend to remove an volunteer-assignment + async function removeVolunteerAssignment(eventID: number, taskID: number) { + const result = await apiCall("DELETE", "events/assignments", { + eventID, + taskID, + }); + + if (result.ok) { + events.reload(); + } + } + // send a delete request to the backend and close the popup on success async function sendDeleteEvent() { if (deleteEvent !== undefined) { @@ -123,6 +176,9 @@ export default function AdminPanel() { if (result.ok) { // store the received events events.reload(); + + // close the delete-confirmaton + setDeleteEvent(undefined); } } } @@ -184,9 +240,13 @@ export default function AdminPanel() { return ( - {!!event.tasks.find((t) => t.taskID === key)?.userName ? ( - alert("implement")}> - {event.tasks.find((t) => t.taskID === key)?.taskName} + {!!event.tasks.find((t) => t.taskID == key)?.userName ? ( + + removeVolunteerAssignment(event.eventID, key as number) + } + > + {event.tasks.find((t) => t.taskID == key)?.userName} ) : ( )} - + + sendVolunteerAssignment( + event.eventID, + key as number, + a as string, + ) + } + > {Object.entries(event.availabilities).map( - ([volunteer, availability]) => ( - ( + + ) as ReactElement & string + } > - {volunteer} ({availability}) - + {volunteers.map((v) => ( + + {v} + + ))} + ), )} diff --git a/client/src/components/AvailabilityChip.tsx b/client/src/components/AvailabilityChip.tsx index 7851c50..7be23a2 100644 --- a/client/src/components/AvailabilityChip.tsx +++ b/client/src/components/AvailabilityChip.tsx @@ -6,11 +6,11 @@ export default function AvailabilityChip({ availability, className, }: { - availability: Availability; + availability?: Availability; className?: string; classNames?: ChipProps["classNames"]; }) { - return ( + return !!availability ? ( {availability.availabilityName} - ); + ) : null; } diff --git a/client/src/lib.ts b/client/src/lib.ts index 8becee2..4519d2d 100644 --- a/client/src/lib.ts +++ b/client/src/lib.ts @@ -59,7 +59,7 @@ export async function apiCall( }, credentials: "include", method, - body: body !== undefined ? JSON.stringify(body) : undefined, + body: prepareBody(body), }); return response; @@ -79,6 +79,19 @@ function getContentType(type: string): string { } } +function prepareBody( + body: object | number | string | boolean | undefined, +): BodyInit | undefined { + switch (typeof body) { + case "object": + return JSON.stringify(body); + case "undefined": + return undefined; + default: + return body.toString(); + } +} + export function classNames(classNames: Record): string { return Object.entries(classNames) .map(([classString, value]) => { @@ -157,11 +170,11 @@ export async function getAvailabilities(): Promise { const result = await apiCall("GET", "availabilities"); if (result.ok) { - const tasks = await result.json(); + const availabilities = await result.json(); - state.patch({ availabilities: tasks }); + state.patch({ availabilities: availabilities }); - return tasks; + return availabilities; } else { return []; }