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:
parent
2d6815efb6
commit
13c77bfc32
2
apps/frontend/next-env.d.ts
vendored
2
apps/frontend/next-env.d.ts
vendored
@ -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.
|
||||||
|
@ -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/>
|
||||||
|
@ -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</>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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.
|
||||||
|
56
apps/frontend/src/components/sub-pages/index.tsx
Normal file
56
apps/frontend/src/components/sub-pages/index.tsx
Normal 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</>)
|
||||||
|
}
|
||||||
|
}
|
14
apps/frontend/src/components/sub-pages/sub-doc-page.tsx
Normal file
14
apps/frontend/src/components/sub-pages/sub-doc-page.tsx
Normal 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>)
|
||||||
|
}
|
@ -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>)
|
||||||
}
|
}
|
201
apps/frontend/src/components/tables/files-table.tsx
Normal file
201
apps/frontend/src/components/tables/files-table.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user