This commit is contained in:
cracklesparkle
2024-10-09 16:51:37 +09:00
parent b88d83cd74
commit 974fc12b34
32 changed files with 3456 additions and 1671 deletions

View File

@ -1,156 +0,0 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Avatar from '@mui/material/Avatar';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Settings from '@mui/icons-material/Settings';
import Logout from '@mui/icons-material/Logout';
import { useNavigate } from 'react-router-dom';
import { logout } from '../store/auth';
import { ListItemText, Switch, styled } from '@mui/material';
import { setDarkMode, usePrefStore } from '../store/preferences';
const Android12Switch = styled(Switch)(({ theme }) => ({
padding: 8,
'& .MuiSwitch-track': {
borderRadius: 22 / 2,
'&::before, &::after': {
content: '""',
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
width: 16,
height: 16,
},
'&::before': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
theme.palette.getContrastText(theme.palette.primary.main),
)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`,
left: 12,
},
'&::after': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
theme.palette.getContrastText(theme.palette.primary.main),
)}" d="M19,13H5V11H19V13Z" /></svg>')`,
right: 12,
},
},
'& .MuiSwitch-thumb': {
boxShadow: 'none',
width: 16,
height: 16,
margin: 2,
},
}));
export default function AccountMenu() {
const navigate = useNavigate()
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const prefStore = usePrefStore()
return (
<React.Fragment>
<Box sx={{ display: 'flex', alignItems: 'center', textAlign: 'center' }}>
<Tooltip title="Account settings">
<IconButton
onClick={handleClick}
size="small"
sx={{ ml: 2 }}
aria-controls={open ? 'account-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
>
<Avatar sx={{ width: 32, height: 32 }}></Avatar>
</IconButton>
</Tooltip>
</Box>
<Menu
anchorEl={anchorEl}
id="account-menu"
open={open}
onClose={handleClose}
slotProps={{
paper: {
elevation: 0,
sx: {
overflow: 'visible',
filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))',
mt: 1.5,
'& .MuiAvatar-root': {
width: 32,
height: 32,
ml: -0.5,
mr: 1,
},
'&::before': {
content: '""',
display: 'block',
position: 'absolute',
top: 0,
right: 14,
width: 10,
height: 10,
bgcolor: 'background.paper',
transform: 'translateY(-50%) rotate(45deg)',
zIndex: 0,
},
},
}
}}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={() => {
}}>
<ListItemIcon>
<Android12Switch
checked={prefStore.darkMode}
onChange={(e) => {
setDarkMode(e.target.checked)
}} />
</ListItemIcon>
<ListItemText>
Тема: {prefStore.darkMode ? "темная" : "светлая"}
</ListItemText>
</MenuItem>
<MenuItem onClick={() => {
navigate('/settings')
}}>
<ListItemIcon>
<Settings fontSize="small" />
</ListItemIcon>
Настройки
</MenuItem>
<MenuItem
onClick={() => {
logout()
navigate("/auth/signin")
}}
>
<ListItemIcon>
<Logout fontSize="small" />
</ListItemIcon>
Выход
</MenuItem>
</Menu>
</React.Fragment>
);
}

View File

@ -1,11 +1,13 @@
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
import { IDocument, IDocumentFolder } from '../interfaces/documents'
import { Box, Breadcrumbs, Button, CircularProgress, Divider, IconButton, Link, List, ListItemButton, SxProps } from '@mui/material'
import { Cancel, Close, Download, Folder, InsertDriveFile, Upload, UploadFile } from '@mui/icons-material'
import React, { useEffect, useRef, useState } from 'react'
import { Box, CircularProgress, Divider, SxProps } from '@mui/material'
import { Folder, InsertDriveFile } from '@mui/icons-material'
import React, { useEffect, useState } from 'react'
import DocumentService from '../services/DocumentService'
import { mutate } from 'swr'
import FileViewer from './modals/FileViewer'
import { ActionIcon, Anchor, Breadcrumbs, Button, FileButton, Flex, Loader, RingProgress, Table, Text } from '@mantine/core'
import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react'
interface FolderProps {
folder: IDocumentFolder;
@ -31,7 +33,7 @@ const FileItemStyle: SxProps = {
function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
return (
<ListItemButton
<Flex
onClick={() => handleFolderClick(folder)}
>
<Box
@ -41,7 +43,7 @@ function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
<Folder />
{folder.name}
</Box>
</ListItemButton>
</Flex>
)
}
@ -69,7 +71,7 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
}, [shouldFetch, file])
return (
<ListItemButton>
<Flex align='center'>
<Box
sx={FileItemStyle}
onClick={() => handleDocumentClick(index)}
@ -79,22 +81,21 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
{doc.name}
</Box>
<Box>
<IconButton
<ActionIcon
onClick={() => {
if (!isLoading) {
setShouldFetch(true)
}
}}
sx={{ ml: 'auto' }}
>
variant='subtle'>
{isLoading ?
<CircularProgress size={24} variant='indeterminate' />
<Loader size='sm' />
:
<Download />
<IconDownload />
}
</IconButton>
</ActionIcon>
</Box>
</ListItemButton>
</Flex>
)
}
@ -105,7 +106,6 @@ export default function FolderViewer() {
const { documents, isLoading: documentsLoading } = useDocuments(currentFolder?.id)
const [uploadProgress, setUploadProgress] = useState(0)
const [isUploading, setIsUploading] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const [fileViewerModal, setFileViewerModal] = useState(false)
const [currentFileNo, setCurrentFileNo] = useState<number>(-1)
@ -128,12 +128,6 @@ export default function FolderViewer() {
setCurrentFolder(newBreadcrumbs[newBreadcrumbs.length - 1])
}
const handleUploadClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click()
}
}
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
setDragOver(true)
@ -150,9 +144,10 @@ export default function FolderViewer() {
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
}
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || [])
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
const handleFileInput = (files: File[] | null) => {
if (files !== null) {
setFilesToUpload((prevFiles) => [...prevFiles, ...files])
}
}
const uploadFiles = async () => {
@ -196,28 +191,21 @@ export default function FolderViewer() {
/>
<Breadcrumbs>
<Link
underline='hover'
color='inherit'
<Anchor
onClick={() => {
setCurrentFolder(null)
setBreadcrumbs([])
}}
sx={{ cursor: 'pointer' }}
>
Главная
</Link>
</Anchor>
{breadcrumbs.map((breadcrumb, index) => (
<Link
<Anchor
key={breadcrumb.id}
underline="hover"
color="inherit"
onClick={() => handleBreadcrumbClick(index)}
sx={{ cursor: 'pointer' }}
>
{breadcrumb.name}
</Link>
</Anchor>
))}
</Breadcrumbs>
@ -232,44 +220,23 @@ export default function FolderViewer() {
p: '16px'
}}>
<Box sx={{ display: 'flex', gap: '16px' }}>
<Button
LinkComponent="label"
role={undefined}
variant="outlined"
tabIndex={-1}
startIcon={
isUploading ? <CircularProgress sx={{ maxHeight: "20px", maxWidth: "20px" }} variant="determinate" value={uploadProgress} /> : <UploadFile />
}
onClick={handleUploadClick}
>
<input
type='file'
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileInput}
onClick={(e) => {
if (e.currentTarget) {
e.currentTarget.value = ''
}
}}
/>
Добавить
</Button>
<FileButton multiple onChange={handleFileInput}>
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
</FileButton>
{filesToUpload.length > 0 &&
<>
<Button
variant="contained"
color="primary"
startIcon={<Upload />}
variant='filled'
leftSection={isUploading ? <RingProgress sections={[{ value: uploadProgress, color: 'blue' }]} /> : <IconFileUpload />}
onClick={uploadFiles}
>
Загрузить все
</Button>
<Button
variant='outlined'
startIcon={<Cancel />}
variant='outline'
leftSection={<IconCancel />}
onClick={() => {
setFilesToUpload([])
}}
@ -283,62 +250,69 @@ export default function FolderViewer() {
<Divider />
{filesToUpload.length > 0 &&
<Box>
<Flex direction='column'>
{filesToUpload.map((file, index) => (
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>
<Box>
<InsertDriveFile />
<span>{file.name}</span>
</Box>
<Flex key={index} p='8px'>
<Flex gap='sm' direction='row' align='center'>
<IconFile />
<Text>{file.name}</Text>
</Flex>
<IconButton sx={{ ml: 'auto' }} onClick={() => {
<ActionIcon onClick={() => {
setFilesToUpload(prev => {
return prev.filter((_, i) => i != index)
})
}}>
<Close />
</IconButton>
</Box>
}} ml='auto' variant='subtle'>
<IconX />
</ActionIcon>
</Flex>
))}
</Box>
</Flex>
}
</Box>
</Box>
}
<List
dense
<Table
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
sx={{
backgroundColor: dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'
}}
>
{currentFolder ? (
documents?.map((doc: IDocument, index: number) => (
<div key={`${doc.id}-${doc.name}`}>
<ItemDocument
doc={doc}
index={index}
handleDocumentClick={handleDocumentClick}
/>
{index < documents.length - 1 && <Divider />}
</div>
))
) : (
folders?.map((folder: IDocumentFolder, index: number) => (
<div key={`${folder.id}-${folder.name}`}>
<ItemFolder
folder={folder}
index={index}
handleFolderClick={handleFolderClick}
/>
{index < folders.length - 1 && <Divider />}
</div>
))
)}
</List>
bg={dragOver ? 'rgba(0, 0, 0, 0.1)' : 'inherit'}
highlightOnHover>
<Table.Thead>
<Table.Tr>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{currentFolder ? (
documents?.map((doc: IDocument, index: number) => (
<Table.Tr key={doc.id}>
<Table.Td>
<ItemDocument
doc={doc}
index={index}
handleDocumentClick={handleDocumentClick}
/>
</Table.Td>
</Table.Tr>
))
) : (
folders?.map((folder: IDocumentFolder, index: number) => (
<Table.Tr key={folder.id}>
<Table.Td>
<ItemFolder
folder={folder}
index={index}
handleFolderClick={handleFolderClick}
/>
</Table.Td>
</Table.Tr>
))
)}
</Table.Tbody>
</Table>
</Box>
)
}

View File

@ -1,7 +1,7 @@
import { SubmitHandler, useForm } from 'react-hook-form'
import { CreateField } from '../interfaces/create'
import { Box, Button, CircularProgress, Stack, SxProps, TextField, Typography } from '@mui/material';
import { AxiosResponse } from 'axios';
import { Button, Loader, Stack, Text, TextInput } from '@mantine/core';
interface Props {
title?: string;
@ -11,7 +11,6 @@ interface Props {
mutateHandler?: any;
defaultValues?: {};
watchValues?: string[];
sx?: SxProps | null;
}
function FormFields({
@ -20,8 +19,7 @@ function FormFields({
fields,
submitButtonText = 'Сохранить',
mutateHandler,
defaultValues,
sx
defaultValues
}: Props) {
const getDefaultValues = (fields: CreateField[]) => {
let result: { [key: string]: string | boolean } = {}
@ -53,20 +51,20 @@ function FormFields({
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Stack sx={sx} spacing={2} width='100%'>
<Typography variant="h6" component="h6" gutterBottom>
{title}
</Typography>
<Stack gap='sm' w='100%'>
{title.length > 0 &&
<Text size="xl" fw={500}>
{title}
</Text>
}
{fields.map((field: CreateField) => {
return (
<TextField
fullWidth
margin='normal'
<TextInput
key={field.key}
type={field.inputType ? field.inputType : 'text'}
label={field.headerName || field.key.charAt(0).toUpperCase() + field.key.slice(1)}
required={field.required || false}
//placeholder="Your name"
type={field.inputType ? field.inputType : 'text'}
{...register(field.key, {
required: field.required ? `${field.headerName} обязателен` : false,
validate: (val: string | boolean) => {
@ -77,21 +75,17 @@ function FormFields({
}
},
})}
error={!!errors[field.key]}
helperText={errors[field.key]?.message}
radius="md"
required={field.required || false}
error={errors[field.key]?.message}
errorProps={errors[field.key]}
/>
)
})}
<Box sx={{
display: "flex",
justifyContent: "space-between",
gap: "8px"
}}>
<Button disabled={isSubmitting || Object.keys(dirtyFields).length === 0 || !isValid} type="submit" variant="contained" color="primary">
{isSubmitting ? <CircularProgress size={16} /> : submitButtonText}
</Button>
</Box>
<Button disabled={isSubmitting || Object.keys(dirtyFields).length === 0 || !isValid} type='submit'>
{isSubmitting ? <Loader size={16} /> : submitButtonText}
</Button>
</Stack>
</form>
)

View File

@ -1,8 +1,8 @@
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'
import { Table } from '@mantine/core'
function ServerData({ id }: IServer) {
const { serverIps } = useServerIps(id, 0, 10)
@ -19,18 +19,34 @@ function ServerData({ id }: IServer) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
{serverIps &&
<FullFeaturedCrudGrid
initialRows={serverIps}
columns={serverIpsColumns}
actions
onRowClick={() => {
//setCurrentServerData(params.row)
//setServerDataOpen(true)
}}
onSave={undefined}
onDelete={undefined}
loading={false}
/>
// <FullFeaturedCrudGrid
// initialRows={serverIps}
// columns={serverIpsColumns}
// actions
// onRowClick={() => {
// //setCurrentServerData(params.row)
// //setServerDataOpen(true)
// }}
// onSave={undefined}
// onDelete={undefined}
// loading={false}
// />
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{serverIpsColumns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
{serverIpsColumns.map(column => (
<Table.Td key={column.field}>{serverIps ? serverIps[column.field] : ''}</Table.Td>
))}
</Table.Tr>
</Table.Tbody>
</Table>
}
</Box>
)

View File

@ -1,16 +1,17 @@
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
import { Fragment, useState } from 'react'
import { IRegion } from '../interfaces/fuel'
import { useHardwares, 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'
import { Autocomplete, CloseButton, Table } from '@mantine/core'
import { IServer } from '../interfaces/servers'
export default function ServerHardware() {
const [open, setOpen] = useState(false)
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
const [selectedOption, setSelectedOption] = useState<number | null>(null)
const { servers, isLoading } = useServers()
const [serverDataOpen, setServerDataOpen] = useState(false)
@ -71,54 +72,96 @@ export default function ServerHardware() {
}
</Dialog>
<form>
<Autocomplete
placeholder="Сервер"
flex={'1'}
data={servers ? servers.map((item: IServer) => ({ label: item.name, value: item.id.toString() })) : []}
onSelect={(e) => console.log(e.currentTarget.value)}
//onChange={(value) => setSearch(value)}
onOptionSubmit={(value) => setSelectedOption(Number(value))}
rightSection={
//search !== '' &&
(
<CloseButton
size="sm"
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
//setSearch('')
setSelectedOption(null)
}}
aria-label="Clear value"
/>
)
}
//value={search}
/>
</form>
{serversLoading ?
<CircularProgress />
:
<FullFeaturedCrudGrid
autoComplete={
<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={servers || []}
loading={isLoading}
value={selectedOption}
renderInput={(params) => (
<TextField
{...params}
label="Сервер"
size='small'
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}} />
)} />}
onSave={() => {
}}
onDelete={ServerService.removeServer}
initialRows={hardwares || []}
columns={hardwareColumns}
actions
onRowClick={(params) => {
setCurrentServerData(params.row)
setServerDataOpen(true)
}}
loading={false}
/>
// <FullFeaturedCrudGrid
// autoComplete={
// <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={servers || []}
// loading={isLoading}
// value={selectedOption}
// renderInput={(params) => (
// <TextField
// {...params}
// label="Сервер"
// size='small'
// InputProps={{
// ...params.InputProps,
// endAdornment: (
// <Fragment>
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
// {params.InputProps.endAdornment}
// </Fragment>
// )
// }} />
// )} />}
// onSave={() => {
// }}
// onDelete={ServerService.removeServer}
// initialRows={hardwares || []}
// columns={hardwareColumns}
// actions
// onRowClick={(params) => {
// setCurrentServerData(params.row)
// setServerDataOpen(true)
// }}
// loading={false}
// />
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{hardwareColumns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
{hardwareColumns.map(column => (
<Table.Td key={column.field}>{hardwares ? hardwares[column.field] : ''}</Table.Td>
))}
</Table.Tr>
</Table.Tbody>
</Table>
}
</>
)

View File

@ -1,16 +1,17 @@
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
import { AppBar, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
import { Fragment, useState } from 'react'
import { IRegion } from '../interfaces/fuel'
import { 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'
import { Autocomplete, CloseButton, Table } from '@mantine/core'
import { IServer } from '../interfaces/servers'
export default function ServerIpsView() {
const [open, setOpen] = useState(false)
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
const [selectedOption, setSelectedOption] = useState<number | null>(null)
const { servers, isLoading } = useServers()
const [serverDataOpen, setServerDataOpen] = useState(false)
@ -69,52 +70,97 @@ export default function ServerIpsView() {
}
</Dialog>
<form>
<Autocomplete
placeholder="Сервер"
flex={'1'}
data={servers ? servers.map((item: IServer) => ({ label: item.name, value: item.id.toString() })) : []}
onSelect={(e) => console.log(e.currentTarget.value)}
//onChange={(value) => setSearch(value)}
onOptionSubmit={(value) => setSelectedOption(Number(value))}
rightSection={
//search !== '' &&
(
<CloseButton
size="sm"
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
//setSearch('')
setSelectedOption(null)
}}
aria-label="Clear value"
/>
)
}
//value={search}
/>
</form>
{serversLoading ?
<CircularProgress />
:
<FullFeaturedCrudGrid
autoComplete={
<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={servers || []}
loading={isLoading}
value={selectedOption}
renderInput={(params) => (
<TextField
{...params}
size='small'
label="Сервер"
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}} />
)} />}
onSave={() => {
}}
onDelete={ServerService.removeServer}
initialRows={serverIps || []}
columns={serverIpsColumns}
actions
onRowClick={(params) => {
setCurrentServerData(params.row)
setServerDataOpen(true)
}} loading={false} />
// <FullFeaturedCrudGrid
// autoComplete={
// <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={servers || []}
// loading={isLoading}
// value={selectedOption}
// renderInput={(params) => (
// <TextField
// {...params}
// size='small'
// label="Сервер"
// InputProps={{
// ...params.InputProps,
// endAdornment: (
// <Fragment>
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
// {params.InputProps.endAdornment}
// </Fragment>
// )
// }} />
// )} />}
// onSave={() => {
// }}
// onDelete={ServerService.removeServer}
// initialRows={serverIps || []}
// columns={serverIpsColumns}
// actions
// onRowClick={(params) => {
// setCurrentServerData(params.row)
// setServerDataOpen(true)
// }} loading={false}
// />
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{serverIpsColumns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
>
{serverIpsColumns.map(column => (
<Table.Td key={column.field}>{servers ? servers[column.field] : ''}</Table.Td>
))}
</Table.Tr>
</Table.Tbody>
</Table>
}
</>
)

View File

@ -1,8 +1,7 @@
import { AppBar, Autocomplete, Box, CircularProgress, Dialog, Grid, IconButton, TextField, Toolbar } from '@mui/material'
import { AppBar, Box, CircularProgress, Dialog, Grid, IconButton, TextField, Toolbar } from '@mui/material'
import { Fragment, 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, GridRenderCellParams } from '@mui/x-data-grid'
import { Close, Cloud, CloudOff } from '@mui/icons-material'
@ -12,22 +11,23 @@ import CardInfo from './CardInfo/CardInfo'
import CardInfoLabel from './CardInfo/CardInfoLabel'
import CardInfoChip from './CardInfo/CardInfoChip'
import { useDebounce } from '@uidotdev/usehooks'
import { Autocomplete, CloseButton, Table } from '@mantine/core'
export default function ServersView() {
const [search, setSearch] = useState<string | null>("")
const [search, setSearch] = useState<string | undefined>("")
const debouncedSearch = useDebounce(search, 500)
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
const [selectedOption, setSelectedOption] = useState<number | null>(null)
const { regions, isLoading } = useRegions(10, 1, debouncedSearch)
const { serversInfo } = useServersInfo(selectedOption?.id)
const { serversInfo } = useServersInfo(selectedOption)
const [serverDataOpen, setServerDataOpen] = useState(false)
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10)
const { servers, isLoading: serversLoading } = useServers(selectedOption, 0, 10)
const serversColumns: GridColDef[] = [
//{ field: 'id', headerName: 'ID', type: "number" },
@ -37,42 +37,43 @@ export default function ServersView() {
{
field: 'region_id',
editable: true,
headerName: 'region_id',
renderCell: (params) => (
<div>
{params.value}
</div>
),
renderEditCell: (params: GridRenderCellParams) => (
<Autocomplete
sx={{ display: 'flex', flexGrow: '1' }}
onInputChange={(_, value) => setSearch(value)}
onChange={(_, value) => {
params.value = value
}}
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
options={regions || []}
loading={isLoading}
value={params.value}
renderInput={(params) => (
<TextField
{...params}
size='small'
variant='standard'
label="Район"
InputProps={{
...params.InputProps,
endAdornment: (
<Fragment>
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</Fragment>
)
}}
/>
)}
/>
),
// renderEditCell: (params: GridRenderCellParams) => (
// <Autocomplete
// sx={{ display: 'flex', flexGrow: '1' }}
// onInputChange={(_, value) => setSearch(value)}
// onChange={(_, value) => {
// params.value = value
// }}
// isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
// getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
// options={regions || []}
// loading={isLoading}
// value={params.value}
// renderInput={(params) => (
// <TextField
// {...params}
// size='small'
// variant='standard'
// label="Район"
// InputProps={{
// ...params.InputProps,
// endAdornment: (
// <Fragment>
// {isLoading ? <CircularProgress color="inherit" size={20} /> : null}
// {params.InputProps.endAdornment}
// </Fragment>
// )
// }}
// />
// )}
// />
// ),
flex: 1
}
]
@ -132,7 +133,50 @@ export default function ServersView() {
</Box>
}
<FullFeaturedCrudGrid
<form>
<Autocomplete
placeholder="Район"
flex={'1'}
data={regions ? regions.map((item: IRegion) => ({ label: item.name, value: item.id.toString() })) : []}
onSelect={(e) => console.log(e.currentTarget.value)}
onChange={(value) => setSearch(value)}
onOptionSubmit={(value) => setSelectedOption(Number(value))}
rightSection={
search !== '' && (
<CloseButton
size="sm"
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setSearch('')
setSelectedOption(null)
}}
aria-label="Clear value"
/>
)
}
value={search}
/>
</form>
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{serversColumns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr>
{serversColumns.map(column => (
<Table.Td key={column.field}>{servers ? servers[column.field] : ''}</Table.Td>
))}
</Table.Tr>
</Table.Tbody>
</Table>
{/* <FullFeaturedCrudGrid
loading={serversLoading}
autoComplete={
<Autocomplete
@ -171,7 +215,7 @@ export default function ServersView() {
setCurrentServerData(params.row)
setServerDataOpen(true)
}}
/>
/> */}
</>
)
}

View File

@ -6,8 +6,7 @@ import View from 'ol/View'
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source'
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Box, Typography, Accordion, AccordionSummary, AccordionDetails, SxProps, Theme } from '@mui/material'
import { Add, Adjust, Api, CircleOutlined, ExpandMore, OpenWith, RectangleOutlined, Straighten, Timeline, Undo, Upload, Warning } from '@mui/icons-material'
import { Stack, Typography } from '@mui/material'
import { Type } from 'ol/geom/Geometry'
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
import Feature from 'ol/Feature'
@ -30,6 +29,8 @@ import { useCities } from '../../hooks/swrHooks'
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { Accordion, ActionIcon, Box, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme } from '@mantine/core'
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconTable, IconUpload } from '@tabler/icons-react'
const MapComponent = () => {
const { cities } = useCities(100, 1)
@ -775,14 +776,17 @@ const MapComponent = () => {
}
}
const mapControlsStyle: SxProps<Theme> = {
const { colorScheme } = useMantineColorScheme();
const mapControlsStyle: MantineStyleProp = {
borderRadius: '4px',
position: 'absolute',
zIndex: '1',
backgroundColor: (theme) =>
theme.palette.mode === 'light'
? '#FFFFFFAA'
: '#000000AA',
// backgroundColor: (theme) =>
// theme.palette.mode === 'light'
// ? '#FFFFFFAA'
// : '#000000AA',
backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA',
backdropFilter: 'blur(8px)'
}
@ -808,134 +812,118 @@ const MapComponent = () => {
}, [nodes])
return (
<Box height={'calc(100% - 64px)'} maxHeight={'100%'} flex={'1'} flexGrow={'1'} position={'relative'}>
<Stack
direction={'column'}
sx={{
...mapControlsStyle,
top: '8px',
right: '8px',
}}
divider={<Divider orientation='horizontal' flexItem />}>
<IconButton onClick={() => {
<Box w={'100%'} h={'calc(100% - 64px)'} mah={'100%'} flex={'1'} pos={'relative'}>
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
<ActionIcon size='lg' variant='transparent' onClick={() => {
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
}}>
<Api />
</IconButton>
<IconApi />
</ActionIcon>
<IconButton onClick={() => {
<ActionIcon size='lg' variant='transparent' onClick={() => {
saveFeatures()
}}>
<Warning />
</IconButton>
<IconExclamationCircle />
</ActionIcon>
<IconButton
<ActionIcon size='lg' variant='transparent' onClick={() => {
draw.current?.removeLastPoint()
}}>
<IconArrowBackUp />
</ActionIcon>
<ActionIcon
size='lg'
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
onClick={() => {
draw.current?.removeLastPoint()
handleToolSelect('Point')
}}>
<Undo />
</IconButton>
<IconPoint />
</ActionIcon>
<IconButton
sx={{ backgroundColor: currentTool === 'Point' ? 'Highlight' : 'transparent' }}
onClick={() => handleToolSelect('Point')}>
<Adjust />
</IconButton>
<ActionIcon
size='lg'
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
onClick={() => {
handleToolSelect('LineString')
}}>
<IconLine />
</ActionIcon>
<IconButton
sx={{ backgroundColor: currentTool === 'LineString' ? 'Highlight' : 'transparent' }}
onClick={() => handleToolSelect('LineString')}>
<Timeline />
</IconButton>
<ActionIcon
size='lg'
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
onClick={() => {
handleToolSelect('Polygon')
}}>
<IconPolygon />
</ActionIcon>
<IconButton
sx={{ backgroundColor: currentTool === 'Polygon' ? 'Highlight' : 'transparent' }}
onClick={() => handleToolSelect('Polygon')}>
<RectangleOutlined />
</IconButton>
<ActionIcon
size='lg'
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
onClick={() => {
handleToolSelect('Circle')
}}>
<IconCircle />
</ActionIcon>
<IconButton
sx={{ backgroundColor: currentTool === 'Circle' ? 'Highlight' : 'transparent' }}
onClick={() => handleToolSelect('Circle')}>
<CircleOutlined />
</IconButton>
<IconButton
<ActionIcon
size='lg'
variant='transparent'
onClick={() => map?.current?.addInteraction(new Translate())}
>
<OpenWith />
</IconButton>
<IconArrowsMove />
</ActionIcon>
<IconButton>
<Straighten />
</IconButton>
</Stack>
<ActionIcon
size='lg'
variant='transparent'
>
<IconRuler />
</ActionIcon>
</ActionIcon.Group>
<Stack
direction={'column'}
sx={{
...mapControlsStyle,
maxWidth: '300px',
width: '100%',
top: '8px',
left: '8px',
}} divider={<Divider orientation='horizontal' flexItem />}
>
<Stack direction={'row'}>
<IconButton onClick={() => submitOverlay()}>
<Upload />
</IconButton>
<IconButton title='Добавить подложку'>
<Add />
</IconButton>
</Stack>
<Stack direction={'row'} padding={'8px'} spacing={4}>
<Slider size='small' aria-label="Opacity" min={0} max={1} step={0.001} defaultValue={satelliteOpacity} value={satelliteOpacity} onChange={(_, value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
<MUISelect
variant='standard'
labelId="demo-simple-select-label"
id="demo-simple-select"
value={satMapsProvider}
label="Satellite Provider"
onChange={(e) => setSatMapsProvider(e.target.value as SatelliteMapsProvider)}
<Flex direction='column' style={{
...mapControlsStyle,
maxWidth: '300px',
width: '100%',
top: '8px',
left: '8px'
}}>
<Flex direction='row'>
<ActionIcon
size='lg'
variant='transparent'
onClick={() => submitOverlay()}
>
<MenuItem value={'google'}>Google</MenuItem>
<MenuItem value={'yandex'}>Яндекс</MenuItem>
<MenuItem value={'custom'}>Custom</MenuItem>
</MUISelect>
</Stack>
<IconUpload style={{ width: rem(20), height: rem(20) }} />
</ActionIcon>
<Accordion disableGutters sx={{ backgroundColor: 'transparent' }} defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMore />}
aria-controls="panel1-content"
id="panel1-header"
<ActionIcon
size='lg'
variant='transparent'
title='Добавить подложку'
>
<Typography>Объекты</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
malesuada lacus ex, sit amet blandit leo lobortis eget.
</Typography>
</AccordionDetails>
<IconPlus style={{ width: rem(20), height: rem(20) }} />
</ActionIcon>
</Flex>
<Flex align='center' direction='row' p='sm' gap='sm'>
<Slider w='100%' min={0} max={1} step={0.001} value={satelliteOpacity} defaultValue={satelliteOpacity} onChange={(value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
<MantineSelect variant='filled' value={satMapsProvider} data={[{ label: 'Google', value: 'google' }, { label: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
</Flex>
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
<Accordion.Item key={'s'} value={'Объекты'}>
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
<Accordion.Panel>{'ASd'}</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Flex>
</Stack>
<Stack direction={'row'}
sx={{
...mapControlsStyle,
bottom: '8px',
left: '8px',
}}
divider={<Divider orientation='vertical' flexItem />}
>
<Flex direction='row' pos='absolute' bottom='8px' left='8px' style={{ ...mapControlsStyle }}>
<Stack>
<Typography>
x: {currentCoordinate?.[0]}
@ -950,20 +938,11 @@ const MapComponent = () => {
X={currentX}
Y={currentY}
</Typography>
</Stack>
</Flex>
<Stack direction={'row'}
sx={{
...mapControlsStyle,
bottom: '8px',
right: '8px',
}}
divider={<Divider orientation='vertical' flexItem />}>
<Stack>
{statusText}
</Stack>
</Stack>
<Flex direction='row' style={{ ...mapControlsStyle, bottom: '8px', right: '8px' }}>
{statusText}
</Flex>
<div
id="map-container"

View File

@ -1,171 +0,0 @@
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import Divider from '@mui/material/Divider';
import Drawer from '@mui/material/Drawer';
import IconButton from '@mui/material/IconButton';
import InboxIcon from '@mui/icons-material/MoveToInbox';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import MailIcon from '@mui/icons-material/Mail';
import MenuIcon from '@mui/icons-material/Menu';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
const drawerWidth = 240;
export default function ResponsiveDrawer() {
//const { window } = props;
const [mobileOpen, setMobileOpen] = React.useState(false);
const [isClosing, setIsClosing] = React.useState(false);
const handleDrawerClose = () => {
setIsClosing(true);
setMobileOpen(false);
};
const handleDrawerTransitionEnd = () => {
setIsClosing(false);
};
const handleDrawerToggle = () => {
if (!isClosing) {
setMobileOpen(!mobileOpen);
}
};
const drawer = (
<div>
<Toolbar />
<Divider />
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</div>
);
return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Dashboard
</Typography>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label="mailbox folders"
>
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Drawer
variant="temporary"
open={mobileOpen}
onTransitionEnd={handleDrawerTransitionEnd}
onClose={handleDrawerClose}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
{drawer}
</Drawer>
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
>
<Toolbar />
<Typography paragraph>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Rhoncus dolor purus non
enim praesent elementum facilisis leo vel. Risus at ultrices mi tempus
imperdiet. Semper risus in hendrerit gravida rutrum quisque non tellus.
Convallis convallis tellus id interdum velit laoreet id donec ultrices.
Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit
adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra
nibh cras. Metus vulputate eu scelerisque felis imperdiet proin fermentum
leo. Mauris commodo quis imperdiet massa tincidunt. Cras tincidunt lobortis
feugiat vivamus at augue. At augue eget arcu dictum varius duis at
consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem donec massa
sapien faucibus et molestie ac.
</Typography>
<Typography paragraph>
Consequat mauris nunc congue nisi vitae suscipit. Fringilla est ullamcorper
eget nulla facilisi etiam dignissim diam. Pulvinar elementum integer enim
neque volutpat ac tincidunt. Ornare suspendisse sed nisi lacus sed viverra
tellus. Purus sit amet volutpat consequat mauris. Elementum eu facilisis
sed odio morbi. Euismod lacinia at quis risus sed vulputate odio. Morbi
tincidunt ornare massa eget egestas purus viverra accumsan in. In hendrerit
gravida rutrum quisque non tellus orci ac. Pellentesque nec nam aliquam sem
et tortor. Habitant morbi tristique senectus et. Adipiscing elit duis
tristique sollicitudin nibh sit. Ornare aenean euismod elementum nisi quis
eleifend. Commodo viverra maecenas accumsan lacus vel facilisis. Nulla
posuere sollicitudin aliquam ultrices sagittis orci a.
</Typography>
</Box>
</Box>
);
}

View File

@ -1,30 +0,0 @@
import { Tab, Tabs } from "@mui/material"
import { Link, matchPath, useLocation } from "react-router-dom"
function useRouteMatch(patterns: readonly string[]) {
const { pathname } = useLocation()
for (let i = 0; i < patterns.length; i += 1) {
const pattern = patterns[i]
const possibleMatch = matchPath(pattern, pathname)
if (possibleMatch !== null) {
return possibleMatch
}
}
return null
}
export default function NavTabs() {
const routeMatch = useRouteMatch(['/', '/user', '/role']);
const currentTab = routeMatch?.pattern?.path;
return (
<Tabs value={currentTab}>
<Tab label="Главная" value="/" to="/" component={Link} />
<Tab label="Пользователи" value="/user" to="/user" component={Link} />
<Tab label="Роли" value="/role" to="/role" component={Link} />
</Tabs>
);
}