feat(admin): add edit dialogs for users, contents, and categories

- Implemented edit functionality for users, contents, and categories, including modals for updating records.
- Enhanced table actions with edit buttons alongside delete.
- Improved user, content, and category fetching with `useCallback` to optimize re-renders.
- Added skeleton loaders and UI updates for better user experience.
This commit is contained in:
Mathis HERRIOT
2026-01-21 13:19:29 +01:00
parent 3a5550d6eb
commit 9b714716f6
3 changed files with 181 additions and 31 deletions

View File

@@ -2,8 +2,15 @@
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Download, Eye, Image as ImageIcon, Trash2, Video } from "lucide-react";
import { useEffect, useState } from "react";
import {
Download,
Edit,
Eye,
Image as ImageIcon,
Trash2,
Video,
} from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
@@ -17,13 +24,17 @@ import {
} from "@/components/ui/table";
import { ContentService } from "@/services/content.service";
import type { Content } from "@/types/content";
import { ContentEditDialog } from "./content-edit-dialog";
export default function AdminContentsPage() {
const [contents, setContents] = useState<Content[]>([]);
const [loading, setLoading] = useState(true);
const [totalCount, setTotalCount] = useState(0);
const [selectedContent, setSelectedContent] = useState<Content | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
useEffect(() => {
const fetchContents = useCallback(() => {
setLoading(true);
ContentService.getExplore({ limit: 20 })
.then((res) => {
setContents(res.data);
@@ -33,6 +44,10 @@ export default function AdminContentsPage() {
.finally(() => setLoading(false));
}, []);
useEffect(() => {
fetchContents();
}, [fetchContents]);
const handleDelete = async (id: string) => {
if (!confirm("Êtes-vous sûr de vouloir supprimer ce contenu ?")) return;
@@ -45,6 +60,11 @@ export default function AdminContentsPage() {
}
};
const handleEdit = (content: Content) => {
setSelectedContent(content);
setDialogOpen(true);
};
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
@@ -61,7 +81,7 @@ export default function AdminContentsPage() {
<TableHead>Auteur</TableHead>
<TableHead>Stats</TableHead>
<TableHead>Date</TableHead>
<TableHead className="w-[50px]"></TableHead>
<TableHead className="w-[100px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -84,11 +104,14 @@ export default function AdminContentsPage() {
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
<TableCell>
<Skeleton className="h-8 w-8 rounded-full" />
</TableCell>
</TableRow>
))
) : contents.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="text-center h-24">
<TableCell colSpan={6} className="text-center h-24">
Aucun contenu trouvé.
</TableCell>
</TableRow>
@@ -132,14 +155,23 @@ export default function AdminContentsPage() {
{format(new Date(content.createdAt), "dd/MM/yyyy", { locale: fr })}
</TableCell>
<TableCell>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(content.id)}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4" />
</Button>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => handleEdit(content)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(content.id)}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
@@ -147,6 +179,12 @@ export default function AdminContentsPage() {
</TableBody>
</Table>
</div>
<ContentEditDialog
content={selectedContent}
open={dialogOpen}
onOpenChange={setDialogOpen}
onSuccess={fetchContents}
/>
</div>
);
}