Compare commits

...

4 Commits

Author SHA1 Message Date
c647d2bedd feat: add delete implementation on table 2025-03-26 19:11:58 +01:00
080ab6dd09 feat: add stats on admin homme page 2025-03-26 19:10:52 +01:00
322f4d8637 "fix: navItem no use links" 2025-03-26 19:08:50 +01:00
93363c875f feat: header & double theme 2025-03-26 14:32:46 +01:00
37 changed files with 2467 additions and 208 deletions

10
svgr.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
declare module '*.svg' {
import { FC, SVGProps } from 'react'
const content: FC<SVGProps<SVGElement>>
export default content
}
declare module '*.svg?url' {
const content: any
export default content
}

View File

@ -1,13 +1,31 @@
import type { NextConfig } from "next";
module.exports = {
webpack(config:any) {
// Grab the existing rule that handles SVG imports
const fileLoaderRule = config.module.rules.find((rule:any) =>
rule.test?.test?.('.svg'),
)
const nextConfig: NextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
config.module.rules.push(
// Reapply the existing rule, but only for svg imports ending in ?url
{
...fileLoaderRule,
test: /\.svg$/i,
resourceQuery: /url/, // *.svg?url
},
// Convert all other *.svg imports to React components
{
test: /\.svg$/i,
issuer: fileLoaderRule.issuer,
resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude if *.svg?url
use: ['@svgr/webpack'],
},
)
// Modify the file loader rule to ignore *.svg, since we have it handled now.
fileLoaderRule.exclude = /\.svg$/i
return config
},
};
export default nextConfig;
// ...other config
}

View File

@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-icons": "^1.3.2",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.4",
@ -18,6 +19,7 @@
"next-auth": "^4.24.11",
"nextjs-toploader": "^3.8.15",
"nuqs": "^2.4.1",
"radix-ui": "^1.1.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
@ -33,6 +35,8 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.2.3",
"install": "^0.13.0",
"npm": "^11.2.0",
"tailwindcss": "^4",
"typescript": "^5"
}

1656
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,48 @@
import Form from "#/components/form/form"
import { loginSchema } from "#/schema/loginSchema"
import { useMutation } from "@tanstack/react-query"
import { signIn } from "next-auth/react"
import { useRouter } from "next/navigation";
export default function LoginPage() {
const router = useRouter()
const mutation = useMutation({
mutationKey: ['login'],
mutationFn: async (data: { email: string; password: string }) => {
try {
const result = await signIn("credentials", {
email: data.email,
password: data.password,
redirect: false,
})
if (result?.error) {
const errorMessage = result.error.includes("CredentialsSignin")
? "Email ou mot de passe incorrect"
: result.error;
console.error(errorMessage)
throw new Error(result.error)
}
return result
} catch (error: any) {
if (error.message.includes("Network Error")) {
console.error("Problème de connexion au serveur");
}
console.error("Autre = ", error);
}
},
onSuccess: () => {
router.push('/admin/home')
},
onError: (error: Error) => {
console.error(error.message)
},
})
return(
<div>
<Form
@ -24,7 +64,7 @@ export default function LoginPage() {
showPasswordToggle: true
}
]}
submit={undefined}
submit={mutation.mutate}
schema={loginSchema}
child={<button type="submit" className="btn-auth">Connexion</button>}
/>

View File

@ -0,0 +1,8 @@
export default function Admins (){
return (
<>
</>
)
}

View File

@ -1,129 +1,122 @@
"use client"
import { icons } from "#/assets/icons"
import Statistics from "#/components/stats"
import Table from "#/components/table/table"
import { Company } from "#/types"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { ColumnDef } from "@tanstack/react-table"
import axios from "axios"
import { useSession } from "next-auth/react"
import Image from "next/image"
export default function HomePage () {
type Payment = {
id: string
amount: number
status: "pending" | "processing" | "success" | "failed"
email: string
}
const {data: session, status} = useSession()
const queryClient = useQueryClient()
console.log("Session = ", session)
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 { mutate, isPending } = 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 !')
}
} catch (error) {
console.error(error)
}
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["companies"] })
refetch()
}
})
const data: Payment[] = [
{
id: "728ed52f",
amount: 100,
status: "pending",
email: "m@example.com",
},
{
id: "728ed521",
amount: 200,
status: "pending",
email: "j@example.com",
},
{
id: "728ed528",
amount: 300,
status: "processing",
email: "f@example.com",
},
{
id: "728ed52g",
amount: 600,
status: "success",
email: "h@example.com",
},
{
id: "728ed520",
amount: 50,
status: "failed",
email: "k@example.com",
},
{
id: "728ed529",
amount: 200,
status: "pending",
email: "l@example.com",
},
{
id: "728ed526",
amount: 150,
status: "processing",
email: "d@example.com",
},
{
id: "728ed523",
amount: 100,
status: "success",
email: "o@example.com",
},
{
id: "728ed52y",
amount: 100,
status: "failed",
email: "v@example.com",
},
// ...
]
const columns: ColumnDef<Payment>[] = [
{
id: "select",
header: ({ table }) => (
<input checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && undefined)
} onChange={(value) => table.toggleAllPageRowsSelected(!!value)} type="checkbox" name="" id="" />
),
cell: ({ row }) => (
<input checked={row.getIsSelected()} onChange={(value) => row.toggleSelected(!!value)} type="checkbox" name="" id="" />
),
},
const columns: ColumnDef<Company>[] = [
{
accessorKey: "name",
header: "Organisations",
},
// {
// accessorKey: "Utilisateurs",
// header: "Utilisateurs",
// },
{
header: "Administrateurs",
cell: ({ row }) => {
const value = String(row.original.owner.first_name) + " " + String(row.original.owner.last_name)
return(
<p>{value}</p>
)
}
},
{
accessorKey: "owner.email",
header: "Adresse e-mail"
},
{
accessorKey: "status",
header: "Status",
header: "Statut"
},
{
accessorKey: "email",
header: ({ column }) => {
id: "delete",
cell: ({ cell }) => {
const id = String(cell.row.original.id)
return (
<p>Email</p>
<div className="relative p-2 cursor-pointer"
onClick={() => { mutate(id) }}
>
<Image alt="" src={icons.trash} className="absolute right-2 top-[-50%] transform translate-middle-y hover:text-blue-500" />
</div>
)
},
},
{
accessorKey: "amount",
header: () => <div className="">Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount"))
const formatted = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount)
return <div className="font-medium">{formatted}</div>
},
},
}
}
]
return(
<>
<div className="space-y-10">
<Statistics />
<Table
columns={columns}
data={data}
data={companies || []}
pageSize={5}
/>
</>
</div>
)
}

View File

@ -1,7 +1,7 @@
import { ReactNode } from "react";
import "../../assets/css/admin.css"
import Sidebar from "../components/sidebar";
import Header from "../components/adminHeader";
import Sidebar from "../../components/admin/sidebar";
import Header from "../../components/admin/adminHeader";
export default function Dashboard({ children }: { children: ReactNode }) {

View File

@ -0,0 +1,8 @@
export default function Organizations (){
return (
<>
</>
)
}

View File

@ -13,7 +13,7 @@ const handler = NextAuth({
async authorize(credentials) {
try {
const response = await axios.post(
'private-docs-api.intside.co/users/login/',
'https://private-docs-api.intside.co/users/login/',
{
email: credentials?.email,
password: credentials?.password,

View File

@ -1,18 +0,0 @@
import Image from "next/image";
import { icons } from "#/assets/icons"
export default function AdminHeader() {
return (
<>
<nav className="header r-flex-between px-[44px] py-[20px] ">
<p className="name text-[26px]">Bienvenue, <span>Ken B.</span> </p>
<div className="r-flex-between justify-center items-center r-gap-12">
<Image src={icons.notificationsIcon} alt="Notifications" />
<Image src={icons.profilePicture} alt="ProfilePicture" />
<Image src={icons.arrowUp} alt="arrowUp" />
</div>
</nav>
</>
)
}

View File

@ -1,38 +0,0 @@
"use client"
import Image from "next/image";
import { icons } from "#/assets/icons"
import NavItem from "./navItem";
import { useState } from "react";
export default function Sidebar() {
const [activeItem, setActiveItem] = useState("home")
const handleNavMenu = (item:string) => {
setActiveItem(item)
console.log("active: ", item);
}
return (
<>
<div className="sidebar r-m-0 d-flex flex-column pt-[25px] max-w-[90px] h-[100vh] relative ">
<div className="logo r-flex-center px-[20px] ">
<Image src={icons.logo} alt="Logo" className="scale-95" />
</div>
<div className="nav-menu r-column-center h-max pt-[160px] r-gap-40 ">
<NavItem link="#" iconSrc={icons.homeIcon} label="Home" isNavHome={true} isActive={activeItem === "home"} onClick={() => handleNavMenu("home") } />
<NavItem link="#" iconSrc={icons.companiesIcon} label="Organizations" isActive={activeItem === "organizations"} onClick={() => handleNavMenu("organizations") } />
<NavItem link="#" iconSrc={icons.userGroup} label="Admins" isActive={activeItem === "admins"} onClick={() => handleNavMenu("admins") } />
</div>
<div className="logout absolute bottom-[40px] left-[28px]">
<button type="button" className="cursor-pointer">
<Image src={icons.logout} alt="Logout" />
</button>
</div>
</div>
</>
)
}

View File

@ -4,12 +4,14 @@
--foreground: #04060F;
--background: #ffffff;
--primary: #246BFD;
--secondary: #9FA8BC;
--danger: #F33F19;
--cinder: #E7E5E4;
}
[ data-theme="dark"] {
--foreground: #04060F;
--background: #ffffff;
--background: #04060F;
--foreground: #ffffff;
}
@media (prefers-color-scheme: dark) {

View File

@ -3,6 +3,8 @@ import { Inter } from "next/font/google";
import "./globals.css";
import NextTopLoader from "nextjs-toploader";
import "../assets/css/ruben-ui.css"
import { AuthProvider } from "#/components/provider/authProvider";
import { QueryClientProvide } from "#/components/provider/queryClient";
const inter = Inter({ subsets: ["latin"] });
@ -16,6 +18,7 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<head>
@ -23,8 +26,12 @@ export default function RootLayout({
<link rel="favicon.svg" href="/favicon.svg" />
</head>
<body className={inter.className}>
<NextTopLoader color="#246BFD" shadow="0" />
{children}
<AuthProvider>
<QueryClientProvide>
<NextTopLoader color="#246BFD" shadow="0" />
{children}
</QueryClientProvide>
</AuthProvider>
</body>
</html>
);

View File

@ -1,10 +0,0 @@
import Image from "next/image";
import Link from "next/link";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<Link href="/admin">See the admin page</Link>
</div>
);
}

View File

@ -25,4 +25,29 @@
.nav-home{
margin-top: -11px;
margin-bottom: -11px;
}
.icon-border{
border: 1px solid var(--cinder);
padding: 8px;
border-radius: 12px;
cursor: pointer;
}
.dropdown-menu{
width: max-content;
background-color: var(--background);
border-radius: 8px;
box-shadow: 0 0 24px #0000001A;
}
.dropdown-item{
}
.dropdown-item a{
width: max-content;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}

175
src/assets/css/dropdown.css Normal file
View File

@ -0,0 +1,175 @@
/* @import "@radix-ui/colors/black-alpha.css";
@import "@radix-ui/colors/mauve.css";
@import "@radix-ui/colors/violet.css"; */
/* reset */
button {
all: unset;
}
.DropdownMenuContent,
.DropdownMenuSubContent {
min-width: 220px;
background-color: white;
border-radius: 6px;
padding: 5px;
box-shadow:
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.DropdownMenuContent[data-side="top"],
.DropdownMenuSubContent[data-side="top"] {
animation-name: slideDownAndFade;
}
.DropdownMenuContent[data-side="right"],
.DropdownMenuSubContent[data-side="right"] {
animation-name: slideLeftAndFade;
}
.DropdownMenuContent[data-side="bottom"],
.DropdownMenuSubContent[data-side="bottom"] {
animation-name: slideUpAndFade;
}
.DropdownMenuContent[data-side="left"],
.DropdownMenuSubContent[data-side="left"] {
animation-name: slideRightAndFade;
}
.DropdownMenuItem,
.DropdownMenuCheckboxItem,
.DropdownMenuRadioItem,
.DropdownMenuSubTrigger {
font-size: 13px;
line-height: 1;
color: var(--violet-11);
border-radius: 3px;
display: flex;
align-items: center;
height: 25px;
padding: 0 5px;
position: relative;
padding-left: 25px;
user-select: none;
outline: none;
}
.DropdownMenuSubTrigger[data-state="open"] {
background-color: var(--violet-4);
color: var(--violet-11);
}
.DropdownMenuItem[data-disabled],
.DropdownMenuCheckboxItem[data-disabled],
.DropdownMenuRadioItem[data-disabled],
.DropdownMenuSubTrigger[data-disabled] {
color: var(--mauve-8);
pointer-events: none;
}
.DropdownMenuItem[data-highlighted],
.DropdownMenuCheckboxItem[data-highlighted],
.DropdownMenuRadioItem[data-highlighted],
.DropdownMenuSubTrigger[data-highlighted] {
background-color: var(--violet-9);
color: var(--violet-1);
}
.DropdownMenuLabel {
padding-left: 25px;
font-size: 12px;
line-height: 25px;
color: var(--mauve-11);
}
.DropdownMenuSeparator {
height: 1px;
background-color: var(--violet-6);
margin: 5px;
}
.DropdownMenuItemIndicator {
position: absolute;
left: 0;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.DropdownMenuArrow {
fill: white;
}
.IconButton {
font-family: inherit;
border-radius: 100%;
height: 35px;
width: 35px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--violet-11);
background-color: white;
box-shadow: 0 2px 10px var(--black-a7);
}
.IconButton:hover {
background-color: var(--violet-3);
}
.IconButton:focus {
box-shadow: 0 0 0 2px black;
}
.RightSlot {
margin-left: auto;
padding-left: 20px;
color: var(--mauve-11);
}
[data-highlighted] > .RightSlot {
color: white;
}
[data-disabled] .RightSlot {
color: var(--mauve-8);
}
@keyframes slideUpAndFade {
from {
opacity: 0;
transform: translateY(2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideRightAndFade {
from {
opacity: 0;
transform: translateX(-2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeftAndFade {
from {
opacity: 0;
transform: translateX(2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}

View File

@ -157,4 +157,17 @@
/* 2XL (XXL) */
@media (min-width: 1536px) {
/* Styles for very large screens */
}
.r-primary{
color: var(--primary);
}
.r-secondary{
color: var(--secondary);
}
.r-info{
color: var(--info);
}
.r-danger{
color: var(--danger);
}

BIN
src/assets/icons.zip Normal file

Binary file not shown.

View File

@ -23,7 +23,7 @@ import messagesIcon from "./message.svg"
import homeIcon from "./NavItem.svg"
import companiesIcon from "./buildings.svg"
import arrowLeft from "./arrowLeft.svg"
import arrowRight from "./arrowLeft.svg"
import arrowRight from "./arrowRight.svg"
import filesIcon from "./ph_files.svg"
import pdfIcon from "./prime_file-pdf.svg"
import wordIcon from "./prime_file-word.svg"
@ -37,6 +37,9 @@ import filterIcon from "./setting-3.svg"
import shareIcon from "./share.svg"
import starIcon from "./star.svg"
import arrowUp from "./Vector.svg"
import sunIcon from "./sun.svg"
import moonIcon from "./moon.svg"
import trash from "./trash.svg"
export const icons = {
@ -78,7 +81,10 @@ export const icons = {
filterIcon,
shareIcon,
starIcon,
arrowUp
arrowUp,
sunIcon,
moonIcon ,
trash
}
@ -139,7 +145,7 @@ export const icons = {
CheckboxchedIcon,
CrossIcon,
AddBlueIcon,
ArchivesIcon,
ArchivesIcon,<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:var(--theme-toggle-moon-icon-display)"><path d="M2.89998 0.499976C2.89998 0.279062 2.72089 0.0999756 2.49998 0.0999756C2.27906 0.0999756 2.09998 0.279062 2.09998 0.499976V1.09998H1.49998C1.27906 1.09998 1.09998 1.27906 1.09998 1.49998C1.09998 1.72089 1.27906 1.89998 1.49998 1.89998H2.09998V2.49998C2.09998 2.72089 2.27906 2.89998 2.49998 2.89998C2.72089 2.89998 2.89998 2.72089 2.89998 2.49998V1.89998H3.49998C3.72089 1.89998 3.89998 1.72089 3.89998 1.49998C3.89998 1.27906 3.72089 1.09998 3.49998 1.09998H2.89998V0.499976ZM5.89998 3.49998C5.89998 3.27906 5.72089 3.09998 5.49998 3.09998C5.27906 3.09998 5.09998 3.27906 5.09998 3.49998V4.09998H4.49998C4.27906 4.09998 4.09998 4.27906 4.09998 4.49998C4.09998 4.72089 4.27906 4.89998 4.49998 4.89998H5.09998V5.49998C5.09998 5.72089 5.27906 5.89998 5.49998 5.89998C5.72089 5.89998 5.89998 5.72089 5.89998 5.49998V4.89998H6.49998C6.72089 4.89998 6.89998 4.72089 6.89998 4.49998C6.89998 4.27906 6.72089 4.09998 6.49998 4.09998H5.89998V3.49998ZM1.89998 6.49998C1.89998 6.27906 1.72089 6.09998 1.49998 6.09998C1.27906 6.09998 1.09998 6.27906 1.09998 6.49998V7.09998H0.499976C0.279062 7.09998 0.0999756 7.27906 0.0999756 7.49998C0.0999756 7.72089 0.279062 7.89998 0.499976 7.89998H1.09998V8.49998C1.09998 8.72089 1.27906 8.89997 1.49998 8.89997C1.72089 8.89997 1.89998 8.72089 1.89998 8.49998V7.89998H2.49998C2.72089 7.89998 2.89998 7.72089 2.89998 7.49998C2.89998 7.27906 2.72089 7.09998 2.49998 7.09998H1.89998V6.49998ZM8.54406 0.98184L8.24618 0.941586C8.03275 0.917676 7.90692 1.1655 8.02936 1.34194C8.17013 1.54479 8.29981 1.75592 8.41754 1.97445C8.91878 2.90485 9.20322 3.96932 9.20322 5.10022C9.20322 8.37201 6.82247 11.0878 3.69887 11.6097C3.45736 11.65 3.20988 11.6772 2.96008 11.6906C2.74563 11.702 2.62729 11.9535 2.77721 12.1072C2.84551 12.1773 2.91535 12.2458 2.98667 12.3128L3.05883 12.3795L3.31883 12.6045L3.50684 12.7532L3.62796 12.8433L3.81491 12.9742L3.99079 13.089C4.11175 13.1651 4.23536 13.2375 4.36157 13.3059L4.62496 13.4412L4.88553 13.5607L5.18837 13.6828L5.43169 13.7686C5.56564 13.8128 5.70149 13.8529 5.83857 13.8885C5.94262 13.9155 6.04767 13.9401 6.15405 13.9622C6.27993 13.9883 6.40713 14.0109 6.53544 14.0298L6.85241 14.0685L7.11934 14.0892C7.24637 14.0965 7.37436 14.1002 7.50322 14.1002C11.1483 14.1002 14.1032 11.1453 14.1032 7.50023C14.1032 7.25044 14.0893 7.00389 14.0623 6.76131L14.0255 6.48407C13.991 6.26083 13.9453 6.04129 13.8891 5.82642C13.8213 5.56709 13.7382 5.31398 13.6409 5.06881L13.5279 4.80132L13.4507 4.63542L13.3766 4.48666C13.2178 4.17773 13.0353 3.88295 12.8312 3.60423L12.6782 3.40352L12.4793 3.16432L12.3157 2.98361L12.1961 2.85951L12.0355 2.70246L11.8134 2.50184L11.4925 2.24191L11.2483 2.06498L10.9562 1.87446L10.6346 1.68894L10.3073 1.52378L10.1938 1.47176L9.95488 1.3706L9.67791 1.2669L9.42566 1.1846L9.10075 1.09489L8.83599 1.03486L8.54406 0.98184ZM10.4032 5.30023C10.4032 4.27588 10.2002 3.29829 9.83244 2.40604C11.7623 3.28995 13.1032 5.23862 13.1032 7.50023C13.1032 10.593 10.596 13.1002 7.50322 13.1002C6.63646 13.1002 5.81597 12.9036 5.08355 12.5522C6.5419 12.0941 7.81081 11.2082 8.74322 10.0416C8.87963 10.2284 9.10028 10.3497 9.34928 10.3497C9.76349 10.3497 10.0993 10.0139 10.0993 9.59971C10.0993 9.24256 9.84965 8.94373 9.51535 8.86816C9.57741 8.75165 9.63653 8.63334 9.6926 8.51332C9.88358 8.63163 10.1088 8.69993 10.35 8.69993C11.0403 8.69993 11.6 8.14028 11.6 7.44993C11.6 6.75976 11.0406 6.20024 10.3505 6.19993C10.3853 5.90487 10.4032 5.60464 10.4032 5.30023Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
NotificationsIcon,
TimerIcon,
Logo,

View File

@ -0,0 +1,62 @@
import { GeneralHtmlAttr } from "#/lib/declarations";
const icons = {
CalendarMark: "Calendar Mark.svg",
Document: "Document.svg",
Ellipse2: "Ellipse 2.svg",
Group1: "Group (1).svg",
Group: "Group.svg",
Icons1: "Icons (1).svg",
Icons: "Icons.svg",
LineDuotone: "Line Duotone.svg",
MenuDots: "Menu Dots.svg",
NavItem: "NavItem.svg",
Path1: "Path (1).svg",
Path: "Path.svg",
Rectangle: "Rectangle.svg",
Search: "Search.svg",
Vector: "Vector.svg",
Add: "add.svg",
Archives: "archives.svg",
ArrowLeft: "arrowLeft.svg",
ArrowRight: "arrowRight.svg",
Buildings: "buildings.svg",
Checked: "checked.svg",
Cross: "cross.svg",
DocumentText: "document-text.svg",
Edit2: "edit-2.svg",
Element3White: "element-3-white.svg",
Element3: "element-3.svg",
EyeSlash: "eye-slash.svg",
Eye: "eye.svg",
IconAdd: "icon-add.svg",
Logo: "logo.svg",
LogoutRed: "logout-red.svg",
Logout: "logout.svg",
Maximize3: "maximize-3.svg",
Message: "message.svg",
Notifications: "notifications.svg",
PhFiles: "ph_files.svg",
PrimeFilePdf: "prime_file-pdf.svg",
PrimeFileWord: "prime_file-word.svg",
Profile2UserBlue: "profile-2user-blue.svg",
Profile2User: "profile-2user.svg",
Profile: "profile.svg",
Setting2: "setting-2.svg",
Setting3: "setting-3.svg",
Share: "share.svg",
Star: "star.svg",
Moon: "moon.svg",
Sun: "sun.svg",
};
export const Icon = ({ name, ...props }: { name: keyof typeof icons } & GeneralHtmlAttr) => {
const IconComponent = icons[name];
if (!IconComponent) return null;
return (
<div {...props}>
<img src={`/icons/${IconComponent}`} alt={name} />
</div>
);
};

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:var(--theme-toggle-moon-icon-display)"><path d="M2.89998 0.499976C2.89998 0.279062 2.72089 0.0999756 2.49998 0.0999756C2.27906 0.0999756 2.09998 0.279062 2.09998 0.499976V1.09998H1.49998C1.27906 1.09998 1.09998 1.27906 1.09998 1.49998C1.09998 1.72089 1.27906 1.89998 1.49998 1.89998H2.09998V2.49998C2.09998 2.72089 2.27906 2.89998 2.49998 2.89998C2.72089 2.89998 2.89998 2.72089 2.89998 2.49998V1.89998H3.49998C3.72089 1.89998 3.89998 1.72089 3.89998 1.49998C3.89998 1.27906 3.72089 1.09998 3.49998 1.09998H2.89998V0.499976ZM5.89998 3.49998C5.89998 3.27906 5.72089 3.09998 5.49998 3.09998C5.27906 3.09998 5.09998 3.27906 5.09998 3.49998V4.09998H4.49998C4.27906 4.09998 4.09998 4.27906 4.09998 4.49998C4.09998 4.72089 4.27906 4.89998 4.49998 4.89998H5.09998V5.49998C5.09998 5.72089 5.27906 5.89998 5.49998 5.89998C5.72089 5.89998 5.89998 5.72089 5.89998 5.49998V4.89998H6.49998C6.72089 4.89998 6.89998 4.72089 6.89998 4.49998C6.89998 4.27906 6.72089 4.09998 6.49998 4.09998H5.89998V3.49998ZM1.89998 6.49998C1.89998 6.27906 1.72089 6.09998 1.49998 6.09998C1.27906 6.09998 1.09998 6.27906 1.09998 6.49998V7.09998H0.499976C0.279062 7.09998 0.0999756 7.27906 0.0999756 7.49998C0.0999756 7.72089 0.279062 7.89998 0.499976 7.89998H1.09998V8.49998C1.09998 8.72089 1.27906 8.89997 1.49998 8.89997C1.72089 8.89997 1.89998 8.72089 1.89998 8.49998V7.89998H2.49998C2.72089 7.89998 2.89998 7.72089 2.89998 7.49998C2.89998 7.27906 2.72089 7.09998 2.49998 7.09998H1.89998V6.49998ZM8.54406 0.98184L8.24618 0.941586C8.03275 0.917676 7.90692 1.1655 8.02936 1.34194C8.17013 1.54479 8.29981 1.75592 8.41754 1.97445C8.91878 2.90485 9.20322 3.96932 9.20322 5.10022C9.20322 8.37201 6.82247 11.0878 3.69887 11.6097C3.45736 11.65 3.20988 11.6772 2.96008 11.6906C2.74563 11.702 2.62729 11.9535 2.77721 12.1072C2.84551 12.1773 2.91535 12.2458 2.98667 12.3128L3.05883 12.3795L3.31883 12.6045L3.50684 12.7532L3.62796 12.8433L3.81491 12.9742L3.99079 13.089C4.11175 13.1651 4.23536 13.2375 4.36157 13.3059L4.62496 13.4412L4.88553 13.5607L5.18837 13.6828L5.43169 13.7686C5.56564 13.8128 5.70149 13.8529 5.83857 13.8885C5.94262 13.9155 6.04767 13.9401 6.15405 13.9622C6.27993 13.9883 6.40713 14.0109 6.53544 14.0298L6.85241 14.0685L7.11934 14.0892C7.24637 14.0965 7.37436 14.1002 7.50322 14.1002C11.1483 14.1002 14.1032 11.1453 14.1032 7.50023C14.1032 7.25044 14.0893 7.00389 14.0623 6.76131L14.0255 6.48407C13.991 6.26083 13.9453 6.04129 13.8891 5.82642C13.8213 5.56709 13.7382 5.31398 13.6409 5.06881L13.5279 4.80132L13.4507 4.63542L13.3766 4.48666C13.2178 4.17773 13.0353 3.88295 12.8312 3.60423L12.6782 3.40352L12.4793 3.16432L12.3157 2.98361L12.1961 2.85951L12.0355 2.70246L11.8134 2.50184L11.4925 2.24191L11.2483 2.06498L10.9562 1.87446L10.6346 1.68894L10.3073 1.52378L10.1938 1.47176L9.95488 1.3706L9.67791 1.2669L9.42566 1.1846L9.10075 1.09489L8.83599 1.03486L8.54406 0.98184ZM10.4032 5.30023C10.4032 4.27588 10.2002 3.29829 9.83244 2.40604C11.7623 3.28995 13.1032 5.23862 13.1032 7.50023C13.1032 10.593 10.596 13.1002 7.50322 13.1002C6.63646 13.1002 5.81597 12.9036 5.08355 12.5522C6.5419 12.0941 7.81081 11.2082 8.74322 10.0416C8.87963 10.2284 9.10028 10.3497 9.34928 10.3497C9.76349 10.3497 10.0993 10.0139 10.0993 9.59971C10.0993 9.24256 9.84965 8.94373 9.51535 8.86816C9.57741 8.75165 9.63653 8.63334 9.6926 8.51332C9.88358 8.63163 10.1088 8.69993 10.35 8.69993C11.0403 8.69993 11.6 8.14028 11.6 7.44993C11.6 6.75976 11.0406 6.20024 10.3505 6.19993C10.3853 5.90487 10.4032 5.60464 10.4032 5.30023Z" fill="#9FA8BC" fill-rule="evenodd" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,4 +1,4 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.10671 7.74667C8.04004 7.74 7.96004 7.74 7.88671 7.74667C6.30004 7.69334 5.04004 6.39334 5.04004 4.79334C5.04004 3.16 6.36004 1.83334 8.00004 1.83334C9.63337 1.83334 10.96 3.16 10.96 4.79334C10.9534 6.39334 9.69337 7.69334 8.10671 7.74667Z" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.77348 10.2067C3.16014 11.2867 3.16014 13.0467 4.77348 14.12C6.60681 15.3467 9.61348 15.3467 11.4468 14.12C13.0601 13.04 13.0601 11.28 11.4468 10.2067C9.62014 8.98666 6.61348 8.98666 4.77348 10.2067Z" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.10671 7.74667C8.04004 7.74 7.96004 7.74 7.88671 7.74667C6.30004 7.69334 5.04004 6.39334 5.04004 4.79334C5.04004 3.16 6.36004 1.83334 8.00004 1.83334C9.63337 1.83334 10.96 3.16 10.96 4.79334C10.9534 6.39334 9.69337 7.69334 8.10671 7.74667Z" stroke="#9FA8BC" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M4.77348 10.2067C3.16014 11.2867 3.16014 13.0467 4.77348 14.12C6.60681 15.3467 9.61348 15.3467 11.4468 14.12C13.0601 13.04 13.0601 11.28 11.4468 10.2067C9.62014 8.98666 6.61348 8.98666 4.77348 10.2067Z" stroke="#9FA8BC" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 731 B

1
src/assets/icons/sun.svg Normal file
View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:var(--theme-toggle-sun-icon-display)"><path d="M7.5 0C7.77614 0 8 0.223858 8 0.5V2.5C8 2.77614 7.77614 3 7.5 3C7.22386 3 7 2.77614 7 2.5V0.5C7 0.223858 7.22386 0 7.5 0ZM2.1967 2.1967C2.39196 2.00144 2.70854 2.00144 2.90381 2.1967L4.31802 3.61091C4.51328 3.80617 4.51328 4.12276 4.31802 4.31802C4.12276 4.51328 3.80617 4.51328 3.61091 4.31802L2.1967 2.90381C2.00144 2.70854 2.00144 2.39196 2.1967 2.1967ZM0.5 7C0.223858 7 0 7.22386 0 7.5C0 7.77614 0.223858 8 0.5 8H2.5C2.77614 8 3 7.77614 3 7.5C3 7.22386 2.77614 7 2.5 7H0.5ZM2.1967 12.8033C2.00144 12.608 2.00144 12.2915 2.1967 12.0962L3.61091 10.682C3.80617 10.4867 4.12276 10.4867 4.31802 10.682C4.51328 10.8772 4.51328 11.1938 4.31802 11.3891L2.90381 12.8033C2.70854 12.9986 2.39196 12.9986 2.1967 12.8033ZM12.5 7C12.2239 7 12 7.22386 12 7.5C12 7.77614 12.2239 8 12.5 8H14.5C14.7761 8 15 7.77614 15 7.5C15 7.22386 14.7761 7 14.5 7H12.5ZM10.682 4.31802C10.4867 4.12276 10.4867 3.80617 10.682 3.61091L12.0962 2.1967C12.2915 2.00144 12.608 2.00144 12.8033 2.1967C12.9986 2.39196 12.9986 2.70854 12.8033 2.90381L11.3891 4.31802C11.1938 4.51328 10.8772 4.51328 10.682 4.31802ZM8 12.5C8 12.2239 7.77614 12 7.5 12C7.22386 12 7 12.2239 7 12.5V14.5C7 14.7761 7.22386 15 7.5 15C7.77614 15 8 14.7761 8 14.5V12.5ZM10.682 10.682C10.8772 10.4867 11.1938 10.4867 11.3891 10.682L12.8033 12.0962C12.9986 12.2915 12.9986 12.608 12.8033 12.8033C12.608 12.9986 12.2915 12.9986 12.0962 12.8033L10.682 11.3891C10.4867 11.1938 10.4867 10.8772 10.682 10.682ZM5.5 7.5C5.5 6.39543 6.39543 5.5 7.5 5.5C8.60457 5.5 9.5 6.39543 9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C6.39543 9.5 5.5 8.60457 5.5 7.5ZM7.5 4.5C5.84315 4.5 4.5 5.84315 4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5C10.5 5.84315 9.15685 4.5 7.5 4.5Z" fill="#9FA8BC" fill-rule="evenodd" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 5.98001C17.67 5.65001 14.32 5.48001 10.98 5.48001C9 5.48001 7.02 5.58001 5.04 5.78001L3 5.98001" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 4.97L8.72 3.66C8.88 2.71 9 2 10.69 2H13.31C15 2 15.13 2.75 15.28 3.67L15.5 4.97" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.8499 9.14001L18.1999 19.21C18.0899 20.78 17.9999 22 15.2099 22H8.7899C5.9999 22 5.9099 20.78 5.7999 19.21L5.1499 9.14001" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3301 16.5H13.6601" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 12.5H14.5" stroke="#9FA8BC" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 925 B

View File

@ -0,0 +1,59 @@
"use client"
import Image from "next/image";
import { icons } from "#/assets/icons"
import * as React from "react";
import { DropdownMenu } from "radix-ui";
import Link from "next/link";
import Theme from "./theme";
import ProfilePicture from "../../assets/icons/profile.svg"
export default function AdminHeader() {
const [open, setOpen] = React.useState(false);
return (
<>
<nav className="header r-flex-between px-[44px] py-[20px] ">
<p className="name text-[26px]">Bienvenue, <span>Ken B.</span> </p>
<div className="r-flex-between justify-center items-center r-gap-12">
<Theme />
{/* <ProfilePicture /> */}
<button type="button" className="icon-border">
<Image src={icons.notificationsIcon} alt="Notifications" />
</button>
<div className="relative">
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownMenu.Trigger asChild>
<div className="r-flex-between justify-center items-center r-gap-12 cursor-pointer ">
<Image src={icons.profilePicture} alt="ProfilePicture" />
<button className="IconButton" aria-label="Customise options">
<Image src={icons.arrowUp} alt="arrowUp" className={`transition-transform duration-300 ${open ? "scale-y-[-1]" : ""}`}/>
</button>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="DropdownMenuContent dropdown-menu r-flex-column r-secondary" sideOffset={0} side="bottom" align="end" >
<DropdownMenu.Item className="DropdownMenuItem dropdown-item text-[14px]">
<Link href="#" className="d-flex items-start ">
<Image src={icons.userIcon} alt="Profil" />
Profil
</Link>
</DropdownMenu.Item>
<DropdownMenu.Item className="DropdownMenuItem dropdown-item text-[14px]">
<Link href="#" className="d-flex items-start r-danger ">
<Image src={icons.logoutRed} alt="Deconnexion" />
Déconnexion
</Link>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
</div>
</nav>
</>
)
}

View File

@ -7,13 +7,12 @@ interface ItemProps {
label: string;
isActive: boolean;
isNavHome?: boolean;
onClick: () => void;
}
export default function NavItem({ link, iconSrc, label, isActive, isNavHome, onClick }: ItemProps) {
export default function NavItem({ link, iconSrc, label, isActive, isNavHome }: ItemProps) {
return (
<>
<Link href={link} onClick={onClick} className={`nav-item r-flex-center ${isActive ? "active" : ""}`} >
<Link href={link} className={`nav-item r-flex-center ${isActive ? "active" : ""}`} >
<Image src={iconSrc} alt={label} className={`scale-100 ${isNavHome ? "nav-home" : ""}`} />
</Link>
</>

View File

@ -0,0 +1,32 @@
"use client"
import Image from "next/image";
import { icons } from "#/assets/icons"
import NavItem from "./navItem";
import { usePathname } from "next/navigation";
import Link from "next/link";
export default function Sidebar() {
const pathname = usePathname();
return (
<>
<div className="sidebar r-m-0 d-flex flex-column pt-[25px] max-w-[90px] h-[100vh] relative ">
<div className="logo r-flex-center px-[20px] ">
<Image src={icons.logo} alt="Logo" className="scale-95" />
</div>
<div className="nav-menu r-column-center h-max pt-[160px] r-gap-40 ">
<NavItem link="/admin/home" iconSrc={icons.homeIcon} label="Home" isNavHome={true} isActive={(pathname === "/admin/") || (pathname === "/admin/home")} />
<NavItem link="/admin/organizations" iconSrc={icons.companiesIcon} label="Organizations" isActive={pathname.startsWith("/admin/organizations")} />
<NavItem link="/admin/admins" iconSrc={icons.userGroup} label="Admins" isActive={pathname.startsWith("/admin/admins")} />
</div>
<div className="logout absolute bottom-[40px] left-[28px]">
<Link href="#" className="cursor-pointer">
<Image src={icons.logout} alt="Logout" />
</Link>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,54 @@
"use client"
import Image from "next/image";
import { useEffect, useState } from "react";
import { icons } from "#/assets/icons"
export default function Theme() {
const [theme, setTheme] = useState<string | null>(null);
useEffect(() => {
if (typeof window !== "undefined") {
const savedTheme = localStorage.getItem("theme") ||
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
setTheme(savedTheme);
document.body.setAttribute("data-theme", savedTheme);
}
}, []);
const handleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
document.body.setAttribute("data-theme", newTheme);
};
useEffect(() => {
if (theme === null) return;
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? "dark" : "light";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
document.body.setAttribute("data-theme", newTheme);
};
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, [theme]);
if (theme === null) return null;
return (
<div onClick={handleTheme} className="theme">
<button type="button" className="icon-border">
{theme === "light" ? (
<Image src={icons.sunIcon} alt="Light Mode" width={20} height={20} className="m-[2px]" />
) : (
<Image src={icons.moonIcon} alt="Dark Mode" width={20} height={20} className="m-[2px]" />
)}
</button>
</div>
);
}

View File

@ -37,6 +37,7 @@ export default function Form({
setErrors(newErrors)
} else {
setErrors({})
submit(result.data)
}
}

View File

@ -0,0 +1,7 @@
'use client'
import { SessionProvider } from "next-auth/react";
import { ReactNode } from "react";
export function AuthProvider({ children }: Readonly<{ children: ReactNode }>) {
return <SessionProvider>{children}</SessionProvider>;
}

View File

@ -0,0 +1,14 @@
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
export function QueryClientProvide({
children,
}: Readonly<{ children: ReactNode }>) {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}

70
src/components/stats.tsx Normal file
View File

@ -0,0 +1,70 @@
import Image from "next/image"
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { useSession } from "next-auth/react";
import { icons } from "#/assets/icons";
import { Stats, StatsType, } from "#/types";
export default function Statistics() {
const { data: session, status } = useSession();
const { data: stats, isLoading} = useQuery({
enabled: status === 'authenticated',
queryKey: ["stats", session?.user.access_token],
queryFn: async () => {
try {
const response = await axios.get(
'https://private-docs-api.intside.co/statistics', {
headers: {
'Authorization': `Bearer ${session?.user.access_token}`
}
}
)
if(response.data) {
return response.data as Stats
}
} catch (error: any) {
console.error(error)
}
}
})
const statsData: StatsType[] = [
{ id: 1, title: 'Organisations', value: stats?.companies, icon: icons.companiesIcon, color: 'blue' },
{ id: 2, title: 'Utilisateurs', value: stats?.users, icon: icons.userIcon, color: 'blue' },
{ id: 3, title: 'Documents', value: stats?.documents, icon: icons.docummentTextIcon, color: 'blue' },
{ id: 4, title: 'Stockage', value: stats?.documents_size, icon: icons.archivesIcon, color: 'blue' }
];
return(
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{statsData.map(({ id, title, value, icon }) => (
<div key={id} className="w-full">
<div className="flex items-center rounded-xl border-2 border-blue-500 p-4 space-x-3">
<div
className="flex items-center justify-center rounded-lg bg-[#E9F0FF] bg-opacity-25 p-2"
style={{ width: '54px', height: '54px' }}
>
<Image
alt={title}
src={icon}
width={32}
height={32}
className="text-blue-500"
/>
</div>
<div className="ml-3">
<p className="text-sm text-gray-500 mb-0">{title}</p>
<p className="font-bold text-2xl mb-0">{ status === "loading" && isLoading ? "Chargement..." : value}</p>
</div>
</div>
</div>
))}
</div>
)
}

View File

@ -11,6 +11,8 @@ import {
} from "@tanstack/react-table"
import { useState } from "react";
import { clsx, type ClassValue } from "clsx"
import Image from "next/image";
import { icons } from "#/assets/icons";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
@ -43,6 +45,7 @@ export default function Table<TData, TValue>({
pageSize: pageSize,
}
},
enableRowSelection: true,
getFilteredRowModel: getFilteredRowModel(),
onRowSelectionChange: setRowSelection,
onColumnFiltersChange: setColumnFilters,
@ -66,9 +69,18 @@ export default function Table<TData, TValue>({
<thead className="h-10">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className="rounded-lg">
<th className="bg-blue-300 p-3 text-start first:rounded-tl-lg">
<input checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && undefined)
}
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
type="checkbox" name="" id=""
/>
</th>
{headerGroup.headers.map((header) => {
return(
<th key={header.id} className="bg-blue-300 p-3 text-start">
<th key={header.id} className="bg-blue-300 p-3 text-start last:rounded-tr-lg">
{flexRender(
header.column.columnDef.header,
header.getContext()
@ -83,6 +95,13 @@ export default function Table<TData, TValue>({
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className={clsx('hover:bg-gray-300 border-t border-gray-200', { 'bg-gray-300': row.getIsSelected()})}>
<td className="p-3 text-start">
<input
checked={row.getIsSelected()}
onChange={(e) => row.toggleSelected(e.target.checked)}
type="checkbox" name="" id=""
/>
</td>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-3 text-start">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
@ -104,11 +123,11 @@ export default function Table<TData, TValue>({
<div className="flex items-center justify-end space-x-2 py-4">
<button
className="border bg-gray-200 shadow-xs hover:bg-gray-300 hover:text-black px-3 py-1 rounded"
className="bg-gray-100 shadow-xs hover:bg-gray-300 px-3 py-1 rounded w-9 h-9"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Précédent
<Image alt="" src={icons.arrowLeft} className="hover:text-blue-400"/>
</button>
<div className="flex space-x-1">
@ -116,10 +135,10 @@ export default function Table<TData, TValue>({
<button
key={pageNumber}
className={clsx(
"px-3 py-1 rounded",
"px-3 py-1 rounded w-9 h-9",
pageNumber === currentPage
? "bg-blue-500 text-white"
: "bg-gray-200 hover:bg-gray-300"
? "bg-[#E9F0FF] text-blue-400"
: "bg-gray-100 hover:bg-gray-300"
)}
onClick={() => table.setPageIndex(pageNumber - 1)}
>
@ -129,11 +148,11 @@ export default function Table<TData, TValue>({
</div>
<button
className="border bg-gray-200 shadow-xs hover:bg-gray-300 hover:text-black px-3 py-1 rounded"
className="w-9 h-9 bg-gray-100 shadow-xs hover:bg-gray-300 hover:text-black px-3 py-1 rounded"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Suivant
<Image alt="" src={icons.arrowRight} />
</button>
</div>
</div>

3
src/lib/declarations.ts Normal file
View File

@ -0,0 +1,3 @@
// src/lib/declarations.ts
export type GeneralHtmlAttr = React.HTMLProps<HTMLDivElement> | React.HTMLProps<HTMLImageElement>;

View File

@ -15,8 +15,38 @@ export interface FloatingLabelInputProps {
export interface FormProps {
title?: string,
fields: FloatingLabelInputProps[],
submit: FormEventHandler<HTMLFormElement> | undefined,
submit: (param: any) => unknown,
className: string,
child: ReactNode,
schema: ZodSchema
}
export interface StatsType {
id: number;
title: string;
value: number | undefined;
icon: string;
color: string;
}
export interface Stats {
companies: number
documents: number
users: number
documents_size: number
}
export interface Company {
id: string
name: string
is_premium: boolean
status: string
owner: Owner
}
export interface Owner {
id: string
first_name: string
email: string
last_name: string
}

View File

@ -19,9 +19,10 @@
}
],
"paths": {
"#/*": ["./src/*"]
"#/*": ["./src/*"],
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["svgr.d.ts", "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}