added zustand for state managing
This commit is contained in:
36
client/package-lock.json
generated
36
client/package-lock.json
generated
@@ -11,7 +11,8 @@
|
|||||||
"@carbon/icons-react": "^11.53.0",
|
"@carbon/icons-react": "^11.53.0",
|
||||||
"next": "15.1.3",
|
"next": "15.1.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
@@ -976,7 +977,7 @@
|
|||||||
"version": "19.0.2",
|
"version": "19.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz",
|
||||||
"integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==",
|
"integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -1842,7 +1843,7 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
@@ -6016,6 +6017,35 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=18.0.0",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=18.0.0",
|
||||||
|
"use-sync-external-store": ">=1.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"@carbon/icons-react": "^11.53.0",
|
"@carbon/icons-react": "^11.53.0",
|
||||||
"next": "15.1.3",
|
"next": "15.1.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import CheckBox from "../CheckBox";
|
import CheckBox from "../CheckBox";
|
||||||
import { Task } from "./Event";
|
|
||||||
import { AddLarge } from "@carbon/icons-react";
|
import { AddLarge } from "@carbon/icons-react";
|
||||||
import Button from "../Button";
|
import Button from "../Button";
|
||||||
|
import zustand, { EventData, ISODate, Task, Tasks } from "../Zustand";
|
||||||
|
|
||||||
interface state {
|
interface state {
|
||||||
date: Date;
|
date: string;
|
||||||
description: string;
|
description: string;
|
||||||
tasks: string[];
|
tasks: Task[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AddEvent(props: { className?: string }) {
|
export default function AddEvent(props: {
|
||||||
const availableTasks = Object.keys(Task).filter((tt) => isNaN(Number(tt)));
|
className?: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
const [state, setState] = useState<state>({
|
const [state, setState] = useState<state>({
|
||||||
date: new Date(),
|
date: ISODate(new Date()),
|
||||||
description: "",
|
description: "",
|
||||||
tasks: [],
|
tasks: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
function toggleTask(task: string) {
|
function toggleTask(task: Task) {
|
||||||
const new_tasks = state.tasks.slice();
|
const new_tasks = state.tasks.slice();
|
||||||
|
|
||||||
const index = new_tasks.indexOf(task);
|
const index = new_tasks.indexOf(task);
|
||||||
@@ -36,26 +37,54 @@ export default function AddEvent(props: { className?: string }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addEvent() {
|
||||||
|
const eventData: EventData = {
|
||||||
|
date: state.date,
|
||||||
|
description: state.description,
|
||||||
|
id: zustand.getState().events.slice(-1)[0].id + 1,
|
||||||
|
tasks: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// add all the tasks
|
||||||
|
state.tasks.forEach((task) => {
|
||||||
|
eventData.tasks[task] = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
zustand.getState().addEvent(eventData);
|
||||||
|
|
||||||
|
props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${props.className ?? ""} flex w-64 flex-col gap-2 rounded-xl bg-accent-5 p-4`}
|
className={`${props.className ?? ""} flex w-64 flex-col gap-2 rounded-xl bg-accent-5 p-4`}
|
||||||
>
|
>
|
||||||
<h1 className="text-2xl">Add Event</h1>
|
<h1 className="text-2xl">Add Event</h1>
|
||||||
<input type="date" />
|
<input
|
||||||
<input type="text" placeholder="Description" />
|
type="date"
|
||||||
{availableTasks.map((tt, ii) => (
|
value={state.date}
|
||||||
<div key={ii}>
|
onChange={(e) => console.log(e.target.value)}
|
||||||
<label className="flex items-center gap-2">
|
/>
|
||||||
<input type="checkbox" className="hidden" />
|
<input
|
||||||
<CheckBox
|
type="text"
|
||||||
state={state.tasks.includes(tt)}
|
placeholder="Description"
|
||||||
onClick={() => toggleTask(tt)}
|
value={state.description}
|
||||||
/>
|
onChange={(e) => setState({ ...state, description: e.target.value })}
|
||||||
{tt}
|
/>
|
||||||
</label>
|
{Tasks.map((task, ii) => (
|
||||||
|
<div
|
||||||
|
key={ii}
|
||||||
|
onClick={() => toggleTask(task)}
|
||||||
|
className="flex cursor-default items-center gap-2"
|
||||||
|
>
|
||||||
|
<CheckBox state={state.tasks.includes(task)} />
|
||||||
|
{task}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button className="ml-auto flex w-fit items-center justify-center gap-2 pr-4">
|
<Button
|
||||||
|
className="ml-auto flex w-fit items-center justify-center gap-2 pr-4"
|
||||||
|
onClick={addEvent}
|
||||||
|
>
|
||||||
<AddLarge size={32} />
|
<AddLarge size={32} />
|
||||||
Add
|
Add
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,30 +1,22 @@
|
|||||||
export enum Task {
|
import { EventData } from "../Zustand";
|
||||||
Audio,
|
|
||||||
Livestream,
|
|
||||||
Camera,
|
|
||||||
Light,
|
|
||||||
StreamAudio,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventData {
|
|
||||||
id: number;
|
|
||||||
date: Date;
|
|
||||||
tasks: Partial<Record<Task, string | undefined>>;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Event(props: EventData) {
|
export default function Event(props: EventData) {
|
||||||
return (
|
return (
|
||||||
<div key={props.id} className="w-64 rounded-xl bg-accent-5 p-4">
|
<div
|
||||||
<h3 className="bold mb-1 text-2xl">{props.date.toLocaleDateString()}</h3>
|
key={props.id}
|
||||||
{props.description !== undefined ? <div>{props.description}</div> : null}
|
className="flex w-64 flex-col gap-2 rounded-xl bg-accent-5 p-4"
|
||||||
<table className="mt-4">
|
>
|
||||||
|
<h3 className="bold mb-1 text-2xl">{props.date}</h3>
|
||||||
|
<div>{props.description}</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<caption>
|
||||||
|
<h4>Task assignment</h4>
|
||||||
|
</caption>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Object.entries(props.tasks).map(([task, person], ii) => (
|
{Object.entries(props.tasks).map(([task, person], ii) => (
|
||||||
<tr key={ii}>
|
<tr key={ii}>
|
||||||
<th className="pr-4 text-left">
|
<th className="pr-4 text-left">{task}</th>
|
||||||
{Task[task as unknown as Task]}
|
|
||||||
</th>
|
|
||||||
<td>{person ?? <span className="text-primary">missing</span>}</td>
|
<td>{person ?? <span className="text-primary">missing</span>}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,39 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AddLarge, CloseLarge } from "@carbon/icons-react";
|
import { AddLarge, CloseLarge } from "@carbon/icons-react";
|
||||||
import Event, { EventData } from "./Event/Event";
|
import Event from "./Event/Event";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import AddEvent from "./Event/AddEvent";
|
import AddEvent from "./Event/AddEvent";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
import zustand from "./Zustand";
|
||||||
|
|
||||||
export default function EventVolunteer() {
|
export default function EventVolunteer() {
|
||||||
const events: EventData[] = [
|
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
date: new Date("2025-01-05"),
|
|
||||||
tasks: {
|
|
||||||
"0": "Mark",
|
|
||||||
},
|
|
||||||
description: "neuer Prädikant",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
date: new Date("2025-01-12"),
|
|
||||||
tasks: {
|
|
||||||
"0": "Mark",
|
|
||||||
"1": undefined,
|
|
||||||
},
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const [showAddItemDialogue, setShowAddItemDialogue] = useState(true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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">
|
||||||
{events.map((ee) => Event(ee))}
|
{zustand.getState().events.map((ee) => Event(ee))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -54,7 +35,10 @@ export default function EventVolunteer() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<AddEvent className="border-2 border-accent-3" />
|
<AddEvent
|
||||||
|
className="border-2 border-accent-3"
|
||||||
|
onClose={() => setShowAddItemDialogue(false)}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="absolute right-2 top-2 aspect-square"
|
className="absolute right-2 top-2 aspect-square"
|
||||||
onClick={() => setShowAddItemDialogue(false)}
|
onClick={() => setShowAddItemDialogue(false)}
|
||||||
|
|||||||
68
client/src/app/components/Zustand.ts
Normal file
68
client/src/app/components/Zustand.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export enum Task {
|
||||||
|
Audio = "Audio",
|
||||||
|
Livestream = "Livestream",
|
||||||
|
Camera = "Camera",
|
||||||
|
Light = "Light",
|
||||||
|
StreamAudio = "Stream Audio",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaskKey = keyof typeof Task;
|
||||||
|
export const Tasks = Object.values(Task) as Task[];
|
||||||
|
|
||||||
|
export interface EventData {
|
||||||
|
id: number;
|
||||||
|
date: string;
|
||||||
|
tasks: Partial<Record<Task, string | undefined>>;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Zustand {
|
||||||
|
events: EventData[];
|
||||||
|
addEvent: (event: EventData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zustand = create<Zustand>()((set) => ({
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
date: "2025-01-05",
|
||||||
|
tasks: {
|
||||||
|
Audio: "Mark",
|
||||||
|
Livestream: undefined,
|
||||||
|
"Stream Audio": undefined,
|
||||||
|
},
|
||||||
|
description: "neuer Prädikant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
date: "2025-01-12",
|
||||||
|
tasks: {
|
||||||
|
Audio: "Mark",
|
||||||
|
Livestream: undefined,
|
||||||
|
},
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
addEvent: (event: EventData) =>
|
||||||
|
set((state) => ({ events: state.events.toSpliced(-1, 0, event) })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function ISODate(dt: string | Date): string {
|
||||||
|
if (typeof dt === "string") {
|
||||||
|
dt = new Date(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = String(dt.getFullYear()).padStart(4, "0");
|
||||||
|
const month = String(dt.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(dt.getDate()).padStart(2, "0");
|
||||||
|
|
||||||
|
const date = `${year}-${month}-${day}`;
|
||||||
|
|
||||||
|
console.debug(date);
|
||||||
|
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default zustand;
|
||||||
Reference in New Issue
Block a user