Add files table and refactor sub-page components

Introduced a files table component for managing file data in the UI. Refactored sub-page components into a separate module for better code organization and maintainability. Adjusted text and links for consistency with language and configuration standards.
This commit is contained in:
Mathis H (Avnyr) 2024-10-21 14:51:49 +02:00
parent 2d6815efb6
commit 13c77bfc32
Signed by: Mathis
GPG Key ID: DD9E0666A747D126
8 changed files with 301 additions and 55 deletions

View File

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@ -15,7 +15,7 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
<body className={"h-screen w-screen bg-card flex flex-col justify-between items-center police-ubuntu dark"}> <body className={"h-screen w-screen bg-card flex flex-col justify-between items-center police-ubuntu"}>
<Header/> <Header/>
{children} {children}
<Footer/> <Footer/>

View File

@ -1,21 +1,11 @@
"use client" "use client"
import { Dispatch, SetStateAction, useState } from 'react'; import { Dispatch, SetStateAction, useState } from 'react';
import { Button } from '../components/ui/button';
import { Home, NotepadTextDashed } from 'lucide-react';
import { NewFileModal } from 'apps/frontend/src/components/new-file-modal';
import { import {
ResizableHandle, ResizableHandle,
ResizablePanel, ResizablePanel,
ResizablePanelGroup ResizablePanelGroup
} from 'apps/frontend/src/components/ui/resizable'; } from 'apps/frontend/src/components/ui/resizable';
import { import { ESubPage, SubPage, SubPageSelector } from '../components/sub-pages';
SubHomePage
} from 'apps/frontend/src/components/sub-pages/sub-home-page';
export enum ESubPage {
Home,
Documentation,
}
export default function HomePage() { export default function HomePage() {
const [currentSubPage, setCurrentSubPage] = useState<ESubPage>(0) const [currentSubPage, setCurrentSubPage] = useState<ESubPage>(0)
@ -43,43 +33,4 @@ export default function HomePage() {
); );
} }
interface SubPageSelectorProps {
currentSubPage: ESubPage
setCurrentSubPage: Dispatch<SetStateAction<ESubPage>>
}
function SubPageSelector(props: SubPageSelectorProps) {
return (
<div className={"w-full flex flex-col justify-center items-stretch pt-4 p-4 gap-2"}>
<Button
onClick={()=>props.setCurrentSubPage(ESubPage.Home)}
disabled={props.currentSubPage === ESubPage.Home}
className={"gap-1 font-bold"}>
<Home/>
Accueil
</Button>
<NewFileModal/>
<Button
onClick={()=>props.setCurrentSubPage(ESubPage.Documentation)}
disabled={props.currentSubPage === ESubPage.Documentation}>
<NotepadTextDashed />
Documentation
</Button>
</div>
)
}
export interface ISubPageProps {
currentSubPage: ESubPage
}
function SubPage(props: ISubPageProps) {
switch (props.currentSubPage) {
case ESubPage.Home:
return (<SubHomePage/>)
case ESubPage.Documentation:
return (<>Doc</>)
default:
return (<>Default</>)
}
}

View File

@ -23,7 +23,7 @@ export function NewFileModal(props: NewFileModalProps) {
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle> <DialogTitle>Ajout d'un fichier</DialogTitle>
<DialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete your account This action cannot be undone. This will permanently delete your account
and remove your data from our servers. and remove your data from our servers.

View File

@ -0,0 +1,56 @@
import { NewFileModal } from "../new-file-modal";
import { Button } from "../ui/button"
import { Home, NotepadTextDashed } from 'lucide-react';
import {
SubHomePage
} from './sub-home-page';
import { Dispatch, SetStateAction } from 'react';
import {
SubDocPage
} from 'apps/frontend/src/components/sub-pages/sub-doc-page';
export interface SubPageSelectorProps {
currentSubPage: ESubPage
setCurrentSubPage: Dispatch<SetStateAction<ESubPage>>
}
export enum ESubPage {
Home,
Documentation,
}
export function SubPageSelector(props: SubPageSelectorProps) {
return (
<div className={"w-full flex flex-col justify-center items-stretch pt-4 p-4 gap-2"}>
<Button
onClick={()=>props.setCurrentSubPage(ESubPage.Home)}
disabled={props.currentSubPage === ESubPage.Home}
className={"gap-1 font-bold"}>
<Home/>
Accueil
</Button>
<NewFileModal/>
<Button
onClick={()=>props.setCurrentSubPage(ESubPage.Documentation)}
disabled={props.currentSubPage === ESubPage.Documentation}>
<NotepadTextDashed />
Documentation
</Button>
</div>
)
}
export interface ISubPageProps {
currentSubPage: ESubPage
}
export function SubPage(props: ISubPageProps) {
switch (props.currentSubPage) {
case ESubPage.Home:
return (<SubHomePage/>)
case ESubPage.Documentation:
return (<SubDocPage/>)
default:
return (<>Default</>)
}
}

View File

@ -0,0 +1,14 @@
import { useState } from 'react';
export interface SubHomePageProps {
}
export function SubDocPage(props: SubHomePageProps) {
const [isLoaded, setIsLoaded] = useState<boolean>(false);
return (<section className={"w-full h-full rounded bg-card flex flex-col"}>
<h1 className={"text-2xl m-2 font-bold"}>Documentations</h1>
</section>)
}

View File

@ -1,4 +1,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { HomeIcon } from 'lucide-react';
import {
FilesDataTable, filesTableColumns
} from 'apps/frontend/src/components/tables/files-table';
export interface SubHomePageProps { export interface SubHomePageProps {
@ -8,7 +12,27 @@ export interface SubHomePageProps {
export function SubHomePage(props: SubHomePageProps) { export function SubHomePage(props: SubHomePageProps) {
const [isLoaded, setIsLoaded] = useState<boolean>(false); const [isLoaded, setIsLoaded] = useState<boolean>(false);
return (<section className={"w-full h-full rounded bg-card"}> return (<section className={"w-full h-full rounded bg-card flex flex-col"}>
<div className={"flex flex-row justify-start items-center gap-2 m-2"}>
<HomeIcon
className={"w-8 h-8 text-secondary"}
/>
<h1 className={"text-2xl font-bold"}>Page principal</h1>
</div>
<div className={"m-1 flex flex-col justify-start items-center w-5/6 self-center h-full"}>
<FilesDataTable columns={filesTableColumns} data={[{
"uuid": "bbc17f8c-244d-4a44-8faf-c5e1ec0786bf",
"fileName": "test",
"checksum": "60d6473dc75edd2e885cc32c098f0379a5dd2d8175de0df1ef7526636b2a03f5",
"extension": "jpeg",
"groupId": null,
"fileSize": 483636,
"fileType": "2c1fb8eb-59b1-4bef-b50d-6bc854f46105",
"isRestricted": false,
"isDocumentation": false,
"uploadedAt": "2024-10-21T11:40:36.350Z",
"uploadedBy": "Avnyr"
}]}/>
</div>
</section>) </section>)
} }

View File

@ -0,0 +1,201 @@
"use client"
import {
ColumnDef,
flexRender,
getCoreRowModel, getPaginationRowModel, getSortedRowModel, SortingState,
useReactTable
} from '@tanstack/react-table';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../ui/table"
import { Button } from '../ui/button';
import { Badge } from '../ui/badge'
import { ArrowUpDown, Clock, Download, Trash } from 'lucide-react';
import { useState } from 'react';
import Link from 'next/link';
// This type is used to define the shape of our data.
// You can use a Zod schema here if you want.
export type IFile = {
uuid: string;
fileName: string;
checksum: string;
extension: string;
groupId: string | null;
fileSize: number;
fileType: string;
isRestricted: boolean;
isDocumentation: boolean;
uploadedAt: string;
uploadedBy: string;
}
function ContextButtonForFile() {
return (<div className={"scale-75"}>
<Button variant={"destructive"}><Trash/></Button>
</div>)
}
export const filesTableColumns: ColumnDef<IFile>[] = [
{
accessorKey: "fileName",
header: ({ column }) => {
return (<div className={"flex justify-center items-center"}>
Nom du fichier
</div>)
},
},
{
accessorKey: "uploadedBy",
header: ({ column }) => {
return (<div className={"flex justify-center items-center"}>
Autheur(s)
</div>)
},
},
{
accessorKey: "uploadedAt",
header: ({ column }) => {
return (
<Button
variant="ghost"
className={"flex w-full"}
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Ajouté le
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
cell: ({ row }) => {
const date = new Date(row.getValue("uploadedAt"))
const formatted = `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} à ${date.getHours()}:${date.getMinutes()}`
return (<div className={"flex justify-center items-center"}>
<Badge
variant="outline"
className={"gap-1 flex w-fit items-center"}>
<Clock className={"w-4 h-4"} />
<p className={"font-light"}>
{formatted}
</p>
</Badge>
</div>)
},
},
{
accessorKey: "extension",
header: ({ column }) => {
return (<div className={"flex justify-center items-center"}>
Extension du fichier
</div>)
},
cell: ({ row }) => {
const extension = row.getValue("extension") as string;
return (<div className={"flex justify-center items-center"}>
<code className={"bg-gray-300 p-1 px-2 rounded-full"}>{extension}</code>
</div>)
},
},
{
id: "actions",
header: ({ column }) => {
return (<div className={"flex justify-center items-center"}>
Actions
</div>)
},
cell: ({ row }) => {
const file = row.original
return (<div className={"flex gap"}>
<Button variant={"ghost"} asChild>
<Link
href={`http://localhost:3333/api/files/${file.uuid}`}
>
<Download />
Télécharger
</Link>
</Button>
<ContextButtonForFile/>
</div>)
},
},
]
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
export function FilesDataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
},
})
return (
<div className="rounded-md border w-full">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Auccun résultat
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)
}