fixed assignments view once again
This commit is contained in:
@@ -33,7 +33,7 @@ export default function Main({ children }: { children: React.ReactNode }) {
|
||||
const response = await welcomeResult.json();
|
||||
|
||||
if (response.userName !== undefined && response.userName !== "") {
|
||||
zustand.getState().reset({ user: response });
|
||||
zustand.getState().patch({ user: response });
|
||||
|
||||
loggedIn = true;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { color2Tailwind, colors } from "@/components/Colorselector";
|
||||
import { apiCall } from "@/lib";
|
||||
import { AddLarge, Edit } from "@carbon/icons-react";
|
||||
import { colors } from "@/components/Colorselector";
|
||||
import { apiCall, getAvailabilities } from "@/lib";
|
||||
import { AddLarge, Edit, TrashCan } from "@carbon/icons-react";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -18,26 +18,20 @@ import { useState } from "react";
|
||||
import AddAvailability from "./AddAvailability";
|
||||
import { Availability } from "./AvailabilityEditor";
|
||||
import EditAvailability from "./EditAvailability";
|
||||
import DeleteConfirmation from "@/components/DeleteConfirmation";
|
||||
import AvailabilityChip from "@/components/AvailabilityChip";
|
||||
import zustand from "@/Zustand";
|
||||
|
||||
export default function Availabilities() {
|
||||
const [showAddAvailability, setShowAddAvailability] = useState(false);
|
||||
const [editAvailability, setEditAvailability] = useState<Availability>();
|
||||
const [deleteAvailability, setDeleteAvailability] = useState<Availability>();
|
||||
|
||||
const availabilities = useAsyncList<Availability>({
|
||||
async load() {
|
||||
const result = await apiCall("GET", "availabilities");
|
||||
|
||||
if (result.ok) {
|
||||
const json = await result.json();
|
||||
|
||||
return {
|
||||
items: json.availabilities,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
items: await getAvailabilities(),
|
||||
};
|
||||
},
|
||||
async sort({ items, sortDescriptor }) {
|
||||
return {
|
||||
@@ -46,7 +40,7 @@ export default function Availabilities() {
|
||||
|
||||
switch (sortDescriptor.column) {
|
||||
case "text":
|
||||
cmp = a.text.localeCompare(b.text);
|
||||
cmp = a.name.localeCompare(b.name);
|
||||
break;
|
||||
case "enabled":
|
||||
if (a.enabled && !b.enabled) {
|
||||
@@ -76,6 +70,26 @@ export default function Availabilities() {
|
||||
},
|
||||
});
|
||||
|
||||
function reload() {
|
||||
// clear the availabilites in the zustand
|
||||
zustand.getState().patch({ availabilities: undefined });
|
||||
|
||||
// refresh the availabilites
|
||||
availabilities.reload();
|
||||
}
|
||||
|
||||
async function sendDeleteAvailability(id: number | undefined) {
|
||||
if (id !== undefined) {
|
||||
const result = await apiCall("DELETE", "availabilities", { id });
|
||||
|
||||
if (result.ok) {
|
||||
reload();
|
||||
|
||||
setDeleteAvailability(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const topContent = (
|
||||
<>
|
||||
<Button
|
||||
@@ -101,10 +115,7 @@ export default function Availabilities() {
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn allowsSorting key="userName">
|
||||
Text
|
||||
</TableColumn>
|
||||
<TableColumn allowsSorting key="color" align="center">
|
||||
Color
|
||||
Name
|
||||
</TableColumn>
|
||||
<TableColumn allowsSorting key="admin" align="center">
|
||||
Enabled
|
||||
@@ -115,35 +126,36 @@ export default function Availabilities() {
|
||||
</TableHeader>
|
||||
<TableBody items={availabilities.items}>
|
||||
{(availability) => (
|
||||
<TableRow key={availability.text}>
|
||||
<TableCell
|
||||
className={`text-${color2Tailwind(availability.color)}`}
|
||||
>
|
||||
{availability.text}
|
||||
</TableCell>
|
||||
<TableRow key={availability.name}>
|
||||
<TableCell>
|
||||
<Chip
|
||||
classNames={{
|
||||
base: `bg-${color2Tailwind(availability.color)}`,
|
||||
}}
|
||||
>
|
||||
{availability.color}
|
||||
</Chip>
|
||||
<AvailabilityChip availability={availability} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox isSelected={availability.enabled} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setEditAvailability(availability)}
|
||||
>
|
||||
<Tooltip content="Edit availability">
|
||||
<Edit />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setEditAvailability(availability)}
|
||||
>
|
||||
<Tooltip content="Edit availability">
|
||||
<Edit />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setDeleteAvailability(availability)}
|
||||
color="danger"
|
||||
className="text-danger"
|
||||
>
|
||||
<TrashCan />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
@@ -153,7 +165,7 @@ export default function Availabilities() {
|
||||
<AddAvailability
|
||||
isOpen={showAddAvailability}
|
||||
onOpenChange={setShowAddAvailability}
|
||||
onSuccess={availabilities.reload}
|
||||
onSuccess={reload}
|
||||
/>
|
||||
|
||||
<EditAvailability
|
||||
@@ -162,8 +174,25 @@ export default function Availabilities() {
|
||||
onOpenChange={(isOpen) =>
|
||||
!isOpen ? setEditAvailability(undefined) : null
|
||||
}
|
||||
onSuccess={availabilities.reload}
|
||||
onSuccess={reload}
|
||||
/>
|
||||
|
||||
<DeleteConfirmation
|
||||
isOpen={!!deleteAvailability}
|
||||
onOpenChange={(isOpen) =>
|
||||
!isOpen ? setDeleteAvailability(undefined) : null
|
||||
}
|
||||
header="Delete Availability"
|
||||
onDelete={() => sendDeleteAvailability(deleteAvailability?.id)}
|
||||
>
|
||||
{!!deleteAvailability ? (
|
||||
<>
|
||||
The availability{" "}
|
||||
<AvailabilityChip availability={deleteAvailability} /> will be
|
||||
deleted.
|
||||
</>
|
||||
) : null}
|
||||
</DeleteConfirmation>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
} from "@heroui/react";
|
||||
import React, { FormEvent, useState } from "react";
|
||||
import React, { FormEvent, useEffect, useState } from "react";
|
||||
|
||||
export interface Availability {
|
||||
text: string;
|
||||
name: string;
|
||||
color: string;
|
||||
id: number | undefined;
|
||||
enabled: boolean;
|
||||
@@ -26,13 +26,22 @@ export default function AvailabilityEditor(props: {
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
onSubmit?: (e: Availability) => void;
|
||||
}) {
|
||||
const [text, setText] = useState(props.value?.text ?? "");
|
||||
const [name, setName] = useState(props.value?.name ?? "");
|
||||
const [color, setColor] = useState(props.value?.color ?? "Red");
|
||||
const [enabled, setEnabled] = useState(props.value?.enabled ?? true);
|
||||
|
||||
// clear the inputs on closing
|
||||
useEffect(() => {
|
||||
if (!props.isOpen) {
|
||||
setName("");
|
||||
setColor("");
|
||||
setEnabled(true);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
function submit(e: FormEvent<HTMLFormElement>) {
|
||||
const formData = Object.fromEntries(new FormData(e.currentTarget)) as {
|
||||
text: string;
|
||||
name: string;
|
||||
color: string;
|
||||
enabled: string;
|
||||
};
|
||||
@@ -64,10 +73,10 @@ export default function AvailabilityEditor(props: {
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Input
|
||||
value={text}
|
||||
onValueChange={setText}
|
||||
name="text"
|
||||
label="Text"
|
||||
value={name}
|
||||
onValueChange={setName}
|
||||
name="name"
|
||||
label="Name"
|
||||
isRequired
|
||||
variant="bordered"
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { apiCall } from "@/lib";
|
||||
import AvailabilityEditor, { Availability } from "./AvailabilityEditor";
|
||||
import { Button } from "@heroui/react";
|
||||
import { Renew } from "@carbon/icons-react";
|
||||
import AvailabilityChip from "@/components/AvailabilityChip";
|
||||
|
||||
export default function EditAvailability(props: {
|
||||
value: Availability | undefined;
|
||||
@@ -24,9 +25,9 @@ export default function EditAvailability(props: {
|
||||
header={
|
||||
<>
|
||||
Edit Availability{" "}
|
||||
<span className="font-numbers font-normal italic">
|
||||
"{props.value?.text}"
|
||||
</span>
|
||||
{!!props.value ? (
|
||||
<AvailabilityChip availability={props.value} className="ms-4" />
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { apiCall } from "@/lib";
|
||||
import TaskEditor, { Task } from "./TaskEditor";
|
||||
import { apiCall, Task } from "@/lib";
|
||||
import TaskEditor from "./TaskEditor";
|
||||
import { Button } from "@heroui/react";
|
||||
import { AddLarge } from "@carbon/icons-react";
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ export default function EditTask(props: {
|
||||
|
||||
return (
|
||||
<TaskEditor
|
||||
key={props.value?.id}
|
||||
key={props.value?.name}
|
||||
header={
|
||||
<>
|
||||
Edit Task{" "}
|
||||
<span className="font-numbers font-normal italic">
|
||||
"{props.value?.text}"
|
||||
"{props.value?.name}"
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
} from "@heroui/react";
|
||||
import React, { FormEvent, useState } from "react";
|
||||
import React, { FormEvent, useEffect, useState } from "react";
|
||||
|
||||
export default function TaskEditor(props: {
|
||||
header: React.ReactNode;
|
||||
@@ -19,13 +19,20 @@ export default function TaskEditor(props: {
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
onSubmit?: (e: Task) => void;
|
||||
}) {
|
||||
const [text, setText] = useState(props.value?.text ?? "");
|
||||
const [name, setName] = useState(props.value?.name ?? "");
|
||||
const [enabled, setEnabled] = useState(props.value?.enabled ?? true);
|
||||
|
||||
// clear the inputs on closing
|
||||
useEffect(() => {
|
||||
if (!props.isOpen) {
|
||||
setName("");
|
||||
setEnabled(true);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
function submit(e: FormEvent<HTMLFormElement>) {
|
||||
const formData = Object.fromEntries(new FormData(e.currentTarget)) as {
|
||||
text: string;
|
||||
color: string;
|
||||
name: string;
|
||||
enabled: string;
|
||||
};
|
||||
|
||||
@@ -56,10 +63,10 @@ export default function TaskEditor(props: {
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Input
|
||||
value={text}
|
||||
onValueChange={setText}
|
||||
name="text"
|
||||
label="Text"
|
||||
value={name}
|
||||
onValueChange={setName}
|
||||
name="name"
|
||||
label="Name"
|
||||
isRequired
|
||||
variant="bordered"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { apiCall, Task } from "@/lib";
|
||||
import { AddLarge, Edit } from "@carbon/icons-react";
|
||||
import { AddLarge, Edit, TrashCan } from "@carbon/icons-react";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Checkbox,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -15,20 +16,23 @@ import { useAsyncList } from "@react-stately/data";
|
||||
import { useState } from "react";
|
||||
import AddTask from "./AddTask";
|
||||
import EditTask from "./EditTask";
|
||||
import DeleteConfirmation from "@/components/DeleteConfirmation";
|
||||
import zustand from "@/Zustand";
|
||||
|
||||
export default function Tasks() {
|
||||
const [showAddTask, setShowAddTask] = useState(false);
|
||||
const [editTask, setEditTask] = useState<Task>();
|
||||
const [deleteTask, setDeleteTask] = useState<Task>();
|
||||
|
||||
const tasks = useAsyncList<Task>({
|
||||
async load() {
|
||||
const result = await apiCall("GET", "tasks");
|
||||
|
||||
if (result.ok) {
|
||||
const json = await result.json();
|
||||
const json = (await result.json()) as Task[];
|
||||
|
||||
return {
|
||||
items: json.tasks,
|
||||
items: json,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -43,7 +47,7 @@ export default function Tasks() {
|
||||
|
||||
switch (sortDescriptor.column) {
|
||||
case "text":
|
||||
cmp = a.text.localeCompare(b.text);
|
||||
cmp = a.name.localeCompare(b.name);
|
||||
break;
|
||||
case "enabled":
|
||||
if (a.enabled && !b.enabled) {
|
||||
@@ -64,6 +68,25 @@ export default function Tasks() {
|
||||
},
|
||||
});
|
||||
|
||||
function reload() {
|
||||
// clear the zustand
|
||||
zustand.getState().patch({ tasks: undefined });
|
||||
|
||||
// reload the tasks
|
||||
tasks.reload();
|
||||
}
|
||||
|
||||
async function sendDeleteTask(id: number | undefined) {
|
||||
if (id !== undefined) {
|
||||
const result = await apiCall("DELETE", "tasks", { id });
|
||||
|
||||
if (result.ok) {
|
||||
tasks.reload();
|
||||
setDeleteTask(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const topContent = (
|
||||
<>
|
||||
<Button
|
||||
@@ -89,7 +112,7 @@ export default function Tasks() {
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn allowsSorting key="userName">
|
||||
Text
|
||||
Name
|
||||
</TableColumn>
|
||||
<TableColumn allowsSorting key="admin" align="center">
|
||||
Enabled
|
||||
@@ -101,21 +124,33 @@ export default function Tasks() {
|
||||
<TableBody items={tasks.items}>
|
||||
{(task) => (
|
||||
<TableRow key={task.id}>
|
||||
<TableCell>{task.text}</TableCell>
|
||||
<TableCell>{task.name}</TableCell>
|
||||
<TableCell>
|
||||
<Checkbox isSelected={task.enabled} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setEditTask(task)}
|
||||
>
|
||||
<Tooltip content="Edit task">
|
||||
<Edit />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setEditTask(task)}
|
||||
>
|
||||
<Tooltip content="Edit task">
|
||||
<Edit />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
onPress={() => setDeleteTask(task)}
|
||||
color="danger"
|
||||
className="text-danger"
|
||||
>
|
||||
<TrashCan />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
@@ -125,15 +160,32 @@ export default function Tasks() {
|
||||
<AddTask
|
||||
isOpen={showAddTask}
|
||||
onOpenChange={setShowAddTask}
|
||||
onSuccess={tasks.reload}
|
||||
onSuccess={reload}
|
||||
/>
|
||||
|
||||
<EditTask
|
||||
value={editTask}
|
||||
isOpen={!!editTask}
|
||||
onOpenChange={(isOpen) => (!isOpen ? setEditTask(undefined) : null)}
|
||||
onSuccess={tasks.reload}
|
||||
onSuccess={reload}
|
||||
/>
|
||||
|
||||
<DeleteConfirmation
|
||||
isOpen={!!deleteTask}
|
||||
onOpenChange={(isOpen) => (!isOpen ? setDeleteTask(undefined) : null)}
|
||||
header="Delete Task"
|
||||
onDelete={() => sendDeleteTask(deleteTask?.id)}
|
||||
>
|
||||
{!!deleteTask ? (
|
||||
<>
|
||||
The task{" "}
|
||||
<span className="font-numbers text-accent-1">
|
||||
{deleteTask.name}
|
||||
</span>{" "}
|
||||
will be deleted.
|
||||
</>
|
||||
) : null}
|
||||
</DeleteConfirmation>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import AddEvent from "@/components/Event/AddEvent";
|
||||
import EditEvent, { EventSubmitData } from "@/components/Event/EditEvent";
|
||||
import EditEvent from "@/components/Event/EditEvent";
|
||||
import LocalDate from "@/components/LocalDate";
|
||||
import { apiCall, getTaskMap } from "@/lib";
|
||||
import { apiCall, getTasks } from "@/lib";
|
||||
import { EventData } from "@/Zustand";
|
||||
import {
|
||||
Add,
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableColumn,
|
||||
TableColumnProps,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
@@ -46,20 +47,30 @@ export default function AdminPanel() {
|
||||
const [deleteEvent, setDeleteEvent] = useState<EventData | undefined>();
|
||||
|
||||
// get the available tasks and craft them into the headers
|
||||
const headers = useAsyncList({
|
||||
const headers = useAsyncList<{
|
||||
key: string | number;
|
||||
label: string;
|
||||
align?: string;
|
||||
}>({
|
||||
async load() {
|
||||
const tasks = await getTaskMap();
|
||||
const tasks = await getTasks();
|
||||
|
||||
return {
|
||||
const headers = {
|
||||
items: [
|
||||
{ key: "date", label: "Date" },
|
||||
{ key: "description", label: "Description" },
|
||||
...Object.values(tasks)
|
||||
...tasks
|
||||
.filter((task) => task.enabled)
|
||||
.map((task) => ({ label: task.text, key: task.text })),
|
||||
{ key: "actions", label: "Action" },
|
||||
.map((task) => ({
|
||||
label: task.name,
|
||||
key: task.id ?? -1,
|
||||
align: "center",
|
||||
})),
|
||||
{ key: "actions", label: "Action", align: "center" },
|
||||
],
|
||||
};
|
||||
|
||||
return headers;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -72,7 +83,9 @@ export default function AdminPanel() {
|
||||
);
|
||||
|
||||
if (result.ok) {
|
||||
return { items: await result.json() };
|
||||
const data = await result.json();
|
||||
|
||||
return { items: data };
|
||||
} else {
|
||||
return { items: [] };
|
||||
}
|
||||
@@ -145,7 +158,7 @@ export default function AdminPanel() {
|
||||
<Edit />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Button>
|
||||
<Button onPress={() => alert("implement")}>
|
||||
<Tooltip content="Duplicate event">
|
||||
<Copy />
|
||||
</Tooltip>
|
||||
@@ -165,13 +178,13 @@ export default function AdminPanel() {
|
||||
);
|
||||
default:
|
||||
// only show the selector, if the task is needed for the event
|
||||
if (Object.keys(event.tasks).includes(key as string)) {
|
||||
if (event.tasks?.some((t) => t.taskID == key)) {
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
{!!event.tasks[key as string] ? (
|
||||
{!!event.tasks.find((t) => t.taskID === key)?.userName ? (
|
||||
<Chip onClose={() => alert("implement")}>
|
||||
{event.tasks[key as string]}
|
||||
{event.tasks.find((t) => t.taskID === key)?.taskName}
|
||||
</Chip>
|
||||
) : (
|
||||
<Button isIconOnly size="sm" radius="md" variant="flat">
|
||||
@@ -203,17 +216,6 @@ export default function AdminPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEvent(data: EventSubmitData) {
|
||||
const result = await apiCall("PATCH", "events", undefined, data);
|
||||
|
||||
if (result.ok) {
|
||||
// clear the selected-event to hide the modal
|
||||
setEditEvent(undefined);
|
||||
|
||||
events.reload();
|
||||
}
|
||||
}
|
||||
|
||||
const topContent = (
|
||||
<div>
|
||||
<Button
|
||||
@@ -251,7 +253,7 @@ export default function AdminPanel() {
|
||||
<TableColumn
|
||||
allowsSorting={task.key === "date"}
|
||||
key={task.key}
|
||||
className=""
|
||||
align={task.align as TableColumnProps<string>["align"]}
|
||||
>
|
||||
{task.label}
|
||||
</TableColumn>
|
||||
@@ -277,8 +279,11 @@ export default function AdminPanel() {
|
||||
<EditEvent
|
||||
isOpen={editEvent !== undefined}
|
||||
onOpenChange={(isOpen) => (!isOpen ? setEditEvent(undefined) : null)}
|
||||
onSubmit={updateEvent}
|
||||
initialState={editEvent}
|
||||
onSuccess={() => {
|
||||
setEditEvent(undefined);
|
||||
events.reload();
|
||||
}}
|
||||
value={editEvent}
|
||||
footer={
|
||||
<Button
|
||||
color="primary"
|
||||
|
||||
Reference in New Issue
Block a user