diff --git a/dashboard/.eslintrc.cjs b/dashboard/.eslintrc.cjs index 0542e28..81a4198 100644 --- a/dashboard/.eslintrc.cjs +++ b/dashboard/.eslintrc.cjs @@ -60,7 +60,6 @@ module.exports = { { allowConstantExport: true }, ], "@typescript-eslint/prefer-optional-chain": "warn", - "@typescript-eslint/consistent-type-imports": "warn", "@typescript-eslint/explicit-function-return-type": "warn", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-inferrable-types": "warn", diff --git a/dashboard/package.json b/dashboard/package.json index 2a7fefa..4d381c3 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -23,9 +23,10 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-query": "^5.45.1", - "@vitejs/plugin-react": "^4.3.1", "@tanstack/react-table": "^8.17.3", + "@vitejs/plugin-react": "^4.3.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "flat": "^6.0.1", diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index 5c07192..4a99b4e 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -32,15 +32,18 @@ importers: '@radix-ui/react-separator': specifier: ^1.1.0 version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.3)(react@18.3.1) '@tanstack/react-query': specifier: ^5.45.1 version: 5.45.1(react@18.3.1) - '@vitejs/plugin-react': - specifier: ^4.3.1 - version: 4.3.1(vite@5.3.1(@types/node@20.14.8)) '@tanstack/react-table': specifier: ^8.17.3 version: 8.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.3.1(@types/node@20.14.8)) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 diff --git a/dashboard/src/components/Dashboard/Dashboard.tsx b/dashboard/src/components/Dashboard/Dashboard.tsx index fa322b8..44775a5 100644 --- a/dashboard/src/components/Dashboard/Dashboard.tsx +++ b/dashboard/src/components/Dashboard/Dashboard.tsx @@ -1,6 +1,6 @@ import SideMenu from "../SideMenu/SideMenu"; -import TreeTable from "../Table/TreeTable"; import TopBar from "../TopBar/TopBar"; +import TreeMonitorListingPage from "../TreeMonitorListingPage/TreeMonitorListingPage"; const Dashboard = () : JSX.Element => { return ( @@ -8,8 +8,8 @@ const Dashboard = () : JSX.Element => { <div className="flex flex-row w-full justify-between"> <SideMenu /> <TopBar /> - <div className="w-full px-16 pt-64 bg-lightGray"> - <TreeTable /> + <div className="w-full px-16 pt-24 bg-lightGray"> + <TreeMonitorListingPage /> </div> </div> </div> diff --git a/dashboard/src/components/Table/TreeTable.tsx b/dashboard/src/components/Table/TreeTable.tsx index bf2e49f..3b1ebc4 100644 --- a/dashboard/src/components/Table/TreeTable.tsx +++ b/dashboard/src/components/Table/TreeTable.tsx @@ -4,7 +4,7 @@ import { TableRow, TableCell } from "../ui/table"; import BaseTable from "./BaseTable"; -interface ITreeTableBody { +export interface ITreeTableBody { name: string; branch: string; commit: string; @@ -12,6 +12,10 @@ interface ITreeTableBody { testStatus: string; } +interface ITreeTable { + treeTableRows: ITreeTableBody[]; +} + const treeTableColumnsLabelId = [ 'treeTable.tree', 'treeTable.branch', @@ -20,44 +24,6 @@ const treeTableColumnsLabelId = [ 'treeTable.test' ]; -const treeTableRows: ITreeTableBody[] = [ - { - name: "stable-rc", - branch: "linux-5.15", - commit: "asidnasidn-oqiwejeoij-oaidnosdnk", - buildStatus: "150 completed", - testStatus: "80 completed" - }, - { - name: "stable-rc", - branch: "linux-5.15", - commit: "asidnasidn-oqiwejeoij-oaidnosdnk", - buildStatus: "10 completed", - testStatus: "150 completed" - }, - { - name: "stable-rc", - branch: "linux-5.15", - commit: "asidnasidn-oqiwejeoij-oaidnosdnk", - buildStatus: "10 completed", - testStatus: "150 completed" - }, - { - name: "stable-rc", - branch: "linux-5.15", - commit: "asidnasidn-oqiwejeoij-oaidnosdnk", - buildStatus: "10 completed", - testStatus: "150 completed" - }, - { - name: "stable-rc", - branch: "linux-5.15", - commit: "asidnasidn-oqiwejeoij-oaidnosdnk", - buildStatus: "10 completed", - testStatus: "150 completed" - } -]; - const TreeTableRow = (row: ITreeTableBody): JSX.Element => { return ( <TableRow> @@ -70,7 +36,7 @@ const TreeTableRow = (row: ITreeTableBody): JSX.Element => { ); }; -const TreeTable = () : JSX.Element => { + const TreeTable = ({treeTableRows}: ITreeTable) : JSX.Element => { const treeTableBody = useMemo(() => { return ( treeTableRows.map((row: ITreeTableBody) => ( diff --git a/dashboard/src/components/TopBar/TopBar.tsx b/dashboard/src/components/TopBar/TopBar.tsx index 6771400..e22094c 100644 --- a/dashboard/src/components/TopBar/TopBar.tsx +++ b/dashboard/src/components/TopBar/TopBar.tsx @@ -11,7 +11,8 @@ const TopBar = (): JSX.Element => { </span> <div className="flex w-2/3 px-6 items-center"> {/* placeholder for search */} - <Input className="w-2/3" type="text" placeholder="Search" /> + {/* TODO: use i18n for the input placeholder */} + <Input className="w-2/3" type="text" placeholder="Search by tree, branch or tag" /> </div> </div> </div> diff --git a/dashboard/src/components/TreeMonitorListingPage/TreeMonitorListingPage.tsx b/dashboard/src/components/TreeMonitorListingPage/TreeMonitorListingPage.tsx new file mode 100644 index 0000000..9f267e0 --- /dev/null +++ b/dashboard/src/components/TreeMonitorListingPage/TreeMonitorListingPage.tsx @@ -0,0 +1,192 @@ +import { FormattedMessage } from "react-intl"; + +import { MdArrowBackIos, MdArrowForwardIos, MdExpandMore } from "react-icons/md"; + +import { useCallback, useState } from "react"; + +import TreeTable, { ITreeTableBody } from "../Table/TreeTable"; +import { Button } from "../ui/button"; + + +interface ITableInformation { + startIndex: number; + endIndex: number; + totalTrees: number; + itemsPerPage: number; + onClickForward: () => void; + onClickBack: () => void; +} + +const TableInfo = ({ + startIndex, + endIndex, + totalTrees, + itemsPerPage, + onClickForward, + onClickBack, +}: ITableInformation): JSX.Element => { + const buttonsClassName = "text-lightBlue font-bold" + const groupsClassName = "flex flex-row items-center gap-2" + return ( + <div className="flex flex-row gap-4 text-sm"> + <div className={groupsClassName}> + <FormattedMessage id="table.showing"/> + <span className="font-bold">{startIndex} - {endIndex}</span> + <FormattedMessage id="table.of"/> + <span className="font-bold">{totalTrees}</span> + <FormattedMessage id="table.tree"/> + </div> + <div className={groupsClassName}> + <FormattedMessage id="table.itemsPerPage"/> + <span className="font-bold">{itemsPerPage}</span> + <MdExpandMore className={buttonsClassName}/> + </div> + <div className="flex flex-row gap-2 items-center"> + <Button variant="outline" onClick={onClickBack}> + <MdArrowBackIos className={buttonsClassName}/> + </Button> + <Button variant="outline" onClick={onClickForward}> + <MdArrowForwardIos className={buttonsClassName}/> + </Button> + </div> + </div> + ); +}; + +const TreeMonitorListingPage = (): JSX.Element => { + const listItems = treeTableRows; + const itemsPerPage = 10; + const [startIndex, setStartIndex] = useState(0); + const [endIndex, setEndIndex] = useState(listItems.length+1 > itemsPerPage ? itemsPerPage : listItems.length+1); + + const onClickGoForward = useCallback(() => { + setStartIndex(endIndex); + setEndIndex(endIndex+itemsPerPage >= listItems.length ? listItems.length : endIndex+itemsPerPage) + }, [endIndex, listItems]); + + const onClickGoBack = useCallback(() => { + setStartIndex(startIndex-itemsPerPage); + setEndIndex(endIndex % itemsPerPage !== 0 ? endIndex - endIndex % itemsPerPage : endIndex - itemsPerPage); + }, [startIndex, endIndex]); + + return ( + <div className="flex flex-col gap-6"> + <div className="flex flex-col items-end gap-4"> + <Button variant="outline" className="rounded-full w-[128px] border-black"> + <div className="flex flex-row gap-1 items-center"> + <FormattedMessage id="global.filters" /> + <MdExpandMore /> + </div> + </Button> + <TableInfo + startIndex={startIndex+1} + endIndex={endIndex} + totalTrees={listItems.length} + itemsPerPage={itemsPerPage} + onClickBack={onClickGoBack} + onClickForward={onClickGoForward} + /> + </div> + <TreeTable treeTableRows={listItems.slice(startIndex, endIndex)}/> + <div className="flex flex-col items-end"> + <TableInfo + startIndex={startIndex+1} + endIndex={endIndex} + totalTrees={listItems.length} + itemsPerPage={itemsPerPage} + onClickBack={onClickGoBack} + onClickForward={onClickGoForward} + /> + </div> + </div> + ); +}; + +const treeTableRows: ITreeTableBody[] = [ + { + name: "stable-rc1", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "150 completed", + testStatus: "80 completed" + }, + { + name: "stable-rc2", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc3", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc4", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc5", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc6", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "150 completed", + testStatus: "80 completed" + }, + { + name: "stable-rc7", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc8", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc9", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc10", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc11", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + }, + { + name: "stable-rc12", + branch: "linux-5.15", + commit: "asidnasidn-oqiwejeoij-oaidnosdnk", + buildStatus: "10 completed", + testStatus: "150 completed" + } + ]; + +export default TreeMonitorListingPage; diff --git a/dashboard/src/components/ui/button.tsx b/dashboard/src/components/ui/button.tsx new file mode 100644 index 0000000..aac8584 --- /dev/null +++ b/dashboard/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "../../lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300", + { + variants: { + variant: { + default: "bg-slate-900 text-slate-50 hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90", + destructive: + "bg-red-500 text-slate-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90", + outline: + "border border-slate-200 bg-white hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50", + secondary: + "bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80", + ghost: "hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50", + link: "text-slate-900 underline-offset-4 hover:underline dark:text-slate-50", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes<HTMLButtonElement>, + VariantProps<typeof buttonVariants> { + asChild?: boolean +} + +const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + <Comp + className={cn(buttonVariants({ variant, size, className }))} + ref={ref} + {...props} + /> + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts index 636d742..38a6cd2 100644 --- a/dashboard/src/locales/messages/index.ts +++ b/dashboard/src/locales/messages/index.ts @@ -2,18 +2,27 @@ import {LOCALES} from '../constants' export const messages = { [LOCALES.EN_US]: { + global: { + filters: "Filters", + }, routes: { dashboard: "Dashboard", - treeMonitor: "Tree Monitor", deviceMonitor: "Device Monitor", labsMonitor: "Labs Monitor", + treeMonitor: "Tree Monitor", }, - treeTable: { + table: { + itemsPerPage: "Items per page:", + of: "of", + showing: "Showing:", tree: "Tree", + }, + treeTable: { branch: "Branch", - commit: "Commit/tag", build: "Build Status", + commit: "Commit/tag", test: "Test Status", - } + tree: "Tree", + }, }, };