Reorganization of backend + new routes
This commit is contained in:
357
frontend/components/planning.tsx
Normal file
357
frontend/components/planning.tsx
Normal file
@@ -0,0 +1,357 @@
|
||||
"use client";
|
||||
|
||||
import { ApiResponse, request, useApi } from "@/hooks/use-api";
|
||||
import "@schedule-x/theme-shadcn/dist/index.css";
|
||||
import { useNextCalendarApp, ScheduleXCalendar } from "@schedule-x/react";
|
||||
import { createEventsServicePlugin } from "@schedule-x/events-service";
|
||||
import { createDragAndDropPlugin } from "@schedule-x/drag-and-drop";
|
||||
import { createResizePlugin } from "@schedule-x/resize";
|
||||
import { createEventRecurrencePlugin } from "@schedule-x/event-recurrence";
|
||||
import {
|
||||
CalendarEventExternal,
|
||||
createViewDay,
|
||||
createViewWeek,
|
||||
} from "@schedule-x/calendar";
|
||||
import { useEffect, useState } from "react";
|
||||
import { format } from "date-fns";
|
||||
import { Dialog, DialogProps } from "@radix-ui/react-dialog";
|
||||
import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { KeyedMutator } from "swr";
|
||||
|
||||
const Planning: React.FC<{
|
||||
events: CalendarEventExternal[];
|
||||
mutate?: KeyedMutator<ApiResponse<CalendarEventExternal[]>>;
|
||||
}> = ({ events, mutate }) => {
|
||||
const plugins = [
|
||||
createEventsServicePlugin(),
|
||||
createDragAndDropPlugin(),
|
||||
createResizePlugin(),
|
||||
createEventRecurrencePlugin(),
|
||||
];
|
||||
const [eventSelected, setEventSelected] =
|
||||
useState<CalendarEventExternal | null>(null);
|
||||
const [newEvent, setNewEvent] = useState<Omit<
|
||||
CalendarEventExternal,
|
||||
"id"
|
||||
> | null>(null);
|
||||
|
||||
const handleEventUpdate = async (eventSelected: CalendarEventExternal) => {
|
||||
const event: CalendarEventExternal = {
|
||||
...eventSelected,
|
||||
start: `${new Date(eventSelected.start).toISOString()}`,
|
||||
end: `${new Date(eventSelected.end).toISOString()}`,
|
||||
};
|
||||
try {
|
||||
const res = await request<undefined>(`/events/${event.id}/update`, {
|
||||
method: "PATCH",
|
||||
body: event,
|
||||
requiresAuth: true,
|
||||
csrfToken: false,
|
||||
});
|
||||
if (res.status === "Error") {
|
||||
console.log("Error");
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
calendar?.events?.update(eventSelected);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const calendar = useNextCalendarApp(
|
||||
{
|
||||
theme: "shadcn",
|
||||
views: [createViewDay(), createViewWeek()],
|
||||
defaultView: "week",
|
||||
isDark: true,
|
||||
isResponsive: true,
|
||||
locale: "fr-FR",
|
||||
dayBoundaries: {
|
||||
start: "06:00",
|
||||
end: "00:00",
|
||||
},
|
||||
events: events.map((event) => ({
|
||||
...event,
|
||||
start: format(new Date(event.start), "yyyy-MM-dd HH:mm"),
|
||||
end: format(new Date(event.end), "yyyy-MM-dd HH:mm"),
|
||||
})),
|
||||
callbacks: {
|
||||
onEventClick(event, e) {
|
||||
setEventSelected(event);
|
||||
},
|
||||
async onEventUpdate(event) {
|
||||
console.log(event);
|
||||
await handleEventUpdate(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
calendar?.events.getAll();
|
||||
}, []);
|
||||
|
||||
const AddButton: React.FC = () => (
|
||||
<Button onClick={() => setNewEvent({})} variant="outline">
|
||||
Nouveau
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="m-8">
|
||||
<AddButton />
|
||||
<ScheduleXCalendar calendarApp={calendar} />
|
||||
</div>
|
||||
{newEvent && (
|
||||
<EventDialog
|
||||
open={newEvent !== null || false}
|
||||
onOpenChange={(open) => {
|
||||
setNewEvent((e) => (open ? e : null));
|
||||
}}
|
||||
event={newEvent}
|
||||
onStartChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setNewEvent((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
start: val,
|
||||
};
|
||||
return ev;
|
||||
});
|
||||
}}
|
||||
onEndChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setNewEvent((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
end: val,
|
||||
};
|
||||
return ev;
|
||||
});
|
||||
}}
|
||||
onAdd={async () => {
|
||||
try {
|
||||
const event: Omit<CalendarEventExternal, "id"> = {
|
||||
...newEvent,
|
||||
start: `${new Date(newEvent.start).toISOString()}`,
|
||||
end: `${new Date(newEvent.end).toISOString()}`,
|
||||
};
|
||||
const res = await request<undefined>(
|
||||
`/events/new`,
|
||||
{
|
||||
method: "POST",
|
||||
body: event,
|
||||
requiresAuth: true,
|
||||
csrfToken: false,
|
||||
},
|
||||
);
|
||||
if (res.status === "Error") {
|
||||
console.log("Error");
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
mutate?.();
|
||||
console.log("Success");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{eventSelected && (
|
||||
<EventDialog
|
||||
open={eventSelected !== null || false}
|
||||
onOpenChange={(open) => {
|
||||
setEventSelected((e) => (open ? e : null));
|
||||
}}
|
||||
event={eventSelected}
|
||||
onStartChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setEventSelected((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
start: val,
|
||||
};
|
||||
return ev;
|
||||
});
|
||||
}}
|
||||
onEndChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setEventSelected((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
end: val,
|
||||
};
|
||||
return ev;
|
||||
});
|
||||
}}
|
||||
onDelete={async () => {
|
||||
calendar?.events?.remove(eventSelected.id);
|
||||
try {
|
||||
const res = await request<undefined>(
|
||||
`/events/${eventSelected.id}/delete`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: eventSelected,
|
||||
requiresAuth: false,
|
||||
csrfToken: false,
|
||||
},
|
||||
);
|
||||
if (res.status === "Error") {
|
||||
console.log("Error");
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
console.log("Success");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
setEventSelected(null);
|
||||
}}
|
||||
onUpdate={async () => {
|
||||
await handleEventUpdate(eventSelected);
|
||||
setEventSelected(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EventDialog: React.FC<
|
||||
{
|
||||
onEndChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||
onStartChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||
onDelete?: () => void;
|
||||
onUpdate?: () => void;
|
||||
onAdd?: () => void;
|
||||
event: CalendarEventExternal | Omit<CalendarEventExternal, "id">;
|
||||
} & DialogProps
|
||||
> = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onEndChange,
|
||||
onStartChange,
|
||||
onDelete,
|
||||
onUpdate,
|
||||
onAdd,
|
||||
event,
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{event.title}</DialogTitle>
|
||||
<DialogDescription>{event.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="start" className="text-right">
|
||||
Début
|
||||
</Label>
|
||||
{/*<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-[240px] pl-3 text-left font-normal",
|
||||
!eventSelected?.start &&
|
||||
"text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{eventSelected?.start ? (
|
||||
format(eventSelected?.start, "PPP")
|
||||
) : (
|
||||
<span>Choisissez une date.</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={
|
||||
new Date(
|
||||
eventSelected?.start ??
|
||||
Date.now(),
|
||||
)
|
||||
}
|
||||
// onSelect={field.onChange}
|
||||
disabled={(date) =>
|
||||
date > new Date() ||
|
||||
date < new Date("1900-01-01")
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover> */}
|
||||
<Input
|
||||
id="start"
|
||||
value={event.start || ""}
|
||||
onChange={onStartChange}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="end" className="text-right">
|
||||
Fin
|
||||
</Label>
|
||||
<Input
|
||||
id="end"
|
||||
value={event.end || ""}
|
||||
onChange={onEndChange}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="flex flex-row justify-end">
|
||||
{onUpdate && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onUpdate()}
|
||||
type="submit"
|
||||
>
|
||||
Actualiser
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => onDelete()}
|
||||
type="submit"
|
||||
>
|
||||
Supprimer
|
||||
</Button>
|
||||
)}
|
||||
{onAdd && !onUpdate && !onDelete && (
|
||||
<Button onClick={() => onAdd()} type="submit">
|
||||
Créer
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default Planning;
|
||||
Reference in New Issue
Block a user