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" />
|
||||
|
||||
// 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 (
|
||||
<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/>
|
||||
{children}
|
||||
<Footer/>
|
||||
|
@ -1,21 +1,11 @@
|
||||
"use client"
|
||||
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 {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup
|
||||
} from 'apps/frontend/src/components/ui/resizable';
|
||||
import {
|
||||
SubHomePage
|
||||
} from 'apps/frontend/src/components/sub-pages/sub-home-page';
|
||||
|
||||
export enum ESubPage {
|
||||
Home,
|
||||
Documentation,
|
||||
}
|
||||
import { ESubPage, SubPage, SubPageSelector } from '../components/sub-pages';
|
||||
|
||||
export default function HomePage() {
|
||||
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>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogTitle>Ajout d'un fichier</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account
|
||||
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 { HomeIcon } from 'lucide-react';
|
||||
import {
|
||||
FilesDataTable, filesTableColumns
|
||||
} from 'apps/frontend/src/components/tables/files-table';
|
||||
|
||||
|
||||
export interface SubHomePageProps {
|
||||
@ -8,7 +12,27 @@ export interface SubHomePageProps {
|
||||
export function SubHomePage(props: SubHomePageProps) {
|
||||
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>)
|
||||
}
|
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