feat: add bulkDelete, tablefilter, rowEdit modal, add Admin modal
This commit is contained in:
parent
cbe135ce8a
commit
68d9c2dd5f
@ -25,6 +25,7 @@
|
|||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"sass": "^1.86.0",
|
"sass": "^1.86.0",
|
||||||
|
"sonner": "^2.0.2",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@ -56,6 +56,9 @@ importers:
|
|||||||
sass:
|
sass:
|
||||||
specifier: ^1.86.0
|
specifier: ^1.86.0
|
||||||
version: 1.86.0
|
version: 1.86.0
|
||||||
|
sonner:
|
||||||
|
specifier: ^2.0.2
|
||||||
|
version: 2.0.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.24.2
|
specifier: ^3.24.2
|
||||||
version: 3.24.2
|
version: 3.24.2
|
||||||
@ -3468,6 +3471,12 @@ packages:
|
|||||||
snake-case@3.0.4:
|
snake-case@3.0.4:
|
||||||
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
|
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
|
||||||
|
|
||||||
|
sonner@2.0.2:
|
||||||
|
resolution: {integrity: sha512-xOeXErZ4blqQd11ZnlDmoRmg+ctUJBkTU8H+HVh9rnWi9Ke28xiL39r4iCTeDX31ODTe/s1MaiaY333dUzLCtA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -7392,6 +7401,11 @@ snapshots:
|
|||||||
dot-case: 3.0.4
|
dot-case: 3.0.4
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
sonner@2.0.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
stable-hash@0.0.5: {}
|
stable-hash@0.0.5: {}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Form from "#/components/form/form"
|
import Form from "#/components/form/form"
|
||||||
import { loginSchema } from "#/schema/loginSchema"
|
import { loginSchema } from "#/schema"
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import { signIn } from "next-auth/react"
|
import { signIn } from "next-auth/react"
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@ -25,6 +25,8 @@ export default function LoginPage() {
|
|||||||
: result.error;
|
: result.error;
|
||||||
console.error(errorMessage)
|
console.error(errorMessage)
|
||||||
throw new Error(result.error)
|
throw new Error(result.error)
|
||||||
|
} else {
|
||||||
|
router.push('/admin/home')
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -35,9 +37,6 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
router.push('/admin/home')
|
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
|
|||||||
@ -3,22 +3,27 @@
|
|||||||
import FloatingLabelInput from "#/components/floatingLabelInput"
|
import FloatingLabelInput from "#/components/floatingLabelInput"
|
||||||
import { Modal } from "#/components/modal"
|
import { Modal } from "#/components/modal"
|
||||||
import Table from "#/components/table/table"
|
import Table from "#/components/table/table"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { ColumnDef } from "@tanstack/react-table"
|
import { ColumnDef } from "@tanstack/react-table"
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import { table } from "console"
|
import Image from "next/image"
|
||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
|
import { DropdownMenu } from "radix-ui"
|
||||||
export interface Admin {
|
import Link from "next/link"
|
||||||
id: string
|
import { icons } from "#/assets/icons"
|
||||||
email: string
|
import { useState } from "react"
|
||||||
first_name: string
|
import Form from "#/components/form/form"
|
||||||
last_name: string
|
import { adminSchema } from "#/schema"
|
||||||
profile: string
|
import { Admin } from "#/types"
|
||||||
}
|
|
||||||
|
|
||||||
export default function Admins (){
|
export default function Admins (){
|
||||||
const {data: session, status} = useSession()
|
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<string | null>(null);
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const { data: users, refetch, isLoading} = useQuery({
|
const { data: users, refetch, isLoading} = useQuery({
|
||||||
enabled: status === 'authenticated',
|
enabled: status === 'authenticated',
|
||||||
@ -42,6 +47,135 @@ export default function Admins (){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (data: { last_name: string; first_name: string; email: string }) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const result = await axios.post(
|
||||||
|
`https://private-docs-api.intside.co/users/`,
|
||||||
|
{
|
||||||
|
last_name: data.last_name,
|
||||||
|
first_name: data.first_name,
|
||||||
|
email: data.email,
|
||||||
|
user_type: "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${session?.user.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(result.status === 200 || result.status === 201) {
|
||||||
|
console.log('ajout réussie !')
|
||||||
|
setOpenModal(false)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.message.includes("Network Error")) {
|
||||||
|
console.error("Problème de connexion au serveur");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Autre = ", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mutationUpdate = useMutation({
|
||||||
|
mutationFn: async (data: { id: string, last_name: string; first_name: string; email: string }) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const result = await axios.put(
|
||||||
|
`https://private-docs-api.intside.co/users/${data.id}/`,
|
||||||
|
{
|
||||||
|
last_name: data.last_name,
|
||||||
|
first_name: data.first_name,
|
||||||
|
email: data.email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${session?.user.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(result.status === 200 || result.status === 201) {
|
||||||
|
console.log('modification réussie !')
|
||||||
|
setOpenEditModal(false)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.message.includes("Network Error")) {
|
||||||
|
console.error("Problème de connexion au serveur");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Autre = ", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
console.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const bulkDeleteMutation = useMutation({
|
||||||
|
mutationFn: async (ids: string[]) => {
|
||||||
|
try {
|
||||||
|
const deletePromises = ids.map(id =>
|
||||||
|
axios.delete(`https://private-docs-api.intside.co/users/${id}/`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${session?.user.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(deletePromises);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate, isPending } = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(
|
||||||
|
`https://private-docs-api.intside.co/users/${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(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] })
|
||||||
|
|
||||||
|
refetch()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const columns: ColumnDef<Admin>[] = [
|
const columns: ColumnDef<Admin>[] = [
|
||||||
{
|
{
|
||||||
header: "Administrateurs",
|
header: "Administrateurs",
|
||||||
@ -90,52 +224,127 @@ export default function Admins (){
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
{
|
||||||
// id: "delete",
|
id: "delete",
|
||||||
// cell: ({ cell }) => {
|
cell: ({ cell }) => {
|
||||||
// const id = String(cell.row.original.id)
|
const admin = cell.row.original
|
||||||
// return (
|
return (
|
||||||
// <div className="relative p-2 cursor-pointer"
|
<div className="flex justify-end p-2 cursor-pointer space-x-2"
|
||||||
// // onClick={() => { mutate(id) }}
|
// onClick={() => { mutate(id) }}
|
||||||
// >
|
>
|
||||||
// <Modal
|
|
||||||
// open={open}
|
|
||||||
// trigger={
|
|
||||||
// <div onClick={() => setOpen(true)}>
|
|
||||||
// <Image alt="" src={icons.trash} className="absolute right-2 top-[-50%] transform translate-middle-y hover:text-blue-500" />
|
|
||||||
// </div>
|
|
||||||
// }
|
|
||||||
// title={
|
|
||||||
// <p className="font-bold text-3xl">Supprimer une organisation</p>
|
|
||||||
// }
|
|
||||||
// content={
|
|
||||||
// <div>
|
|
||||||
// <p>Voulez-vous vraiment supprimer cette organisation ?</p>
|
|
||||||
|
|
||||||
// <div className="grid grid-cols-2 gap-3 mt-3">
|
<Modal
|
||||||
// <button
|
open={openEditModal && selectedAdminId === admin.id}
|
||||||
// className="bg-blue-100 text-blue-600 py-2 px-4 text-lg rounded-full text-center hover:bg-blue-200"
|
onOpenChange={(isOpen) => {
|
||||||
// onClick={() => { setOpen(false) }}
|
if (!isOpen) {
|
||||||
// >
|
setSelectedAdminId(null);
|
||||||
// Annuler
|
setOpenEditModal(false);
|
||||||
// </button>
|
}
|
||||||
// <button
|
}}
|
||||||
// className="bg-red-500 text-white py-2 px-4 text-lg rounded-full text-center hover:bg-red-600"
|
trigger={
|
||||||
// onClick={() => {
|
<div onClick={() =>{
|
||||||
// mutate(id)
|
setSelectedAdminId(admin.id);
|
||||||
// setOpen(false)
|
setOpenEditModal(true)
|
||||||
// }}
|
}}>
|
||||||
// >
|
<Image alt="" width={24} height={24} src={icons.editIcon} className="hover:text-blue-500" />
|
||||||
// Supprimer
|
</div>
|
||||||
// </button>
|
}
|
||||||
// </div>
|
title={
|
||||||
// </div>
|
<p className="font-bold text-3xl">Modifier un admin</p>
|
||||||
// }
|
}
|
||||||
// />
|
content={
|
||||||
// </div>
|
<>
|
||||||
// )
|
<Form
|
||||||
// }
|
fields={[
|
||||||
// }
|
{
|
||||||
|
name: "id", // Ajoutez un champ caché pour l'ID
|
||||||
|
type: "hidden",
|
||||||
|
defaultValue: admin.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last_name",
|
||||||
|
label: "Nom",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer le nom de l'admin",
|
||||||
|
defaultValue: admin.last_name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first_name",
|
||||||
|
label: "Prénom",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer le prénom de l'admin",
|
||||||
|
defaultValue: admin.first_name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
label: "Adresse e-mail",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer l'email de l'admin",
|
||||||
|
defaultValue: admin.email
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "statut",
|
||||||
|
// label: "Adresse e-mail",
|
||||||
|
// type: "select",
|
||||||
|
// placeholder: "Entrer l'email de l'admin",
|
||||||
|
// options
|
||||||
|
// }
|
||||||
|
]}
|
||||||
|
submit={mutationUpdate.mutate}
|
||||||
|
schema={adminSchema}
|
||||||
|
child={<button onClick={() => setOpenModal(false)} type="submit" className="btn-auth">{isLoading ? "Chargement..." : "Modifier"}</button>}
|
||||||
|
/></>
|
||||||
|
}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={openDeleteModal && selectedAdminId === admin.id}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setSelectedAdminId(null);
|
||||||
|
setOpenDeleteModal(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<div onClick={() => {
|
||||||
|
setSelectedAdminId(admin.id);
|
||||||
|
setOpenDeleteModal(true)
|
||||||
|
}}>
|
||||||
|
<Image alt="" src={icons.trash} className=" hover:text-blue-500" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
<p className="font-bold text-3xl">Supprimer un admin</p>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<p>Voulez-vous vraiment supprimer cet admin ?</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3 mt-3">
|
||||||
|
<button
|
||||||
|
className="bg-blue-100 text-blue-600 py-2 px-4 text-lg rounded-full text-center hover:bg-blue-200"
|
||||||
|
onClick={() => { setOpenDeleteModal(false) }}
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="bg-red-500 text-white py-2 px-4 text-lg rounded-full text-center hover:bg-red-600"
|
||||||
|
onClick={() => {
|
||||||
|
mutate(admin.id)
|
||||||
|
setOpenDeleteModal(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -144,30 +353,82 @@ export default function Admins (){
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
data={users || []}
|
data={users || []}
|
||||||
pageSize={5}
|
pageSize={5}
|
||||||
header={(table) => (
|
header={(table) => {
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 md:flex-row w-full">
|
const ids = table.getRowModel().rows.filter((row) => row.getIsSelected()).map(row => row.original.id )
|
||||||
<div className="flex">
|
if(bulkDeleteMutation.isSuccess) {
|
||||||
<input checked={
|
table.toggleAllPageRowsSelected(false)
|
||||||
table.getIsAllPageRowsSelected() ||
|
}
|
||||||
(table.getIsSomePageRowsSelected() && undefined)
|
return (
|
||||||
}
|
<>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 md:flex-row w-full">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<input checked={table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && undefined)}
|
||||||
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
||||||
type="checkbox" name="" id=""
|
type="checkbox" name="" id="" />
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-between md:justify-end space-x-3">
|
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
||||||
<Modal
|
<DropdownMenu.Trigger asChild>
|
||||||
trigger={
|
<p className="cursor-pointer rounded-full bg-gray-300 text-gray-500 p-2">
|
||||||
<button className="p-3 bg-blue-600 text-white rounded-full">
|
Sélectionner une action
|
||||||
Ajouter un admin
|
</p>
|
||||||
</button>
|
</DropdownMenu.Trigger>
|
||||||
}
|
|
||||||
/>
|
<DropdownMenu.Portal>
|
||||||
<FloatingLabelInput name="search" placeholder="Effectuer une recherche" type="text" onChange={(value) => table.setGlobalFilter(value)} />
|
<DropdownMenu.Content className="min-w-[150px] shadow-sm bg-white rounded-md p-1" sideOffset={5}>
|
||||||
</div>
|
<DropdownMenu.Item onClick={() => bulkDeleteMutation.mutate(ids)} className="p-2 text-[14px] cursor-pointer hover:bg-blue-100 hover:border-blue-100 hover:text-blue-500 hover:rounded-md outline-none">
|
||||||
</div>
|
Supprimer
|
||||||
)}
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between md:justify-end space-x-3">
|
||||||
|
<Modal
|
||||||
|
title="Ajouter un admin"
|
||||||
|
open={openModal}
|
||||||
|
trigger={<div onClick={() => setOpenModal(true)} className="cursor-pointer p-3 bg-blue-600 text-white rounded-full">
|
||||||
|
Ajouter un admin
|
||||||
|
</div>}
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<Form
|
||||||
|
fields={[
|
||||||
|
{
|
||||||
|
name: "last_name",
|
||||||
|
label: "Nom",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer le nom de l'admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first_name",
|
||||||
|
label: "Prénom",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer le prénom de l'admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
label: "Adresse e-mail",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Entrer l'email de l'admin"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
submit={mutation.mutate}
|
||||||
|
schema={adminSchema}
|
||||||
|
child={<button type="submit" className="btn-auth">{isLoading ? "Chargement..." : "Créer le compte"}</button>}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FloatingLabelInput name="search" placeholder="Effectuer une recherche" type="text" onChange={(value) => table.setGlobalFilter(value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -135,6 +135,11 @@ export default function HomePage () {
|
|||||||
>
|
>
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
|
onOpenChange={(isOpen)=>{
|
||||||
|
if(!isOpen) {
|
||||||
|
setOpen(isOpen)
|
||||||
|
}
|
||||||
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<div onClick={() => setOpen(true)}>
|
<div onClick={() => setOpen(true)}>
|
||||||
<Image alt="" src={icons.trash} className="absolute right-2 top-[-50%] transform translate-middle-y hover:text-blue-500" />
|
<Image alt="" src={icons.trash} className="absolute right-2 top-[-50%] transform translate-middle-y hover:text-blue-500" />
|
||||||
|
|||||||
@ -32,8 +32,7 @@ export default function FloatingLabelInput({
|
|||||||
return (
|
return (
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<select
|
<select
|
||||||
className="input-form focus:ring-2 focus:ring-blue-500"
|
className="input-form focus:ring-2 focus:ring-blue-500 outline-none"
|
||||||
required
|
|
||||||
name={name}
|
name={name}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
>
|
>
|
||||||
@ -52,9 +51,8 @@ export default function FloatingLabelInput({
|
|||||||
name={name}
|
name={name}
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="input-form focus:ring-2 focus:ring-blue-500 pr-10"
|
className="input-form focus:ring-2 focus:ring-blue-500 pr-10 outline-none"
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
required
|
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{showPasswordToggle && (
|
{showPasswordToggle && (
|
||||||
@ -89,8 +87,7 @@ export default function FloatingLabelInput({
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="input-form focus:ring-2 focus:ring-blue-500"
|
className="input-form focus:ring-2 focus:ring-blue-500 outline-none"
|
||||||
required
|
|
||||||
name={name}
|
name={name}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|||||||
@ -5,33 +5,38 @@ export function Modal({
|
|||||||
trigger,
|
trigger,
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
open
|
open,
|
||||||
|
onOpenChange
|
||||||
}: {
|
}: {
|
||||||
trigger: ReactNode;
|
trigger: ReactNode;
|
||||||
title: string | ReactNode;
|
title?: string | ReactNode;
|
||||||
content: ReactNode;
|
content: ReactNode;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const [toggle, setToggle] = useState(open);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={toggle}>
|
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
{trigger}
|
{trigger}
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 bg-black/25 z-40" />
|
<Dialog.Overlay className="fixed inset-0 bg-black/25 z-40" />
|
||||||
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md bg-white rounded-lg shadow-xl z-50 p-6">
|
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md bg-white rounded-lg shadow-xl z-50 p-6"
|
||||||
|
// onPointerDownOutside={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<Dialog.Title className="text-xl font-bold text-center my-4">
|
<Dialog.Title className="text-xl font-bold text-center my-4">
|
||||||
{title}
|
{title}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
{content}
|
|
||||||
|
|
||||||
{/* <div className="absolute top-4 right-4 text-gray-500 hover:text-gray-700 cursor-pointer"
|
<div className=" justify-center">
|
||||||
onClick={() => {setToggle(false)}}
|
{content}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute top-4 right-4 text-gray-500 hover:text-gray-700 cursor-pointer"
|
||||||
|
onClick={() => {onOpenChange?.(false)}}
|
||||||
>
|
>
|
||||||
Fermer
|
X
|
||||||
</div> */}
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
getFilteredRowModel,
|
getFilteredRowModel,
|
||||||
Table as TableType,
|
Table as TableType,
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
import { cloneElement, isValidElement, ReactNode, useState } from "react";
|
import { cloneElement, isValidElement, ReactNode, useEffect, useRef, useState } from "react";
|
||||||
import { clsx, type ClassValue } from "clsx"
|
import { clsx, type ClassValue } from "clsx"
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { icons } from "#/assets/icons";
|
import { icons } from "#/assets/icons";
|
||||||
@ -53,6 +53,22 @@ export default function Table<TData, TValue>({
|
|||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const headerCheckboxRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (headerCheckboxRef.current) {
|
||||||
|
headerCheckboxRef.current.indeterminate =
|
||||||
|
table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("SELECTED ALL = ", table.getSelectedRowModel().rows)
|
||||||
|
console.log("SELECTED = ", table.getRowModel().rows.filter((row) => row.getIsSelected()).map(row => row.original))
|
||||||
|
}, [
|
||||||
|
table.getIsSomePageRowsSelected(),
|
||||||
|
table.getIsAllPageRowsSelected(),
|
||||||
|
table.getRowModel()
|
||||||
|
]);
|
||||||
|
|
||||||
const totalPages = table.getPageCount()
|
const totalPages = table.getPageCount()
|
||||||
const currentPage = table.getState().pagination.pageIndex + 1
|
const currentPage = table.getState().pagination.pageIndex + 1
|
||||||
|
|
||||||
@ -85,18 +101,17 @@ export default function Table<TData, TValue>({
|
|||||||
<thead className="h-10">
|
<thead className="h-10">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id} className="rounded-lg">
|
<tr key={headerGroup.id} className="rounded-lg">
|
||||||
<th className="bg-blue-300 p-3 text-start first:rounded-tl-lg">
|
<th className="bg-[#E9F0FF] p-3 text-start first:rounded-tl-lg">
|
||||||
<input checked={
|
<input
|
||||||
table.getIsAllPageRowsSelected() ||
|
ref={headerCheckboxRef}
|
||||||
(table.getIsSomePageRowsSelected() && undefined)
|
checked={!!table.getIsAllPageRowsSelected()}
|
||||||
}
|
|
||||||
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
|
||||||
type="checkbox" name="" id=""
|
type="checkbox" name="" id=""
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return(
|
return(
|
||||||
<th key={header.id} className="bg-blue-300 p-3 text-start last:rounded-tr-lg">
|
<th key={header.id} className="bg-[#E9F0FF] p-3 text-start last:rounded-tr-lg">
|
||||||
{flexRender(
|
{flexRender(
|
||||||
header.column.columnDef.header,
|
header.column.columnDef.header,
|
||||||
header.getContext()
|
header.getContext()
|
||||||
|
|||||||
@ -4,3 +4,10 @@ export const loginSchema = z.object({
|
|||||||
email: z.string().email("Email invalide"),
|
email: z.string().email("Email invalide"),
|
||||||
password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
|
password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const adminSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
last_name: z.string(),
|
||||||
|
first_name: z.string(),
|
||||||
|
email: z.string().min(1, "L'email est requis").email("Email invalide"),
|
||||||
|
});
|
||||||
@ -4,7 +4,7 @@ import { ZodSchema } from "zod";
|
|||||||
export interface FloatingLabelInputProps {
|
export interface FloatingLabelInputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
type: 'text' | 'password' | 'select' | 'email' | 'number';
|
type: 'text' | 'password' | 'select' | 'email' | 'number' | 'hidden';
|
||||||
options?: string[];
|
options?: string[];
|
||||||
button?: React.ReactNode;
|
button?: React.ReactNode;
|
||||||
showPasswordToggle?: boolean;
|
showPasswordToggle?: boolean;
|
||||||
@ -17,7 +17,7 @@ export interface FormProps {
|
|||||||
title?: string,
|
title?: string,
|
||||||
fields: FloatingLabelInputProps[],
|
fields: FloatingLabelInputProps[],
|
||||||
submit: (param: any) => unknown,
|
submit: (param: any) => unknown,
|
||||||
className: string,
|
className?: string,
|
||||||
child: ReactNode,
|
child: ReactNode,
|
||||||
schema: ZodSchema
|
schema: ZodSchema
|
||||||
}
|
}
|
||||||
@ -52,3 +52,11 @@ export interface Owner {
|
|||||||
email: string
|
email: string
|
||||||
last_name: string
|
last_name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Admin {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
profile: string
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user