From 4283bd20bb819e7393ef1c449ca5e34bf11f4f85 Mon Sep 17 00:00:00 2001 From: cracklesparkle Date: Mon, 15 Jul 2024 17:48:48 +0900 Subject: [PATCH] DataGrid cell autocomplete --- frontend_reactjs/src/App.tsx | 100 ++++++- .../src/components/CardInfo/CardInfo.tsx | 23 ++ .../src/components/CardInfo/CardInfoChip.tsx | 27 ++ .../src/components/CardInfo/CardInfoLabel.tsx | 24 ++ .../components/DataGridCellAutocomplete.tsx | 79 ++++++ .../src/components/ServerHardware.tsx | 70 ++--- .../src/components/ServerIpsView.tsx | 70 ++--- .../src/components/ServerStorages.tsx | 68 ++--- .../src/components/ServersView.tsx | 152 +++++----- .../src/components/TableEditable.tsx | 24 +- frontend_reactjs/src/hooks/swrHooks.ts | 12 +- .../src/layouts/DashboardLayout.tsx | 63 +---- frontend_reactjs/src/pages/ApiTest.tsx | 262 +----------------- 13 files changed, 457 insertions(+), 517 deletions(-) create mode 100644 frontend_reactjs/src/components/CardInfo/CardInfo.tsx create mode 100644 frontend_reactjs/src/components/CardInfo/CardInfoChip.tsx create mode 100644 frontend_reactjs/src/components/CardInfo/CardInfoLabel.tsx create mode 100644 frontend_reactjs/src/components/DataGridCellAutocomplete.tsx diff --git a/frontend_reactjs/src/App.tsx b/frontend_reactjs/src/App.tsx index 9c4d1b3..ea7f401 100644 --- a/frontend_reactjs/src/App.tsx +++ b/frontend_reactjs/src/App.tsx @@ -15,6 +15,90 @@ import Documents from "./pages/Documents" import Reports from "./pages/Reports" import Boilers from "./pages/Boilers" import Servers from "./pages/Servers" +import { Api, Assignment, Cloud, Factory, Home, Login, People, Shield, Storage } from "@mui/icons-material" + +export const pages = [ + { + label: "", + path: "/auth/signin", + icon: , + component: , + drawer: false, + dashboard: false, + }, + { + label: "", + path: "/auth/signup", + icon: , + component: , + drawer: false, + dashboard: false, + }, + { + label: "Главная", + path: "/", + icon: , + component:
, + drawer: true, + dashboard: true + }, + { + label: "Пользователи", + path: "/user", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "Роли", + path: "/role", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "Документы", + path: "/documents", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "Отчеты", + path: "/reports", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "Серверы", + path: "/servers", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "Котельные", + path: "/boilers", + icon: , + component: , + drawer: true, + dashboard: true + }, + { + label: "API Test", + path: "/api-test", + icon: , + component: , + drawer: true, + dashboard: true + }, +] function App() { const auth = useAuthStore() @@ -44,19 +128,15 @@ function App() { }> - } /> - } /> + {pages.filter((page) => !page.dashboard).map((page, index) => ( + + ))} : }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + {pages.filter((page) => page.dashboard).map((page, index) => ( + + ))} } /> diff --git a/frontend_reactjs/src/components/CardInfo/CardInfo.tsx b/frontend_reactjs/src/components/CardInfo/CardInfo.tsx new file mode 100644 index 0000000..49dca84 --- /dev/null +++ b/frontend_reactjs/src/components/CardInfo/CardInfo.tsx @@ -0,0 +1,23 @@ +import { Box, Chip, Divider, Paper, Typography } from '@mui/material' +import React, { PropsWithChildren, ReactElement, ReactNode } from 'react' + +interface CardInfoProps extends PropsWithChildren { + label: string; +} + +export default function CardInfo({ + children, + label +}: CardInfoProps) { + return ( + + + {label} + + + + + {children} + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/CardInfo/CardInfoChip.tsx b/frontend_reactjs/src/components/CardInfo/CardInfoChip.tsx new file mode 100644 index 0000000..6c1e00a --- /dev/null +++ b/frontend_reactjs/src/components/CardInfo/CardInfoChip.tsx @@ -0,0 +1,27 @@ +import { Cloud } from '@mui/icons-material'; +import { Chip, SvgIconTypeMap } from '@mui/material' +import { OverridableComponent } from '@mui/material/OverridableComponent'; +import React, { ReactElement } from 'react' + +interface CardInfoChipProps { + status: boolean; + label: string; + iconOn: ReactElement + iconOff: ReactElement +} + +export default function CardInfoChip({ + status, + label, + iconOn, + iconOff +}: CardInfoChipProps) { + return ( + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/CardInfo/CardInfoLabel.tsx b/frontend_reactjs/src/components/CardInfo/CardInfoLabel.tsx new file mode 100644 index 0000000..53c0609 --- /dev/null +++ b/frontend_reactjs/src/components/CardInfo/CardInfoLabel.tsx @@ -0,0 +1,24 @@ +import { Box, Typography } from '@mui/material' +import React from 'react' + +interface CardInfoLabelProps { + label: string; + value: string | number; +} + +export default function CardInfoLabel({ + label, + value +}: CardInfoLabelProps) { + return ( + + + {label} + + + + {value} + + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/DataGridCellAutocomplete.tsx b/frontend_reactjs/src/components/DataGridCellAutocomplete.tsx new file mode 100644 index 0000000..b6c0793 --- /dev/null +++ b/frontend_reactjs/src/components/DataGridCellAutocomplete.tsx @@ -0,0 +1,79 @@ +import { Autocomplete, CircularProgress, TextField } from '@mui/material' +import React, { Fragment, useEffect, useState } from 'react' + +interface DataGridCellAutocompleteProps { + selectedOption: any; + setSelectedOption: any; + isLoading: boolean; + setDebouncedSearch: any; + options: any; + setOptions: any; +} + +export default function DataGridCellAutocomplete({ + selectedOption, + setSelectedOption, + isLoading, + setDebouncedSearch, + options, + setOptions +}: DataGridCellAutocompleteProps) { + const [open, setOpen] = useState(false) + const [search, setSearch] = useState("") + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearch(search) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [search]) + + const handleInputChange = (value: string) => { + setSearch(value) + } + + const handleOptionChange = (value: any | null) => { + setSelectedOption(value) + } + + return ( + { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + onInputChange={(_, value) => handleInputChange(value)} + onChange={(_, value) => handleOptionChange(value)} + filterOptions={(x) => x} + isOptionEqualToValue={(option: any, value: any) => option.name === value.name} + getOptionLabel={(option: any) => option.name ? option.name : ""} + options={options} + loading={isLoading} + value={selectedOption} + renderInput={(params) => ( + + {isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/ServerHardware.tsx b/frontend_reactjs/src/components/ServerHardware.tsx index 10c10de..4d257c7 100644 --- a/frontend_reactjs/src/components/ServerHardware.tsx +++ b/frontend_reactjs/src/components/ServerHardware.tsx @@ -90,44 +90,46 @@ export default function ServerHardware() { } - { - setOpen(true) - }} - onClose={() => { - setOpen(false) - }} - onInputChange={(_, value) => handleInputChange(value)} - onChange={(_, value) => handleOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} - getOptionLabel={(option: IRegion) => option.name ? option.name : ""} - options={options} - loading={isLoading} - value={selectedOption} - renderInput={(params) => ( - - {isLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - {serversLoading ? : - servers && + hardwares && { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + onInputChange={(_, value) => handleInputChange(value)} + onChange={(_, value) => handleOptionChange(value)} + filterOptions={(x) => x} + isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} + getOptionLabel={(option: IRegion) => option.name ? option.name : ""} + options={options} + loading={isLoading} + value={selectedOption} + renderInput={(params) => ( + + {isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + } onSave={(id: any) => { console.log(id) }} diff --git a/frontend_reactjs/src/components/ServerIpsView.tsx b/frontend_reactjs/src/components/ServerIpsView.tsx index ff00070..adf6302 100644 --- a/frontend_reactjs/src/components/ServerIpsView.tsx +++ b/frontend_reactjs/src/components/ServerIpsView.tsx @@ -88,44 +88,46 @@ export default function ServerIpsView() { } - { - setOpen(true) - }} - onClose={() => { - setOpen(false) - }} - onInputChange={(_, value) => handleInputChange(value)} - onChange={(_, value) => handleOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} - getOptionLabel={(option: IRegion) => option.name ? option.name : ""} - options={options} - loading={isLoading} - value={selectedOption} - renderInput={(params) => ( - - {isLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - {serversLoading ? : - servers && + serverIps && { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + onInputChange={(_, value) => handleInputChange(value)} + onChange={(_, value) => handleOptionChange(value)} + filterOptions={(x) => x} + isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} + getOptionLabel={(option: IRegion) => option.name ? option.name : ""} + options={options} + loading={isLoading} + value={selectedOption} + renderInput={(params) => ( + + {isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + } onSave={(id: any) => { console.log(id) }} diff --git a/frontend_reactjs/src/components/ServerStorages.tsx b/frontend_reactjs/src/components/ServerStorages.tsx index cab425c..82d84de 100644 --- a/frontend_reactjs/src/components/ServerStorages.tsx +++ b/frontend_reactjs/src/components/ServerStorages.tsx @@ -87,44 +87,46 @@ export default function ServerStorage() { } - { - setOpen(true) - }} - onClose={() => { - setOpen(false) - }} - onInputChange={(_, value) => handleInputChange(value)} - onChange={(_, value) => handleOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} - getOptionLabel={(option: IRegion) => option.name ? option.name : ""} - options={options} - loading={isLoading} - value={selectedOption} - renderInput={(params) => ( - - {isLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - {serversLoading ? : storages && { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + onInputChange={(_, value) => handleInputChange(value)} + onChange={(_, value) => handleOptionChange(value)} + filterOptions={(x) => x} + isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} + getOptionLabel={(option: IRegion) => option.name ? option.name : ""} + options={options} + loading={isLoading} + value={selectedOption} + renderInput={(params) => ( + + {isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + } onSave={(id: any) => { console.log(id) }} diff --git a/frontend_reactjs/src/components/ServersView.tsx b/frontend_reactjs/src/components/ServersView.tsx index b9a880f..d42e7cb 100644 --- a/frontend_reactjs/src/components/ServersView.tsx +++ b/frontend_reactjs/src/components/ServersView.tsx @@ -4,13 +4,18 @@ import { IRegion } from '../interfaces/fuel' import { useRegions, useServers, useServersInfo } from '../hooks/swrHooks' import FullFeaturedCrudGrid from './TableEditable' import ServerService from '../services/ServersService' -import { GridColDef } from '@mui/x-data-grid' +import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid' import { Close, Cloud, CloudOff } from '@mui/icons-material' import ServerData from './ServerData' import { IServersInfo } from '../interfaces/servers' +import CardInfo from './CardInfo/CardInfo' +import CardInfoLabel from './CardInfo/CardInfoLabel' +import CardInfoChip from './CardInfo/CardInfoChip' +import DataGridCellAutocomplete from './DataGridCellAutocomplete' export default function ServersView() { const [open, setOpen] = useState(false) + const [editOpen, setEditOpen] = useState(false) const [options, setOptions] = useState([]) const [search, setSearch] = useState("") const [debouncedSearch, setDebouncedSearch] = useState("") @@ -52,6 +57,23 @@ export default function ServersView() { const serversColumns: GridColDef[] = [ //{ field: 'id', headerName: 'ID', type: "number" }, { field: 'name', headerName: 'Название', type: "string", editable: true }, + { + field: 'region_id', + editable: true, + renderEditCell: (params: GridRenderCellParams) => ( + { + params.value = value + }} + isLoading={isLoading} + setDebouncedSearch={setDebouncedSearch} + options={options} + setOptions={setOptions} + /> + ), + width: 200 + } ] return ( @@ -88,83 +110,24 @@ export default function ServersView() { } - { - setOpen(true) - }} - onClose={() => { - setOpen(false) - }} - onInputChange={(_, value) => handleInputChange(value)} - onChange={(_, value) => handleOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} - getOptionLabel={(option: IRegion) => option.name ? option.name : ""} - options={options} - loading={isLoading} - value={selectedOption} - renderInput={(params) => ( - - {isLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - - {servers && + {serversInfo && - {serversInfo && - - {serversInfo.map((serverInfo: IServersInfo) => ( - - - - {serverInfo.name} - - - - - - - Количество IP: - - - - {serverInfo.IPs_count} - - - - - - Количество серверов: - - - - {serverInfo.servers_count} - - - - : serverInfo.status === "Offline" ? : } - variant="outlined" - label={serverInfo.status} - color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"} - /> - - - ))} - - } + + {serversInfo.map((serverInfo: IServersInfo) => ( + + + + + } + iconOff={} + /> + + + ))} + } @@ -173,6 +136,41 @@ export default function ServersView() { : servers && { + setOpen(true) + }} + onClose={() => { + setOpen(false) + }} + onInputChange={(_, value) => handleInputChange(value)} + onChange={(_, value) => handleOptionChange(value)} + filterOptions={(x) => x} + isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} + getOptionLabel={(option: IRegion) => option.name ? option.name : ""} + options={options} + loading={isLoading} + value={selectedOption} + renderInput={(params) => ( + + {isLoading ? : null} + {params.InputProps.endAdornment} + + ) + }} + /> + )} + /> + } onSave={(id: any) => { console.log(id) }} diff --git a/frontend_reactjs/src/components/TableEditable.tsx b/frontend_reactjs/src/components/TableEditable.tsx index 3c228ed..2df6736 100644 --- a/frontend_reactjs/src/components/TableEditable.tsx +++ b/frontend_reactjs/src/components/TableEditable.tsx @@ -24,6 +24,7 @@ import { useGridApiContext, } from '@mui/x-data-grid'; import { AxiosResponse } from 'axios'; +import { Autocomplete } from '@mui/material'; interface EditToolbarProps { setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void; @@ -31,10 +32,11 @@ interface EditToolbarProps { newModel: (oldModel: GridRowModesModel) => GridRowModesModel, ) => void; columns: GridColDef[]; + autoComplete?: React.ReactElement | null; } function EditToolbar(props: EditToolbarProps) { - const { setRows, setRowModesModel, columns } = props; + const { setRows, setRowModesModel, columns, autoComplete } = props; const handleClick = () => { const id = Date.now().toString(36) @@ -50,6 +52,12 @@ function EditToolbar(props: EditToolbarProps) { } else { newValues[column.field] = undefined } + + if (column.field === 'region_id') { + // column.valueGetter = (value: any) => { + // console.log(value) + // } + } }) setRows((oldRows) => [...oldRows, { id, ...newValues, isNew: true }]); @@ -60,7 +68,13 @@ function EditToolbar(props: EditToolbarProps) { }; return ( - + + {autoComplete && + + {autoComplete} + + } + @@ -75,6 +89,7 @@ interface DataGridProps { onRowClick: GridEventListener<"rowClick">; onSave: any; onDelete: (data: any) => Promise>; + autoComplete?: React.ReactElement | null; } export default function FullFeaturedCrudGrid({ @@ -83,7 +98,8 @@ export default function FullFeaturedCrudGrid({ actions = false, onRowClick, onSave, - onDelete + onDelete, + autoComplete }: DataGridProps) { const [rows, setRows] = React.useState(initialRows); const [rowModesModel, setRowModesModel] = React.useState({}); @@ -206,7 +222,7 @@ export default function FullFeaturedCrudGrid({ toolbar: EditToolbar as GridSlots['toolbar'], }} slotProps={{ - toolbar: { setRows, setRowModesModel, columns }, + toolbar: { setRows, setRowModesModel, columns, autoComplete }, }} /> diff --git a/frontend_reactjs/src/hooks/swrHooks.ts b/frontend_reactjs/src/hooks/swrHooks.ts index c9ccbf2..0a168f9 100644 --- a/frontend_reactjs/src/hooks/swrHooks.ts +++ b/frontend_reactjs/src/hooks/swrHooks.ts @@ -189,8 +189,7 @@ export function useServers(region_id?: number, offset?: number, limit?: number) region_id ? `/api/servers?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/servers?offset=${offset || 0}&limit=${limit || 10}`, (url: string) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false, - revalidateOnMount: false + revalidateOnFocus: false } ) @@ -203,11 +202,10 @@ export function useServers(region_id?: number, offset?: number, limit?: number) export function useServersInfo(region_id?: number, offset?: number, limit?: number) { const { data, error, isLoading } = useSWR( - region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : null, - (url) => fetcher(url, BASE_URL.servers), + region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/servers_info?offset=${offset || 0}&limit=${limit || 10}`, + (url: string) => fetcher(url, BASE_URL.servers), { revalidateOnFocus: false, - revalidateOnMount: false } ) @@ -224,7 +222,6 @@ export function useServer(server_id?: number) { (url) => fetcher(url, BASE_URL.servers), { revalidateOnFocus: false, - revalidateOnMount: false } ) @@ -241,7 +238,6 @@ export function useServerIps(server_id?: number | null, offset?: number, limit?: (url: string) => fetcher(url, BASE_URL.servers), { revalidateOnFocus: false, - revalidateOnMount: false } ) @@ -260,7 +256,6 @@ export function useHardwares(server_id?: number, offset?: number, limit?: number (url: string) => fetcher(url, BASE_URL.servers), { revalidateOnFocus: false, - revalidateOnMount: false } ) @@ -296,7 +291,6 @@ export function useStorages(hardware_id?: number, offset?: number, limit?: numbe (url: string) => fetcher(url, BASE_URL.servers), { revalidateOnFocus: false, - revalidateOnMount: false } ) diff --git a/frontend_reactjs/src/layouts/DashboardLayout.tsx b/frontend_reactjs/src/layouts/DashboardLayout.tsx index 3fb3472..aed23ef 100644 --- a/frontend_reactjs/src/layouts/DashboardLayout.tsx +++ b/frontend_reactjs/src/layouts/DashboardLayout.tsx @@ -11,13 +11,14 @@ import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; import Container from '@mui/material/Container'; import MenuIcon from '@mui/icons-material/Menu'; -import { Api, Assignment, Cloud, Factory, Home, People, Shield, Storage, } from '@mui/icons-material'; -import { ListItem, ListItemButton, ListItemIcon, ListItemText, } from '@mui/material'; +import { Api, Assignment, Cloud, Dashboard, Factory, Home, People, Shield, Storage, } from '@mui/icons-material'; +import { colors, ListItem, ListItemButton, ListItemIcon, ListItemText, } from '@mui/material'; import { Outlet, useNavigate } from 'react-router-dom'; import { UserData } from '../interfaces/auth'; import { getUserData, useAuthStore } from '../store/auth'; import { useTheme } from '@emotion/react'; import AccountMenu from '../components/AccountMenu'; +import { pages } from '../App'; const drawerWidth: number = 240; @@ -69,49 +70,6 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' }), ); -const pages = [ - { - label: "Главная", - path: "/", - icon: - }, - { - label: "Пользователи", - path: "/user", - icon: - }, - { - label: "Роли", - path: "/role", - icon: - }, - { - label: "Документы", - path: "/documents", - icon: - }, - { - label: "Отчеты", - path: "/reports", - icon: - }, - { - label: "Серверы", - path: "/servers", - icon: - }, - { - label: "Котельные", - path: "/boilers", - icon: - }, - { - label: "API Test", - path: "/api-test", - icon: - }, -] - export default function DashboardLayout() { const theme = useTheme() const innerTheme = createTheme(theme) @@ -186,17 +144,12 @@ export default function DashboardLayout() { {userData?.login} - {/* - - - - */} - + - {pages.map((item, index) => ( + {pages.filter((page) => page.drawer).map((item, index) => ( @@ -253,10 +207,7 @@ export default function DashboardLayout() { diff --git a/frontend_reactjs/src/pages/ApiTest.tsx b/frontend_reactjs/src/pages/ApiTest.tsx index 7d0388c..de42be9 100644 --- a/frontend_reactjs/src/pages/ApiTest.tsx +++ b/frontend_reactjs/src/pages/ApiTest.tsx @@ -1,268 +1,10 @@ -import { AppBar, Autocomplete, Box, Chip, CircularProgress, Dialog, Divider, Grid, IconButton, Paper, TextField, Toolbar, Typography, colors } from "@mui/material" -import { useBoilers, useCities, useRegions, useServers, useServersInfo } from "../hooks/swrHooks" -import { Fragment, useEffect, useState } from "react" -import { IBoiler, ICity, IRegion } from "../interfaces/fuel" -import { DataGrid, GridColDef } from "@mui/x-data-grid" -import ServerData from "../components/ServerData" -import { IServer, IServersInfo } from "../interfaces/servers" -import { Close, Cloud, CloudOff, Storage } from "@mui/icons-material" +import { Box } from "@mui/material" + export default function ApiTest() { - const [open, setOpen] = useState(false) - const [options, setOptions] = useState([]) - const [search, setSearch] = useState(null) - const [debouncedSearch, setDebouncedSearch] = useState("") - const [selectedOption, setSelectedOption] = useState(null) - const { regions, isLoading } = useRegions(10, 1, debouncedSearch) - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedSearch(search) - }, 500) - - return () => { - clearTimeout(handler) - } - }, [search]) - - useEffect(() => { - if (regions) { - setOptions([...regions]) - } - }, [regions]) - - const handleInputChange = (value: string) => { - setSearch(value) - } - - const handleOptionChange = (value: IRegion | null) => { - setSelectedOption(value) - } - - const [citiesOpen, setCitiesOpen] = useState(false) - const [citiesPage, setCitiesPage] = useState(1) - const [citiesSearch, setCitiesSearch] = useState('') - const [debouncedCitySearch, setDebouncedCitySearch] = useState('') - const { cities, isLoading: citiesLoading } = useCities(10, citiesPage, debouncedCitySearch) - const [citiesOptions, setCitiesOptions] = useState([]) - const [selectedCityOption, setSelectedCityOption] = useState(null) - - const handleCityInputChange = (value: string) => { - setCitiesSearch(value) - } - - const handleCityOptionChange = (value: ICity | null) => { - setSelectedCityOption(value) - } - - useEffect(() => { - if (cities) { - setCitiesOptions([...cities]) - } - }, [cities]) - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedCitySearch(citiesSearch) - }, 500) - - return () => { - clearTimeout(handler) - } - }, [citiesSearch]) - - useEffect(() => { - setCitiesPage(1) - setCitiesSearch("") - }, []) - - const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10) - - const serversColumns: GridColDef[] = [ - { field: 'id', headerName: 'ID', type: "number" }, - { field: 'name', headerName: 'Название', type: "string" }, - ] - - const [serverDataOpen, setServerDataOpen] = useState(false) - const [currentServerData, setCurrentServerData] = useState(null) - - const { serversInfo } = useServersInfo(selectedOption?.id) - return ( - { - setServerDataOpen(false) - }} - aria-labelledby="modal-modal-title" - aria-describedby="modal-modal-description"> - - - { - setServerDataOpen(false) - }} - aria-label="close" - > - - - - - {currentServerData && - - } - - - - - - - Servers - - - - - { - setOpen(true) - }} - onClose={() => { - setOpen(false) - }} - onInputChange={(_, value) => handleInputChange(value)} - onChange={(_, value) => handleOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name} - getOptionLabel={(option: IRegion) => option.name ? option.name : ""} - options={options} - loading={isLoading} - value={selectedOption} - renderInput={(params) => ( - - {isLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - - { - setCitiesOpen(true) - }} - onClose={() => { - setCitiesOpen(false) - }} - onInputChange={(_, value) => handleCityInputChange(value)} - onChange={(_, value) => handleCityOptionChange(value)} - filterOptions={(x) => x} - isOptionEqualToValue={(option: ICity, value: ICity) => option.name === value.name} - getOptionLabel={(option: ICity) => option.name ? option.name : ""} - options={citiesOptions} - loading={citiesLoading} - value={selectedCityOption} - renderInput={(params) => ( - - {citiesLoading ? : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> - - {servers && - - - Информация - - - {serversInfo && - - {serversInfo.map((serverInfo: IServersInfo) => ( - - - - {serverInfo.name} - - - - - - - Количество IP: - - - - {serverInfo.IPs_count} - - - - - - Количество серверов: - - - - {serverInfo.servers_count} - - - - : serverInfo.status === "Offline" ? : } - variant="outlined" - label={serverInfo.status} - color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"} - /> - - - ))} - - } - - } - - {serversLoading ? - - : - servers && - { - setCurrentServerData(params.row) - setServerDataOpen(true) - }} - /> - } - ) } \ No newline at end of file