CRUD Events and React interaction
This commit is contained in:
@@ -48,7 +48,7 @@ export default function ShortcodesPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<div className="container mx-auto px-4 py-10">
|
||||
<h1 className="text-2xl font-bold mb-5">Shortcodes</h1>
|
||||
{isLoading && (
|
||||
<Loader2 className="flex w-full min-w-0 flex-col gap-1 justify-center animate-spin" />
|
||||
|
||||
@@ -1,276 +1,20 @@
|
||||
"use client";
|
||||
import Planning from "@/components/planning.tsx";
|
||||
import { useApi } from "@/hooks/use-api";
|
||||
import { type CalendarEventExternal } from "@schedule-x/calendar";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
import { 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 {
|
||||
CalendarEventExternal,
|
||||
createViewDay,
|
||||
createViewWeek,
|
||||
} from "@schedule-x/calendar";
|
||||
import { useEffect, useState } from "react";
|
||||
import { format } from "date-fns";
|
||||
import { Dialog } 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 {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { requestFormReset } from "react-dom";
|
||||
const Page = () => {
|
||||
const {
|
||||
data: requestedEvents,
|
||||
isLoading,
|
||||
success,
|
||||
mutate,
|
||||
} = useApi<CalendarEventExternal[]>("/events", undefined, false, false);
|
||||
|
||||
const Planning = () => {
|
||||
const plugins = [createEventsServicePlugin()];
|
||||
const [eventSelected, setEventSelected] =
|
||||
useState<CalendarEventExternal | null>(null);
|
||||
const [events, setEvents] = useState<CalendarEventExternal[]>([
|
||||
{
|
||||
id: "1", // TODO put an uuid there
|
||||
title: "Event 1",
|
||||
start: format(new Date(Date.now()), "yyyy-MM-dd HH:mm"),
|
||||
end: format(
|
||||
new Date(Date.now() + 1 * 3600 * 1000),
|
||||
"yyyy-MM-dd HH:mm",
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
const [requestCreateEvent, setRequestCreateEvent] = useState(false)
|
||||
|
||||
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,
|
||||
callbacks: {
|
||||
onEventClick(event, e) {
|
||||
setEventSelected(event);
|
||||
},
|
||||
async onClickDateTime(dateTime) {
|
||||
setRequestCreateEvent(true)
|
||||
const newEvent: CalendarEventExternal = {
|
||||
id: "5",
|
||||
title: "Event 1",
|
||||
start: dateTime,
|
||||
end: format(
|
||||
new Date(dateTime).getTime() + (1000 * 60 * 60),
|
||||
"yyyy-MM-dd HH:mm",
|
||||
),
|
||||
}
|
||||
try {
|
||||
const res = await request<undefined>(
|
||||
`/events/new`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(newEvent),
|
||||
requiresAuth: true,
|
||||
csrfToken: false
|
||||
},)
|
||||
if (res.status === "Error") {
|
||||
console.log("Error")
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
console.log("Success")
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
);
|
||||
|
||||
const {data: requestedEvents, isLoading, success} = useApi("/events", {
|
||||
onSuccess: (data) => {
|
||||
calendar?.events.set(data)
|
||||
}
|
||||
}, false, false)
|
||||
|
||||
useEffect(() => {
|
||||
// get all events
|
||||
calendar?.events.getAll();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="m-8">
|
||||
<ScheduleXCalendar calendarApp={calendar} />
|
||||
</div>
|
||||
<Dialog
|
||||
open={eventSelected !== null || false}
|
||||
onOpenChange={(open) => {
|
||||
setEventSelected((e) => (open ? e : null));
|
||||
}}
|
||||
>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{eventSelected?.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{eventSelected?.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={eventSelected?.start || ""}
|
||||
onChange={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
setEventSelected((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
start: val,
|
||||
};
|
||||
return ev;
|
||||
});
|
||||
}}
|
||||
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={eventSelected?.end || ""}
|
||||
onChange={(e) => {
|
||||
const val = e.currentTarget.value
|
||||
setEventSelected((ev) => {
|
||||
if (ev)
|
||||
return {
|
||||
...ev,
|
||||
end: val,
|
||||
};
|
||||
return ev;
|
||||
})
|
||||
}}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="flex flex-row justify-end">
|
||||
<Button className="bg-red-700"
|
||||
onClick={async () => {
|
||||
calendar?.events?.remove(eventSelected!.id)
|
||||
try {
|
||||
const res = await request<undefined>(
|
||||
`/events/${eventSelected!.id}/delete`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: JSON.stringify(eventSelected),
|
||||
requiresAuth: false,
|
||||
csrfToken: false
|
||||
},)
|
||||
if (res.status === "Error") {
|
||||
console.log("Error")
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
console.log("Success")
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}}
|
||||
type="submit">
|
||||
Supprimer
|
||||
</Button>
|
||||
<Button className="bg-blue-500"
|
||||
onClick={async () => {
|
||||
calendar?.events?.update(eventSelected!)
|
||||
try {
|
||||
const res = await request<undefined>(
|
||||
`/events/${eventSelected!.id}/update`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(eventSelected),
|
||||
requiresAuth: true,
|
||||
csrfToken: false
|
||||
},)
|
||||
if (res.status === "Error") {
|
||||
console.log("Error")
|
||||
}
|
||||
if (res.status === "Success") {
|
||||
console.log("Success")
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}}
|
||||
type="submit">
|
||||
Actualiser
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
if (isLoading) return <Loader2 className="animate-spin" />;
|
||||
if (success)
|
||||
return <Planning events={requestedEvents ?? []} mutate={mutate} />;
|
||||
};
|
||||
|
||||
export default Planning;
|
||||
export default Page;
|
||||
|
||||
238
frontend/components/members-table.tsx
Normal file
238
frontend/components/members-table.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { UpdateMemberDialog } from "./UpdateMemberDialog";
|
||||
import { AddMemberDialog } from "./AddMemberDialog";
|
||||
|
||||
interface Member {
|
||||
user_id: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
password: string;
|
||||
phone: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
const initialMembers: Member[] = [
|
||||
// Add some sample data here
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
user_id: "1",
|
||||
firstname: "John",
|
||||
lastname: "Doe",
|
||||
email: "john@example.com",
|
||||
password: "********",
|
||||
phone: "1234567890",
|
||||
role: "User",
|
||||
},
|
||||
// Add more sample members...
|
||||
];
|
||||
|
||||
export function MembersTable() {
|
||||
const [members, setMembers] = useState<Member[]>(initialMembers);
|
||||
const [selectMode, setSelectMode] = useState(false);
|
||||
const [selectedMembers, setSelectedMembers] = useState<string[]>([]);
|
||||
const [updateDialogOpen, setUpdateDialogOpen] = useState(false);
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||
const [currentMember, setCurrentMember] = useState<Member | null>(null);
|
||||
|
||||
const toggleSelectMode = () => {
|
||||
setSelectMode(!selectMode);
|
||||
setSelectedMembers([]);
|
||||
};
|
||||
|
||||
const toggleMemberSelection = (userId: string) => {
|
||||
setSelectedMembers((prev) =>
|
||||
prev.includes(userId)
|
||||
? prev.filter((id) => id !== userId)
|
||||
: [...prev, userId],
|
||||
);
|
||||
};
|
||||
|
||||
const handleUpdate = (member: Member) => {
|
||||
setCurrentMember(member);
|
||||
setUpdateDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = (userId: string) => {
|
||||
setMembers((prev) =>
|
||||
prev.filter((member) => member.user_id !== userId),
|
||||
);
|
||||
};
|
||||
|
||||
const handleAdd = (newMember: Member) => {
|
||||
setMembers((prev) => [
|
||||
...prev,
|
||||
{ ...newMember, user_id: String(prev.length + 1) },
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<Button onClick={toggleSelectMode}>
|
||||
{selectMode ? "Cancel Selection" : "Select"}
|
||||
</Button>
|
||||
<Button onClick={() => setAddDialogOpen(true)}>
|
||||
Add New Member
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<ScrollArea className="h-[400px] rounded-md border">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
|
||||
<TableRow>
|
||||
{selectMode && (
|
||||
<TableHead className="w-[50px]">
|
||||
Select
|
||||
</TableHead>
|
||||
)}
|
||||
<TableHead>User ID</TableHead>
|
||||
<TableHead>First Name</TableHead>
|
||||
<TableHead>Last Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Password</TableHead>
|
||||
<TableHead>Phone</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
<TableHead className="text-right">
|
||||
Actions
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{members.map((member) => (
|
||||
<TableRow key={member.user_id}>
|
||||
{selectMode && (
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
checked={selectedMembers.includes(
|
||||
member.user_id,
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
toggleMemberSelection(
|
||||
member.user_id,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{member.user_id}</TableCell>
|
||||
<TableCell>{member.firstname}</TableCell>
|
||||
<TableCell>{member.lastname}</TableCell>
|
||||
<TableCell>{member.email}</TableCell>
|
||||
<TableCell>{member.password}</TableCell>
|
||||
<TableCell>{member.phone}</TableCell>
|
||||
<TableCell>{member.role}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mr-2"
|
||||
onClick={() => handleUpdate(member)}
|
||||
>
|
||||
Modify
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleDelete(member.user_id)
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<UpdateMemberDialog
|
||||
isOpen={updateDialogOpen}
|
||||
onClose={() => setUpdateDialogOpen(false)}
|
||||
member={currentMember}
|
||||
onUpdate={(updatedMember) => {
|
||||
setMembers((prev) =>
|
||||
prev.map((m) =>
|
||||
m.user_id === updatedMember.user_id
|
||||
? updatedMember
|
||||
: m,
|
||||
),
|
||||
);
|
||||
setUpdateDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
<AddMemberDialog
|
||||
isOpen={addDialogOpen}
|
||||
onClose={() => setAddDialogOpen(false)}
|
||||
onAdd={handleAdd}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
356
frontend/components/planning.tsx.tsx
Normal file
356
frontend/components/planning.tsx.tsx
Normal file
@@ -0,0 +1,356 @@
|
||||
"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) {
|
||||
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;
|
||||
@@ -9,55 +9,47 @@ import {
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import type IShortcode from "@/interfaces/IShortcode";
|
||||
|
||||
interface ShortcodeDialogProps {
|
||||
onSave: (shortcode: Omit<IShortcode, "id">) => void;
|
||||
onSave: (shortcode: IShortcode) => void;
|
||||
open: boolean;
|
||||
setOpen: () => void;
|
||||
shortcode?: IShortcode;
|
||||
}
|
||||
|
||||
export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [code, setCode] = useState("");
|
||||
const [type, setType] = useState<"value" | "media">("value");
|
||||
const [value, setValue] = useState("");
|
||||
const [mediaId, setMediaId] = useState("");
|
||||
export default function ShortcodeDialog({
|
||||
onSave,
|
||||
open,
|
||||
setOpen,
|
||||
shortcode,
|
||||
}: ShortcodeDialogProps) {
|
||||
const [_shortcode, setShortcode] = useState<IShortcode>(
|
||||
shortcode ?? { code: "", type: "", id: 0 },
|
||||
);
|
||||
|
||||
const handleSave = () => {
|
||||
onSave({ code, type, value, media_id: mediaId });
|
||||
setOpen(false);
|
||||
onSave(_shortcode);
|
||||
setOpen();
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setCode("");
|
||||
setType("value");
|
||||
setValue("");
|
||||
setMediaId("");
|
||||
setShortcode({ code: "", type: "", id: 0 });
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Add New Shortcode</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Shortcode</DialogTitle>
|
||||
<DialogTitle>Ajouter un nouveau shortcode</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new shortcode here. Click save when you're
|
||||
done.
|
||||
Créer un nouveau shortcode ici. Cliquez enregistrer
|
||||
quand vous avez fini.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
@@ -67,29 +59,41 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
||||
</Label>
|
||||
<Input
|
||||
id="code"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
value={_shortcode.code}
|
||||
onChange={(e) =>
|
||||
setShortcode((p) => ({
|
||||
...p,
|
||||
code: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
defaultValue={type}
|
||||
onValueChange={(v) => setType(v as "value" | "media")}
|
||||
defaultValue={_shortcode.type}
|
||||
onValueChange={(v) =>
|
||||
setShortcode((p) => ({ ...p, type: v }))
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="value">Value</TabsTrigger>
|
||||
<TabsTrigger value="value">Valeur</TabsTrigger>
|
||||
<TabsTrigger value="media">Media</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="value">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="value" className="text-right">
|
||||
Value
|
||||
Valeur
|
||||
</Label>
|
||||
<Input
|
||||
id="value"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
value={_shortcode.value}
|
||||
onChange={(e) =>
|
||||
setShortcode((p) => ({
|
||||
...p,
|
||||
value: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
@@ -101,8 +105,13 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
||||
</Label>
|
||||
<Input
|
||||
id="mediaId"
|
||||
value={mediaId}
|
||||
onChange={(e) => setMediaId(e.target.value)}
|
||||
value={_shortcode.media_id}
|
||||
onChange={(e) =>
|
||||
setShortcode((p) => ({
|
||||
...p,
|
||||
media_id: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
@@ -111,7 +120,7 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" onClick={handleSave}>
|
||||
Save shortcode
|
||||
Enregistrer
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import type IShortcode from "@/interfaces/IShortcode";
|
||||
import ShortcodeDialog from "@/components/shortcode-dialogue";
|
||||
import { useState } from "react";
|
||||
|
||||
interface ShortcodeTableProps {
|
||||
shortcodes: IShortcode[];
|
||||
@@ -32,10 +33,25 @@ export function ShortcodeTable({
|
||||
onDelete,
|
||||
onAdd,
|
||||
}: ShortcodeTableProps) {
|
||||
const [shortcodeSelected, setUpdateDialog] = useState<IShortcode | null>(
|
||||
null,
|
||||
);
|
||||
const [addDialog, setAddDialog] = useState<boolean>(false);
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<ShortcodeDialog onSave={onAdd} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
setAddDialog(true);
|
||||
}}
|
||||
>
|
||||
Ajouter
|
||||
</Button>
|
||||
<ShortcodeDialog
|
||||
onSave={onAdd}
|
||||
open={addDialog}
|
||||
setOpen={() => setAddDialog(false)}
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
@@ -44,7 +60,7 @@ export function ShortcodeTable({
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Code</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Value</TableHead>
|
||||
<TableHead>Valeur</TableHead>
|
||||
<TableHead>Media ID</TableHead>
|
||||
<TableHead className="w-[80px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
@@ -69,7 +85,7 @@ export function ShortcodeTable({
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">
|
||||
Open menu
|
||||
Ouvrir le menu
|
||||
</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -77,17 +93,17 @@ export function ShortcodeTable({
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
onUpdate(shortcode)
|
||||
setUpdateDialog(shortcode)
|
||||
}
|
||||
>
|
||||
Update
|
||||
Mettre à jour
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
onDelete(shortcode.code)
|
||||
}
|
||||
>
|
||||
Delete
|
||||
Supprimer
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -96,6 +112,15 @@ export function ShortcodeTable({
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
{shortcodeSelected && (
|
||||
<ShortcodeDialog
|
||||
onSave={onUpdate}
|
||||
shortcode={shortcodeSelected}
|
||||
open={shortcodeSelected ? true : false}
|
||||
setOpen={() => setUpdateDialog(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
30
frontend/components/ui/checkbox.tsx
Normal file
30
frontend/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox };
|
||||
50
frontend/components/ui/scroll-area.tsx
Normal file
50
frontend/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<
|
||||
typeof ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
));
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
|
||||
|
||||
export { ScrollArea, ScrollBar };
|
||||
94
frontend/package-lock.json
generated
94
frontend/package-lock.json
generated
@@ -11,20 +11,26 @@
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.1.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||
"@radix-ui/react-popover": "^1.1.4",
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.4",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@schedule-x/drag-and-drop": "^2.15.1",
|
||||
"@schedule-x/event-modal": "^2.15.1",
|
||||
"@schedule-x/event-recurrence": "^2.15.1",
|
||||
"@schedule-x/events-service": "^2.14.3",
|
||||
"@schedule-x/react": "^2.13.3",
|
||||
"@schedule-x/resize": "^2.15.1",
|
||||
"@schedule-x/theme-default": "^2.14.3",
|
||||
"@schedule-x/theme-shadcn": "^2.14.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -1067,6 +1073,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.3.tgz",
|
||||
"integrity": "sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/react-use-previous": "1.1.0",
|
||||
"@radix-ui/react-use-size": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
|
||||
@@ -1588,6 +1624,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
|
||||
"integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-select": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
|
||||
@@ -1936,6 +2003,28 @@
|
||||
"preact": "^10.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@schedule-x/drag-and-drop": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/drag-and-drop/-/drag-and-drop-2.15.1.tgz",
|
||||
"integrity": "sha512-CBT2AUgVfMTz0tGDk8U++gzx7cnl/WrvX3XBENJ+cSb7PqR/4nz8ONvv3smJGsEM/GR53Efv9gdeBIAln9Mt7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@schedule-x/event-modal": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/event-modal/-/event-modal-2.15.1.tgz",
|
||||
"integrity": "sha512-pSSI9W7JE7ClU3wXuPNbiirX4kVK6kZ40NryWmRyx1Unko6VmlLLEOeQrqkeEH6A89jTHUCgWyxZWzIdQCLHxQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@preact/signals": "^1.1.5",
|
||||
"preact": "^10.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@schedule-x/event-recurrence": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/event-recurrence/-/event-recurrence-2.15.1.tgz",
|
||||
"integrity": "sha512-jkkGzofxq4C1mnX4RCbIoA1e5Z4G3+riBALOEusd7AoBXGxUFBtks+f0S1YZH0Taa7WCD6b2RptihwoNbDP6Rg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@schedule-x/events-service": {
|
||||
"version": "2.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.14.3.tgz",
|
||||
@@ -1953,6 +2042,11 @@
|
||||
"react-dom": "^16.7.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@schedule-x/resize": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/resize/-/resize-2.15.1.tgz",
|
||||
"integrity": "sha512-NPTJ08ATPFcv38uO/9Of1Re1SiU7VZXvVgLVBZpOGGcwDBPt8FzD6LkWCQBgx6Bz+DLLtSd+QjEgt1tOhi2sJA=="
|
||||
},
|
||||
"node_modules/@schedule-x/theme-default": {
|
||||
"version": "2.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@schedule-x/theme-default/-/theme-default-2.14.3.tgz",
|
||||
|
||||
@@ -12,20 +12,26 @@
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-avatar": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.1.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||
"@radix-ui/react-popover": "^1.1.4",
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.4",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@schedule-x/drag-and-drop": "^2.15.1",
|
||||
"@schedule-x/event-modal": "^2.15.1",
|
||||
"@schedule-x/event-recurrence": "^2.15.1",
|
||||
"@schedule-x/events-service": "^2.14.3",
|
||||
"@schedule-x/react": "^2.13.3",
|
||||
"@schedule-x/resize": "^2.15.1",
|
||||
"@schedule-x/theme-default": "^2.14.3",
|
||||
"@schedule-x/theme-shadcn": "^2.14.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
Reference in New Issue
Block a user