CRUD Events and React interaction
This commit is contained in:
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 };
|
||||
Reference in New Issue
Block a user