Tables, cards, (servers)

This commit is contained in:
cracklesparkle
2024-07-12 17:44:44 +09:00
parent f9de1124c3
commit 416e2e39b5
9 changed files with 802 additions and 86 deletions

View File

@ -13,6 +13,8 @@ import { useEffect, useState } from "react"
import { Box, CircularProgress } from "@mui/material" import { Box, CircularProgress } from "@mui/material"
import Documents from "./pages/Documents" import Documents from "./pages/Documents"
import Reports from "./pages/Reports" import Reports from "./pages/Reports"
import Boilers from "./pages/Boilers"
import Servers from "./pages/Servers"
function App() { function App() {
const auth = useAuthStore() const auth = useAuthStore()
@ -52,6 +54,8 @@ function App() {
<Route path="/role" element={<Roles />} /> <Route path="/role" element={<Roles />} />
<Route path="/documents" element={<Documents />} /> <Route path="/documents" element={<Documents />} />
<Route path="/reports" element={<Reports />} /> <Route path="/reports" element={<Reports />} />
<Route path="/servers" element={<Servers />} />
<Route path="/boilers" element={<Boilers />} />
<Route path="/api-test" element={<ApiTest />} /> <Route path="/api-test" element={<ApiTest />} />
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Route> </Route>

View File

@ -120,7 +120,7 @@ export default function AccountMenu() {
}}> }}>
<ListItemIcon> <ListItemIcon>
<Android12Switch <Android12Switch
defaultChecked={prefStore.darkMode} checked={prefStore.darkMode}
onChange={(e) => { onChange={(e) => {
setDarkMode(e.target.checked) setDarkMode(e.target.checked)
}} /> }} />

View File

@ -0,0 +1,17 @@
import { Box } from '@mui/material'
import { IServer } from '../interfaces/servers'
import { useServerIps } from '../hooks/swrHooks'
function ServerData({ id, name, region_id }: IServer) {
const { serverIps } = useServerIps(id, 0, 10)
return (
<Box>
{serverIps &&
JSON.stringify(serverIps)
}
</Box>
)
}
export default ServerData

View File

@ -0,0 +1,227 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import {
GridRowsProp,
GridRowModesModel,
GridRowModes,
DataGrid,
GridColDef,
GridToolbarContainer,
GridActionsCellItem,
GridEventListener,
GridRowId,
GridRowModel,
GridRowEditStopReasons,
GridSlots,
GridRowProps,
GridRow,
useGridApiContext,
} from '@mui/x-data-grid';
interface EditToolbarProps {
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
) => void;
columns: GridColDef[];
}
function EditToolbar(props: EditToolbarProps) {
const { setRows, setRowModesModel, columns } = props;
const handleClick = () => {
const id = Date.now().toString(36)
const newValues: any = {};
columns.forEach(column => {
if (column.type === 'number') {
newValues[column.field] = 0
} else if (column.type === 'string') {
newValues[column.field] = ''
} else if (column.type === 'boolean') {
newValues[column.field] = false
} else {
newValues[column.field] = undefined
}
})
setRows((oldRows) => [...oldRows, { id, ...newValues, isNew: true }]);
setRowModesModel((oldModel) => ({
...oldModel,
[id]: { mode: GridRowModes.Edit, fieldToFocus: columns[0].field },
}));
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Добавить
</Button>
</GridToolbarContainer>
);
}
interface DataGridProps {
initialRows: GridRowsProp;
columns: GridColDef[];
actions: boolean;
onRowClick: GridEventListener<"rowClick">
}
export default function FullFeaturedCrudGrid({
initialRows,
columns,
actions = false,
onRowClick
}: DataGridProps) {
const [rows, setRows] = React.useState(initialRows);
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>({});
const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
if (params.reason === GridRowEditStopReasons.rowFocusOut) {
event.defaultMuiPrevented = true;
}
};
const handleEditClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};
const handleSaveClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};
const handleDeleteClick = (id: GridRowId) => () => {
setRows(rows.filter((row) => row.id !== id));
};
const handleCancelClick = (id: GridRowId) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true },
});
const editedRow = rows.find((row) => row.id === id);
if (editedRow!.isNew) {
setRows(rows.filter((row) => row.id !== id));
}
};
const processRowUpdate = (newRow: GridRowModel) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
return updatedRow;
};
const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
setRowModesModel(newRowModesModel);
};
const actionColumns: GridColDef[] = [
{
field: 'actions',
type: 'actions',
headerName: 'Действия',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
sx={{
color: 'primary.main',
}}
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>,
];
}
return [
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
}
]
return (
<Box
sx={{
height: 500,
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<DataGrid
rows={rows}
columns={actions ? [...columns, ...actionColumns] : columns}
editMode="row"
rowModesModel={rowModesModel}
onRowClick={onRowClick}
onRowModesModelChange={handleRowModesModelChange}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
slots={{
toolbar: EditToolbar as GridSlots['toolbar'],
row: CustomRow as GridSlots['row']
}}
slotProps={{
toolbar: { setRows, setRowModesModel, columns },
}}
/>
</Box>
);
}
function CustomRow(props: GridRowProps) {
const { id, row, rowId, ...other } = props;
const apiRef = useGridApiContext();
// Custom styles or logic can go here
return (
<Box
component="div"
{...other}
>
{Object.entries(row).map(([field, value]) => (
<Box key={field} sx={{ flex: 1, padding: '0 8px' }}>
{apiRef.current.getCellParams(rowId, field).formattedValue as React.ReactNode}
</Box>
))}
</Box>
);
}

View File

@ -234,7 +234,7 @@ export function useServer(server_id?: number) {
export function useServerIps(server_id?: number, offset?: number, limit?: number) { export function useServerIps(server_id?: number, offset?: number, limit?: number) {
const { data, error, isLoading } = useSWR( const { data, error, isLoading } = useSWR(
server_id ? `/api/servers?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null, server_id ? `/api/server_ips?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
(url) => fetcher(url, BASE_URL.servers), (url) => fetcher(url, BASE_URL.servers),
{ {
revalidateOnFocus: false revalidateOnFocus: false

View File

@ -11,7 +11,7 @@ import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Container from '@mui/material/Container'; import Container from '@mui/material/Container';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import { Api, Assignment, Home, People, Shield, Storage, } from '@mui/icons-material'; import { Api, Assignment, Cloud, Factory, Home, People, Shield, Storage, } from '@mui/icons-material';
import { ListItem, ListItemButton, ListItemIcon, ListItemText, } from '@mui/material'; import { ListItem, ListItemButton, ListItemIcon, ListItemText, } from '@mui/material';
import { Outlet, useNavigate } from 'react-router-dom'; import { Outlet, useNavigate } from 'react-router-dom';
import { UserData } from '../interfaces/auth'; import { UserData } from '../interfaces/auth';
@ -95,6 +95,16 @@ const pages = [
path: "/reports", path: "/reports",
icon: <Assignment /> icon: <Assignment />
}, },
{
label: "Серверы",
path: "/servers",
icon: <Cloud />
},
{
label: "Котельные",
path: "/boilers",
icon: <Factory />
},
{ {
label: "API Test", label: "API Test",
path: "/api-test", path: "/api-test",
@ -241,7 +251,13 @@ export default function DashboardLayout() {
}} }}
> >
<Toolbar /> <Toolbar />
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}> <Container
maxWidth="lg"
sx={{
mt: 4,
mb: 4
}}
>
<Outlet /> <Outlet />
</Container> </Container>
</Box> </Box>

View File

@ -1,8 +1,11 @@
import { Autocomplete, Box, CircularProgress, Paper, TextField, Typography } from "@mui/material" import { AppBar, Autocomplete, Box, Chip, CircularProgress, Dialog, Divider, Grid, IconButton, Paper, TextField, Toolbar, Typography, colors } from "@mui/material"
import { useBoilers, useRegions, useServers } from "../hooks/swrHooks" import { useBoilers, useCities, useRegions, useServers, useServersInfo } from "../hooks/swrHooks"
import { Fragment, useEffect, useState } from "react" import { Fragment, useEffect, useState } from "react"
import { IBoiler, IRegion } from "../interfaces/fuel" import { IBoiler, ICity, IRegion } from "../interfaces/fuel"
import { DataGrid, GridColDef } from "@mui/x-data-grid" 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"
export default function ApiTest() { export default function ApiTest() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@ -36,27 +39,43 @@ export default function ApiTest() {
setSelectedOption(value) setSelectedOption(value)
} }
const [boilersPage, setBoilersPage] = useState(1) const [citiesOpen, setCitiesOpen] = useState(false)
const [boilerSearch, setBoilerSearch] = useState("") const [citiesPage, setCitiesPage] = useState(1)
const [debouncedBoilerSearch, setDebouncedBoilerSearch] = useState("") const [citiesSearch, setCitiesSearch] = useState('')
const { boilers } = useBoilers(10, boilersPage, debouncedBoilerSearch) const [debouncedCitySearch, setDebouncedCitySearch] = useState('')
const { cities, isLoading: citiesLoading } = useCities(10, citiesPage, debouncedCitySearch)
const [citiesOptions, setCitiesOptions] = useState<ICity[]>([])
const [selectedCityOption, setSelectedCityOption] = useState<ICity | null>(null)
const handleCityInputChange = (value: string) => {
setCitiesSearch(value)
}
const handleCityOptionChange = (value: ICity | null) => {
setSelectedCityOption(value)
}
useEffect(() => {
if (cities) {
setCitiesOptions([...cities])
}
}, [cities])
useEffect(() => { useEffect(() => {
const handler = setTimeout(() => { const handler = setTimeout(() => {
setDebouncedBoilerSearch(boilerSearch) setDebouncedCitySearch(citiesSearch)
}, 500) }, 500)
return () => { return () => {
clearTimeout(handler) clearTimeout(handler)
} }
}, [boilerSearch]) }, [citiesSearch])
useEffect(() => { useEffect(() => {
setBoilersPage(1) setCitiesPage(1)
setBoilerSearch("") setCitiesSearch("")
}, []) }, [])
//const { cities } = useCities(10, 1)
const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10) const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10)
const serversColumns: GridColDef[] = [ const serversColumns: GridColDef[] = [
@ -64,21 +83,53 @@ export default function ApiTest() {
{ field: 'name', headerName: 'Название', type: "string" }, { field: 'name', headerName: 'Название', type: "string" },
] ]
const boilersColumns: GridColDef[] = [ const [serverDataOpen, setServerDataOpen] = useState(false)
{ field: 'id', headerName: 'ID', type: "number" }, const [currentServerData, setCurrentServerData] = useState<any | null>(null)
{ field: 'boiler_name', headerName: 'Название', type: "string" },
{ field: 'boiler_code', headerName: 'Код', type: "string" }, const { serversInfo } = useServersInfo(selectedOption?.id)
{ field: 'id_city', headerName: 'Город', type: "string" },
{ field: 'activity', headerName: 'Активен', type: "boolean" },
]
return ( return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Paper elevation={1}> <Dialog
fullScreen
open={serverDataOpen}
onClose={() => {
setServerDataOpen(false)
}}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description">
<AppBar sx={{ position: 'sticky' }}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={() => {
setServerDataOpen(false)
}}
aria-label="close"
>
<Close />
</IconButton>
</Toolbar>
</AppBar>
{currentServerData &&
<ServerData
id={currentServerData?.id}
region_id={currentServerData?.region_id}
name={currentServerData?.name}
/>
}
</Dialog>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Storage />
<Typography variant='h6' fontWeight='600'> <Typography variant='h6' fontWeight='600'>
Servers Servers
</Typography> </Typography>
</Box>
<Autocomplete <Autocomplete
open={open} open={open}
@ -113,6 +164,90 @@ export default function ApiTest() {
)} )}
/> />
<Autocomplete
open={citiesOpen}
onOpen={() => {
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) => (
<TextField
{...params}
label="Город"
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{citiesLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}}
/>
)}
/>
{servers &&
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Typography variant='h6' fontWeight='600'>
Информация
</Typography>
{serversInfo &&
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }}>
{serversInfo.map((serverInfo: IServersInfo) => (
<Grid item xs={1} sm={1} md={1}>
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
<Typography fontWeight={600}>
{serverInfo.name}
</Typography>
<Divider />
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography>
Количество IP:
</Typography>
<Typography variant="h6" fontWeight={600}>
{serverInfo.IPs_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography>
Количество серверов:
</Typography>
<Typography variant="h6" fontWeight={600}>
{serverInfo.servers_count}
</Typography>
</Box>
<Chip
icon={serverInfo.status === "Online" ? <Cloud /> : serverInfo.status === "Offline" ? <CloudOff /> : <CloudOff />}
variant="outlined"
label={serverInfo.status}
color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"}
/>
</Paper>
</Grid>
))}
</Grid>
}
</Box>
}
{serversLoading ? {serversLoading ?
<CircularProgress /> <CircularProgress />
: :
@ -121,28 +256,13 @@ export default function ApiTest() {
rowSelection={false} rowSelection={false}
rows={servers} rows={servers}
columns={serversColumns} columns={serversColumns}
onRowClick={(params, event, details) => {
setCurrentServerData(params.row)
setServerDataOpen(true)
}}
/> />
} }
</Box> </Box>
</Paper>
<Paper elevation={1}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Typography variant='h6' fontWeight='600'>
Boilers
</Typography>
{boilers &&
<DataGrid
rows={boilers.map((boiler: IBoiler) => {
return { ...boiler, id: boiler.id_object }
})}
columns={boilersColumns}
/>
}
</Box>
</Paper>
</Box> </Box>
) )
} }

View File

@ -0,0 +1,57 @@
import { Box, Typography } from '@mui/material'
import { DataGrid, GridColDef } from '@mui/x-data-grid'
import React, { useEffect, useState } from 'react'
import { IBoiler } from '../interfaces/fuel'
import { useBoilers } from '../hooks/swrHooks'
function Boilers() {
const [boilersPage, setBoilersPage] = useState(1)
const [boilerSearch, setBoilerSearch] = useState("")
const [debouncedBoilerSearch, setDebouncedBoilerSearch] = useState("")
const { boilers } = useBoilers(10, boilersPage, debouncedBoilerSearch)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedBoilerSearch(boilerSearch)
}, 500)
return () => {
clearTimeout(handler)
}
}, [boilerSearch])
useEffect(() => {
setBoilersPage(1)
setBoilerSearch("")
}, [])
const boilersColumns: GridColDef[] = [
{ field: 'id', headerName: 'ID', type: "number" },
{ field: 'boiler_name', headerName: 'Название', type: "string" },
{ field: 'boiler_code', headerName: 'Код', type: "string" },
{ field: 'id_city', headerName: 'Город', type: "string" },
{ field: 'activity', headerName: 'Активен', type: "boolean" },
]
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Typography variant='h6' fontWeight='600'>
Котельные
</Typography>
{boilers &&
<DataGrid
rows={boilers.map((boiler: IBoiler) => {
return { ...boiler, id: boiler.id_object }
})}
columns={boilersColumns}
/>
}
</Box>
</Box>
)
}
export default Boilers

View File

@ -0,0 +1,275 @@
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"
export default function Servers() {
const [open, setOpen] = useState(false)
const [options, setOptions] = useState<IRegion[]>([])
const [search, setSearch] = useState<string | null>(null)
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
const [selectedOption, setSelectedOption] = useState<IRegion | null>(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<ICity[]>([])
const [selectedCityOption, setSelectedCityOption] = useState<ICity | null>(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", editable: true },
]
const [serverDataOpen, setServerDataOpen] = useState(false)
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
const { serversInfo } = useServersInfo(selectedOption?.id)
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Dialog
fullScreen
open={serverDataOpen}
onClose={() => {
setServerDataOpen(false)
}}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description">
<AppBar sx={{ position: 'sticky' }}>
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={() => {
setServerDataOpen(false)
}}
aria-label="close"
>
<Close />
</IconButton>
</Toolbar>
</AppBar>
{currentServerData &&
<ServerData
id={currentServerData?.id}
region_id={currentServerData?.region_id}
name={currentServerData?.name}
/>
}
</Dialog>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Typography variant='h6' fontWeight='600'>
Servers by region
</Typography>
<Autocomplete
open={open}
onOpen={() => {
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) => (
<TextField
{...params}
label="Район"
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}}
/>
)}
/>
<Autocomplete
hidden
open={citiesOpen}
onOpen={() => {
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) => (
<TextField
{...params}
label="Город"
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{citiesLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}}
/>
)}
/>
{servers &&
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Typography variant='h6' fontWeight='600'>
Информация
</Typography>
{serversInfo &&
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }}>
{serversInfo.map((serverInfo: IServersInfo) => (
<Grid key={`si-${serverInfo.id}`} item xs={1} sm={1} md={1}>
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
<Typography fontWeight={600}>
{serverInfo.name}
</Typography>
<Divider />
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography>
Количество IP:
</Typography>
<Typography variant="h6" fontWeight={600}>
{serverInfo.IPs_count}
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography>
Количество серверов:
</Typography>
<Typography variant="h6" fontWeight={600}>
{serverInfo.servers_count}
</Typography>
</Box>
<Chip
icon={serverInfo.status === "Online" ? <Cloud /> : serverInfo.status === "Offline" ? <CloudOff /> : <CloudOff />}
variant="outlined"
label={serverInfo.status}
color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"}
/>
</Paper>
</Grid>
))}
</Grid>
}
</Box>
}
{serversLoading ?
<CircularProgress />
:
servers &&
<FullFeaturedCrudGrid
initialRows={servers}
columns={serversColumns}
actions
onRowClick={(params, event, details) => {
setCurrentServerData(params.row)
setServerDataOpen(true)
}}
/>
}
{/* <BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
width={500}
height={300}
/> */}
</Box>
</Box>
)
}