From efec258ae3c8cdca5d1a2d4d50596d175dfa0e34 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Tue, 25 Feb 2025 18:37:00 +0100 Subject: [PATCH] Article listing in dashboard --- frontend/app/(auth)/dashboard/blogs/blogs.tsx | 252 ++++++++++++++++++ frontend/app/(auth)/dashboard/blogs/page.tsx | 21 ++ frontend/components/app-sidebar.tsx | 15 +- 3 files changed, 277 insertions(+), 11 deletions(-) create mode 100644 frontend/app/(auth)/dashboard/blogs/blogs.tsx create mode 100644 frontend/app/(auth)/dashboard/blogs/page.tsx diff --git a/frontend/app/(auth)/dashboard/blogs/blogs.tsx b/frontend/app/(auth)/dashboard/blogs/blogs.tsx new file mode 100644 index 0000000..34114c5 --- /dev/null +++ b/frontend/app/(auth)/dashboard/blogs/blogs.tsx @@ -0,0 +1,252 @@ +"use client"; + +import * as React from "react"; +import { format } from "date-fns"; +import { CircleX, Edit, MoreHorizontal, Trash } from "lucide-react"; +import Link from "next/link"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Blog, Category } from "@/types/types"; +import { useApi } from "@/hooks/use-api"; +import request from "@/lib/request"; +import IUser from "@/interfaces/IUser"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useCallback } from "react"; +import hasPermissions from "@/lib/hasPermissions"; + +export default function BlogTable({ user }: { user: IUser }) { + const searchParams = useSearchParams(); + const pathname = usePathname(); // Get current path + + const { replace } = useRouter(); + + const canUpdate = hasPermissions(user.roles, { blogs: ["update"] }); + const canInsert = hasPermissions(user.roles, { blogs: ["insert"] }); + const canDelete = hasPermissions(user.roles, { blogs: ["delete"] }); + + const updateSearchParam = useCallback( + (key: string, value?: string) => { + const params = new URLSearchParams(searchParams); + if (value) { + params.set(key, value); + } else { + params.delete(key); + } + replace(`${pathname}?${params.toString()}`); + }, + [searchParams, pathname, replace], + ); + + let url = "/blogs"; + const category = searchParams.get("category"); + if (category) url += `?category=${category}`; + + const blogs = useApi(url, {}, false, false); + + const categories = useApi( + "/blogs/categories", + {}, + false, + false, + ); + + const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); + const [blogToDelete, setBlogToDelete] = React.useState(null); + + const handleDelete = async (blog: Blog) => { + setBlogToDelete(blog); + setDeleteDialogOpen(true); + }; + + const confirmDelete = async () => { + if (!blogToDelete) return; + + try { + const res = await request(`/blogs/${blogToDelete.blogID}/delete`, { + method: "DELETE", + requiresAuth: true, + }); + + if (res.status === "Success") { + blogs.mutate(); + } + + setDeleteDialogOpen(false); + setBlogToDelete(null); + } catch (error) { + console.error("Failed to delete blog:", error); + } + }; + + return ( +
+
+
+ + {category && ( + + )} +
+ {canInsert && ( + + )} +
+ +
+ + + + Titre + Catégorie + Auteur + Publié + {(canUpdate || canDelete) && ( + + Actions + + )} + + + + {blogs.data?.map((blog) => ( + + + {blog.title} + + {blog.category} + + {blog.author.firstname}{" "} + {blog.author.lastname} + + + {format( + new Date(blog.published), + "MMM d, yyyy", + )} + + {(canDelete || canUpdate) && ( + + + + + + + {canUpdate && ( + + + + Modifier + + + )} + {canDelete && ( + + handleDelete(blog) + } + > + + Supprimer + + )} + + + + )} + + ))} + +
+
+ + + + + Êtes-vous sûr ? + + Cela supprimera définitivement l'article{" "} + + {blogToDelete?.title} + + . + + +
+ + +
+
+
+
+ ); +} diff --git a/frontend/app/(auth)/dashboard/blogs/page.tsx b/frontend/app/(auth)/dashboard/blogs/page.tsx new file mode 100644 index 0000000..c235f96 --- /dev/null +++ b/frontend/app/(auth)/dashboard/blogs/page.tsx @@ -0,0 +1,21 @@ +"use server"; +import getMe from "@/lib/getMe"; +import hasPermissions from "@/lib/hasPermissions"; +import { redirect } from "next/navigation"; +import BlogTable from "./blogs"; + +export default async function Page() { + const me = await getMe(); + if ( + !me || + me.status === "Error" || + !me.data || + !hasPermissions(me.data.roles, { + events: ["get"], + }) + ) { + redirect("/dashboard"); + } + + return ; +} diff --git a/frontend/components/app-sidebar.tsx b/frontend/components/app-sidebar.tsx index f73ad33..8bad163 100644 --- a/frontend/components/app-sidebar.tsx +++ b/frontend/components/app-sidebar.tsx @@ -28,6 +28,7 @@ import { } from "@/components/ui/sidebar"; import useMe from "@/hooks/use-me"; import { useEffect } from "react"; +import { title } from "process"; // This is sample data. const data = { @@ -78,16 +79,8 @@ const data = { icon: BookOpen, items: [ { - title: "Catégorie 1", - url: "/dashboard/blogs/categorie-1", - }, - { - title: "Catégorie 2", - url: "/dashboard/blogs/categorie-2", - }, - { - title: "Nouvelle catégorie", - url: "/dashboard/blogs/categories/new", + title: "Articles", + url: "/dashboard/blogs", }, { title: "Nouvel article", @@ -96,7 +89,7 @@ const data = { ], }, { - title: "Settings", + title: "Configurations", url: "/dashboard/settings", icon: Settings2, items: [