diff --git a/src/app/admin/admins/page.tsx b/src/app/admin/admins/page.tsx index 5bf52e1..05df663 100644 --- a/src/app/admin/admins/page.tsx +++ b/src/app/admin/admins/page.tsx @@ -348,7 +348,7 @@ export default function Admins() { open={openModal} onOpenChange={(isOpen) => { if (!isOpen) { - setOpen(false); + setOpenModal(false); } }} trigger={ diff --git a/src/app/admin/organizations/page.tsx b/src/app/admin/organizations/page.tsx index a5fc92e..98c7f29 100644 --- a/src/app/admin/organizations/page.tsx +++ b/src/app/admin/organizations/page.tsx @@ -1,8 +1,433 @@ +"use client"; +import { useState } from "react"; +import Image from "next/image"; +import { useSession } from "next-auth/react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { ColumnDef } from "@tanstack/react-table"; +import axios from "axios"; +import { DropdownMenu } from "radix-ui"; +import FloatingLabelInput from "#/components/floatingLabelInput"; +import { Modal } from "#/components/modal"; +import Table from "#/components/table/table"; +import Form from "#/components/form/form"; +import { icons } from "#/assets/icons"; +import { adminSchema, companySchema } from "#/schema"; +import { Admin, Company } from "#/types"; + +export default function Organizations() { + const { data: session, status } = useSession(); + const [open, setOpen] = useState(false); + const [openModal, setOpenModal] = useState(false); + const [openDeleteModal, setOpenDeleteModal] = useState(false); + const [openEditModal, setOpenEditModal] = useState(false); + const [selectedAdminId, setSelectedAdminId] = useState(null); + + const queryClient = useQueryClient(); + + const { + data: companies, + refetch, + isLoading, + } = useQuery({ + enabled: status === "authenticated", + queryKey: ["companies"], + queryFn: async () => { + try { + const response = await axios.get( + "https://private-docs-api.intside.co/companies", + { + headers: { + Authorization: `Bearer ${session?.user.access_token}`, + }, + } + ); + + if (response.data) { + return response.data.data as Company[]; + } + } catch (error) { + console.error(error); + } + }, + }); + + const { + data: users, + } = useQuery({ + enabled: status === "authenticated", + queryKey: ["organizations"], + queryFn: async () => { + try { + const response = await axios.get( + "https://private-docs-api.intside.co/users", + { + headers: { Authorization: `Bearer ${session?.user.access_token}` }, + } + ); + return response.data.data.map((user: Admin) => ({ + id: user.id, + name: `${user.first_name} ${user.last_name}`, + })); + } catch (error) { + console.error(error); + return []; + } + }, + }); + + const createMutation = useMutation({ + mutationFn: async (data: { + name: string; + description: string; + status: string; + is_premium: string; + owner: string; + }) => { + try { + const response = await axios.post( + "https://private-docs-api.intside.co/companies/", + data, + { headers: { Authorization: `Bearer ${session?.user.access_token}` } } + ); + + if (response.status === 200 || response.status === 201) { + console.log("ajout réussie !"); + setOpenModal(false); + } + } catch (error) { + console.error("Erreur lors de la création", error); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["organizations"] }); + refetch(); + }, + }); + + const updateMutation = useMutation({ + mutationFn: async (data: { + id: string; + last_name: string; + first_name: string; + email: string; + }) => { + try { + const response = await axios.put( + `https://private-docs-api.intside.co/companies/${data.id}/`, + data, + { headers: { Authorization: `Bearer ${session?.user.access_token}` } } + ); + + if (response.status === 200 || response.status === 201) { + console.log("modification réussie !"); + setOpenEditModal(false); + } + } catch (error) { + console.error("Erreur lors de la mise à jour", error); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["organizations"] }); + refetch(); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: async (id: string) => { + try { + const response = await axios.delete( + `https://private-docs-api.intside.co/companies/${id}/`, + { + headers: { Authorization: `Bearer ${session?.user.access_token}` }, + } + ); + + if (response.status === 200 || response.status === 201) { + console.log("Suppresion réussie !"); + setOpenDeleteModal(false); + } + } catch (error) { + console.error("Erreur lors de la suppression", error); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["organizations"] }); + refetch(); + }, + }); + + const bulkDeleteMutation = useMutation({ + mutationFn: async (ids: string[]) => { + try { + const deletePromises = ids.map((id) => + axios.delete(`https://private-docs-api.intside.co/companies/${id}/`, { + headers: { Authorization: `Bearer ${session?.user.access_token}` }, + }) + ); + await Promise.all(deletePromises); + } catch (error) { + console.error("Erreur lors de la suppression groupée", error); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["organizations"] }); + refetch(); + }, + }); + + const columns: ColumnDef[] = [ + { + accessorKey: "name", + header: "Organisations", + }, + { + accessorKey: "total_users", + header: "Utilisateurs", + }, + { + header: "Administrateurs", + cell: ({ row }) => { + const value = String(row.original.owner.first_name) + " " + String(row.original.owner.last_name) + const initials = String(row.original.owner.first_name[0]) + String(row.original.owner.last_name[0]) + return( +
+
+ {initials} +
+

{value}

+
+ ) + } + }, + { + accessorKey: "owner.email", + header: "Adresse e-mail" + }, + { + accessorKey: "status", + header: "Statut", + cell: ({ cell }) => { + const status = String(cell.getValue()) + return ( +

+ { + status === "active" ? "Actif" : + status === "inactive" ? "Inactif" : + status === "pending" ? "En attente" : + status === "blocked" ? "Bloquée" : + "" + } +

+ ) + } + }, + { + id: "delete", + cell: ({ row }) => { + const company = row.original; + return ( +
{ mutate(id) }} + > + + {/* Modal de suppression */} + { + if (!isOpen) { + setSelectedAdminId(null); + setOpenDeleteModal(false); + } + }} + trigger={ +
{ + setSelectedAdminId(company.id); + setOpenDeleteModal(true); + }} + > + Supprimer +
+ } + title="Supprimer une organisation" + content={ +
+

Voulez-vous vraiment supprimer cette organisation ?

+
+ + +
+
+ } + /> +
+ ) + } + } + ] -export default function Organizations (){ return ( - <> - - ) -} \ No newline at end of file + { + const selectedIds = table + .getRowModel() + .rows.filter((row) => row.getIsSelected()) + .map((row) => row.original.id); + + return ( +
+
+ + table.toggleAllPageRowsSelected(e.target.checked) + } + /> + + + +

+ Sélectionner une action +

+
+ + + + bulkDeleteMutation.mutate(selectedIds)} + className="p-2 text-[14px] cursor-pointer hover:bg-blue-100 hover:text-blue-500 rounded-md" + > + Supprimer + + + +
+
+ +
+ {/* Modal d'ajout */} + { + if (!isOpen) { + setOpenModal(false); + } + }} + trigger={ +
setOpenModal(true)} + className="cursor-pointer p-3 bg-blue-600 text-white rounded-full" + > + Ajouter une organisation +
+ } + content={ +
({ + label: user.name, + value: user.id, + })) || [], + }, + ]} + submit={createMutation.mutate} + schema={companySchema} + child={ + + } + /> + } + /> + + table.setGlobalFilter(value)} + button={ + + } + /> +
+
+ ); + }} + /> + ); +} diff --git a/src/components/floatingLabelInput.tsx b/src/components/floatingLabelInput.tsx index 0ce83cb..b9f87fb 100644 --- a/src/components/floatingLabelInput.tsx +++ b/src/components/floatingLabelInput.tsx @@ -37,7 +37,7 @@ export default function FloatingLabelInput({ defaultValue={defaultValue} > {options?.map((option, index) => ( - + ))} {button &&
{button}
} diff --git a/src/schema/index.ts b/src/schema/index.ts index b28bb6a..c289865 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -10,4 +10,11 @@ export const adminSchema = z.object({ last_name: z.string(), first_name: z.string(), email: z.string().min(1, "L'email est requis").email("Email invalide"), -}); \ No newline at end of file +}); + +export const companySchema = z.object({ + name: z.string().min(1, "Le nom est requis"), + description: z.string(), + status: z.string(), + owner: z.string() +}) \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 03efa94..a5c93f7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,11 +1,15 @@ import { FormEventHandler, ReactNode } from "react"; import { ZodSchema } from "zod"; +export interface Option { + label: string + value: string +} export interface FloatingLabelInputProps { label?: string; placeholder?: string; - type: 'text' | 'password' | 'select' | 'email' | 'number' | 'hidden' | 'search'; - options?: string[]; + type: 'text' | 'password' | 'select' | 'email' | 'number' | 'hidden' | 'search' | 'textarea'; + options?: Option[]; button?: React.ReactNode; showPasswordToggle?: boolean; name: string;