From e62e678fba419837380ebed328059a6d5b1e03e2 Mon Sep 17 00:00:00 2001 From: HasanMu Date: Wed, 12 Feb 2025 16:09:24 +0700 Subject: [PATCH] feat: styling index page backbone, fishbone, devices & tower --- package-lock.json | 32 ++++++ package.json | 1 + src/components/IconButton.tsx | 176 ++++++++++++++++++++++++++++++ src/components/ReactTable.tsx | 143 ++++++++++++++++++++++++ src/layouts/Dashboard/Header.tsx | 2 +- src/layouts/Dashboard/Sidebar.tsx | 5 + src/pages/Backbone.tsx | 69 ++++++++++-- src/pages/Devices.tsx | 111 +++++++++++++++++++ src/pages/Fishbone.tsx | 93 ++++++++++++++++ src/pages/Towers.tsx | 96 ++++++++++++++++ src/routes/index.tsx | 6 + src/theme/palette.ts | 16 +-- 12 files changed, 731 insertions(+), 19 deletions(-) create mode 100644 src/components/IconButton.tsx create mode 100644 src/components/ReactTable.tsx create mode 100644 src/pages/Devices.tsx create mode 100644 src/pages/Fishbone.tsx create mode 100644 src/pages/Towers.tsx diff --git a/package-lock.json b/package-lock.json index e625b9c..7ccd5ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/public-sans": "^5.1.2", "@mui/icons-material": "^6.4.3", "@mui/material": "^6.4.3", + "@tanstack/react-table": "^8.21.2", "iconsax-react": "^0.0.8", "lodash": "^4.17.21", "react": "^19.0.0", @@ -1625,6 +1626,37 @@ "tslib": "^2.6.2" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", + "integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==", + "dependencies": { + "@tanstack/table-core": "8.21.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", + "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index 060b85c..af66e12 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@fontsource/public-sans": "^5.1.2", "@mui/icons-material": "^6.4.3", "@mui/material": "^6.4.3", + "@tanstack/react-table": "^8.21.2", "iconsax-react": "^0.0.8", "lodash": "^4.17.21", "react": "^19.0.0", diff --git a/src/components/IconButton.tsx b/src/components/IconButton.tsx new file mode 100644 index 0000000..6a4ad30 --- /dev/null +++ b/src/components/IconButton.tsx @@ -0,0 +1,176 @@ +import { forwardRef, ReactNode, ReactPortal, Ref } from 'react'; + +// material-ui +import { alpha, styled, useTheme } from '@mui/material'; +import MuiIconButton, { IconButtonProps } from '@mui/material/IconButton'; + +// project-imports +import getColors from '../utils/getColors'; +import getShadow from '../utils/getShadow'; + +// types +import { ButtonVariantProps, ExtendedStyleProps, IconButtonShapeProps } from '../types/extended'; + +// ==============================|| ICON BUTTON - COLOR STYLE ||============================== // + +interface IconButtonStyleProps extends ExtendedStyleProps { + variant?: ButtonVariantProps; +} + +function getColorStyle({ variant, theme, color }: IconButtonStyleProps) { + const colors = getColors(theme, color); + const { lighter, light, dark, main, contrastText } = colors; + + const buttonShadow = `${color}Button`; + const shadows = getShadow(theme, buttonShadow); + + const commonShadow = { + '&::after': { + boxShadow: `0 0 6px 6px ${alpha(main, 0.9)}` + }, + '&:active::after': { + boxShadow: `0 0 0 0 ${alpha(main, 0.9)}` + }, + '&:focus-visible': { + outline: `2px solid ${dark}`, + outlineOffset: 2 + } + }; + + console.log(main); + + switch (variant) { + case 'contained': + return { + color: contrastText, + backgroundColor: main, + '&:hover': { + backgroundColor: dark + }, + ...commonShadow + }; + case 'light': + return { + color: main, + backgroundColor: lighter, + '&:hover': { + backgroundColor: light + }, + ...commonShadow + }; + case 'shadow': + return { + boxShadow: shadows, + color: contrastText, + backgroundColor: main, + '&:hover': { + boxShadow: 'none', + backgroundColor: dark + }, + ...commonShadow + }; + case 'outlined': + return { + '&:hover': { + backgroundColor: 'transparent', + color: dark, + borderColor: dark + }, + ...commonShadow + }; + case 'dashed': + return { + backgroundColor: lighter, + '&:hover': { + color: dark, + borderColor: dark + }, + ...commonShadow + }; + case 'text': + default: + return { + '&:hover': { + color: dark, + backgroundColor: lighter + }, + ...commonShadow + }; + } +} + +// ==============================|| ICON BUTTON - STYLED ||============================== // + +interface StyleProps extends IconButtonStyleProps { + shape?: IconButtonShapeProps; +} + +const IconButtonStyle = styled(MuiIconButton, { shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'shape' })( + ({ theme, variant, shape, color }: StyleProps) => ({ + position: 'relative', + '::after': { + content: '""', + display: 'block', + position: 'absolute', + left: 0, + top: 0, + width: '100%', + height: '100%', + borderRadius: shape === 'rounded' ? '50%' : 8, + opacity: 0, + transition: 'all 0.5s' + }, + + ':active::after': { + position: 'absolute', + borderRadius: shape === 'rounded' ? '50%' : 8, + left: 0, + top: 0, + opacity: 1, + transition: '0s' + }, + ...(shape === 'rounded' && { + borderRadius: '50%' + }), + ...(variant === 'outlined' && { + border: '1px solid', + borderColor: 'inherit' + }), + ...(variant === 'dashed' && { + border: '1px dashed', + borderColor: 'inherit' + }), + ...(variant !== 'text' && { + '&.Mui-disabled': { + backgroundColor: theme.palette.secondary[200] + } + }), + ...getColorStyle({ variant, theme, color }) + }) +); + +// ==============================|| ICON BUTTON - EXTENDED ||============================== // + +export interface Props extends IconButtonProps { + shape?: IconButtonShapeProps; + variant?: ButtonVariantProps; + children: ReactNode; + tooltip?: boolean | ReactNode | ReactPortal; +} + +function IconButton( + { variant = 'text', shape = 'square', children, color = 'primary', tooltip, ...others }: Props, + ref: Ref +) { + const theme = useTheme(); + + return ( + + {children} + + ); +} + +IconButton.displayName = 'IconButton'; + +export default forwardRef(IconButton); diff --git a/src/components/ReactTable.tsx b/src/components/ReactTable.tsx new file mode 100644 index 0000000..5a03880 --- /dev/null +++ b/src/components/ReactTable.tsx @@ -0,0 +1,143 @@ +import InputAdornment from "@mui/material/InputAdornment"; +import Paper from "@mui/material/Paper"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableContainer from "@mui/material/TableContainer"; +import TableCell from "@mui/material/TableCell"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import TablePagination from "@mui/material/TablePagination"; +import TextField from "@mui/material/TextField"; +import Box from "@mui/material/Box"; + +import { + flexRender, + useReactTable, + ColumnDef, + getCoreRowModel, + getSortedRowModel, + getFilteredRowModel, + getPaginationRowModel, + SortingState, + ColumnFiltersState, +} from "@tanstack/react-table"; +import { useState } from "react"; +import { SearchNormal } from "iconsax-react"; + +interface ReactTableProps { + columns: ColumnDef[]; + data: T[]; + striped?: boolean; + title?: string; +} + +function ComponentReactTable({ + columns, + data, + striped, + title, +}: ReactTableProps) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [globalFilter, setGlobalFilter] = useState(""); + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnFilters, + globalFilter, + }, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + return ( + + + + + + setGlobalFilter(e.target.value)} + placeholder="Search all columns..." + sx={{ mb: 2 }} + slotProps={{ + input: { + startAdornment: + + , + }, + }} + /> + + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder ? null : ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {{ + asc: " 🔼", + desc: " 🔽", + }[header.column.getIsSorted() as string] ?? null} + + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} + +
+
+ table.setPageIndex(page)} + rowsPerPage={table.getState().pagination.pageSize} + onRowsPerPageChange={(e) => table.setPageSize(Number(e.target.value))} + rowsPerPageOptions={[5, 10, 25, 50]} + /> +
+ ); +} + +export default function ReactTable({ + striped, + title, + data, + columns, +}: ReactTableProps) { + return ; +} diff --git a/src/layouts/Dashboard/Header.tsx b/src/layouts/Dashboard/Header.tsx index 0dbcaa0..1b1d165 100644 --- a/src/layouts/Dashboard/Header.tsx +++ b/src/layouts/Dashboard/Header.tsx @@ -63,7 +63,7 @@ export default function Header({ }} > - Hi, Admins + Hi, Admin [] = [ + { + header: "Backbone", + accessorKey: "backbone", + }, + { + header: "Device Start", + accessorKey: "device_start", + }, + { + header: "Device End", + accessorKey: "device_end", + }, + { + header: "Total Core", + accessorKey: "total_core", + }, + { + header: "Total Fishbone", + accessorKey: "total_fishbone", + }, + { + header: "", + accessorKey: "action", + enableSorting: false, + cell: () => ( + <> + + + + + ), + }, + ]; + return ( @@ -33,16 +86,12 @@ export default function Backbone() { - - Live From Space - - - Mac Miller - + diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx new file mode 100644 index 0000000..19999cb --- /dev/null +++ b/src/pages/Devices.tsx @@ -0,0 +1,111 @@ +import Box from "@mui/material/Box"; +import Breadcrumbs from "@mui/material/Breadcrumbs"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid2"; +import Typography from "@mui/material/Typography"; +import AddIcon from "@mui/icons-material/Add"; +import { Home } from "@mui/icons-material"; +import ReactTable from "../components/ReactTable"; +import { ColumnDef } from "@tanstack/react-table"; +import IconButton from "../components/IconButton"; +import { Edit } from "iconsax-react"; + +interface UserData { + device_code: string; + device_type: string; + address: string; + port_amount: string; + status: string; +} + +export default function Devices() { + const data: UserData[] = [ + { device_code: 'DV-CODE-1', device_type: "OTB", address: "Jl. Contoh 1", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-2', device_type: "OTB", address: "Jl. Contoh 2", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-3', device_type: "OTB", address: "Jl. Contoh 3", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-4', device_type: "OTB", address: "Jl. Contoh 4", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-5', device_type: "OTB", address: "Jl. Contoh 5", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-6', device_type: "OTB", address: "Jl. Contoh 6", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-7', device_type: "OTB", address: "Jl. Contoh 7", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-8', device_type: "OTB", address: "Jl. Contoh 8", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-9', device_type: "OTB", address: "Jl. Contoh 9", port_amount: '16', status: "Active" }, + { device_code: 'DV-CODE-10', device_type: "OTB", address: "Jl. Contoh 10", port_amount: '16', status: "Active" }, + ]; + + // Define columns + const columns: ColumnDef[] = [ + { + header: "Device Code", + accessorKey: "device_code", + }, + { + header: "Device Type", + accessorKey: "device_type", + }, + { + header: "Address", + accessorKey: "address", + }, + { + header: "Total Port", + accessorKey: "port_amount", + }, + { + header: "Status", + accessorKey: "status", + cell: ({ row }) => ( + + {row.original.status} + + ) + }, + { + header: '', + accessorKey: "action", + enableSorting: false, + cell: () => ( + <> + + + + + ) + } + ]; + + return ( + + + + Devices + + + + + + + + + + Devices + + + + + + + + + + + ); +} diff --git a/src/pages/Fishbone.tsx b/src/pages/Fishbone.tsx new file mode 100644 index 0000000..3495213 --- /dev/null +++ b/src/pages/Fishbone.tsx @@ -0,0 +1,93 @@ +import Box from "@mui/material/Box"; +import Breadcrumbs from "@mui/material/Breadcrumbs"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid2"; +import Typography from "@mui/material/Typography"; +import AddIcon from "@mui/icons-material/Add"; +import { Home } from "@mui/icons-material"; +import ReactTable from "../components/ReactTable"; +import { ColumnDef } from "@tanstack/react-table"; +import IconButton from "../components/IconButton"; +import { Edit } from "iconsax-react"; + +interface UserData { + backbone: string; + device_start: string; + device_end: string; + total_core: string; +} + +export default function Fishbone() { + const data: UserData[] = [ + { backbone: 'BB-BDG-SMD', device_start: "DV-CODE-1", device_end: "DV-CODE-2", total_core: '24 Core', }, + { backbone: 'BB-BDG-SBG', device_start: "DV-CODE-3", device_end: "DV-CODE-4", total_core: '24 Core', }, + ]; + + // Define columns + const columns: ColumnDef[] = [ + { + header: "Backbone", + accessorKey: "backbone", + }, + { + header: "Device Start", + accessorKey: "device_start", + }, + { + header: "Device End", + accessorKey: "device_end", + }, + { + header: "Total Core", + accessorKey: "total_core", + }, + { + header: '', + accessorKey: "action", + enableSorting: false, + cell: () => ( + <> + + + + + ) + } + ]; + + return ( + + + + Fishbone + + + + + + + + + + Fishbone + + + + + + + + + + + ); +} diff --git a/src/pages/Towers.tsx b/src/pages/Towers.tsx new file mode 100644 index 0000000..47ccb63 --- /dev/null +++ b/src/pages/Towers.tsx @@ -0,0 +1,96 @@ +import Box from "@mui/material/Box"; +import Breadcrumbs from "@mui/material/Breadcrumbs"; +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid2"; +import Typography from "@mui/material/Typography"; +import AddIcon from "@mui/icons-material/Add"; +import { Home } from "@mui/icons-material"; +import ReactTable from "../components/ReactTable"; +import { ColumnDef } from "@tanstack/react-table"; +import IconButton from "../components/IconButton"; +import { Edit } from "iconsax-react"; + +interface UserData { + tower_code: string; + device: string; + address: string; +} + +export default function Towers() { + const data: UserData[] = [ + { tower_code: 'TW-CODE-1', device: "DV-CODE-1", address: "Jl. Contoh 1" }, + { tower_code: 'TW-CODE-2', device: "DV-CODE-2", address: "Jl. Contoh 2" }, + { tower_code: 'TW-CODE-3', device: "", address: "Jl. Contoh 3" }, + { tower_code: 'TW-CODE-4', device: "", address: "Jl. Contoh 4" }, + { tower_code: 'TW-CODE-5', device: "DV-CODE-5", address: "Jl. Contoh 5" }, + { tower_code: 'TW-CODE-6', device: "", address: "Jl. Contoh 6" }, + { tower_code: 'TW-CODE-7', device: "", address: "Jl. Contoh 7" }, + { tower_code: 'TW-CODE-8', device: "DV-CODE-8", address: "Jl. Contoh 8" }, + { tower_code: 'TW-CODE-9', device: "", address: "Jl. Contoh 9" }, + { tower_code: 'TW-CODE-10', device: "DV-CODE-10", address: "Jl. Contoh 10" }, + ]; + + // Define columns + const columns: ColumnDef[] = [ + { + header: "Tower Code", + accessorKey: "tower_code", + }, + { + header: "Device", + accessorKey: "device", + }, + { + header: "Address", + accessorKey: "address", + }, + { + header: '', + accessorKey: "action", + enableSorting: false, + cell: () => ( + <> + + + + + ) + } + ]; + + return ( + + + + Towers + + + + + + + + + + Towers + + + + + + + + + + + ); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 04e3e5c..31fbf13 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -5,6 +5,9 @@ import Login from "../pages/auth/Login"; import Dashboard from "../pages/Dashboard"; import DashboardLayout from "../layouts/DashboardLayout"; import Backbone from "../pages/Backbone"; +import Devices from "../pages/Devices"; +import Fishbone from "../pages/Fishbone"; +import Towers from "../pages/Towers"; export default function Routes() { return ( @@ -14,6 +17,9 @@ export default function Routes() { }> } /> } /> + } /> + } /> + } /> {/* Auth routes */} }> diff --git a/src/theme/palette.ts b/src/theme/palette.ts index 1694cbd..4d78ed9 100644 --- a/src/theme/palette.ts +++ b/src/theme/palette.ts @@ -48,8 +48,8 @@ const Palette = (mode: ThemeMode) => { 900: "#4e5161", }, error: { - lighter: "#fef3f2", - light: "#ffe3e1", + lighter: "#ffe3e1", + light: "#ffccc8", main: "#f44334", dark: "#e22f20", darker: "#be2417", @@ -65,8 +65,8 @@ const Palette = (mode: ThemeMode) => { '900': '#82221a', }, warning: { - lighter: "#fffbec", - light: "#ffd96d", + lighter: "#fff6d3", + light: "#ffeaa5", main: "#fb8c00", dark: "#cc6902", darker: "#a1500b", @@ -82,7 +82,7 @@ const Palette = (mode: ThemeMode) => { '900': '#82430c' }, info: { - lighter: "#ecfdff", + lighter: "#d0f6fd", light: "#a7edfa", main: "#16c0e8", dark: "#106c8e", @@ -99,8 +99,8 @@ const Palette = (mode: ThemeMode) => { '900': '#174962', }, success: { - lighter: "#f3faf3", - light: "#e3f5e3", + lighter: "#e3f5e3", + light: "#c8eac9", main: "#4caf50", dark: "#358438", darker: "#2d6830", @@ -136,7 +136,7 @@ const Palette = (mode: ThemeMode) => { divider: mode === ThemeMode.DARK ? alpha(paletteColor.secondary.darker!, 0.05) : alpha(paletteColor.secondary.light!, 0.65), background: { paper: mode === ThemeMode.DARK ? paletteColor.secondary[100] : '#fff', - default: mode !== ThemeMode.DARK ? '#fff' : paletteColor.secondary.lighter + default: mode !== ThemeMode.DARK ? '#fff' : paletteColor.secondary[900] } } });