added user-overview for setting availability

This commit is contained in:
z1glr
2025-01-06 21:40:14 +00:00
parent 9a7a80ac80
commit 063f22569d
9 changed files with 190 additions and 63 deletions

View File

@@ -17,6 +17,7 @@
"@nextui-org/divider": "^2.2.5", "@nextui-org/divider": "^2.2.5",
"@nextui-org/input": "^2.4.8", "@nextui-org/input": "^2.4.8",
"@nextui-org/modal": "^2.2.7", "@nextui-org/modal": "^2.2.7",
"@nextui-org/radio": "^2.3.8",
"@nextui-org/select": "^2.4.9", "@nextui-org/select": "^2.4.9",
"@nextui-org/system": "^2.4.6", "@nextui-org/system": "^2.4.6",
"@nextui-org/table": "^2.2.8", "@nextui-org/table": "^2.2.8",
@@ -1334,6 +1335,31 @@
"react-dom": ">=18 || >=19.0.0-rc.0" "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": { "node_modules/@nextui-org/react-rsc-utils": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@nextui-org/react-rsc-utils/-/react-rsc-utils-2.1.1.tgz", "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" "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": { "node_modules/@react-aria/selection": {
"version": "3.21.0", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.21.0.tgz", "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" "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": { "node_modules/@react-stately/selection": {
"version": "3.18.0", "version": "3.18.0",
"resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.18.0.tgz", "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" "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": { "node_modules/@react-types/select": {
"version": "3.9.8", "version": "3.9.8",
"resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.8.tgz", "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.8.tgz",

View File

@@ -18,6 +18,7 @@
"@nextui-org/divider": "^2.2.5", "@nextui-org/divider": "^2.2.5",
"@nextui-org/input": "^2.4.8", "@nextui-org/input": "^2.4.8",
"@nextui-org/modal": "^2.2.7", "@nextui-org/modal": "^2.2.7",
"@nextui-org/radio": "^2.3.8",
"@nextui-org/select": "^2.4.9", "@nextui-org/select": "^2.4.9",
"@nextui-org/system": "^2.4.6", "@nextui-org/system": "^2.4.6",
"@nextui-org/table": "^2.2.8", "@nextui-org/table": "^2.2.8",

View File

@@ -1,8 +1,4 @@
import { import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
DateFormatter as IntlDateFormatter,
parseZonedDateTime,
ZonedDateTime,
} from "@internationalized/date";
import { create } from "zustand"; import { create } from "zustand";
export type Task = string; export type Task = string;
@@ -21,7 +17,7 @@ export const Availabilities: Availability[] = ["yes", "maybe", "no"];
export interface EventData { export interface EventData {
id: number; id: number;
date: ZonedDateTime; date: string;
tasks: Partial<Record<Task, string | undefined>>; tasks: Partial<Record<Task, string | undefined>>;
volunteers: Partial<Record<string, Availability>>; volunteers: Partial<Record<string, Availability>>;
description: string; description: string;
@@ -37,7 +33,7 @@ const zustand = create<Zustand>()((set) => ({
{ {
id: 0, id: 0,
// date: parseDateTime("2025-01-05T11:00[Europe/Berlin]").toString(), // 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: { tasks: {
Audio: "Mark", Audio: "Mark",
Livestream: undefined, Livestream: undefined,
@@ -48,7 +44,7 @@ const zustand = create<Zustand>()((set) => ({
}, },
{ {
id: 1, id: 1,
date: parseZonedDateTime("2025-01-12T11:00[Europe/Berlin]"), date: "2025-01-12T11:00[Europe/Berlin]",
tasks: { tasks: {
Audio: "Mark", Audio: "Mark",
Livestream: undefined, Livestream: undefined,

View File

@@ -6,6 +6,7 @@ import { useState } from "react";
import AddEvent from "../components/Event/AddEvent"; import AddEvent from "../components/Event/AddEvent";
import zustand from "../Zustand"; import zustand from "../Zustand";
import { Button } from "@nextui-org/button"; import { Button } from "@nextui-org/button";
import AssignmentTable from "@/components/Event/AssignmentTable";
export default function EventVolunteer() { export default function EventVolunteer() {
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false); const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
@@ -14,15 +15,10 @@ export default function EventVolunteer() {
<div className="relative flex-1 p-4"> <div className="relative flex-1 p-4">
<h2 className="mb-4 text-center text-4xl">Overview</h2> <h2 className="mb-4 text-center text-4xl">Overview</h2>
<div className="flex flex-wrap justify-center gap-4"> <div className="flex flex-wrap justify-center gap-4">
{zustand.getState().events.map((ee, ii) => ( {zustand.getState().events.map((ee) => (
<Event <Event key={ee.id} event={ee}>
key={ii} <AssignmentTable tasks={ee.tasks} />
date={ee.date} </Event>
description={ee.description}
id={ee.id}
tasks={ee.tasks}
volunteers={ee.volunteers}
/>
))} ))}
</div> </div>

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import AddEvent from "@/components/Event/AddEvent"; 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 zustand, { Availability, EventData, Task, Tasks } from "@/Zustand";
import { Add, Copy, Edit, TrashCan } from "@carbon/icons-react"; import { Add, Copy, Edit, TrashCan } from "@carbon/icons-react";
import { Button, ButtonGroup } from "@nextui-org/button"; import { Button, ButtonGroup } from "@nextui-org/button";
@@ -25,6 +25,26 @@ import { Tooltip } from "@nextui-org/tooltip";
import { useAsyncList } from "@react-stately/data"; import { useAsyncList } from "@react-stately/data";
import React, { Key, useState } from "react"; 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() { export default function AdminPanel() {
const tasks = [ const tasks = [
{ key: "date", label: "Date" }, { key: "date", label: "Date" },
@@ -70,7 +90,7 @@ export default function AdminPanel() {
options={{ dateStyle: "medium", timeStyle: "short" }} options={{ dateStyle: "medium", timeStyle: "short" }}
className="font-bold" className="font-bold"
> >
{event[key].toDate()} {event[key]}
</LocalDate> </LocalDate>
); );
case "description": case "description":
@@ -104,26 +124,6 @@ export default function AdminPanel() {
</div> </div>
); );
default: 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 ( return (
<Select <Select
variant="underlined" variant="underlined"
@@ -230,7 +230,7 @@ export default function AdminPanel() {
The event{" "} The event{" "}
<span className="font-numbers text-accent-1"> <span className="font-numbers text-accent-1">
<LocalDate options={{ dateStyle: "long", timeStyle: "short" }}> <LocalDate options={{ dateStyle: "long", timeStyle: "short" }}>
{activeEvent.date.toDate()} {activeEvent.date}
</LocalDate> </LocalDate>
</span>{" "} </span>{" "}
will be deleted. will be deleted.

View File

@@ -1,3 +1,42 @@
export default function OverviewPersonal() { import Event from "@/components/Event/Event";
return <div>foobar</div>; 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>
);
} }

View 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>
);
}

View File

@@ -1,9 +1,18 @@
"use client";
import { Card, CardBody, CardHeader } from "@nextui-org/card"; import { Card, CardBody, CardHeader } from "@nextui-org/card";
import { Divider } from "@nextui-org/divider"; import { Divider } from "@nextui-org/divider";
import LocalDate from "./LocalDate"; import LocalDate from "../LocalDate";
import { EventData } from "@/Zustand"; 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 ( return (
<Card <Card
classNames={{ classNames={{
@@ -21,29 +30,15 @@ export default function Event(props: EventData) {
timeZone: "Europe/Berlin", // TODO: check with actual backend timeZone: "Europe/Berlin", // TODO: check with actual backend
}} }}
> >
{props.date.toDate()} {event.date}
</LocalDate> </LocalDate>
</h3> </h3>
</CardHeader> </CardHeader>
<Divider /> <Divider />
<CardBody> <CardBody>
<div>{props.description}</div> <div>{event.description}</div>
<table> {children}
<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>
</CardBody> </CardBody>
</Card> </Card>
); );

View File

@@ -1,16 +1,19 @@
"use local"; "use local";
import { DateFormatter } from "@/Zustand"; import { DateFormatter } from "@/Zustand";
import { parseZonedDateTime } from "@internationalized/date";
import { useLocale } from "@react-aria/i18n"; import { useLocale } from "@react-aria/i18n";
export default function LocalDate(props: { export default function LocalDate(props: {
children: Date; children: string;
className?: string; className?: string;
options: Intl.DateTimeFormatOptions; options: Intl.DateTimeFormatOptions;
}) { }) {
const formatter = new DateFormatter(useLocale().locale, props.options); const formatter = new DateFormatter(useLocale().locale, props.options);
return ( return (
<span className={props.className}>{formatter.format(props.children)}</span> <span className={props.className}>
{formatter.format(parseZonedDateTime(props.children).toDate())}
</span>
); );
} }