forked from clan/clan-core
start machine list cleanup
This commit is contained in:
parent
778349b72d
commit
808bd3defd
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ export const config: PaletteConfig = {
|
||||
*/
|
||||
baseColors: {
|
||||
neutral: {
|
||||
keyColor: "#92898a",
|
||||
keyColor: "#808080",
|
||||
tones: [2, 5, 8, 92, 95, 98],
|
||||
},
|
||||
green: {
|
||||
@ -43,7 +43,7 @@ export const config: PaletteConfig = {
|
||||
},
|
||||
blue: {
|
||||
keyColor: "#1B7AC5",
|
||||
tones: [5, 95],
|
||||
tones: [1, 2, 3, 5, 95, 98],
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { Sidebar } from "@/components/sidebar";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import {
|
||||
Button,
|
||||
CssBaseline,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
@ -71,7 +70,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<>
|
||||
<Background />
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<div className="flex h-screen overflow-hidden bg-neutral-95">
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<Sidebar
|
||||
show={showSidebarDerived}
|
||||
@ -133,21 +132,7 @@ export default function RootLayout({
|
||||
|
||||
<div className="px-1">
|
||||
<div className="relative flex h-full flex-1 flex-col">
|
||||
<main>
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
appState.setAppState((s) => ({
|
||||
...s,
|
||||
isJoined: !s.isJoined,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Toggle Joined
|
||||
</Button>
|
||||
|
||||
{children}
|
||||
</main>
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
<>
|
||||
{!clanDir && <div>No clan selected</div>}
|
||||
{clanDir && (
|
||||
<MachineContextProvider flakeName={clanDir}>
|
||||
<MachineContextProvider clanDir={clanDir}>
|
||||
{children}
|
||||
</MachineContextProvider>
|
||||
)}
|
||||
|
@ -21,19 +21,19 @@ const commonOptions: Partial<ThemeOptions> = {
|
||||
|
||||
const commonPalette: Partial<PaletteOptions> = {
|
||||
primary: {
|
||||
main: palette.green50.value,
|
||||
main: palette.blue50.value,
|
||||
},
|
||||
secondary: {
|
||||
main: palette.green50.value,
|
||||
main: palette.green60.value,
|
||||
},
|
||||
info: {
|
||||
main: palette.blue50.value,
|
||||
},
|
||||
success: {
|
||||
main: palette.green50.value,
|
||||
main: palette.green60.value,
|
||||
},
|
||||
warning: {
|
||||
main: palette.yellow50.value,
|
||||
main: palette.yellow80.value,
|
||||
},
|
||||
error: {
|
||||
main: palette.red50.value,
|
||||
|
@ -8,13 +8,9 @@ interface DashboardCardProps {
|
||||
const DashboardCard = (props: DashboardCardProps) => {
|
||||
const { children, title } = props;
|
||||
return (
|
||||
<div
|
||||
className="h-full w-full
|
||||
border border-solid border-neutral-80 bg-neutral-98
|
||||
shadow-sm shadow-neutral-60 dark:border-none dark:bg-neutral-5 dark:shadow-none"
|
||||
>
|
||||
<div className="h-full w-full bg-white dark:bg-neutral-5">
|
||||
<div className="h-full w-full px-3 py-2">
|
||||
<Typography variant="h6" color={"secondary"}>
|
||||
<Typography variant="h6" color={"primary"}>
|
||||
{title}
|
||||
</Typography>
|
||||
{children}
|
||||
|
@ -13,7 +13,7 @@ const AppCard = (props: AppCardProps) => {
|
||||
<div
|
||||
role="button"
|
||||
className="flex h-40 w-40 cursor-pointer items-center justify-center rounded-3xl p-2
|
||||
align-middle shadow-md ring-2 ring-inset ring-purple-50
|
||||
align-middle shadow-md ring-2 ring-inset ring-blue-90
|
||||
hover:bg-neutral-90 focus:bg-neutral-90 active:bg-neutral-80
|
||||
dark:hover:bg-neutral-10 dark:focus:bg-neutral-10 dark:active:bg-neutral-20"
|
||||
>
|
||||
|
@ -48,7 +48,7 @@ export const QuickActions = () => {
|
||||
{actions.map(({ id, icon, label, eventHandler }) => (
|
||||
<Fab
|
||||
className="w-fit self-center shadow-none"
|
||||
color="secondary"
|
||||
color="primary"
|
||||
key={id}
|
||||
onClick={eventHandler}
|
||||
variant="extended"
|
||||
|
@ -34,7 +34,7 @@ interface AppContextProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
const mock = {
|
||||
data: { flakes: [] },
|
||||
data: { flakes: ["example_clan"] },
|
||||
};
|
||||
|
||||
// list_clans
|
||||
|
@ -2,62 +2,54 @@
|
||||
|
||||
import { useListMachines } from "@/api/machine/machine";
|
||||
import { Machine, MachinesResponse } from "@/api/model";
|
||||
import { clanErrorToast } from "@/error/errorToast";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
import React, {
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
createContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { KeyedMutator } from "swr";
|
||||
|
||||
type Filter = {
|
||||
name: keyof Machine;
|
||||
value: Machine[keyof Machine];
|
||||
type PartialRecord<K extends keyof any, T> = {
|
||||
[P in K]?: T;
|
||||
};
|
||||
type Filters = Filter[];
|
||||
|
||||
type MachineContextType =
|
||||
| {
|
||||
rawData: AxiosResponse<MachinesResponse, any> | undefined;
|
||||
data: Machine[];
|
||||
isLoading: boolean;
|
||||
flakeName: string;
|
||||
error: AxiosError<any> | undefined;
|
||||
isValidating: boolean;
|
||||
export type MachineFilter = PartialRecord<
|
||||
keyof Machine,
|
||||
Machine[keyof Machine]
|
||||
>;
|
||||
|
||||
filters: Filters;
|
||||
setFilters: Dispatch<SetStateAction<Filters>>;
|
||||
mutate: KeyedMutator<AxiosResponse<MachinesResponse, any>>;
|
||||
swrKey: string | false | Record<any, any>;
|
||||
}
|
||||
| {
|
||||
isLoading: true;
|
||||
data: readonly [];
|
||||
};
|
||||
type MachineContextType = {
|
||||
rawData: AxiosResponse<MachinesResponse, any> | undefined;
|
||||
data: Machine[];
|
||||
isLoading: boolean;
|
||||
error: AxiosError<any> | undefined;
|
||||
isValidating: boolean;
|
||||
|
||||
const initialState = {
|
||||
isLoading: true,
|
||||
data: [],
|
||||
} as const;
|
||||
filters: MachineFilter;
|
||||
setFilters: Dispatch<SetStateAction<MachineFilter>>;
|
||||
mutate: KeyedMutator<AxiosResponse<MachinesResponse, any>>;
|
||||
swrKey: string | false | Record<any, any>;
|
||||
};
|
||||
|
||||
export function CreateMachineContext() {
|
||||
return createContext<MachineContextType>({
|
||||
...initialState,
|
||||
});
|
||||
return createContext<MachineContextType>({} as MachineContextType);
|
||||
}
|
||||
|
||||
interface MachineContextProviderProps {
|
||||
children: ReactNode;
|
||||
flakeName: string;
|
||||
clanDir: string;
|
||||
}
|
||||
|
||||
const MachineContext = CreateMachineContext();
|
||||
|
||||
export const MachineContextProvider = (props: MachineContextProviderProps) => {
|
||||
const { children, flakeName } = props;
|
||||
const { children, clanDir } = props;
|
||||
const {
|
||||
data: rawData,
|
||||
isLoading,
|
||||
@ -65,18 +57,27 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => {
|
||||
isValidating,
|
||||
mutate,
|
||||
swrKey,
|
||||
} = useListMachines({ flake_dir: flakeName });
|
||||
const [filters, setFilters] = useState<Filters>([]);
|
||||
} = useListMachines({ flake_dir: clanDir });
|
||||
|
||||
const [filters, setFilters] = useState<MachineFilter>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
clanErrorToast(error);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!isLoading && !error && !isValidating && rawData) {
|
||||
if (!isLoading && rawData) {
|
||||
const { machines } = rawData.data;
|
||||
return machines.filter((m) =>
|
||||
filters.every((f) => m[f.name] === f.value),
|
||||
return machines.filter(
|
||||
(m) =>
|
||||
!filters.name ||
|
||||
m.name.toLowerCase().includes(filters.name.toLowerCase()),
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}, [isLoading, error, isValidating, rawData, filters]);
|
||||
}, [isLoading, filters, rawData]);
|
||||
|
||||
return (
|
||||
<MachineContext.Provider
|
||||
@ -85,7 +86,7 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => {
|
||||
data,
|
||||
|
||||
isLoading,
|
||||
flakeName,
|
||||
|
||||
error,
|
||||
isValidating,
|
||||
|
||||
|
@ -13,13 +13,10 @@ import { ReactNode } from "react";
|
||||
import { tw } from "@/utils/tailwind";
|
||||
import AppsIcon from "@mui/icons-material/Apps";
|
||||
import BackupIcon from "@mui/icons-material/Backup";
|
||||
import DashboardIcon from "@mui/icons-material/Dashboard";
|
||||
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||
import DevicesIcon from "@mui/icons-material/Devices";
|
||||
import LanIcon from "@mui/icons-material/Lan";
|
||||
import Link from "next/link";
|
||||
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import DashboardIcon from "@mui/icons-material/Dashboard";
|
||||
import DevicesIcon from "@mui/icons-material/Devices";
|
||||
import Link from "next/link";
|
||||
|
||||
type MenuEntry = {
|
||||
icon: ReactNode;
|
||||
@ -49,24 +46,24 @@ const menuEntries: MenuEntry[] = [
|
||||
to: "/applications",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
icon: <LanIcon />,
|
||||
label: "Network",
|
||||
to: "/network",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
icon: <DesignServicesIcon />,
|
||||
label: "Templates",
|
||||
to: "/templates",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
icon: <BackupIcon />,
|
||||
label: "Backups",
|
||||
to: "/backups",
|
||||
disabled: true,
|
||||
},
|
||||
// {
|
||||
// icon: <LanIcon />,
|
||||
// label: "Network",
|
||||
// to: "/network",
|
||||
// disabled: true,
|
||||
// },
|
||||
// {
|
||||
// icon: <DesignServicesIcon />,
|
||||
// label: "Templates",
|
||||
// to: "/templates",
|
||||
// disabled: false,
|
||||
// },
|
||||
];
|
||||
|
||||
const hideSidebar = tw`-translate-x-14 lg:-translate-x-64`;
|
||||
@ -75,38 +72,35 @@ const showSidebar = tw`lg:translate-x-0`;
|
||||
interface SidebarProps {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
clanSelect: React.ReactNode;
|
||||
}
|
||||
export function Sidebar(props: SidebarProps) {
|
||||
const { show, onClose, clanSelect } = props;
|
||||
const { show, onClose } = props;
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={tw`${
|
||||
show ? showSidebar : hideSidebar
|
||||
} z-9999 static left-0 top-0 flex h-screen w-14 flex-col overflow-x-hidden overflow-y-hidden bg-neutral-10 transition duration-150 ease-in-out dark:bg-neutral-2 lg:w-64`}
|
||||
} z-9999 static left-0 top-0 flex h-screen w-14 flex-col overflow-x-hidden overflow-y-hidden bg-blue-3 transition duration-150 ease-in-out lg:w-64`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 overflow-hidden px-0 py-5 lg:p-6">
|
||||
<div className="mt-8 hidden w-full text-center font-semibold text-white lg:block">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Clan Logo"
|
||||
width={75}
|
||||
height={75}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col py-6 mt-8">
|
||||
<div className="hidden w-full max-w-xs text-center shadow-sm lg:block">
|
||||
<h3 className="m-0 pb-2 w-full font-semibold text-white">
|
||||
Clan Dashboard
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center overflow-hidden">
|
||||
<div className="hidden w-full text-center font-semibold text-white lg:block">
|
||||
<Image
|
||||
src="/clan-white.png"
|
||||
alt="Clan Logo"
|
||||
width={102}
|
||||
height={75}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-center">{clanSelect}</div>
|
||||
<Divider
|
||||
flexItem
|
||||
className="mx-8 mb-4 mt-9 hidden bg-neutral-40 lg:block"
|
||||
/>
|
||||
<div className="flex w-full justify-center">
|
||||
<IconButton size="large" className="text-white" onClick={onClose}>
|
||||
<ChevronLeftIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Divider flexItem className="mx-8 my-4 hidden bg-blue-40 lg:block" />
|
||||
<div className="flex flex-col overflow-hidden overflow-y-auto">
|
||||
<List className="mb-14 px-0 pb-4 text-white lg:mt-1 lg:px-4">
|
||||
{menuEntries.map((menuEntry, idx) => {
|
||||
@ -140,22 +134,11 @@ export function Sidebar(props: SidebarProps) {
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Divider
|
||||
flexItem
|
||||
className="mx-8 my-10 hidden bg-neutral-40 lg:block"
|
||||
/>
|
||||
<div className="mx-auto mb-8 hidden w-full max-w-xs rounded-sm px-4 py-6 text-center align-bottom shadow-sm lg:block">
|
||||
<h3 className="mb-2 w-full font-semibold text-white">
|
||||
Clan.lol Admin
|
||||
</h3>
|
||||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
className="inline-block w-full rounded-md p-2 text-center text-white hover:text-purple-60/95"
|
||||
>
|
||||
Donate
|
||||
</a>
|
||||
<Divider flexItem className="mx-8 my-4 hidden bg-blue-40 lg:block" />
|
||||
<div className="flex w-full justify-center py-2">
|
||||
<IconButton size="large" className="text-white" onClick={onClose}>
|
||||
<ChevronLeftIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
@ -1,82 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Grid2 from "@mui/material/Unstable_Grid2";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
import { PieCards } from "./pieCards";
|
||||
import { PieData, NodePieChart } from "./nodePieChart";
|
||||
import { Machine } from "@/api/model/machine";
|
||||
import { Status } from "@/api/model";
|
||||
|
||||
interface EnhancedTableToolbarProps {
|
||||
tableData: readonly Machine[];
|
||||
}
|
||||
|
||||
export function EnhancedTableToolbar(
|
||||
props: React.PropsWithChildren<EnhancedTableToolbarProps>,
|
||||
) {
|
||||
const { tableData } = props;
|
||||
const theme = useTheme();
|
||||
const is_lg = useMediaQuery(theme.breakpoints.down("lg"));
|
||||
|
||||
const pieData: PieData[] = useMemo(() => {
|
||||
const online = tableData.filter(
|
||||
(row) => row.status === Status.online,
|
||||
).length;
|
||||
const offline = tableData.filter(
|
||||
(row) => row.status === Status.offline,
|
||||
).length;
|
||||
const pending = tableData.filter(
|
||||
(row) => row.status === Status.unknown,
|
||||
).length;
|
||||
|
||||
return [
|
||||
{ name: "Online", value: online, color: theme.palette.success.main },
|
||||
{ name: "Offline", value: offline, color: theme.palette.error.main },
|
||||
{ name: "Pending", value: pending, color: theme.palette.warning.main },
|
||||
];
|
||||
}, [tableData, theme]);
|
||||
|
||||
return (
|
||||
<Grid2 container spacing={1}>
|
||||
{/* Pie Chart Grid */}
|
||||
<Grid2
|
||||
key="PieChart"
|
||||
md={6}
|
||||
xs={12}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box height={350} width={400}>
|
||||
<NodePieChart data={pieData} showLabels={is_lg} />
|
||||
</Box>
|
||||
</Grid2>
|
||||
|
||||
{/* Card Stack Grid */}
|
||||
<Grid2
|
||||
key="CardStack"
|
||||
lg={6}
|
||||
display="flex"
|
||||
sx={{ display: { lg: "flex", xs: "none", md: "flex" } }}
|
||||
>
|
||||
<PieCards pieData={pieData} />
|
||||
</Grid2>
|
||||
|
||||
{/*Toolbar Grid */}
|
||||
<Grid2
|
||||
key="Toolbar"
|
||||
xs={12}
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ pl: { sm: 2 }, pr: { xs: 1, sm: 1 }, pt: { xs: 1, sm: 3 } }}
|
||||
>
|
||||
{props.children}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
);
|
||||
}
|
@ -1,38 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { CircularProgress, Grid, useTheme } from "@mui/material";
|
||||
import { CircularProgress, Grid } from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import TablePagination from "@mui/material/TablePagination";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { ChangeEvent, useMemo, useState } from "react";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
import { Machine } from "@/api/model/machine";
|
||||
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
|
||||
import { useMachines } from "../hooks/useMachines";
|
||||
import { EnhancedTableToolbar } from "./enhancedTableToolbar";
|
||||
import { NodeTableContainer } from "./nodeTableContainer";
|
||||
import { SearchBar } from "./searchBar";
|
||||
import { StickySpeedDial } from "./stickySpeedDial";
|
||||
|
||||
export function NodeTable() {
|
||||
const { isLoading, data: machines } = useMachines();
|
||||
|
||||
const theme = useTheme();
|
||||
const is_xs = useMediaQuery(theme.breakpoints.only("xs"));
|
||||
const { isLoading, data: machines, rawData, setFilters } = useMachines();
|
||||
|
||||
const [selected, setSelected] = useState<string | undefined>(undefined);
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [filteredList, setFilteredList] = useState<readonly Machine[]>([]);
|
||||
|
||||
const tableData = useMemo(() => {
|
||||
const tableData = machines.map((machine) => {
|
||||
return { name: machine.name, status: machine.status };
|
||||
});
|
||||
setFilteredList(tableData);
|
||||
return tableData;
|
||||
}, [machines]);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
@ -60,19 +43,13 @@ export function NodeTable() {
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Paper sx={{ width: "100%", mb: 2 }}>
|
||||
<StickySpeedDial selected={selected} />
|
||||
<EnhancedTableToolbar tableData={tableData}>
|
||||
<Grid2 xs={12}>
|
||||
<SearchBar
|
||||
tableData={tableData}
|
||||
setFilteredList={setFilteredList}
|
||||
/>
|
||||
</Grid2>
|
||||
</EnhancedTableToolbar>
|
||||
|
||||
<Paper sx={{ width: "100%", mb: 2, p: { xs: 0, lg: 2 } }} elevation={0}>
|
||||
<SearchBar
|
||||
allData={rawData?.data.machines || []}
|
||||
setQuery={setFilters}
|
||||
/>
|
||||
<NodeTableContainer
|
||||
tableData={filteredList}
|
||||
tableData={machines}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
dense={false}
|
||||
@ -80,12 +57,10 @@ export function NodeTable() {
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
|
||||
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
labelRowsPerPage={is_xs ? "Rows" : "Rows per page:"}
|
||||
component="div"
|
||||
count={filteredList.length}
|
||||
count={machines.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
|
@ -6,14 +6,15 @@ import { Autocomplete, InputAdornment, TextField } from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react";
|
||||
import { useDebounce } from "../hooks/useDebounce";
|
||||
import { MachineFilter } from "../hooks/useMachines";
|
||||
|
||||
export interface SearchBarProps {
|
||||
tableData: readonly Machine[];
|
||||
setFilteredList: Dispatch<SetStateAction<readonly Machine[]>>;
|
||||
allData: Machine[];
|
||||
setQuery: Dispatch<SetStateAction<MachineFilter>>;
|
||||
}
|
||||
|
||||
export function SearchBar(props: SearchBarProps) {
|
||||
const { tableData, setFilteredList } = props;
|
||||
const { allData, setQuery } = props;
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const debouncedSearch = useDebounce(search, 250);
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -22,7 +23,6 @@ export function SearchBar(props: SearchBarProps) {
|
||||
function handleEsc(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (event.key === "Escape") {
|
||||
setSearch("");
|
||||
setFilteredList(tableData);
|
||||
}
|
||||
|
||||
// check if the key is Enter
|
||||
@ -32,32 +32,21 @@ export function SearchBar(props: SearchBarProps) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedSearch) {
|
||||
const filtered: Machine[] = tableData.filter((row) => {
|
||||
return row.name.toLowerCase().includes(debouncedSearch.toLowerCase());
|
||||
});
|
||||
setFilteredList(filtered);
|
||||
}
|
||||
}, [debouncedSearch, tableData, setFilteredList]);
|
||||
setQuery((filters) => ({ ...filters, name: debouncedSearch }));
|
||||
}, [debouncedSearch, setQuery]);
|
||||
|
||||
const handleInputChange = (event: any, value: string) => {
|
||||
if (value === "") {
|
||||
setFilteredList(tableData);
|
||||
}
|
||||
|
||||
console.log({ value });
|
||||
setSearch(value);
|
||||
};
|
||||
|
||||
const suggestions = useMemo(
|
||||
() => tableData.map((row) => row.name),
|
||||
[tableData],
|
||||
);
|
||||
const options = useMemo(() => allData.map((row) => row.name), [allData]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
autoComplete
|
||||
options={suggestions}
|
||||
options={options}
|
||||
renderOption={(props: any, option: any) => {
|
||||
return (
|
||||
<li {...props} key={option}>
|
||||
|
@ -36,6 +36,7 @@ module.exports = {
|
||||
white: common.white.value,
|
||||
black: common.black.value,
|
||||
neutral: getTailwindColors(palette)("neutral"),
|
||||
blue: getTailwindColors(palette)("blue"),
|
||||
purple: {
|
||||
...getTailwindColors(palette)("purple"),
|
||||
DEFAULT: palette.purple50.value,
|
||||
|
Loading…
Reference in New Issue
Block a user