From ac7e97527f47f7f329ea31e9b49a31c406f95551 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:58:47 +0100 Subject: [PATCH] CRUD Events and React interaction --- backend/api/core/schemas.go | 8 +- backend/api/delete_event.go | 5 +- backend/api/new_blog.go | 8 - backend/api/new_event.go | 9 +- backend/api/update_event.go | 5 +- backend/api/update_shortcode.go | 11 +- .../dashboard/settings/shortcodes/page.tsx | 2 +- frontend/app/(main)/planning/page.tsx | 286 +------------- frontend/components/members-table.tsx | 238 ++++++++++++ frontend/components/planning.tsx.tsx | 356 ++++++++++++++++++ frontend/components/shortcode-dialogue.tsx | 85 +++-- frontend/components/shortcodes-table.tsx | 37 +- frontend/components/ui/checkbox.tsx | 30 ++ frontend/components/ui/scroll-area.tsx | 50 +++ frontend/package-lock.json | 94 +++++ frontend/package.json | 6 + package-lock.json | 6 - 17 files changed, 887 insertions(+), 349 deletions(-) create mode 100644 frontend/components/members-table.tsx create mode 100644 frontend/components/planning.tsx.tsx create mode 100644 frontend/components/ui/checkbox.tsx create mode 100644 frontend/components/ui/scroll-area.tsx delete mode 100644 package-lock.json diff --git a/backend/api/core/schemas.go b/backend/api/core/schemas.go index 5660a58..a2b6e35 100644 --- a/backend/api/core/schemas.go +++ b/backend/api/core/schemas.go @@ -97,11 +97,11 @@ func Verify(ctx context.Context, email, password string) (*User, error) { type Event struct { 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"` - ScheduleStart time.Time `bun:"schedule_start,notnull" json:"scheduleStart"` - ScheduleEnd time.Time `bun:"schedule_end,notnull" json:"scheduleEnd"` - Status Status `bun:"status,notnull,default:Inactive" json:"status"` + ScheduleStart time.Time `bun:"schedule_start,notnull" json:"start"` + ScheduleEnd time.Time `bun:"schedule_end,notnull" json:"end"` + Status Status `bun:"status,notnull,default:'Inactive'" json:"status"` } type EventToUser struct { diff --git a/backend/api/delete_event.go b/backend/api/delete_event.go index 586eac4..578229d 100644 --- a/backend/api/delete_event.go +++ b/backend/api/delete_event.go @@ -13,7 +13,7 @@ func HandleDeleteEvent(w http.ResponseWriter, r *http.Request) { var event core.Event res, err := core.DB.NewDelete(). Model(&event). - Where("id = ?", uuid). + Where("event_id = ?", uuid). Returning("*"). Exec(context.Background()) if err != nil { @@ -24,10 +24,9 @@ func HandleDeleteEvent(w http.ResponseWriter, r *http.Request) { return } log.Println(res) - + core.JSONSuccess{ Status: core.Success, Message: "Event deleted.", }.Respond(w, http.StatusOK) } - diff --git a/backend/api/new_blog.go b/backend/api/new_blog.go index 29419f2..de12e4d 100644 --- a/backend/api/new_blog.go +++ b/backend/api/new_blog.go @@ -10,14 +10,6 @@ import ( ) 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) if err != nil { core.JSONError{ diff --git a/backend/api/new_event.go b/backend/api/new_event.go index 6abba18..cade7c5 100644 --- a/backend/api/new_event.go +++ b/backend/api/new_event.go @@ -18,18 +18,19 @@ func HandleCreateEvent(w http.ResponseWriter, r *http.Request) { }.Respond(w, http.StatusBadRequest) return } - - _, err = core.DB.NewInsert().Model(&event).Exec(context.Background()) + + _, err = core.DB.NewInsert().Model(&event).Exec(context.Background()) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusNotAcceptable) - } + return + } core.JSONSuccess{ Status: core.Success, Message: "Event created", - Data: event, + Data: event, }.Respond(w, http.StatusCreated) } diff --git a/backend/api/update_event.go b/backend/api/update_event.go index ed143ae..753f40a 100644 --- a/backend/api/update_event.go +++ b/backend/api/update_event.go @@ -22,7 +22,7 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) { } event_uuid := r.PathValue("event_uuid") - event.EventID, err = uuid.FromBytes([]byte(event_uuid)) + event.EventID, err = uuid.Parse(event_uuid) if err != nil { core.JSONError{ Status: core.Error, @@ -44,7 +44,7 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) { // } _, err = core.DB.NewUpdate(). - Model(event). + Model(&event). OmitZero(). WherePK(). Exec(context.Background()) @@ -62,4 +62,3 @@ func HandleUpdateEvent(w http.ResponseWriter, r *http.Request) { Data: event, }.Respond(w, http.StatusOK) } - diff --git a/backend/api/update_shortcode.go b/backend/api/update_shortcode.go index ab699a3..17c98f9 100644 --- a/backend/api/update_shortcode.go +++ b/backend/api/update_shortcode.go @@ -13,10 +13,11 @@ import ( ) type UpdateShortcodeArgs struct { - Code *string `json:"code"` // The shortcode value - Type *core.ShortcodeType `json:"type"` - Value *string `json:"value"` - MediaID *uuid.UUID `json:"media_id"` // Nullable reference to another table's ID + ID *int64 `json:"id,omitempty"` + Code *string `json:"code,omitempty"` // The shortcode value + Type *core.ShortcodeType `bun:"shortcode_type" json:"type,omitempty"` + 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) { @@ -51,7 +52,7 @@ func HandleUpdateShortcode(w http.ResponseWriter, r *http.Request) { code := r.PathValue("shortcode") _, err = updateQuery. - Where("code = ?", code). + Where("id = ? OR code = ?", updateArgs.ID, code). Returning("*"). Exec(context.Background()) diff --git a/frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx b/frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx index 03fe4cd..cd508f9 100644 --- a/frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx +++ b/frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx @@ -48,7 +48,7 @@ export default function ShortcodesPage() { }; return ( -
+

Shortcodes

{isLoading && ( diff --git a/frontend/app/(main)/planning/page.tsx b/frontend/app/(main)/planning/page.tsx index 3584f6a..b7fbcb9 100644 --- a/frontend/app/(main)/planning/page.tsx +++ b/frontend/app/(main)/planning/page.tsx @@ -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("/events", undefined, false, false); -const Planning = () => { - const plugins = [createEventsServicePlugin()]; - const [eventSelected, setEventSelected] = - useState(null); - const [events, setEvents] = useState([ - { - 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( - `/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 ( -
-
- -
- { - setEventSelected((e) => (open ? e : null)); - }} - > - - - {eventSelected?.title} - - {eventSelected?.description} - - - -
-
- - {/* - - - - - - date > new Date() || - date < new Date("1900-01-01") - } - initialFocus - /> - - */} - { - const val = e.currentTarget.value; - setEventSelected((ev) => { - if (ev) - return { - ...ev, - start: val, - }; - return ev; - }); - }} - className="col-span-3" - /> -
-
- - { - const val = e.currentTarget.value - setEventSelected((ev) => { - if (ev) - return { - ...ev, - end: val, - }; - return ev; - }) - }} - className="col-span-3" - /> -
-
- - - - -
-
-
- ); + if (isLoading) return ; + if (success) + return ; }; -export default Planning; +export default Page; diff --git a/frontend/components/members-table.tsx b/frontend/components/members-table.tsx new file mode 100644 index 0000000..399ec59 --- /dev/null +++ b/frontend/components/members-table.tsx @@ -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(initialMembers); + const [selectMode, setSelectMode] = useState(false); + const [selectedMembers, setSelectedMembers] = useState([]); + const [updateDialogOpen, setUpdateDialogOpen] = useState(false); + const [addDialogOpen, setAddDialogOpen] = useState(false); + const [currentMember, setCurrentMember] = useState(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 ( +
+
+ + +
+
+ + + + + {selectMode && ( + + Select + + )} + User ID + First Name + Last Name + Email + Password + Phone + Role + + Actions + + + + + {members.map((member) => ( + + {selectMode && ( + + + toggleMemberSelection( + member.user_id, + ) + } + /> + + )} + {member.user_id} + {member.firstname} + {member.lastname} + {member.email} + {member.password} + {member.phone} + {member.role} + + + + + + ))} + +
+
+
+ setUpdateDialogOpen(false)} + member={currentMember} + onUpdate={(updatedMember) => { + setMembers((prev) => + prev.map((m) => + m.user_id === updatedMember.user_id + ? updatedMember + : m, + ), + ); + setUpdateDialogOpen(false); + }} + /> + setAddDialogOpen(false)} + onAdd={handleAdd} + /> +
+ ); +} diff --git a/frontend/components/planning.tsx.tsx b/frontend/components/planning.tsx.tsx new file mode 100644 index 0000000..675612c --- /dev/null +++ b/frontend/components/planning.tsx.tsx @@ -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>; +}> = ({ events, mutate }) => { + const plugins = [ + createEventsServicePlugin(), + createDragAndDropPlugin(), + createResizePlugin(), + createEventRecurrencePlugin(), + ]; + const [eventSelected, setEventSelected] = + useState(null); + const [newEvent, setNewEvent] = useState | 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(`/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 = () => ( + + ); + + return ( +
+
+ + +
+ {newEvent && ( + { + 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 = { + ...newEvent, + start: `${new Date(newEvent.start).toISOString()}`, + end: `${new Date(newEvent.end).toISOString()}`, + }; + const res = await request( + `/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 && ( + { + 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( + `/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); + }} + /> + )} +
+ ); +}; + +const EventDialog: React.FC< + { + onEndChange: React.ChangeEventHandler; + onStartChange: React.ChangeEventHandler; + onDelete?: () => void; + onUpdate?: () => void; + onAdd?: () => void; + event: CalendarEventExternal | Omit; + } & DialogProps +> = ({ + open, + onOpenChange, + onEndChange, + onStartChange, + onDelete, + onUpdate, + onAdd, + event, +}) => { + return ( + + + + {event.title} + {event.description} + + +
+
+ + {/* + + + + + + date > new Date() || + date < new Date("1900-01-01") + } + initialFocus + /> + + */} + +
+
+ + +
+
+ + {onUpdate && ( + + )} + {onDelete && ( + + )} + {onAdd && !onUpdate && !onDelete && ( + + )} + +
+
+ ); +}; + +export default Planning; diff --git a/frontend/components/shortcode-dialogue.tsx b/frontend/components/shortcode-dialogue.tsx index c80a401..d0c4564 100644 --- a/frontend/components/shortcode-dialogue.tsx +++ b/frontend/components/shortcode-dialogue.tsx @@ -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) => 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( + 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 ( - - - - Add New Shortcode + Ajouter un nouveau shortcode - Create a new shortcode here. Click save when you're - done. + Créer un nouveau shortcode ici. Cliquez enregistrer + quand vous avez fini.
@@ -67,29 +59,41 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) { setCode(e.target.value)} + value={_shortcode.code} + onChange={(e) => + setShortcode((p) => ({ + ...p, + code: e.target.value, + })) + } className="col-span-3" />
setType(v as "value" | "media")} + defaultValue={_shortcode.type} + onValueChange={(v) => + setShortcode((p) => ({ ...p, type: v })) + } className="w-full" > - Value + Valeur Media
setValue(e.target.value)} + value={_shortcode.value} + onChange={(e) => + setShortcode((p) => ({ + ...p, + value: e.target.value, + })) + } className="col-span-3" />
@@ -101,8 +105,13 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) { setMediaId(e.target.value)} + value={_shortcode.media_id} + onChange={(e) => + setShortcode((p) => ({ + ...p, + media_id: e.target.value, + })) + } className="col-span-3" />
@@ -111,7 +120,7 @@ export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
diff --git a/frontend/components/shortcodes-table.tsx b/frontend/components/shortcodes-table.tsx index ff6a3e9..95b30e5 100644 --- a/frontend/components/shortcodes-table.tsx +++ b/frontend/components/shortcodes-table.tsx @@ -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( + null, + ); + const [addDialog, setAddDialog] = useState(false); return (
- + + setAddDialog(false)} + />
@@ -44,7 +60,7 @@ export function ShortcodeTable({ IDCodeType - Value + ValeurMedia IDActions @@ -69,7 +85,7 @@ export function ShortcodeTable({ className="h-8 w-8 p-0" > - Open menu + Ouvrir le menu @@ -77,17 +93,17 @@ export function ShortcodeTable({ - onUpdate(shortcode) + setUpdateDialog(shortcode) } > - Update + Mettre à jour onDelete(shortcode.code) } > - Delete + Supprimer @@ -96,6 +112,15 @@ export function ShortcodeTable({ ))}
+ + {shortcodeSelected && ( + setUpdateDialog(null)} + /> + )}
); diff --git a/frontend/components/ui/checkbox.tsx b/frontend/components/ui/checkbox.tsx new file mode 100644 index 0000000..577375b --- /dev/null +++ b/frontend/components/ui/checkbox.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/frontend/components/ui/scroll-area.tsx b/frontend/components/ui/scroll-area.tsx new file mode 100644 index 0000000..98234fc --- /dev/null +++ b/frontend/components/ui/scroll-area.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)); +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef< + typeof ScrollAreaPrimitive.ScrollAreaScrollbar + > +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)); +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; + +export { ScrollArea, ScrollBar }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 38b23fb..38596ed 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 3a8e60b..994a335 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c7ff983..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "latosa-escrima.fr", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}