add: responsive layout for sidebar and dashboard
This commit is contained in:
@@ -10,3 +10,11 @@ Update floco dependencies:
|
|||||||
|
|
||||||
`nix run github:aakropotkin/floco -- translate -pt -o ./nix/pdefs.nix`
|
`nix run github:aakropotkin/floco -- translate -pt -o ./nix/pdefs.nix`
|
||||||
|
|
||||||
|
|
||||||
|
The prettier tailwind class sorting is not yet working properly with our devShell integration.
|
||||||
|
|
||||||
|
To sort classnames manually:
|
||||||
|
|
||||||
|
`cd /clan-core/pkgs/ui/`
|
||||||
|
|
||||||
|
`prettier -w ./src/ --config pconf.cjs`
|
||||||
|
|||||||
4
pkgs/ui/pconf.cjs
Normal file
4
pkgs/ui/pconf.cjs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// prettier.config.js
|
||||||
|
module.exports = {
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
};
|
||||||
@@ -3,13 +3,21 @@
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
import {
|
||||||
|
CssBaseline,
|
||||||
|
IconButton,
|
||||||
|
ThemeProvider,
|
||||||
|
useMediaQuery,
|
||||||
|
} from "@mui/material";
|
||||||
import { ChangeEvent, useState } from "react";
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
import { StyledEngineProvider } from "@mui/material/styles";
|
import { StyledEngineProvider } from "@mui/material/styles";
|
||||||
|
|
||||||
import { darkTheme, lightTheme } from "./theme/themes";
|
import { darkTheme, lightTheme } from "./theme/themes";
|
||||||
import { Sidebar } from "@/components/sidebar";
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
|
import { ChevronLeft } from "@mui/icons-material";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
const roboto = localFont({
|
const roboto = localFont({
|
||||||
src: [
|
src: [
|
||||||
@@ -26,12 +34,18 @@ export default function RootLayout({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||||
let [useDarkTheme, setUseDarkTheme] = useState(false);
|
let [useDarkTheme, setUseDarkTheme] = useState(false);
|
||||||
let [theme, setTheme] = useState(useDarkTheme ? darkTheme : lightTheme);
|
let [showSidebar, setShowSidebar] = useState(true);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (useDarkTheme !== userPrefersDarkmode) {
|
||||||
|
// Enable dark theme if the user prefers dark mode
|
||||||
|
setUseDarkTheme(userPrefersDarkmode);
|
||||||
|
}
|
||||||
|
}, [userPrefersDarkmode, useDarkTheme, setUseDarkTheme]);
|
||||||
|
|
||||||
const changeThemeHandler = (target: ChangeEvent, currentValue: boolean) => {
|
const changeThemeHandler = (target: ChangeEvent, currentValue: boolean) => {
|
||||||
setUseDarkTheme(currentValue);
|
setUseDarkTheme(currentValue);
|
||||||
setTheme(currentValue ? darkTheme : lightTheme);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,13 +57,42 @@ export default function RootLayout({
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
<StyledEngineProvider injectFirst>
|
<StyledEngineProvider injectFirst>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={useDarkTheme ? darkTheme : lightTheme}>
|
||||||
<body id="__next" className={roboto.className}>
|
<body id="__next" className={roboto.className}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<div className="flex h-screen overflow-hidden">
|
<div className="flex h-screen overflow-hidden">
|
||||||
<Sidebar />
|
<Sidebar
|
||||||
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
show={showSidebar}
|
||||||
<main>{children}</main>
|
onClose={() => setShowSidebar(false)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col w-full h-full">
|
||||||
|
<div className="static min-h-10 top-0 mb-2 py-2">
|
||||||
|
<div className="grid grid-cols-3">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<IconButton
|
||||||
|
hidden={true}
|
||||||
|
onClick={() => setShowSidebar((c) => !c)}
|
||||||
|
>
|
||||||
|
{!showSidebar && <MenuIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 block lg:hidden w-full text-center font-semibold text-white ">
|
||||||
|
<Image
|
||||||
|
src="/logo.svg"
|
||||||
|
alt="Clan Logo"
|
||||||
|
width={58}
|
||||||
|
height={58}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-1">
|
||||||
|
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
||||||
|
<main>{children}</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,38 +1,43 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import { alpha } from '@mui/material/styles';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Table from '@mui/material/Table';
|
|
||||||
import TableBody from '@mui/material/TableBody';
|
|
||||||
import TableCell from '@mui/material/TableCell';
|
|
||||||
import TableContainer from '@mui/material/TableContainer';
|
|
||||||
import TableHead from '@mui/material/TableHead';
|
|
||||||
import TablePagination from '@mui/material/TablePagination';
|
|
||||||
import TableRow from '@mui/material/TableRow';
|
|
||||||
import TableSortLabel from '@mui/material/TableSortLabel';
|
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
||||||
import Switch from '@mui/material/Switch';
|
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
|
||||||
import { visuallyHidden } from '@mui/utils';
|
|
||||||
import CircleIcon from '@mui/icons-material/Circle';
|
|
||||||
import Stack from '@mui/material/Stack/Stack';
|
|
||||||
import ModeIcon from '@mui/icons-material/Mode';
|
|
||||||
import ClearIcon from '@mui/icons-material/Clear';
|
|
||||||
import Fade from '@mui/material/Fade/Fade';
|
|
||||||
import NodePieChart, { PieData } from './NodePieChart';
|
|
||||||
import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2
|
|
||||||
import { Card, CardContent, Container, FormGroup, useTheme } from '@mui/material';
|
|
||||||
import hexRgb from 'hex-rgb';
|
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { alpha } from "@mui/material/styles";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Table from "@mui/material/Table";
|
||||||
|
import TableBody from "@mui/material/TableBody";
|
||||||
|
import TableCell from "@mui/material/TableCell";
|
||||||
|
import TableContainer from "@mui/material/TableContainer";
|
||||||
|
import TableHead from "@mui/material/TableHead";
|
||||||
|
import TablePagination from "@mui/material/TablePagination";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import TableSortLabel from "@mui/material/TableSortLabel";
|
||||||
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import Checkbox from "@mui/material/Checkbox";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
import { visuallyHidden } from "@mui/utils";
|
||||||
|
import CircleIcon from "@mui/icons-material/Circle";
|
||||||
|
import Stack from "@mui/material/Stack/Stack";
|
||||||
|
import ModeIcon from "@mui/icons-material/Mode";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import Fade from "@mui/material/Fade/Fade";
|
||||||
|
import NodePieChart, { PieData } from "./NodePieChart";
|
||||||
|
import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Container,
|
||||||
|
FormGroup,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import hexRgb from "hex-rgb";
|
||||||
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
|
|
||||||
export interface TableData {
|
export interface TableData {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -47,7 +52,6 @@ export enum NodeStatus {
|
|||||||
Pending,
|
Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface HeadCell {
|
interface HeadCell {
|
||||||
disablePadding: boolean;
|
disablePadding: boolean;
|
||||||
id: keyof TableData;
|
id: keyof TableData;
|
||||||
@@ -57,22 +61,22 @@ interface HeadCell {
|
|||||||
|
|
||||||
const headCells: readonly HeadCell[] = [
|
const headCells: readonly HeadCell[] = [
|
||||||
{
|
{
|
||||||
id: 'name',
|
id: "name",
|
||||||
alignRight: false,
|
alignRight: false,
|
||||||
disablePadding: false,
|
disablePadding: false,
|
||||||
label: 'DISPLAY NAME & ID',
|
label: "DISPLAY NAME & ID",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'status',
|
id: "status",
|
||||||
alignRight: false,
|
alignRight: false,
|
||||||
disablePadding: false,
|
disablePadding: false,
|
||||||
label: 'STATUS',
|
label: "STATUS",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'last_seen',
|
id: "last_seen",
|
||||||
alignRight: false,
|
alignRight: false,
|
||||||
disablePadding: false,
|
disablePadding: false,
|
||||||
label: 'LAST SEEN',
|
label: "LAST SEEN",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -86,7 +90,7 @@ function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Order = 'asc' | 'desc';
|
type Order = "asc" | "desc";
|
||||||
|
|
||||||
function getComparator<Key extends keyof any>(
|
function getComparator<Key extends keyof any>(
|
||||||
order: Order,
|
order: Order,
|
||||||
@@ -95,7 +99,7 @@ function getComparator<Key extends keyof any>(
|
|||||||
a: { [key in Key]: number | string | boolean },
|
a: { [key in Key]: number | string | boolean },
|
||||||
b: { [key in Key]: number | string | boolean },
|
b: { [key in Key]: number | string | boolean },
|
||||||
) => number {
|
) => number {
|
||||||
return order === 'desc'
|
return order === "desc"
|
||||||
? (a, b) => descendingComparator(a, b, orderBy)
|
? (a, b) => descendingComparator(a, b, orderBy)
|
||||||
: (a, b) => -descendingComparator(a, b, orderBy);
|
: (a, b) => -descendingComparator(a, b, orderBy);
|
||||||
}
|
}
|
||||||
@@ -104,7 +108,10 @@ function getComparator<Key extends keyof any>(
|
|||||||
// stableSort() brings sort stability to non-modern browsers (notably IE11). If you
|
// stableSort() brings sort stability to non-modern browsers (notably IE11). If you
|
||||||
// only support modern browsers you can replace stableSort(exampleArray, exampleComparator)
|
// only support modern browsers you can replace stableSort(exampleArray, exampleComparator)
|
||||||
// with exampleArray.slice().sort(exampleComparator)
|
// with exampleArray.slice().sort(exampleComparator)
|
||||||
function stableSort<T>(array: readonly T[], comparator: (a: T, b: T) => number) {
|
function stableSort<T>(
|
||||||
|
array: readonly T[],
|
||||||
|
comparator: (a: T, b: T) => number,
|
||||||
|
) {
|
||||||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
|
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
|
||||||
stabilizedThis.sort((a, b) => {
|
stabilizedThis.sort((a, b) => {
|
||||||
const order = comparator(a[0], b[0]);
|
const order = comparator(a[0], b[0]);
|
||||||
@@ -116,18 +123,18 @@ function stableSort<T>(array: readonly T[], comparator: (a: T, b: T) => number)
|
|||||||
return stabilizedThis.map((el) => el[0]);
|
return stabilizedThis.map((el) => el[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface EnhancedTableProps {
|
interface EnhancedTableProps {
|
||||||
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof TableData) => void;
|
onRequestSort: (
|
||||||
|
event: React.MouseEvent<unknown>,
|
||||||
|
property: keyof TableData,
|
||||||
|
) => void;
|
||||||
order: Order;
|
order: Order;
|
||||||
orderBy: string;
|
orderBy: string;
|
||||||
rowCount: number;
|
rowCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EnhancedTableHead(props: EnhancedTableProps) {
|
function EnhancedTableHead(props: EnhancedTableProps) {
|
||||||
const { order, orderBy, onRequestSort } =
|
const { order, orderBy, onRequestSort } = props;
|
||||||
props;
|
|
||||||
const createSortHandler =
|
const createSortHandler =
|
||||||
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
||||||
onRequestSort(event, property);
|
onRequestSort(event, property);
|
||||||
@@ -139,19 +146,19 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
|||||||
{headCells.map((headCell) => (
|
{headCells.map((headCell) => (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={headCell.id}
|
key={headCell.id}
|
||||||
align={headCell.alignRight ? 'right' : 'left'}
|
align={headCell.alignRight ? "right" : "left"}
|
||||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
padding={headCell.disablePadding ? "none" : "normal"}
|
||||||
sortDirection={orderBy === headCell.id ? order : false}
|
sortDirection={orderBy === headCell.id ? order : false}
|
||||||
>
|
>
|
||||||
<TableSortLabel
|
<TableSortLabel
|
||||||
active={orderBy === headCell.id}
|
active={orderBy === headCell.id}
|
||||||
direction={orderBy === headCell.id ? order : 'asc'}
|
direction={orderBy === headCell.id ? order : "asc"}
|
||||||
onClick={createSortHandler(headCell.id)}
|
onClick={createSortHandler(headCell.id)}
|
||||||
>
|
>
|
||||||
{headCell.label}
|
{headCell.label}
|
||||||
{orderBy === headCell.id ? (
|
{orderBy === headCell.id ? (
|
||||||
<Box component="span" sx={visuallyHidden}>
|
<Box component="span" sx={visuallyHidden}>
|
||||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
{order === "desc" ? "sorted descending" : "sorted ascending"}
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
</TableSortLabel>
|
</TableSortLabel>
|
||||||
@@ -162,8 +169,6 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface EnhancedTableToolbarProps {
|
interface EnhancedTableToolbarProps {
|
||||||
selected: string | undefined;
|
selected: string | undefined;
|
||||||
tableData: TableData[];
|
tableData: TableData[];
|
||||||
@@ -172,41 +177,49 @@ interface EnhancedTableToolbarProps {
|
|||||||
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
||||||
const { selected, onClear, tableData } = props;
|
const { selected, onClear, tableData } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matches = useMediaQuery(theme.breakpoints.down('lg'));
|
const matches = useMediaQuery(theme.breakpoints.down("lg"));
|
||||||
const isSelected = selected != undefined;
|
const isSelected = selected != undefined;
|
||||||
const [debug, setDebug] = React.useState<boolean>(false);
|
const [debug, setDebug] = React.useState<boolean>(false);
|
||||||
const debugSx = debug ? {
|
const debugSx = debug
|
||||||
'--Grid-borderWidth': '1px',
|
? {
|
||||||
borderTop: 'var(--Grid-borderWidth) solid',
|
"--Grid-borderWidth": "1px",
|
||||||
borderLeft: 'var(--Grid-borderWidth) solid',
|
borderTop: "var(--Grid-borderWidth) solid",
|
||||||
borderColor: 'divider',
|
borderLeft: "var(--Grid-borderWidth) solid",
|
||||||
'& > div': {
|
borderColor: "divider",
|
||||||
borderRight: 'var(--Grid-borderWidth) solid',
|
"& > div": {
|
||||||
borderBottom: 'var(--Grid-borderWidth) solid',
|
borderRight: "var(--Grid-borderWidth) solid",
|
||||||
borderColor: 'divider',
|
borderBottom: "var(--Grid-borderWidth) solid",
|
||||||
}
|
borderColor: "divider",
|
||||||
} : {};
|
},
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
const pieData = React.useMemo(() => {
|
const pieData = React.useMemo(() => {
|
||||||
const online = tableData.filter((row) => row.status === NodeStatus.Online).length;
|
const online = tableData.filter(
|
||||||
const offline = tableData.filter((row) => row.status === NodeStatus.Offline).length;
|
(row) => row.status === NodeStatus.Online,
|
||||||
const pending = tableData.filter((row) => row.status === NodeStatus.Pending).length;
|
).length;
|
||||||
|
const offline = tableData.filter(
|
||||||
|
(row) => row.status === NodeStatus.Offline,
|
||||||
|
).length;
|
||||||
|
const pending = tableData.filter(
|
||||||
|
(row) => row.status === NodeStatus.Pending,
|
||||||
|
).length;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ name: 'Online', value: online, color: '#2E7D32' },
|
{ name: "Online", value: online, color: "#2E7D32" },
|
||||||
{ name: 'Offline', value: offline, color: '#db3927' },
|
{ name: "Offline", value: offline, color: "#db3927" },
|
||||||
{ name: 'Pending', value: pending, color: '#FFBB28' },
|
{ name: "Pending", value: pending, color: "#FFBB28" },
|
||||||
];
|
];
|
||||||
}, [tableData]);
|
}, [tableData]);
|
||||||
|
|
||||||
const cardData = React.useMemo(() => {
|
const cardData = React.useMemo(() => {
|
||||||
return pieData.filter((pieItem) => pieItem.value > 0).concat(
|
return pieData
|
||||||
{
|
.filter((pieItem) => pieItem.value > 0)
|
||||||
name: 'Total',
|
.concat({
|
||||||
|
name: "Total",
|
||||||
value: pieData.reduce((a, b) => a + b.value, 0),
|
value: pieData.reduce((a, b) => a + b.value, 0),
|
||||||
color: '#000000'
|
color: "#000000",
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}, [pieData]);
|
}, [pieData]);
|
||||||
|
|
||||||
const cardStack = (
|
const cardStack = (
|
||||||
@@ -217,17 +230,38 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
display="flex"
|
display="flex"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
justifyContent="flex-start"
|
justifyContent="flex-start"
|
||||||
flexWrap="wrap">
|
flexWrap="wrap"
|
||||||
|
>
|
||||||
{cardData.map((pieItem) => (
|
{cardData.map((pieItem) => (
|
||||||
<Card key={pieItem.name} sx={{ marginBottom: 2, marginRight: 2, width: 110, height: 110, backgroundColor: hexRgb(pieItem.color, { format: 'css', alpha: 0.18 }) }}>
|
<Card
|
||||||
<CardContent >
|
key={pieItem.name}
|
||||||
<Typography variant="h4" component="div" gutterBottom={true} textAlign="center">
|
sx={{
|
||||||
|
marginBottom: 2,
|
||||||
|
marginRight: 2,
|
||||||
|
width: 110,
|
||||||
|
height: 110,
|
||||||
|
backgroundColor: hexRgb(pieItem.color, {
|
||||||
|
format: "css",
|
||||||
|
alpha: 0.18,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
component="div"
|
||||||
|
gutterBottom={true}
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
{pieItem.value}
|
{pieItem.value}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ mb: 1.5 }} color="text.secondary" textAlign="center">
|
<Typography
|
||||||
|
sx={{ mb: 1.5 }}
|
||||||
|
color="text.secondary"
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
{pieItem.name}
|
{pieItem.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -240,15 +274,19 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
pl: { sm: 2 },
|
pl: { sm: 2 },
|
||||||
pr: { xs: 1, sm: 1 },
|
pr: { xs: 1, sm: 1 },
|
||||||
bgcolor: (theme) =>
|
bgcolor: (theme) =>
|
||||||
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
alpha(
|
||||||
}}>
|
theme.palette.primary.main,
|
||||||
|
theme.palette.action.activatedOpacity,
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tooltip title="Clear">
|
<Tooltip title="Clear">
|
||||||
<IconButton onClick={onClear}>
|
<IconButton onClick={onClear}>
|
||||||
<ClearIcon />
|
<ClearIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ flex: '1 1 100%' }}
|
sx={{ flex: "1 1 100%" }}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
style={{ fontSize: 18, marginBottom: 3, marginLeft: 3 }}
|
style={{ fontSize: 18, marginBottom: 3, marginLeft: 3 }}
|
||||||
component="div"
|
component="div"
|
||||||
@@ -260,7 +298,7 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
<ModeIcon />
|
<ModeIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Toolbar >
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
||||||
const unselectedToolbar = (
|
const unselectedToolbar = (
|
||||||
@@ -270,16 +308,15 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
pr: { xs: 1, sm: 1 },
|
pr: { xs: 1, sm: 1 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ flex: '1 1 100%' }} ></Box>
|
<Box sx={{ flex: "1 1 100%" }}></Box>
|
||||||
<Tooltip title="Filter list">
|
<Tooltip title="Filter list">
|
||||||
<IconButton>
|
<IconButton>
|
||||||
<FilterListIcon />
|
<FilterListIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Toolbar >
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid2 container spacing={1} sx={debugSx}>
|
<Grid2 container spacing={1} sx={debugSx}>
|
||||||
<Grid2 key="Header" xs={6}>
|
<Grid2 key="Header" xs={6}>
|
||||||
@@ -295,19 +332,41 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
{/* Debug Controls */}
|
{/* Debug Controls */}
|
||||||
<Grid2 key="Debug-Controls" xs={6} justifyContent="right" display="flex">
|
<Grid2 key="Debug-Controls" xs={6} justifyContent="right" display="flex">
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormControlLabel control={<Switch onChange={() => { setDebug(!debug) }} checked={debug} />} label="Debug" />
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
onChange={() => {
|
||||||
|
setDebug(!debug);
|
||||||
|
}}
|
||||||
|
checked={debug}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Debug"
|
||||||
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
{/* Pie Chart Grid */}
|
{/* Pie Chart Grid */}
|
||||||
<Grid2 key="PieChart" lg={6} sm={12} display="flex" justifyContent="center" alignItems="center">
|
<Grid2
|
||||||
|
key="PieChart"
|
||||||
|
lg={6}
|
||||||
|
sm={12}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
<Box height={350} width={400}>
|
<Box height={350} width={400}>
|
||||||
<NodePieChart data={pieData} showLabels={matches} />
|
<NodePieChart data={pieData} showLabels={matches} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
{/* Card Stack Grid */}
|
{/* Card Stack Grid */}
|
||||||
<Grid2 key="CardStack" lg={6} display="flex" sx={{ display: { lg: 'flex', sm: 'none' } }} >
|
<Grid2
|
||||||
|
key="CardStack"
|
||||||
|
lg={6}
|
||||||
|
display="flex"
|
||||||
|
sx={{ display: { lg: "flex", sm: "none" } }}
|
||||||
|
>
|
||||||
{cardStack}
|
{cardStack}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
@@ -315,12 +374,10 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
<Grid2 key="Toolbar" xs={12}>
|
<Grid2 key="Toolbar" xs={12}>
|
||||||
{isSelected ? selectedToolbar : unselectedToolbar}
|
{isSelected ? selectedToolbar : unselectedToolbar}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
</Grid2>
|
</Grid2>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderLastSeen(last_seen: number) {
|
function renderLastSeen(last_seen: number) {
|
||||||
return (
|
return (
|
||||||
<Typography component="div" align="left" variant="body1">
|
<Typography component="div" align="left" variant="body1">
|
||||||
@@ -376,13 +433,13 @@ function renderStatus(status: NodeStatus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeTableProps {
|
export interface NodeTableProps {
|
||||||
tableData: TableData[]
|
tableData: TableData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NodeTable(props: NodeTableProps) {
|
export default function NodeTable(props: NodeTableProps) {
|
||||||
let { tableData } = props;
|
let { tableData } = props;
|
||||||
const [order, setOrder] = React.useState<Order>('asc');
|
const [order, setOrder] = React.useState<Order>("asc");
|
||||||
const [orderBy, setOrderBy] = React.useState<keyof TableData>('status');
|
const [orderBy, setOrderBy] = React.useState<keyof TableData>("status");
|
||||||
const [selected, setSelected] = React.useState<string | undefined>(undefined);
|
const [selected, setSelected] = React.useState<string | undefined>(undefined);
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [dense, setDense] = React.useState(false);
|
const [dense, setDense] = React.useState(false);
|
||||||
@@ -392,8 +449,8 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
event: React.MouseEvent<unknown>,
|
event: React.MouseEvent<unknown>,
|
||||||
property: keyof TableData,
|
property: keyof TableData,
|
||||||
) => {
|
) => {
|
||||||
const isAsc = orderBy === property && order === 'asc';
|
const isAsc = orderBy === property && order === "asc";
|
||||||
setOrder(isAsc ? 'desc' : 'asc');
|
setOrder(isAsc ? "desc" : "asc");
|
||||||
setOrderBy(property);
|
setOrderBy(property);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -410,7 +467,9 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
setRowsPerPage(parseInt(event.target.value, 10));
|
setRowsPerPage(parseInt(event.target.value, 10));
|
||||||
setPage(0);
|
setPage(0);
|
||||||
};
|
};
|
||||||
@@ -431,18 +490,20 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
[order, orderBy, page, rowsPerPage, tableData],
|
[order, orderBy, page, rowsPerPage, tableData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper elevation={1} sx={{ margin: 5 }}>
|
<Paper elevation={1} sx={{ margin: 5 }}>
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
<Paper sx={{ width: '100%', mb: 2 }}>
|
<Paper sx={{ width: "100%", mb: 2 }}>
|
||||||
<EnhancedTableToolbar tableData={tableData} selected={selected} onClear={() => setSelected(undefined)} />
|
<EnhancedTableToolbar
|
||||||
|
tableData={tableData}
|
||||||
|
selected={selected}
|
||||||
|
onClear={() => setSelected(undefined)}
|
||||||
|
/>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table
|
<Table
|
||||||
sx={{ minWidth: 750 }}
|
sx={{ minWidth: 750 }}
|
||||||
aria-labelledby="tableTitle"
|
aria-labelledby="tableTitle"
|
||||||
size={dense ? 'small' : 'medium'}
|
size={dense ? "small" : "medium"}
|
||||||
>
|
>
|
||||||
<EnhancedTableHead
|
<EnhancedTableHead
|
||||||
order={order}
|
order={order}
|
||||||
@@ -464,17 +525,17 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
key={row.name}
|
key={row.name}
|
||||||
selected={isItemSelected}
|
selected={isItemSelected}
|
||||||
sx={{ cursor: 'pointer' }}
|
sx={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<TableCell
|
<TableCell component="th" id={labelId} scope="row">
|
||||||
component="th"
|
|
||||||
id={labelId}
|
|
||||||
scope="row"
|
|
||||||
>
|
|
||||||
{renderName(row.name, row.id)}
|
{renderName(row.name, row.id)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">{renderStatus(row.status)}</TableCell>
|
<TableCell align="right">
|
||||||
<TableCell align="right">{renderLastSeen(row.last_seen)}</TableCell>
|
{renderStatus(row.status)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
{renderLastSeen(row.last_seen)}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,45 +1,50 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from "react";
|
||||||
import { PieChart, Pie, Sector, Cell, ResponsiveContainer, Legend } from 'recharts';
|
import {
|
||||||
import { useTheme } from '@mui/material/styles';
|
PieChart,
|
||||||
import { Box, Color } from '@mui/material';
|
Pie,
|
||||||
|
Sector,
|
||||||
|
Cell,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Legend,
|
||||||
|
} from "recharts";
|
||||||
|
import { useTheme } from "@mui/material/styles";
|
||||||
|
import { Box, Color } from "@mui/material";
|
||||||
|
|
||||||
export interface PieData {
|
export interface PieData {
|
||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
color: string;
|
color: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PieData[];
|
data: PieData[];
|
||||||
showLabels?: boolean;
|
showLabels?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function NodePieChart(props: Props ) {
|
export default function NodePieChart(props: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const {data, showLabels} = props;
|
const { data, showLabels } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
return (
|
<Box height={350}>
|
||||||
<Box height={350}>
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<PieChart>
|
||||||
<PieChart>
|
<Pie
|
||||||
<Pie
|
data={data}
|
||||||
data={data}
|
innerRadius={85}
|
||||||
innerRadius={85}
|
outerRadius={120}
|
||||||
outerRadius={120}
|
fill={theme.palette.primary.main}
|
||||||
fill={theme.palette.primary.main}
|
dataKey="value"
|
||||||
dataKey="value"
|
nameKey="name"
|
||||||
nameKey="name"
|
label={showLabels}
|
||||||
label={showLabels}
|
>
|
||||||
>
|
{data.map((entry, index) => (
|
||||||
{data.map((entry, index) => (
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
))}
|
||||||
))}
|
</Pie>
|
||||||
</Pie>
|
<Legend verticalAlign="bottom" />
|
||||||
<Legend verticalAlign="bottom" />
|
</PieChart>
|
||||||
</PieChart>
|
</ResponsiveContainer>
|
||||||
</ResponsiveContainer>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import NodeList, { NodeStatus, TableData } from "./NodeList";
|
import NodeList, { NodeStatus, TableData } from "./NodeList";
|
||||||
@@ -6,42 +6,98 @@ import NodeList, { NodeStatus, TableData } from "./NodeList";
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
|
||||||
function createData(
|
function createData(
|
||||||
name: string,
|
name: string,
|
||||||
id: string,
|
id: string,
|
||||||
status: NodeStatus,
|
status: NodeStatus,
|
||||||
last_seen: number,
|
last_seen: number,
|
||||||
|
|
||||||
): TableData {
|
): TableData {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
return {
|
id,
|
||||||
name,
|
status,
|
||||||
id,
|
last_seen: last_seen,
|
||||||
status,
|
};
|
||||||
last_seen: last_seen,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableData = [
|
const tableData = [
|
||||||
createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", NodeStatus.Pending, 0),
|
createData(
|
||||||
createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", NodeStatus.Online, 0),
|
"Matchbox",
|
||||||
createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", NodeStatus.Offline, 16.0),
|
"42:0:f21:6916:e333:c47e:4b5c:e74c",
|
||||||
createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", NodeStatus.Offline, 6.0),
|
NodeStatus.Pending,
|
||||||
createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", NodeStatus.Offline, 13),
|
0,
|
||||||
createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", NodeStatus.Online, 0),
|
),
|
||||||
createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", NodeStatus.Online, 0),
|
createData(
|
||||||
createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", NodeStatus.Online, 0.0),
|
"Ahorn",
|
||||||
createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", NodeStatus.Offline, 7.0),
|
"42:0:3c46:b51c:b34d:b7e1:3b02:8d24",
|
||||||
createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", NodeStatus.Offline, 2),
|
NodeStatus.Online,
|
||||||
createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
|
0,
|
||||||
createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
|
),
|
||||||
createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", NodeStatus.Online, 0),
|
createData(
|
||||||
|
"Yellow",
|
||||||
|
"42:0:3c46:98ac:9c80:4f25:50e3:1d8f",
|
||||||
|
NodeStatus.Offline,
|
||||||
|
16.0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Rauter",
|
||||||
|
"42:0:61ea:b777:61ea:803:f885:3523",
|
||||||
|
NodeStatus.Offline,
|
||||||
|
6.0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Porree",
|
||||||
|
"42:0:e644:4499:d034:895e:34c8:6f9a",
|
||||||
|
NodeStatus.Offline,
|
||||||
|
13,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Helsinki",
|
||||||
|
"42:0:3c46:fd4a:acf9:e971:6036:8047",
|
||||||
|
NodeStatus.Online,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Kelle",
|
||||||
|
"42:0:3c46:362d:a9aa:4996:c78e:839a",
|
||||||
|
NodeStatus.Online,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Shodan",
|
||||||
|
"42:0:3c46:6745:adf4:a844:26c4:bf91",
|
||||||
|
NodeStatus.Online,
|
||||||
|
0.0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Qubasa",
|
||||||
|
"42:0:3c46:123e:bbea:3529:db39:6764",
|
||||||
|
NodeStatus.Offline,
|
||||||
|
7.0,
|
||||||
|
),
|
||||||
|
createData(
|
||||||
|
"Green",
|
||||||
|
"42:0:a46e:5af:632c:d2fe:a71d:cde0",
|
||||||
|
NodeStatus.Offline,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
createData("Gum", "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
|
||||||
|
createData("Xu", "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
|
||||||
|
createData(
|
||||||
|
"Zaatar",
|
||||||
|
"42:0:3c46:156e:10b6:3bd6:6e82:b2cd",
|
||||||
|
NodeStatus.Online,
|
||||||
|
0,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ backgroundColor: "#e9ecf5", height: "100%", width: "100%" }} display="inline-block" id="rootBox">
|
<Box
|
||||||
<NodeList tableData={tableData} />
|
sx={{ backgroundColor: "#e9ecf5", height: "100%", width: "100%" }}
|
||||||
</Box>
|
display="inline-block"
|
||||||
);
|
id="rootBox"
|
||||||
}
|
>
|
||||||
|
<NodeList tableData={tableData} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,60 @@
|
|||||||
import { Button } from "@mui/material";
|
interface DashboardCardProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
const DashboardCard = (props: DashboardCardProps) => {
|
||||||
|
const { children } = props;
|
||||||
|
return (
|
||||||
|
<div className="col-span-full border border-dashed border-slate-400 lg:col-span-1">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DashboardPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
const DashboardPanel = (props: DashboardPanelProps) => {
|
||||||
|
const { children } = props;
|
||||||
|
return (
|
||||||
|
<div className="col-span-full border border-dashed border-slate-400 lg:col-span-2">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SplitDashboardCardProps {
|
||||||
|
children?: React.ReactNode[];
|
||||||
|
}
|
||||||
|
const SplitDashboardCard = (props: SplitDashboardCardProps) => {
|
||||||
|
const { children } = props;
|
||||||
|
return (
|
||||||
|
<div className="col-span-full lg:col-span-1">
|
||||||
|
<div className="grid h-full grid-cols-1 gap-4">
|
||||||
|
{children?.map((row, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="col-span-full border border-dashed border-slate-400"
|
||||||
|
>
|
||||||
|
{row}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex justify-center items-center h-screen">
|
<div className="flex h-screen w-full">
|
||||||
<div className="grid">
|
<div className="grid w-full grid-cols-3 gap-4">
|
||||||
Welcome to the Dashboard
|
<DashboardCard>Current CLAN Overview</DashboardCard>
|
||||||
<Button variant="contained" color="primary">
|
<DashboardCard>Recent Activity Log</DashboardCard>
|
||||||
LOL
|
<SplitDashboardCard>
|
||||||
</Button>
|
<div>Notifications</div>
|
||||||
|
<div>Quick Action</div>
|
||||||
|
</SplitDashboardCard>
|
||||||
|
<DashboardPanel>Panel</DashboardPanel>
|
||||||
|
<DashboardCard>Side Bar (misc)</DashboardCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { createTheme } from "@mui/material/styles";
|
import { createTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
|
|
||||||
export const darkTheme = createTheme({
|
export const darkTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "dark",
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const lightTheme = createTheme({
|
export const lightTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "light",
|
mode: "light",
|
||||||
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Divider,
|
Divider,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
@@ -7,7 +9,7 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
|
|
||||||
import DashboardIcon from "@mui/icons-material/Dashboard";
|
import DashboardIcon from "@mui/icons-material/Dashboard";
|
||||||
import DevicesIcon from "@mui/icons-material/Devices";
|
import DevicesIcon from "@mui/icons-material/Devices";
|
||||||
@@ -16,6 +18,9 @@ import AppsIcon from "@mui/icons-material/Apps";
|
|||||||
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||||
import BackupIcon from "@mui/icons-material/Backup";
|
import BackupIcon from "@mui/icons-material/Backup";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { tw } from "@/utils/tailwind";
|
||||||
|
|
||||||
|
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||||
|
|
||||||
type MenuEntry = {
|
type MenuEntry = {
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
@@ -58,11 +63,23 @@ const menuEntries: MenuEntry[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Sidebar() {
|
const hideSidebar = tw`-translate-x-12 absolute lg:-translate-x-64`;
|
||||||
|
const showSidebar = tw`lg:translate-x-0 static`;
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
show: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
export function Sidebar(props: SidebarProps) {
|
||||||
|
const { show, onClose } = props;
|
||||||
return (
|
return (
|
||||||
<aside className="absolute left-0 top-0 z-9999 flex h-screen w-12 sm:w-64 flex-col overflow-y-hidden bg-zinc-950 dark:bg-boxdark sm:static">
|
<aside
|
||||||
<div className="flex items-center justify-between gap-2 px-6 py-5.5 lg:py-6.5">
|
className={tw`${
|
||||||
<div className="mt-8 font-semibold text-white w-full text-center hidden sm:block">
|
show ? showSidebar : hideSidebar
|
||||||
|
} z-9999 dark:bg-boxdark left-0 top-0 flex h-screen w-12 flex-col overflow-x-hidden overflow-y-hidden bg-zinc-950 lg:w-64 transition ease-in-out duration-150`}
|
||||||
|
>
|
||||||
|
<div className="py-5.5 lg:py-6.5 flex items-center justify-between gap-2 overflow-hidden px-0 lg:px-6">
|
||||||
|
<div className="mt-8 hidden w-full text-center font-semibold text-white lg:block">
|
||||||
<Image
|
<Image
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
alt="Clan Logo"
|
alt="Clan Logo"
|
||||||
@@ -72,20 +89,32 @@ export function Sidebar() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Divider flexItem className="bg-zinc-600 my-9 mx-8" />
|
<Divider
|
||||||
<div className="overflow-hidden flex flex-col overflow-y-auto duration-200 ease-linear">
|
flexItem
|
||||||
<List className="pb-4 mb-14 px-4 lg:mt-1 lg:px-6 text-white">
|
className="mx-8 mb-4 mt-9 bg-zinc-600 hidden lg:block"
|
||||||
|
/>
|
||||||
|
<div className="w-full flex justify-center">
|
||||||
|
<IconButton size="large" className="text-white" onClick={onClose}>
|
||||||
|
<ChevronLeftIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col overflow-hidden overflow-y-auto">
|
||||||
|
<List className="mb-14 px-0 pb-4 text-white lg:px-4 lg:mt-1">
|
||||||
{menuEntries.map((menuEntry, idx) => {
|
{menuEntries.map((menuEntry, idx) => {
|
||||||
return (
|
return (
|
||||||
<ListItem key={idx}>
|
<ListItem
|
||||||
|
key={idx}
|
||||||
|
disablePadding
|
||||||
|
className="!overflow-hidden py-2"
|
||||||
|
>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
className="justify-center sm:justify-normal"
|
className="justify-center lg:justify-normal"
|
||||||
LinkComponent={Link}
|
LinkComponent={Link}
|
||||||
href={menuEntry.to}
|
href={menuEntry.to}
|
||||||
>
|
>
|
||||||
<ListItemIcon
|
<ListItemIcon
|
||||||
color="inherit"
|
color="inherit"
|
||||||
className="justify-center sm:justify-normal text-white"
|
className="justify-center overflow-hidden text-white lg:justify-normal"
|
||||||
>
|
>
|
||||||
{menuEntry.icon}
|
{menuEntry.icon}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@@ -94,24 +123,24 @@ export function Sidebar() {
|
|||||||
primaryTypographyProps={{
|
primaryTypographyProps={{
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
}}
|
}}
|
||||||
className="hidden sm:block"
|
className="hidden lg:block"
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
<Divider flexItem className="bg-zinc-600 mx-8 my-10" />
|
|
||||||
<div className="hidden sm:block mx-auto mb-8 w-full max-w-60 rounded-sm py-6 px-4 text-center shadow-default align-bottom">
|
<Divider flexItem className="mx-8 my-10 bg-zinc-600 hidden lg:block" />
|
||||||
|
<div className="max-w-60 shadow-default mx-auto mb-8 hidden w-full rounded-sm px-4 py-6 text-center align-bottom lg:block">
|
||||||
<h3 className="mb-1 w-full font-semibold text-white">
|
<h3 className="mb-1 w-full font-semibold text-white">
|
||||||
Clan.lol Admin
|
Clan.lol Admin
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow"
|
rel="nofollow"
|
||||||
className="w-full text-center rounded-md bg-primary p-2 text-white hover:bg-opacity-95"
|
className="bg-primary w-full rounded-md p-2 text-center text-white hover:bg-opacity-95"
|
||||||
>
|
>
|
||||||
Donate
|
Donate
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user