added user-overview for setting availability
This commit is contained in:
75
client/package-lock.json
generated
75
client/package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@nextui-org/divider": "^2.2.5",
|
||||
"@nextui-org/input": "^2.4.8",
|
||||
"@nextui-org/modal": "^2.2.7",
|
||||
"@nextui-org/radio": "^2.3.8",
|
||||
"@nextui-org/select": "^2.4.9",
|
||||
"@nextui-org/system": "^2.4.6",
|
||||
"@nextui-org/table": "^2.2.8",
|
||||
@@ -1334,6 +1335,31 @@
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextui-org/radio": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@nextui-org/radio/-/radio-2.3.8.tgz",
|
||||
"integrity": "sha512-ntwjpQ/WT8zQ3Fw5io65VeH2Q68LOgZ4lII7a6x35NDa7Eda1vlYroMAw/vxK8iyZYlUBSJdsoj2FU/10hBPmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nextui-org/form": "2.1.8",
|
||||
"@nextui-org/react-utils": "2.1.3",
|
||||
"@nextui-org/shared-utils": "2.1.2",
|
||||
"@react-aria/focus": "3.19.0",
|
||||
"@react-aria/interactions": "3.22.5",
|
||||
"@react-aria/radio": "3.10.10",
|
||||
"@react-aria/utils": "3.26.0",
|
||||
"@react-aria/visually-hidden": "3.8.18",
|
||||
"@react-stately/radio": "3.10.9",
|
||||
"@react-types/radio": "3.8.5",
|
||||
"@react-types/shared": "3.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nextui-org/system": ">=2.4.0",
|
||||
"@nextui-org/theme": ">=2.4.3",
|
||||
"react": ">=18 || >=19.0.0-rc.0",
|
||||
"react-dom": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextui-org/react-rsc-utils": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@nextui-org/react-rsc-utils/-/react-rsc-utils-2.1.1.tgz",
|
||||
@@ -2080,6 +2106,27 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/radio": {
|
||||
"version": "3.10.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.10.tgz",
|
||||
"integrity": "sha512-NVdeOVrsrHgSfwL2jWCCXFsWZb+RMRZErj5vthHQW4nkHECGOzeX56VaLWTSvdoCPqi9wdIX8A6K9peeAIgxzA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-aria/focus": "^3.19.0",
|
||||
"@react-aria/form": "^3.0.11",
|
||||
"@react-aria/i18n": "^3.12.4",
|
||||
"@react-aria/interactions": "^3.22.5",
|
||||
"@react-aria/label": "^3.7.13",
|
||||
"@react-aria/utils": "^3.26.0",
|
||||
"@react-stately/radio": "^3.10.9",
|
||||
"@react-types/radio": "^3.8.5",
|
||||
"@react-types/shared": "^3.26.0",
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/selection": {
|
||||
"version": "3.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.21.0.tgz",
|
||||
@@ -2431,6 +2478,22 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/radio": {
|
||||
"version": "3.10.9",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.9.tgz",
|
||||
"integrity": "sha512-kUQ7VdqFke8SDRCatw2jW3rgzMWbvw+n2imN2THETynI47NmNLzNP11dlGO2OllRtTrsLhmBNlYHa3W62pFpAw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-stately/form": "^3.1.0",
|
||||
"@react-stately/utils": "^3.10.5",
|
||||
"@react-types/radio": "^3.8.5",
|
||||
"@react-types/shared": "^3.26.0",
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/selection": {
|
||||
"version": "3.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.18.0.tgz",
|
||||
@@ -2663,6 +2726,18 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-types/radio": {
|
||||
"version": "3.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.5.tgz",
|
||||
"integrity": "sha512-gSImTPid6rsbJmwCkTliBIU/npYgJHOFaI3PNJo7Y0QTAnFelCtYeFtBiWrFodSArSv7ASqpLLUEj9hZu/rxIg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@react-types/shared": "^3.26.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-types/select": {
|
||||
"version": "3.9.8",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.8.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@nextui-org/divider": "^2.2.5",
|
||||
"@nextui-org/input": "^2.4.8",
|
||||
"@nextui-org/modal": "^2.2.7",
|
||||
"@nextui-org/radio": "^2.3.8",
|
||||
"@nextui-org/select": "^2.4.9",
|
||||
"@nextui-org/system": "^2.4.6",
|
||||
"@nextui-org/table": "^2.2.8",
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
DateFormatter as IntlDateFormatter,
|
||||
parseZonedDateTime,
|
||||
ZonedDateTime,
|
||||
} from "@internationalized/date";
|
||||
import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
|
||||
import { create } from "zustand";
|
||||
|
||||
export type Task = string;
|
||||
@@ -21,7 +17,7 @@ export const Availabilities: Availability[] = ["yes", "maybe", "no"];
|
||||
|
||||
export interface EventData {
|
||||
id: number;
|
||||
date: ZonedDateTime;
|
||||
date: string;
|
||||
tasks: Partial<Record<Task, string | undefined>>;
|
||||
volunteers: Partial<Record<string, Availability>>;
|
||||
description: string;
|
||||
@@ -37,7 +33,7 @@ const zustand = create<Zustand>()((set) => ({
|
||||
{
|
||||
id: 0,
|
||||
// date: parseDateTime("2025-01-05T11:00[Europe/Berlin]").toString(),
|
||||
date: parseZonedDateTime("2025-01-05T11:00[Europe/Berlin]"),
|
||||
date: "2025-01-05T11:00[Europe/Berlin]",
|
||||
tasks: {
|
||||
Audio: "Mark",
|
||||
Livestream: undefined,
|
||||
@@ -48,7 +44,7 @@ const zustand = create<Zustand>()((set) => ({
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
date: parseZonedDateTime("2025-01-12T11:00[Europe/Berlin]"),
|
||||
date: "2025-01-12T11:00[Europe/Berlin]",
|
||||
tasks: {
|
||||
Audio: "Mark",
|
||||
Livestream: undefined,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useState } from "react";
|
||||
import AddEvent from "../components/Event/AddEvent";
|
||||
import zustand from "../Zustand";
|
||||
import { Button } from "@nextui-org/button";
|
||||
import AssignmentTable from "@/components/Event/AssignmentTable";
|
||||
|
||||
export default function EventVolunteer() {
|
||||
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
|
||||
@@ -14,15 +15,10 @@ export default function EventVolunteer() {
|
||||
<div className="relative flex-1 p-4">
|
||||
<h2 className="mb-4 text-center text-4xl">Overview</h2>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{zustand.getState().events.map((ee, ii) => (
|
||||
<Event
|
||||
key={ii}
|
||||
date={ee.date}
|
||||
description={ee.description}
|
||||
id={ee.id}
|
||||
tasks={ee.tasks}
|
||||
volunteers={ee.volunteers}
|
||||
/>
|
||||
{zustand.getState().events.map((ee) => (
|
||||
<Event key={ee.id} event={ee}>
|
||||
<AssignmentTable tasks={ee.tasks} />
|
||||
</Event>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import AddEvent from "@/components/Event/AddEvent";
|
||||
import LocalDate from "@/components/Event/LocalDate";
|
||||
import LocalDate from "@/components/LocalDate";
|
||||
import zustand, { Availability, EventData, Task, Tasks } from "@/Zustand";
|
||||
import { Add, Copy, Edit, TrashCan } from "@carbon/icons-react";
|
||||
import { Button, ButtonGroup } from "@nextui-org/button";
|
||||
@@ -25,6 +25,26 @@ import { Tooltip } from "@nextui-org/tooltip";
|
||||
import { useAsyncList } from "@react-stately/data";
|
||||
import React, { Key, useState } from "react";
|
||||
|
||||
function availability2Tailwind(availability?: Availability) {
|
||||
switch (availability) {
|
||||
case "yes":
|
||||
return "";
|
||||
default:
|
||||
return "italic";
|
||||
}
|
||||
}
|
||||
|
||||
function availability2Color(availability?: Availability) {
|
||||
switch (availability) {
|
||||
case "yes":
|
||||
return "default";
|
||||
case "maybe":
|
||||
return "warning";
|
||||
default:
|
||||
return "danger";
|
||||
}
|
||||
}
|
||||
|
||||
export default function AdminPanel() {
|
||||
const tasks = [
|
||||
{ key: "date", label: "Date" },
|
||||
@@ -70,7 +90,7 @@ export default function AdminPanel() {
|
||||
options={{ dateStyle: "medium", timeStyle: "short" }}
|
||||
className="font-bold"
|
||||
>
|
||||
{event[key].toDate()}
|
||||
{event[key]}
|
||||
</LocalDate>
|
||||
);
|
||||
case "description":
|
||||
@@ -104,26 +124,6 @@ export default function AdminPanel() {
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
function availability2Tailwind(availability?: Availability) {
|
||||
switch (availability) {
|
||||
case "yes":
|
||||
return "";
|
||||
default:
|
||||
return "italic";
|
||||
}
|
||||
}
|
||||
|
||||
function availability2Color(availability?: Availability) {
|
||||
switch (availability) {
|
||||
case "yes":
|
||||
return "default";
|
||||
case "maybe":
|
||||
return "warning";
|
||||
default:
|
||||
return "danger";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
variant="underlined"
|
||||
@@ -230,7 +230,7 @@ export default function AdminPanel() {
|
||||
The event{" "}
|
||||
<span className="font-numbers text-accent-1">
|
||||
<LocalDate options={{ dateStyle: "long", timeStyle: "short" }}>
|
||||
{activeEvent.date.toDate()}
|
||||
{activeEvent.date}
|
||||
</LocalDate>
|
||||
</span>{" "}
|
||||
will be deleted.
|
||||
|
||||
@@ -1,3 +1,42 @@
|
||||
export default function OverviewPersonal() {
|
||||
return <div>foobar</div>;
|
||||
import Event from "@/components/Event/Event";
|
||||
import zustand, { Availabilities, Availability } from "@/Zustand";
|
||||
import { Radio, RadioGroup } from "@nextui-org/radio";
|
||||
|
||||
function availability2Color(availability: Availability) {
|
||||
switch (availability) {
|
||||
case "yes":
|
||||
return "success";
|
||||
case "maybe":
|
||||
return "warning";
|
||||
default:
|
||||
return "danger";
|
||||
}
|
||||
}
|
||||
|
||||
export default function OverviewPersonal() {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-4 text-center text-4xl">Upcoming Events</h2>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{zustand.getState().events.map((ev) => (
|
||||
<Event key={ev.id} event={ev}>
|
||||
<RadioGroup className="mt-auto" orientation="horizontal">
|
||||
{Availabilities.map((availability) => (
|
||||
<Radio
|
||||
value={availability}
|
||||
key={availability}
|
||||
color={availability2Color(availability)}
|
||||
classNames={{
|
||||
label: `text-${availability2Color(availability)}`,
|
||||
}}
|
||||
>
|
||||
{availability}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</Event>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
22
client/src/components/Event/AssignmentTable.tsx
Normal file
22
client/src/components/Event/AssignmentTable.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { EventData } from "@/Zustand";
|
||||
|
||||
export default function AssignmentTable({
|
||||
tasks,
|
||||
}: {
|
||||
tasks: EventData["tasks"];
|
||||
}) {
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{Object.entries(tasks).map(([task, person]) => (
|
||||
<tr key={task}>
|
||||
<th className="pr-4 text-left">{task}</th>
|
||||
<td>
|
||||
{person ?? <span className="italic text-highlight">missing</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardBody, CardHeader } from "@nextui-org/card";
|
||||
import { Divider } from "@nextui-org/divider";
|
||||
import LocalDate from "./LocalDate";
|
||||
import LocalDate from "../LocalDate";
|
||||
import { EventData } from "@/Zustand";
|
||||
import React from "react";
|
||||
|
||||
export default function Event(props: EventData) {
|
||||
export default function Event({
|
||||
event,
|
||||
children,
|
||||
}: {
|
||||
event: EventData;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card
|
||||
classNames={{
|
||||
@@ -21,29 +30,15 @@ export default function Event(props: EventData) {
|
||||
timeZone: "Europe/Berlin", // TODO: check with actual backend
|
||||
}}
|
||||
>
|
||||
{props.date.toDate()}
|
||||
{event.date}
|
||||
</LocalDate>
|
||||
</h3>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<div>{props.description}</div>
|
||||
<div>{event.description}</div>
|
||||
|
||||
<table>
|
||||
<caption>
|
||||
<h4>Task assignment</h4>
|
||||
</caption>
|
||||
<tbody>
|
||||
{Object.entries(props.tasks).map(([task, person], ii) => (
|
||||
<tr key={ii}>
|
||||
<th className="pr-4 text-left">{task}</th>
|
||||
<td>
|
||||
{person ?? <span className="text-highlight">missing</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{children}
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use local";
|
||||
|
||||
import { DateFormatter } from "@/Zustand";
|
||||
import { parseZonedDateTime } from "@internationalized/date";
|
||||
import { useLocale } from "@react-aria/i18n";
|
||||
|
||||
export default function LocalDate(props: {
|
||||
children: Date;
|
||||
children: string;
|
||||
className?: string;
|
||||
options: Intl.DateTimeFormatOptions;
|
||||
}) {
|
||||
const formatter = new DateFormatter(useLocale().locale, props.options);
|
||||
|
||||
return (
|
||||
<span className={props.className}>{formatter.format(props.children)}</span>
|
||||
<span className={props.className}>
|
||||
{formatter.format(parseZonedDateTime(props.children).toDate())}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user