feat: upadate data-table component by adding dynamic header component
This commit is contained in:
parent
80cf569f8b
commit
cbe135ce8a
@ -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
38
pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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(
|
||||
<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>
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user