added availability changing to events view
This commit is contained in:
@@ -10,10 +10,16 @@ export interface BaseEvent {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type EventAvailability = BaseEvent & {
|
||||
availability: number;
|
||||
};
|
||||
|
||||
export type EventData = BaseEvent & {
|
||||
tasks: TaskAssignment[];
|
||||
};
|
||||
|
||||
export type EventDataWithAvailability = EventData & EventAvailability;
|
||||
|
||||
export interface TaskAssignment {
|
||||
taskID: number;
|
||||
taskName: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import MyEvents from "./MyEvents";
|
||||
import PengingEvents from "./PendingEvents";
|
||||
import PendingEvents from "./PendingEvents";
|
||||
|
||||
export default function Overview() {
|
||||
return (
|
||||
@@ -12,7 +12,7 @@ export default function Overview() {
|
||||
<h1 className="mb-4 mt-8 text-center text-4xl lg:mt-0">
|
||||
Pending Events
|
||||
</h1>
|
||||
<PengingEvents />
|
||||
<PendingEvents />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import AvailabilityChip from "@/components/AvailabilityChip";
|
||||
import AvailabilitySelector from "@/components/Event/AvailabilitySelector";
|
||||
import Event from "@/components/Event/Event";
|
||||
import { apiCall, getAvailabilities } from "@/lib";
|
||||
import { BaseEvent } from "@/Zustand";
|
||||
import { Select, SelectItem } from "@heroui/react";
|
||||
import { apiCall } from "@/lib";
|
||||
import { EventAvailability } from "@/Zustand";
|
||||
import { useAsyncList } from "@react-stately/data";
|
||||
|
||||
type EventAvailability = BaseEvent & {
|
||||
availability: number;
|
||||
};
|
||||
|
||||
export default function PengingEvents() {
|
||||
export default function PendingEvents() {
|
||||
// get the events the user hasn't yet inserted his availability for
|
||||
const events = useAsyncList({
|
||||
async load() {
|
||||
@@ -32,56 +27,11 @@ export default function PengingEvents() {
|
||||
},
|
||||
});
|
||||
|
||||
// the individual, selectable availabilities
|
||||
const availabilities = useAsyncList({
|
||||
async load() {
|
||||
return {
|
||||
items: (await getAvailabilities()).filter((a) => a.enabled),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
async function setAvailability(eventID: number, availabilityID: number) {
|
||||
await apiCall(
|
||||
"PUT",
|
||||
"events/user/availability",
|
||||
{ eventID },
|
||||
availabilityID,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-center gap-4">
|
||||
{events.items.map((e) => (
|
||||
<Event key={e.eventID} event={e}>
|
||||
<Select
|
||||
items={availabilities.items}
|
||||
label="Availability"
|
||||
variant="bordered"
|
||||
className="mt-auto"
|
||||
isMultiline
|
||||
renderValue={(availability) => (
|
||||
<div>
|
||||
{availability.map((a) =>
|
||||
!!a.data ? (
|
||||
<AvailabilityChip key={a.key} availability={a.data} />
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
onSelectionChange={(a) =>
|
||||
setAvailability(e.eventID, parseInt(a.anchorKey ?? ""))
|
||||
}
|
||||
>
|
||||
{(availability) => (
|
||||
<SelectItem
|
||||
key={availability.availabilityID}
|
||||
textValue={availability.availabilityName}
|
||||
>
|
||||
<AvailabilityChip availability={availability} />
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
<AvailabilitySelector event={e} className="mt-auto" />
|
||||
</Event>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -3,23 +3,31 @@ import AddEvent from "@/components/Event/AddEvent";
|
||||
import AssignmentTable from "@/components/Event/AssignmentTable";
|
||||
import Event from "@/components/Event/Event";
|
||||
import { apiCall } from "@/lib";
|
||||
import zustand, { EventData } from "@/Zustand";
|
||||
import zustand, { EventDataWithAvailability } from "@/Zustand";
|
||||
import { Add } from "@carbon/icons-react";
|
||||
import { Button } from "@heroui/react";
|
||||
import { Button, Tab, Tabs } from "@heroui/react";
|
||||
import { useAsyncList } from "@react-stately/data";
|
||||
import { useState } from "react";
|
||||
import AvailabilitySelector from "@/components/Event/AvailabilitySelector";
|
||||
|
||||
export default function Events() {
|
||||
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
|
||||
const admin = zustand((state) => state.user?.admin);
|
||||
const [filter, setFilter] = useState<string | number>("");
|
||||
|
||||
const events = useAsyncList<EventData>({
|
||||
const user = zustand((state) => state.user);
|
||||
|
||||
const events = useAsyncList({
|
||||
async load() {
|
||||
const result = await apiCall<EventData[]>("GET", "events/assignments");
|
||||
const result = await apiCall<EventDataWithAvailability[]>(
|
||||
"GET",
|
||||
"events/user/assignmentAvailability",
|
||||
);
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
|
||||
console.debug(data);
|
||||
|
||||
return {
|
||||
items: data,
|
||||
};
|
||||
@@ -31,20 +39,52 @@ export default function Events() {
|
||||
},
|
||||
});
|
||||
|
||||
function showEvent(event: EventDataWithAvailability): boolean {
|
||||
switch (filter) {
|
||||
case "assigned":
|
||||
return event.tasks.some((t) => {
|
||||
return t.userName === user?.userName;
|
||||
});
|
||||
|
||||
case "pending":
|
||||
return event.availability === null;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex-1">
|
||||
<h2 className="mb-4 text-center text-4xl">Upcoming Events</h2>
|
||||
<div className="relative flex flex-1 flex-col gap-4">
|
||||
<h2 className="text-center text-4xl">Upcoming Events</h2>
|
||||
|
||||
<Tabs
|
||||
selectedKey={filter}
|
||||
onSelectionChange={setFilter}
|
||||
color="primary"
|
||||
className="mx-auto"
|
||||
>
|
||||
<Tab key="all" title="All" />
|
||||
<Tab key="pending" title="Pending" />
|
||||
<Tab key="assigned" title="Assigned" />
|
||||
</Tabs>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{events.items.map((ee, ii) => (
|
||||
<Event key={ii} event={ee}>
|
||||
{events.items.filter(showEvent).map((e) => (
|
||||
<Event key={e.eventID} event={e}>
|
||||
<div className="mt-auto">
|
||||
<AssignmentTable tasks={ee.tasks} />
|
||||
<AvailabilitySelector
|
||||
event={e}
|
||||
className="mb-2"
|
||||
startSelection={e.availability}
|
||||
/>
|
||||
<AssignmentTable tasks={e.tasks} />
|
||||
</div>
|
||||
</Event>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{admin ? (
|
||||
{user?.admin ? (
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
|
||||
@@ -5,13 +5,15 @@ export default function AssignmentTable({
|
||||
tasks,
|
||||
highlightTask,
|
||||
highlightUser,
|
||||
className,
|
||||
}: {
|
||||
tasks: EventData["tasks"];
|
||||
highlightUser?: string;
|
||||
highlightTask?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<table>
|
||||
<table className={className}>
|
||||
<tbody>
|
||||
{tasks.map((task) => (
|
||||
<tr
|
||||
|
||||
81
client/src/components/Event/AvailabilitySelector.tsx
Normal file
81
client/src/components/Event/AvailabilitySelector.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Select, Selection, SelectItem } from "@heroui/react";
|
||||
import AvailabilityChip from "../AvailabilityChip";
|
||||
import { apiCall, getAvailabilities } from "@/lib";
|
||||
import { useAsyncList } from "@react-stately/data";
|
||||
import { EventAvailability } from "@/Zustand";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function AvailabilitySelector({
|
||||
event,
|
||||
className,
|
||||
startSelection,
|
||||
}: {
|
||||
event: EventAvailability;
|
||||
className?: string;
|
||||
startSelection?: number;
|
||||
}) {
|
||||
const [value, setValue] = useState<Selection>(new Set([]));
|
||||
|
||||
// the individual, selectable availabilities
|
||||
const availabilities = useAsyncList({
|
||||
async load() {
|
||||
const availabilities = (await getAvailabilities()).filter(
|
||||
(a) => a.enabled,
|
||||
);
|
||||
|
||||
// if the availabilities contain the startSelection, set it
|
||||
if (
|
||||
!!startSelection &&
|
||||
availabilities.some((a) => a.availabilityID === startSelection)
|
||||
) {
|
||||
setValue(new Set([startSelection.toString()]));
|
||||
}
|
||||
|
||||
return {
|
||||
items: availabilities,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
async function setAvailability(eventID: number, availabilityID: number) {
|
||||
await apiCall(
|
||||
"PUT",
|
||||
"events/user/availability",
|
||||
{ eventID },
|
||||
availabilityID,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={availabilities.items}
|
||||
label="Availability"
|
||||
variant="bordered"
|
||||
className={className}
|
||||
isMultiline
|
||||
renderValue={(availability) => (
|
||||
<div>
|
||||
{availability.map((a) =>
|
||||
!!a.data ? (
|
||||
<AvailabilityChip key={a.key} availability={a.data} />
|
||||
) : null,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
selectedKeys={value}
|
||||
onSelectionChange={(a) => {
|
||||
void setAvailability(event.eventID, parseInt(a.anchorKey ?? ""));
|
||||
setValue(a);
|
||||
}}
|
||||
>
|
||||
{(availability) => (
|
||||
<SelectItem
|
||||
key={availability.availabilityID}
|
||||
textValue={availability.availabilityName}
|
||||
>
|
||||
<AvailabilityChip availability={availability} />
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import LocalDate from "../LocalDate";
|
||||
import { BaseEvent } from "@/Zustand";
|
||||
import { Card, CardBody, CardHeader, Divider } from "@heroui/react";
|
||||
import { Card, CardBody, CardHeader, Divider, Textarea } from "@heroui/react";
|
||||
import React from "react";
|
||||
|
||||
export default function Event({
|
||||
@@ -34,7 +34,12 @@ export default function Event({
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<div>{event.description}</div>
|
||||
<Textarea
|
||||
isReadOnly
|
||||
label="Description"
|
||||
defaultValue={event.description}
|
||||
variant="bordered"
|
||||
/>
|
||||
|
||||
{children}
|
||||
</CardBody>
|
||||
|
||||
Reference in New Issue
Block a user