feat: upadate data-table component by adding dynamic header component

This commit is contained in:
Orace.A 2025-03-27 00:04:02 +01:00
parent 80cf569f8b
commit cbe135ce8a
7 changed files with 251 additions and 11 deletions

View File

@ -10,6 +10,7 @@
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/themes": "^3.2.1",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.4",

38
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
'@radix-ui/react-dialog':
specifier: ^1.1.6
version: 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/themes':
specifier: ^3.2.1
version: 3.2.1(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/react-query':
specifier: ^5.69.0
version: 5.69.0(react@19.0.0)
@ -1012,6 +1015,9 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@radix-ui/colors@3.0.0':
resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==}
'@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
@ -1658,6 +1664,19 @@ packages:
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@radix-ui/themes@3.2.1':
resolution: {integrity: sha512-WJL2YKAGItkunwm3O4cLTFKCGJTfAfF6Hmq7f5bCo1ggqC9qJQ/wfg/25AAN72aoEM1yqXZQ+pslsw48AFR0Xg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: 16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: 16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@ -2128,6 +2147,9 @@ packages:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@ -4685,6 +4707,8 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.1
optional: true
'@radix-ui/colors@3.0.0': {}
'@radix-ui/number@1.1.0': {}
'@radix-ui/primitive@1.1.1': {}
@ -5380,6 +5404,18 @@ snapshots:
'@radix-ui/rect@1.1.0': {}
'@radix-ui/themes@3.2.1(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/colors': 3.0.0
classnames: 2.5.1
radix-ui: 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-remove-scroll-bar: 2.3.8(@types/react@19.0.12)(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.12
'@types/react-dom': 19.0.4(@types/react@19.0.12)
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.11.0': {}
@ -5893,6 +5929,8 @@ snapshots:
dependencies:
readdirp: 4.1.2
classnames@2.5.1: {}
client-only@0.0.1: {}
clsx@2.1.1: {}

View File

@ -1,8 +1,174 @@
"use client"
import FloatingLabelInput from "#/components/floatingLabelInput"
import { Modal } from "#/components/modal"
import Table from "#/components/table/table"
import { useQuery } from "@tanstack/react-query"
import { ColumnDef } from "@tanstack/react-table"
import axios from "axios"
import { table } from "console"
import { useSession } from "next-auth/react"
export interface Admin {
id: string
email: string
first_name: string
last_name: string
profile: string
}
export default function Admins (){
const {data: session, status} = useSession()
const { data: users, refetch, isLoading} = useQuery({
enabled: status === 'authenticated',
queryKey: ["users"],
queryFn: async () => {
try {
const response = await axios.get(
'https://private-docs-api.intside.co/users', {
headers: {
'Authorization': `Bearer ${session?.user.access_token}`
}
}
)
if(response.data) {
return response.data.data as Admin[]
}
} catch (error) {
console.error(error)
}
}
})
const columns: ColumnDef<Admin>[] = [
{
header: "Administrateurs",
cell: ({ row }) => {
const value = String(row.original.first_name) + " " + String(row.original.last_name)
const initials = String(row.original.first_name[0]) + String(row.original.last_name[0])
return(
<div className="flex space-x-2 items-center">
<div className="flex items-center justify-center bg-[#DCDCFE] text-[#246BFD] w-10 h-10 rounded-full">
{initials}
</div>
<p>{value}</p>
</div>
)
}
},
{
accessorKey: "email",
header: "Adresse e-mail"
},
// {
// accessorKey: "status",
// header: "Statut",
// cell: ({ cell }) => {
// const status = String(cell.getValue())
// return (
// <p
// className={`rounded-full px-2 py-1 font-medium text-sm w-20 h-6 text-center
// ${
// status === "active" ? "bg-[#ECF9E8] text-[#49C91E]" :
// status === "inactive" ? "bg-[#E7EBF3] text-[#9FA8BC]" :
// status === "pending" ? "bg-[#EAF7FC] text-[#30B2EA]" :
// status === "blocked" ? "bg-[#FDEBE8] text-[#F33F19]" :
// ""
// }
// `}
// >
// {
// status === "active" ? "Actif" :
// status === "inactive" ? "Inactif" :
// status === "pending" ? "En attente" :
// status === "blocked" ? "Bloquée" :
// ""
// }
// </p>
// )
// }
// },
// {
// id: "delete",
// cell: ({ cell }) => {
// const id = String(cell.row.original.id)
// return (
// <div className="relative p-2 cursor-pointer"
// // 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">
// <button
// className="bg-blue-100 text-blue-600 py-2 px-4 text-lg rounded-full text-center hover:bg-blue-200"
// onClick={() => { setOpen(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(id)
// setOpen(false)
// }}
// >
// Supprimer
// </button>
// </div>
// </div>
// }
// />
// </div>
// )
// }
// }
]
return (
<>
<Table
columns={columns}
data={users || []}
pageSize={5}
header={(table) => (
<div className="grid grid-cols-1 md:grid-cols-2 md:flex-row w-full">
<div className="flex">
<input checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && undefined)
}
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
type="checkbox" name="" id=""
/>
</div>
<div className="flex justify-between md:justify-end space-x-3">
<Modal
trigger={
<button className="p-3 bg-blue-600 text-white rounded-full">
Ajouter un admin
</button>
}
/>
<FloatingLabelInput name="search" placeholder="Effectuer une recherche" type="text" onChange={(value) => table.setGlobalFilter(value)} />
</div>
</div>
)}
/>
</>
)
}

View File

@ -74,16 +74,22 @@ export default function HomePage () {
accessorKey: "name",
header: "Organisations",
},
// {
// accessorKey: "Utilisateurs",
// header: "Utilisateurs",
// },
{
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(
<p>{value}</p>
<div className="flex space-x-2 items-center">
<div className="flex items-center justify-center bg-[#DCDCFE] text-[#246BFD] w-10 h-10 rounded-full">
{initials}
</div>
<p>{value}</p>
</div>
)
}
},

View File

@ -13,10 +13,19 @@ export default function FloatingLabelInput({
button,
showPasswordToggle = false,
name,
defaultValue
defaultValue,
onChange
}: FloatingLabelInputProps) {
const [showPassword, setShowPassword] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const value = e.target.value;
if (onChange) {
onChange(value);
}
};
const renderInput = () => {
switch(type) {
case 'select':
@ -46,6 +55,7 @@ export default function FloatingLabelInput({
className="input-form focus:ring-2 focus:ring-blue-500 pr-10"
defaultValue={defaultValue}
required
/>
{showPasswordToggle && (
<button
@ -83,6 +93,7 @@ export default function FloatingLabelInput({
required
name={name}
defaultValue={defaultValue}
onChange={handleChange}
/>
{button && <div className="absolute right-0 top-1/2 transform -translate-y-1/2">{button}</div>}
</div>

View File

@ -5,11 +5,11 @@ import {
getCoreRowModel,
useReactTable,
getPaginationRowModel,
SortingState,
ColumnFiltersState,
getFilteredRowModel,
Table as TableType,
} from "@tanstack/react-table"
import { useState } from "react";
import { cloneElement, isValidElement, ReactNode, useState } from "react";
import { clsx, type ClassValue } from "clsx"
import Image from "next/image";
import { icons } from "#/assets/icons";
@ -17,13 +17,15 @@ import { icons } from "#/assets/icons";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[],
pageSize?: number
pageSize?: number,
header?: ReactNode | ((table: TableType<TData>) => ReactNode)
}
export default function Table<TData, TValue>({
columns,
data,
pageSize = 10
pageSize = 10,
header
}: DataTableProps<TData, TValue>) {
const [rowSelection, setRowSelection] = useState({})
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
@ -62,8 +64,22 @@ export default function Table<TData, TValue>({
return pages
}
const render = () => {
if(!header) return null
return(
<div className="mb-4">
{typeof header === 'function'
? header(table)
: header}
</div>
)
}
return(
<div>
{render()}
<div className="rounded-lg border border-gray-200 w-auto">
<table className="w-full overflow-x-auto rounded-lg">
<thead className="h-10">

View File

@ -2,7 +2,7 @@ import { FormEventHandler, ReactNode } from "react";
import { ZodSchema } from "zod";
export interface FloatingLabelInputProps {
label: string;
label?: string;
placeholder?: string;
type: 'text' | 'password' | 'select' | 'email' | 'number';
options?: string[];
@ -10,6 +10,7 @@ export interface FloatingLabelInputProps {
showPasswordToggle?: boolean;
name: string;
defaultValue?: string;
onChange?: (value: string) => void;
}
export interface FormProps {
@ -42,6 +43,7 @@ export interface Company {
is_premium: boolean
status: string
owner: Owner
total_users: number
}
export interface Owner {