From becaaa35bc38811aafe81b0c2b5b917fff4547b7 Mon Sep 17 00:00:00 2001 From: z1glr Date: Fri, 24 Jan 2025 00:27:53 +0000 Subject: [PATCH] added my assigned events overview --- backend/pkg/db/events/events.go | 65 ++++++---- client/src/Zustand.ts | 2 +- client/src/app/MyEvents.tsx | 14 ++- client/src/app/Overview.tsx | 4 +- .../src/app/assignments/VolunteerSelector.tsx | 116 ++++++++++++++++++ client/src/app/assignments/page.tsx | 116 ++---------------- .../src/components/Event/AssignmentTable.tsx | 14 ++- 7 files changed, 197 insertions(+), 134 deletions(-) create mode 100644 client/src/app/assignments/VolunteerSelector.tsx diff --git a/backend/pkg/db/events/events.go b/backend/pkg/db/events/events.go index 49b71d9..0f4de3a 100644 --- a/backend/pkg/db/events/events.go +++ b/backend/pkg/db/events/events.go @@ -25,13 +25,13 @@ type EventAssignment struct { UserName *string `db:"userName" json:"userName"` } -type EventWithAssignment struct { +type EventWithAssignments struct { EventData Tasks []EventAssignment `json:"tasks"` } type EventWithAvailabilities struct { - EventWithAssignment + EventWithAssignments Availabilities availabilities.AvailabilityMap `json:"availabilities"` } @@ -41,22 +41,22 @@ type EventCreate struct { Tasks []int `json:"tasks" validate:"required,min=1"` } -// transform the database-entry to an Event -func (e EventData) Event() (EventWithAssignment, error) { +// transform the database-entry to an WithAssignments +func (e EventData) WithAssignments() (EventWithAssignments, error) { // get the assignments associated with the event if assignemnts, err := Assignments(e.EventID); err != nil { - return EventWithAssignment{}, err + return EventWithAssignments{}, err } else { - return EventWithAssignment{ + return EventWithAssignments{ EventData: e, Tasks: assignemnts, }, nil } } -func (e EventData) EventWithAvailabilities() (EventWithAvailabilities, error) { +func (e EventData) WithAvailabilities() (EventWithAvailabilities, error) { // get the event with assignments - if event, err := e.Event(); err != nil { + if event, err := e.WithAssignments(); err != nil { return EventWithAvailabilities{}, err // get the availabilities @@ -64,8 +64,8 @@ func (e EventData) EventWithAvailabilities() (EventWithAvailabilities, error) { return EventWithAvailabilities{}, err } else { return EventWithAvailabilities{ - EventWithAssignment: event, - Availabilities: availabilities, + EventWithAssignments: event, + Availabilities: availabilities, }, nil } } @@ -173,15 +173,15 @@ func All() ([]EventData, error) { } } -func WithAssignments() ([]EventWithAssignment, error) { +func WithAssignments() ([]EventWithAssignments, error) { // get all events if eventsDB, err := All(); err != nil { return nil, err } else { - events := make([]EventWithAssignment, len(eventsDB)) + events := make([]EventWithAssignments, len(eventsDB)) for ii, e := range eventsDB { - if ev, err := e.Event(); err != nil { + if ev, err := e.WithAssignments(); err != nil { logger.Logger.Error().Msgf("can't get assignments for event with assignmentID = %d: %v", e.EventID, err) } else { events[ii] = ev @@ -200,8 +200,14 @@ func WithAvailabilities() ([]EventWithAvailabilities, error) { events := make([]EventWithAvailabilities, len(eventsDB)) for ii, e := range eventsDB { - if ev, err := e.EventWithAvailabilities(); err != nil { + if ev, err := e.WithAvailabilities(); err != nil { logger.Logger.Error().Msgf("can't get availabilities for event with eventID = %d: %v", e.EventID, err) + + // remove the last element from the return-slice, since there is now one element less + if len(events) > 0 { + events = events[:len(events)-1] + } + } else { events[ii] = ev } @@ -250,20 +256,33 @@ func Assignments(eventID int) ([]EventAssignment, error) { } } -func User(userName string) ([]EventWithAssignment, error) { +func User(userName string) ([]EventWithAssignments, error) { // get all assignments of the user - // var events []EventWithAssignment - var events []struct { - EventData - TaskID int `db:"taskID"` - UserName string `db:"userName"` - } + // var eventsDB []EventWithAssignment + var eventsDB []EventData - if err := db.DB.Select(&events, "SELECT DISTINCT * FROM USER_ASSIGNMENTS INNER JOIN EVENTS ON USER_ASSIGNMENTS.eventID = EVENTS.eventID WHERE userName = $1", userName); err != nil { + // get all the events where the volunteer is assigned a task + if err := db.DB.Select(&eventsDB, "SELECT DISTINCT EVENTS.date, EVENTS.description, EVENTS.eventID FROM USER_ASSIGNMENTS INNER JOIN EVENTS ON USER_ASSIGNMENTS.eventID = EVENTS.eventID WHERE userName = $1", userName); err != nil { return nil, err } else { - return nil, nil + // for each event create an event with assignments + events := make([]EventWithAssignments, len(eventsDB)) + + for ii, event := range eventsDB { + if eventsWithAssignment, err := event.WithAssignments(); err != nil { + logger.Logger.Error().Msgf("can't get assignments for event with eventID = %d: %v", event.EventID, err) + + // remove the last element from the return-slice, since there is now one element less + if len(events) > 0 { + events = events[:len(events)-1] + } + } else { + events[ii] = eventsWithAssignment + } + } + + return events, nil } } diff --git a/client/src/Zustand.ts b/client/src/Zustand.ts index 8690054..f456442 100644 --- a/client/src/Zustand.ts +++ b/client/src/Zustand.ts @@ -14,7 +14,7 @@ export type EventData = BaseEvent & { tasks: TaskAssignment[]; }; -interface TaskAssignment { +export interface TaskAssignment { taskID: number; taskName: string; userName: string | null; diff --git a/client/src/app/MyEvents.tsx b/client/src/app/MyEvents.tsx index 08824f5..fe94224 100644 --- a/client/src/app/MyEvents.tsx +++ b/client/src/app/MyEvents.tsx @@ -1,10 +1,14 @@ "use client"; +import AssignmentTable from "@/components/Event/AssignmentTable"; +import Event from "@/components/Event/Event"; import { apiCall } from "@/lib"; -import { EventData } from "@/Zustand"; +import zustand, { EventData } from "@/Zustand"; import { useAsyncList } from "@react-stately/data"; export default function MyEvents() { + const user = zustand((state) => state.user); + const events = useAsyncList({ async load() { const result = await apiCall("GET", "events/user/assigned"); @@ -26,8 +30,12 @@ export default function MyEvents() { }); return ( -
-

{events.items.map((e) => e.date)}

+
+ {events.items.map((e) => ( + + + + ))}
); } diff --git a/client/src/app/Overview.tsx b/client/src/app/Overview.tsx index 17fec71..b54eecf 100644 --- a/client/src/app/Overview.tsx +++ b/client/src/app/Overview.tsx @@ -8,8 +8,8 @@ export default function Overview() {

My Events

-

- events that I don't have entered an availability yet +

+ events that I don't have entered an availability yet

diff --git a/client/src/app/assignments/VolunteerSelector.tsx b/client/src/app/assignments/VolunteerSelector.tsx new file mode 100644 index 0000000..56ee6f9 --- /dev/null +++ b/client/src/app/assignments/VolunteerSelector.tsx @@ -0,0 +1,116 @@ +import AvailabilityChip from "@/components/AvailabilityChip"; +import { TaskAssignment } from "@/Zustand"; +import { AddLarge } from "@carbon/icons-react"; +import { + Button, + Chip, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, +} from "@heroui/react"; +import { EventWithAvailabilities } from "./page"; +import { ReactElement } from "react"; +import { Availability } from "../admin/(availabilities)/AvailabilityEditor"; +import { apiCall, classNames } from "@/lib"; + +export default function VolunteerSelector({ + event, + task, + getAvailabilityById, + onReloadRequest, +}: { + event: EventWithAvailabilities; + task: TaskAssignment; + getAvailabilityById: (availabilityID: number) => Availability; + onReloadRequest: () => void; +}) { + async function sendVolunteerAssignment( + eventID: number, + taskID: number, + userName: string, + ) { + const result = await apiCall( + "PUT", + "events/assignments", + { eventID, taskID }, + userName, + ); + + if (result.ok) { + onReloadRequest(); + } + } + + // 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) { + onReloadRequest(); + } + } + + return ( + + + {!!event.tasks.find((t) => t.taskID == task.taskID)?.userName ? ( + + removeVolunteerAssignment(event.eventID, task.taskID) + } + > + {event.tasks.find((t) => t.taskID == task.taskID)?.userName} + + ) : ( + + )} + + + sendVolunteerAssignment(event.eventID, task.taskID, a as string) + } + > + {Object.entries(event.availabilities).map( + ([availabilityId, volunteers], iAvailability, aAvailabilities) => ( + + ) as ReactElement & string + } + > + {volunteers.map((v) => ( + + {v} + + ))} + + ), + )} + + + ); +} diff --git a/client/src/app/assignments/page.tsx b/client/src/app/assignments/page.tsx index c7387a2..159d0a8 100644 --- a/client/src/app/assignments/page.tsx +++ b/client/src/app/assignments/page.tsx @@ -7,7 +7,6 @@ import { apiCall, getAvailabilities, getTasks } from "@/lib"; import { EventData } from "@/Zustand"; import { Add, - AddLarge, Copy, Edit, NotAvailable, @@ -17,12 +16,6 @@ import { import { Button, ButtonGroup, - Chip, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownSection, - DropdownTrigger, Modal, ModalBody, ModalContent, @@ -38,11 +31,11 @@ import { Tooltip, } from "@heroui/react"; import { useAsyncList } from "@react-stately/data"; -import React, { Key, ReactElement, useEffect, useState } from "react"; +import React, { Key, useEffect, useState } from "react"; import { Availability } from "../admin/(availabilities)/AvailabilityEditor"; -import AvailabilityChip from "@/components/AvailabilityChip"; +import VolunteerSelector from "./VolunteerSelector"; -type EventWithAvailabilities = EventData & { +export type EventWithAvailabilities = EventData & { availabilities: Record; }; @@ -137,35 +130,6 @@ export default function AdminPanel() { } // 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) { @@ -236,72 +200,16 @@ export default function AdminPanel() { ); default: // only show the selector, if the task is needed for the event - if (event.tasks?.some((t) => t.taskID == key)) { + const task = event.tasks.find((t) => t.taskID == key); + + if (!!task) { return ( - - - {!!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( - ( - [availabilityId, volunteers], - iAvailability, - aAvailabilities, - ) => ( - - ) as ReactElement & string - } - > - {volunteers.map((v) => ( - - {v} - - ))} - - ), - )} - - + ); } else { return ; diff --git a/client/src/components/Event/AssignmentTable.tsx b/client/src/components/Event/AssignmentTable.tsx index 2e3f8a5..54c565d 100644 --- a/client/src/components/Event/AssignmentTable.tsx +++ b/client/src/components/Event/AssignmentTable.tsx @@ -1,15 +1,27 @@ +import { classNames } from "@/lib"; import { EventData } from "@/Zustand"; export default function AssignmentTable({ tasks, + highlightTask, + highlightUser, }: { tasks: EventData["tasks"]; + highlightUser?: string; + highlightTask?: string; }) { return ( {tasks.map((task) => ( - +
{task.taskName} {task.userName ?? (