added zustand for state managing

This commit is contained in:
z1glr
2025-01-04 01:01:35 +00:00
parent b392978c17
commit 26030a5026
6 changed files with 174 additions and 70 deletions

View File

@@ -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
}
}
} }
} }
} }

View File

@@ -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",

View File

@@ -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" />
<CheckBox
state={state.tasks.includes(tt)}
onClick={() => toggleTask(tt)}
/> />
{tt} <input
</label> type="text"
placeholder="Description"
value={state.description}
onChange={(e) => setState({ ...state, description: e.target.value })}
/>
{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>

View File

@@ -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>
))} ))}

View File

@@ -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)}

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