CRUD Events and React interaction
This commit is contained in:
@@ -97,11 +97,11 @@ func Verify(ctx context.Context, email, password string) (*User, error) {
|
|||||||
type Event struct {
|
type Event struct {
|
||||||
bun.BaseModel `bun:"table:events"`
|
bun.BaseModel `bun:"table:events"`
|
||||||
|
|
||||||
EventID uuid.UUID `bun:"type:uuid,pk,default:gen_random_uuid()" json:"eventID"`
|
EventID uuid.UUID `bun:"event_id,type:uuid,pk,default:gen_random_uuid()" json:"id"`
|
||||||
CreationDate time.Time `bun:"creation_date,notnull,default:current_timestamp" json:"creationDate"`
|
CreationDate time.Time `bun:"creation_date,notnull,default:current_timestamp" json:"creationDate"`
|
||||||
ScheduleStart time.Time `bun:"schedule_start,notnull" json:"scheduleStart"`
|
ScheduleStart time.Time `bun:"schedule_start,notnull" json:"start"`
|
||||||
ScheduleEnd time.Time `bun:"schedule_end,notnull" json:"scheduleEnd"`
|
ScheduleEnd time.Time `bun:"schedule_end,notnull" json:"end"`
|
||||||
Status Status `bun:"status,notnull,default:Inactive" json:"status"`
|
Status Status `bun:"status,notnull,default:'Inactive'" json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventToUser struct {
|
type EventToUser struct {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func HandleDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
var event core.Event
|
var event core.Event
|
||||||
res, err := core.DB.NewDelete().
|
res, err := core.DB.NewDelete().
|
||||||
Model(&event).
|
Model(&event).
|
||||||
Where("id = ?", uuid).
|
Where("event_id = ?", uuid).
|
||||||
Returning("*").
|
Returning("*").
|
||||||
Exec(context.Background())
|
Exec(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,4 +30,3 @@ func HandleDeleteEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
Message: "Event deleted.",
|
Message: "Event deleted.",
|
||||||
}.Respond(w, http.StatusOK)
|
}.Respond(w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandleCreateBlog(w http.ResponseWriter, r *http.Request) {
|
func HandleCreateBlog(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
core.JSONError{
|
|
||||||
Status: core.Error,
|
|
||||||
Message: "Method is not allowed",
|
|
||||||
}.Respond(w, http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
core.JSONError{
|
core.JSONError{
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ func HandleCreateEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
Status: core.Error,
|
Status: core.Error,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
}.Respond(w, http.StatusNotAcceptable)
|
}.Respond(w, http.StatusNotAcceptable)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
core.JSONSuccess{
|
core.JSONSuccess{
|
||||||
Status: core.Success,
|
Status: core.Success,
|
||||||
Message: "Event created",
|
Message: "Event created",
|
||||||
Data: event,
|
Data: event,
|
||||||
}.Respond(w, http.StatusCreated)
|
}.Respond(w, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
event_uuid := r.PathValue("event_uuid")
|
event_uuid := r.PathValue("event_uuid")
|
||||||
event.EventID, err = uuid.FromBytes([]byte(event_uuid))
|
event.EventID, err = uuid.Parse(event_uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
core.JSONError{
|
core.JSONError{
|
||||||
Status: core.Error,
|
Status: core.Error,
|
||||||
@@ -44,7 +44,7 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
_, err = core.DB.NewUpdate().
|
_, err = core.DB.NewUpdate().
|
||||||
Model(event).
|
Model(&event).
|
||||||
OmitZero().
|
OmitZero().
|
||||||
WherePK().
|
WherePK().
|
||||||
Exec(context.Background())
|
Exec(context.Background())
|
||||||
@@ -62,4 +62,3 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) {
|
|||||||
Data: event,
|
Data: event,
|
||||||
}.Respond(w, http.StatusOK)
|
}.Respond(w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UpdateShortcodeArgs struct {
|
type UpdateShortcodeArgs struct {
|
||||||
Code *string `json:"code"` // The shortcode value
|
ID *int64 `json:"id,omitempty"`
|
||||||
Type *core.ShortcodeType `json:"type"`
|
Code *string `json:"code,omitempty"` // The shortcode value
|
||||||
Value *string `json:"value"`
|
Type *core.ShortcodeType `bun:"shortcode_type" json:"type,omitempty"`
|
||||||
MediaID *uuid.UUID `json:"media_id"` // Nullable reference to another table's ID
|
Value *string `json:"value,omitempty"`
|
||||||
|
MediaID *uuid.UUID `json:"media_id,omitempty"` // Nullable reference to another table's ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleUpdateShortcode(w http.ResponseWriter, r *http.Request) {
|
func HandleUpdateShortcode(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -51,7 +52,7 @@ func HandleUpdateShortcode(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
code := r.PathValue("shortcode")
|
code := r.PathValue("shortcode")
|
||||||
_, err = updateQuery.
|
_, err = updateQuery.
|
||||||
Where("code = ?", code).
|
Where("id = ? OR code = ?", updateArgs.ID, code).
|
||||||
Returning("*").
|
Returning("*").
|
||||||
Exec(context.Background())
|
Exec(context.Background())
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function ShortcodesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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>
|
<h1 className="text-2xl font-bold mb-5">Shortcodes</h1>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<Loader2 className="flex w-full min-w-0 flex-col gap-1 justify-center animate-spin" />
|
<Loader2 className="flex w-full min-w-0 flex-col gap-1 justify-center animate-spin" />
|
||||||
|
|||||||
@@ -1,276 +1,20 @@
|
|||||||
"use client";
|
"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";
|
const Page = () => {
|
||||||
import "@schedule-x/theme-shadcn/dist/index.css";
|
const {
|
||||||
import { useNextCalendarApp, ScheduleXCalendar } from "@schedule-x/react";
|
data: requestedEvents,
|
||||||
import { createEventsServicePlugin } from "@schedule-x/events-service";
|
isLoading,
|
||||||
import {
|
success,
|
||||||
CalendarEventExternal,
|
mutate,
|
||||||
createViewDay,
|
} = useApi<CalendarEventExternal[]>("/events", undefined, false, false);
|
||||||
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 Planning = () => {
|
if (isLoading) return <Loader2 className="animate-spin" />;
|
||||||
const plugins = [createEventsServicePlugin()];
|
if (success)
|
||||||
const [eventSelected, setEventSelected] =
|
return <Planning events={requestedEvents ?? []} mutate={mutate} />;
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import type IShortcode from "@/interfaces/IShortcode";
|
import type IShortcode from "@/interfaces/IShortcode";
|
||||||
|
|
||||||
interface ShortcodeDialogProps {
|
interface ShortcodeDialogProps {
|
||||||
onSave: (shortcode: Omit<IShortcode, "id">) => void;
|
onSave: (shortcode: IShortcode) => void;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: () => void;
|
||||||
|
shortcode?: IShortcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
export default function ShortcodeDialog({
|
||||||
const [open, setOpen] = useState(false);
|
onSave,
|
||||||
const [code, setCode] = useState("");
|
open,
|
||||||
const [type, setType] = useState<"value" | "media">("value");
|
setOpen,
|
||||||
const [value, setValue] = useState("");
|
shortcode,
|
||||||
const [mediaId, setMediaId] = useState("");
|
}: ShortcodeDialogProps) {
|
||||||
|
const [_shortcode, setShortcode] = useState<IShortcode>(
|
||||||
|
shortcode ?? { code: "", type: "", id: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
onSave({ code, type, value, media_id: mediaId });
|
onSave(_shortcode);
|
||||||
setOpen(false);
|
setOpen();
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setCode("");
|
setShortcode({ code: "", type: "", id: 0 });
|
||||||
setType("value");
|
|
||||||
setValue("");
|
|
||||||
setMediaId("");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline">Add New Shortcode</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add New Shortcode</DialogTitle>
|
<DialogTitle>Ajouter un nouveau shortcode</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Create a new shortcode here. Click save when you're
|
Créer un nouveau shortcode ici. Cliquez enregistrer
|
||||||
done.
|
quand vous avez fini.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<div className="grid gap-4 py-4">
|
||||||
@@ -67,29 +59,41 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
|||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="code"
|
id="code"
|
||||||
value={code}
|
value={_shortcode.code}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setShortcode((p) => ({
|
||||||
|
...p,
|
||||||
|
code: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
className="col-span-3"
|
className="col-span-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue={type}
|
defaultValue={_shortcode.type}
|
||||||
onValueChange={(v) => setType(v as "value" | "media")}
|
onValueChange={(v) =>
|
||||||
|
setShortcode((p) => ({ ...p, type: v }))
|
||||||
|
}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="value">Value</TabsTrigger>
|
<TabsTrigger value="value">Valeur</TabsTrigger>
|
||||||
<TabsTrigger value="media">Media</TabsTrigger>
|
<TabsTrigger value="media">Media</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="value">
|
<TabsContent value="value">
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
<Label htmlFor="value" className="text-right">
|
<Label htmlFor="value" className="text-right">
|
||||||
Value
|
Valeur
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="value"
|
id="value"
|
||||||
value={value}
|
value={_shortcode.value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setShortcode((p) => ({
|
||||||
|
...p,
|
||||||
|
value: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
className="col-span-3"
|
className="col-span-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,8 +105,13 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
|||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="mediaId"
|
id="mediaId"
|
||||||
value={mediaId}
|
value={_shortcode.media_id}
|
||||||
onChange={(e) => setMediaId(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setShortcode((p) => ({
|
||||||
|
...p,
|
||||||
|
media_id: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
className="col-span-3"
|
className="col-span-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +120,7 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
|||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type="submit" onClick={handleSave}>
|
<Button type="submit" onClick={handleSave}>
|
||||||
Save shortcode
|
Enregistrer
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { MoreHorizontal } from "lucide-react";
|
import { MoreHorizontal } from "lucide-react";
|
||||||
import type IShortcode from "@/interfaces/IShortcode";
|
import type IShortcode from "@/interfaces/IShortcode";
|
||||||
import ShortcodeDialog from "@/components/shortcode-dialogue";
|
import ShortcodeDialog from "@/components/shortcode-dialogue";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface ShortcodeTableProps {
|
interface ShortcodeTableProps {
|
||||||
shortcodes: IShortcode[];
|
shortcodes: IShortcode[];
|
||||||
@@ -32,10 +33,25 @@ export function ShortcodeTable({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onAdd,
|
onAdd,
|
||||||
}: ShortcodeTableProps) {
|
}: ShortcodeTableProps) {
|
||||||
|
const [shortcodeSelected, setUpdateDialog] = useState<IShortcode | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const [addDialog, setAddDialog] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<ShortcodeDialog onSave={onAdd} />
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setAddDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ajouter
|
||||||
|
</Button>
|
||||||
|
<ShortcodeDialog
|
||||||
|
onSave={onAdd}
|
||||||
|
open={addDialog}
|
||||||
|
setOpen={() => setAddDialog(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
@@ -44,7 +60,7 @@ export function ShortcodeTable({
|
|||||||
<TableHead>ID</TableHead>
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Code</TableHead>
|
<TableHead>Code</TableHead>
|
||||||
<TableHead>Type</TableHead>
|
<TableHead>Type</TableHead>
|
||||||
<TableHead>Value</TableHead>
|
<TableHead>Valeur</TableHead>
|
||||||
<TableHead>Media ID</TableHead>
|
<TableHead>Media ID</TableHead>
|
||||||
<TableHead className="w-[80px]">Actions</TableHead>
|
<TableHead className="w-[80px]">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -69,7 +85,7 @@ export function ShortcodeTable({
|
|||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
>
|
>
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
Open menu
|
Ouvrir le menu
|
||||||
</span>
|
</span>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -77,17 +93,17 @@ export function ShortcodeTable({
|
|||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onUpdate(shortcode)
|
setUpdateDialog(shortcode)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Update
|
Mettre à jour
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onDelete(shortcode.code)
|
onDelete(shortcode.code)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Delete
|
Supprimer
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@@ -96,6 +112,15 @@ export function ShortcodeTable({
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
{shortcodeSelected && (
|
||||||
|
<ShortcodeDialog
|
||||||
|
onSave={onUpdate}
|
||||||
|
shortcode={shortcodeSelected}
|
||||||
|
open={shortcodeSelected ? true : false}
|
||||||
|
setOpen={() => setUpdateDialog(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-avatar": "^1.1.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-collapsible": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||||
"@radix-ui/react-popover": "^1.1.4",
|
"@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-select": "^2.1.4",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.6",
|
"@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/events-service": "^2.14.3",
|
||||||
"@schedule-x/react": "^2.13.3",
|
"@schedule-x/react": "^2.13.3",
|
||||||
|
"@schedule-x/resize": "^2.15.1",
|
||||||
"@schedule-x/theme-default": "^2.14.3",
|
"@schedule-x/theme-default": "^2.14.3",
|
||||||
"@schedule-x/theme-shadcn": "^2.14.3",
|
"@schedule-x/theme-shadcn": "^2.14.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"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": {
|
"node_modules/@radix-ui/react-collapsible": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.4.tgz",
|
||||||
@@ -1936,6 +2003,28 @@
|
|||||||
"preact": "^10.19.2"
|
"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": {
|
"node_modules/@schedule-x/events-service": {
|
||||||
"version": "2.14.3",
|
"version": "2.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@schedule-x/events-service/-/events-service-2.14.3.tgz",
|
"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"
|
"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": {
|
"node_modules/@schedule-x/theme-default": {
|
||||||
"version": "2.14.3",
|
"version": "2.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@schedule-x/theme-default/-/theme-default-2.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/@schedule-x/theme-default/-/theme-default-2.14.3.tgz",
|
||||||
|
|||||||
@@ -12,20 +12,26 @@
|
|||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-avatar": "^1.1.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-collapsible": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
"@radix-ui/react-navigation-menu": "^1.2.3",
|
||||||
"@radix-ui/react-popover": "^1.1.4",
|
"@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-select": "^2.1.4",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.6",
|
"@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/events-service": "^2.14.3",
|
||||||
"@schedule-x/react": "^2.13.3",
|
"@schedule-x/react": "^2.13.3",
|
||||||
|
"@schedule-x/resize": "^2.15.1",
|
||||||
"@schedule-x/theme-default": "^2.14.3",
|
"@schedule-x/theme-default": "^2.14.3",
|
||||||
"@schedule-x/theme-shadcn": "^2.14.3",
|
"@schedule-x/theme-shadcn": "^2.14.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "latosa-escrima.fr",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user