diff --git a/frontend_reactjs/src/components/ServerData.tsx b/frontend_reactjs/src/components/ServerData.tsx index 3aaf26a..65477de 100644 --- a/frontend_reactjs/src/components/ServerData.tsx +++ b/frontend_reactjs/src/components/ServerData.tsx @@ -1,14 +1,33 @@ import { Box } from '@mui/material' import { IServer } from '../interfaces/servers' import { useServerIps } from '../hooks/swrHooks' +import FullFeaturedCrudGrid from './TableEditable' +import { GridColDef } from '@mui/x-data-grid' function ServerData({ id, name, region_id }: IServer) { const { serverIps } = useServerIps(id, 0, 10) + const serverIpsColumns: GridColDef[] = [ + { field: 'id', headerName: 'ID', type: 'number' }, + { field: 'server_id', headerName: 'Server ID', type: 'number' }, + { field: 'name', headerName: 'Название', type: 'string' }, + { field: 'is_actual', headerName: 'Действителен', type: 'boolean' }, + { field: 'ip', headerName: 'IP', type: 'string' }, + { field: 'servername', headerName: 'Название сервера', type: 'string' }, + ] + return ( - + {serverIps && - JSON.stringify(serverIps) + { + //setCurrentServerData(params.row) + //setServerDataOpen(true) + }} + /> } ) diff --git a/frontend_reactjs/src/components/ServerHardware.tsx b/frontend_reactjs/src/components/ServerHardware.tsx new file mode 100644 index 0000000..10c10de --- /dev/null +++ b/frontend_reactjs/src/components/ServerHardware.tsx @@ -0,0 +1,146 @@ +import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material' +import React, { Fragment, useEffect, useState } from 'react' +import { IRegion } from '../interfaces/fuel' +import { useHardwares, useRegions, useServerIps, useServers } from '../hooks/swrHooks' +import FullFeaturedCrudGrid from './TableEditable' +import ServerService from '../services/ServersService' +import { GridColDef } from '@mui/x-data-grid' +import { Close } from '@mui/icons-material' +import ServerData from './ServerData' + +export default function ServerHardware() { + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [search, setSearch] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [selectedOption, setSelectedOption] = useState(null) + const { servers, isLoading } = useServers() + + const [serverDataOpen, setServerDataOpen] = useState(false) + const [currentServerData, setCurrentServerData] = useState(null) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearch(search) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [search]) + + useEffect(() => { + if (servers) { + setOptions([...servers]) + } + }, [servers]) + + const handleInputChange = (value: string) => { + setSearch(value) + } + + const handleOptionChange = (value: IRegion | null) => { + setSelectedOption(value) + } + + const { hardwares, isLoading: serversLoading } = useHardwares(selectedOption?.id, 0, 10) + + const hardwareColumns: GridColDef[] = [ + { field: 'id', headerName: 'ID', type: 'number' }, + { field: 'name', headerName: 'Название', type: 'string' }, + { field: 'server_id', headerName: 'Server ID', type: 'number' }, + { field: 'servername', headerName: 'Название сервера', type: 'string' }, + { field: 'os_info', headerName: 'ОС', type: 'string' }, + { field: 'ram', headerName: 'ОЗУ', type: 'string' }, + { field: 'processor', headerName: 'Проц.', type: 'string' }, + { field: 'storages_count', headerName: 'Кол-во хранилищ', type: 'number' }, + ] + + return ( + <> + { + setServerDataOpen(false) + }} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description"> + + + { + setServerDataOpen(false) + }} + aria-label="close" + > + + + + + + {currentServerData && + + } + + + { + 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 && + { + console.log(id) + }} + onDelete={ServerService.removeServer} + initialRows={hardwares} + columns={hardwareColumns} + actions + onRowClick={(params, event, details) => { + setCurrentServerData(params.row) + setServerDataOpen(true) + }} + /> + } + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/ServerIpsView.tsx b/frontend_reactjs/src/components/ServerIpsView.tsx new file mode 100644 index 0000000..ff00070 --- /dev/null +++ b/frontend_reactjs/src/components/ServerIpsView.tsx @@ -0,0 +1,144 @@ +import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material' +import React, { Fragment, useEffect, useState } from 'react' +import { IRegion } from '../interfaces/fuel' +import { useRegions, useServerIps, useServers } from '../hooks/swrHooks' +import FullFeaturedCrudGrid from './TableEditable' +import ServerService from '../services/ServersService' +import { GridColDef } from '@mui/x-data-grid' +import { Close } from '@mui/icons-material' +import ServerData from './ServerData' + +export default function ServerIpsView() { + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [search, setSearch] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [selectedOption, setSelectedOption] = useState(null) + const { servers, isLoading } = useServers() + + const [serverDataOpen, setServerDataOpen] = useState(false) + const [currentServerData, setCurrentServerData] = useState(null) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearch(search) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [search]) + + useEffect(() => { + if (servers) { + setOptions([...servers]) + } + }, [servers]) + + const handleInputChange = (value: string) => { + setSearch(value) + } + + const handleOptionChange = (value: IRegion | null) => { + setSelectedOption(value) + } + + const { serverIps, isLoading: serversLoading } = useServerIps(selectedOption?.id, 0, 10) + + const serverIpsColumns: GridColDef[] = [ + { field: 'id', headerName: 'ID', type: 'number' }, + { field: 'server_id', headerName: 'Server ID', type: 'number' }, + { field: 'name', headerName: 'Название', type: 'string' }, + { field: 'is_actual', headerName: 'Действителен', type: 'boolean' }, + { field: 'ip', headerName: 'IP', type: 'string' }, + { field: 'servername', headerName: 'Название сервера', type: 'string' }, + ] + + return ( + <> + { + setServerDataOpen(false) + }} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description"> + + + { + setServerDataOpen(false) + }} + aria-label="close" + > + + + + + + {currentServerData && + + } + + + { + 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 && + { + console.log(id) + }} + onDelete={ServerService.removeServer} + initialRows={serverIps} + columns={serverIpsColumns} + actions + onRowClick={(params, event, details) => { + setCurrentServerData(params.row) + setServerDataOpen(true) + }} + /> + } + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/ServerStorages.tsx b/frontend_reactjs/src/components/ServerStorages.tsx new file mode 100644 index 0000000..cab425c --- /dev/null +++ b/frontend_reactjs/src/components/ServerStorages.tsx @@ -0,0 +1,143 @@ +import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material' +import React, { Fragment, useEffect, useState } from 'react' +import { IRegion } from '../interfaces/fuel' +import { useHardwares, useRegions, useServerIps, useServers, useStorages } from '../hooks/swrHooks' +import FullFeaturedCrudGrid from './TableEditable' +import ServerService from '../services/ServersService' +import { GridColDef } from '@mui/x-data-grid' +import { Close } from '@mui/icons-material' +import ServerData from './ServerData' + +export default function ServerStorage() { + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [search, setSearch] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [selectedOption, setSelectedOption] = useState(null) + const { hardwares, isLoading } = useHardwares() + + const [serverDataOpen, setServerDataOpen] = useState(false) + const [currentServerData, setCurrentServerData] = useState(null) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearch(search) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [search]) + + useEffect(() => { + if (hardwares) { + setOptions([...hardwares]) + } + }, [hardwares]) + + const handleInputChange = (value: string) => { + setSearch(value) + } + + const handleOptionChange = (value: IRegion | null) => { + setSelectedOption(value) + } + + const { storages, isLoading: serversLoading } = useStorages(selectedOption?.id, 0, 10) + + const storageColumns: GridColDef[] = [ + { field: 'id', headerName: 'ID', type: 'number' }, + { field: 'hardware_id', headerName: 'Hardware ID', type: 'number' }, + { field: 'name', headerName: 'Название', type: 'string' }, + { field: 'size', headerName: 'Размер', type: 'string' }, + { field: 'storage_type', headerName: 'Тип хранилища', type: 'string' }, + ] + + return ( + <> + { + setServerDataOpen(false) + }} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description"> + + + { + setServerDataOpen(false) + }} + aria-label="close" + > + + + + + + {currentServerData && + + } + + + { + 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 && + { + console.log(id) + }} + onDelete={ServerService.removeServer} + initialRows={storages} + columns={storageColumns} + actions + onRowClick={(params, event, details) => { + setCurrentServerData(params.row) + setServerDataOpen(true) + }} + /> + } + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/ServersView.tsx b/frontend_reactjs/src/components/ServersView.tsx new file mode 100644 index 0000000..b9a880f --- /dev/null +++ b/frontend_reactjs/src/components/ServersView.tsx @@ -0,0 +1,191 @@ +import { AppBar, Autocomplete, Box, Chip, CircularProgress, Dialog, Divider, Grid, IconButton, Paper, TextField, Toolbar, Typography } from '@mui/material' +import React, { Fragment, useEffect, useState } from 'react' +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 { Close, Cloud, CloudOff } from '@mui/icons-material' +import ServerData from './ServerData' +import { IServersInfo } from '../interfaces/servers' + +export default function ServersView() { + const [open, setOpen] = useState(false) + const [options, setOptions] = useState([]) + const [search, setSearch] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + const [selectedOption, setSelectedOption] = useState(null) + const { regions, isLoading } = useRegions(10, 1, debouncedSearch) + + const { serversInfo } = useServersInfo(selectedOption?.id) + + + const [serverDataOpen, setServerDataOpen] = useState(false) + const [currentServerData, setCurrentServerData] = useState(null) + + 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 { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10) + + const serversColumns: GridColDef[] = [ + //{ field: 'id', headerName: 'ID', type: "number" }, + { field: 'name', headerName: 'Название', type: "string", editable: true }, + ] + + return ( + <> + { + setServerDataOpen(false) + }} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description"> + + + { + setServerDataOpen(false) + }} + aria-label="close" + > + + + + + + {currentServerData && + + } + + + { + 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.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 && + { + console.log(id) + }} + onDelete={ServerService.removeServer} + initialRows={servers} + columns={serversColumns} + actions + onRowClick={(params, event, details) => { + setCurrentServerData(params.row) + setServerDataOpen(true) + }} + /> + } + + ) +} \ No newline at end of file diff --git a/frontend_reactjs/src/components/TableEditable.tsx b/frontend_reactjs/src/components/TableEditable.tsx index 637f6ec..3c228ed 100644 --- a/frontend_reactjs/src/components/TableEditable.tsx +++ b/frontend_reactjs/src/components/TableEditable.tsx @@ -23,6 +23,7 @@ import { GridRow, useGridApiContext, } from '@mui/x-data-grid'; +import { AxiosResponse } from 'axios'; interface EditToolbarProps { setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void; @@ -71,14 +72,18 @@ interface DataGridProps { initialRows: GridRowsProp; columns: GridColDef[]; actions: boolean; - onRowClick: GridEventListener<"rowClick"> + onRowClick: GridEventListener<"rowClick">; + onSave: any; + onDelete: (data: any) => Promise>; } export default function FullFeaturedCrudGrid({ initialRows, columns, actions = false, - onRowClick + onRowClick, + onSave, + onDelete }: DataGridProps) { const [rows, setRows] = React.useState(initialRows); const [rowModesModel, setRowModesModel] = React.useState({}); @@ -95,10 +100,13 @@ export default function FullFeaturedCrudGrid({ const handleSaveClick = (id: GridRowId) => () => { setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }); + onSave?.(id) }; const handleDeleteClick = (id: GridRowId) => () => { setRows(rows.filter((row) => row.id !== id)); + onDelete?.(id).then(response => { + }) }; const handleCancelClick = (id: GridRowId) => () => { @@ -196,7 +204,6 @@ export default function FullFeaturedCrudGrid({ processRowUpdate={processRowUpdate} slots={{ toolbar: EditToolbar as GridSlots['toolbar'], - row: CustomRow as GridSlots['row'] }} slotProps={{ toolbar: { setRows, setRowModesModel, columns }, @@ -204,24 +211,4 @@ export default function FullFeaturedCrudGrid({ /> ); -} - -function CustomRow(props: GridRowProps) { - const { id, row, rowId, ...other } = props; - const apiRef = useGridApiContext(); - - // Custom styles or logic can go here - - return ( - - {Object.entries(row).map(([field, value]) => ( - - {apiRef.current.getCellParams(rowId, field).formattedValue as React.ReactNode} - - ))} - - ); } \ No newline at end of file diff --git a/frontend_reactjs/src/hooks/swrHooks.ts b/frontend_reactjs/src/hooks/swrHooks.ts index 2524b58..c9ccbf2 100644 --- a/frontend_reactjs/src/hooks/swrHooks.ts +++ b/frontend_reactjs/src/hooks/swrHooks.ts @@ -186,10 +186,11 @@ export function useBoilers(limit?: number, page?: number, search?: string) { export function useServers(region_id?: number, offset?: number, limit?: number) { const { data, error, isLoading } = useSWR( - region_id ? `/api/servers?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : null, - (url) => fetcher(url, BASE_URL.servers), + 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 + revalidateOnFocus: false, + revalidateOnMount: false } ) @@ -205,7 +206,8 @@ export function useServersInfo(region_id?: number, offset?: number, limit?: numb region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : null, (url) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false + revalidateOnFocus: false, + revalidateOnMount: false } ) @@ -221,7 +223,8 @@ export function useServer(server_id?: number) { server_id ? `/api/server/${server_id}` : null, (url) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false + revalidateOnFocus: false, + revalidateOnMount: false } ) @@ -232,12 +235,13 @@ export function useServer(server_id?: number) { } } -export function useServerIps(server_id?: number, offset?: number, limit?: number) { +export function useServerIps(server_id?: number | null, offset?: number, limit?: number) { const { data, error, isLoading } = useSWR( - server_id ? `/api/server_ips?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null, - (url) => fetcher(url, BASE_URL.servers), + server_id ? `/api/server_ips?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/server_ips?offset=${offset || 0}&limit=${limit || 10}`, + (url: string) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false + revalidateOnFocus: false, + revalidateOnMount: false } ) @@ -252,10 +256,11 @@ export function useServerIps(server_id?: number, offset?: number, limit?: number export function useHardwares(server_id?: number, offset?: number, limit?: number) { const { data, error, isLoading } = useSWR( - server_id ? `/api/hardwares?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null, - (url) => fetcher(url, BASE_URL.servers), + server_id ? `/api/hardwares?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/hardwares?offset=${offset || 0}&limit=${limit || 10}`, + (url: string) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false + revalidateOnFocus: false, + revalidateOnMount: false } ) @@ -287,10 +292,11 @@ export function useHardware(hardware_id?: number) { export function useStorages(hardware_id?: number, offset?: number, limit?: number) { const { data, error, isLoading } = useSWR( - hardware_id ? `/api/storages?hardware_id=${hardware_id}&offset=${offset || 0}&limit=${limit || 10}` : null, - (url) => fetcher(url, BASE_URL.servers), + hardware_id ? `/api/storages?hardware_id=${hardware_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/storages?offset=${offset || 0}&limit=${limit || 10}`, + (url: string) => fetcher(url, BASE_URL.servers), { - revalidateOnFocus: false + revalidateOnFocus: false, + revalidateOnMount: false } ) diff --git a/frontend_reactjs/src/pages/Servers.tsx b/frontend_reactjs/src/pages/Servers.tsx index 1cff584..1c406aa 100644 --- a/frontend_reactjs/src/pages/Servers.tsx +++ b/frontend_reactjs/src/pages/Servers.tsx @@ -1,267 +1,66 @@ -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 { BarChart } from "@mui/x-charts" -import FullFeaturedCrudGrid from "../components/TableEditable" +import { Box, Tab, Tabs } from "@mui/material" +import { useState } from "react" +import ServersView from "../components/ServersView" +import ServerIpsView from "../components/ServerIpsView" +import ServerHardware from "../components/ServerHardware" +import ServerStorage from "../components/ServerStorages" export default function Servers() { - 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) + const [currentTab, setCurrentTab] = useState(0) - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedSearch(search) - }, 500) - - return () => { - clearTimeout(handler) - } - }, [search]) - - useEffect(() => { - if (regions) { - setOptions([...regions]) - } - }, [regions]) - - const handleInputChange = (value: string) => { - setSearch(value) + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setCurrentTab(newValue); } - const handleOptionChange = (value: IRegion | null) => { - setSelectedOption(value) + interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; } - 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) + function CustomTabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); } - 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", editable: true }, - ] - - 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 by region - - - { - 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} - - ) - }} - /> - )} - /> - -