forked from VinokurovVE/tests
Drop @mui, addded ems api
This commit is contained in:
@ -18,9 +18,10 @@ import PasswordReset from "./pages/auth/PasswordReset"
|
||||
import MapTest from "./pages/MapTest"
|
||||
import MonitorPage from "./pages/MonitorPage"
|
||||
import DashboardLayout from "./layouts/DashboardLayout"
|
||||
import { IconApi, IconBuildingFactory2, IconDeviceDesktopAnalytics, IconFiles, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconTable, IconUsers } from "@tabler/icons-react"
|
||||
import { IconApi, IconBuildingFactory2, IconComponents, IconDeviceDesktopAnalytics, IconFiles, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconTable, IconUsers } from "@tabler/icons-react"
|
||||
import { Box, Loader } from "@mantine/core"
|
||||
import TableTest from "./pages/TableTest"
|
||||
import ComponentTest from "./pages/ComponentTest"
|
||||
|
||||
// Определение страниц с путями и компонентом для рендера
|
||||
export const pages = [
|
||||
@ -148,7 +149,7 @@ export const pages = [
|
||||
component: <MonitorPage />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
label: "Table test",
|
||||
@ -159,6 +160,15 @@ export const pages = [
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
label: "Component test",
|
||||
path: "/component-test",
|
||||
icon: <IconComponents />,
|
||||
component: <ComponentTest />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
function App() {
|
||||
|
23
client/src/actions/map.ts
Normal file
23
client/src/actions/map.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Coordinate } from "ol/coordinate";
|
||||
import { IGeometryType } from "../interfaces/map";
|
||||
|
||||
export const uploadCoordinates = async (coordinates: Coordinate[], type: IGeometryType) => {
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_EMS_URL}/nodes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ coordinates, object_id: 1, type: type }) // Replace with actual object_id
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('Node created:', data);
|
||||
} else {
|
||||
console.error('Failed to upload coordinates');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { Divider, Paper, Typography } from '@mui/material'
|
||||
import { Divider, Flex, Text } from '@mantine/core';
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface CardInfoProps extends PropsWithChildren {
|
||||
@ -10,14 +10,14 @@ export default function CardInfo({
|
||||
label
|
||||
}: CardInfoProps) {
|
||||
return (
|
||||
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
||||
<Typography fontWeight={600}>
|
||||
<Flex direction='column' gap='sm' p='sm'>
|
||||
<Text fw={600}>
|
||||
{label}
|
||||
</Typography>
|
||||
</Text>
|
||||
|
||||
<Divider />
|
||||
|
||||
{children}
|
||||
</Paper>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Chip } from '@mui/material'
|
||||
import { Chip } from '@mantine/core';
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
interface CardInfoChipProps {
|
||||
@ -17,9 +17,10 @@ export default function CardInfoChip({
|
||||
return (
|
||||
<Chip
|
||||
icon={status ? iconOn : iconOff}
|
||||
variant="outlined"
|
||||
label={label}
|
||||
color={status ? "success" : "error"}
|
||||
/>
|
||||
variant='outline'
|
||||
>
|
||||
{label}
|
||||
</Chip>
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { Flex, Text } from '@mantine/core';
|
||||
interface CardInfoLabelProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
@ -9,14 +9,14 @@ export default function CardInfoLabel({
|
||||
value
|
||||
}: CardInfoLabelProps) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography>
|
||||
<Flex justify='space-between' align='center'>
|
||||
<Text>
|
||||
{label}
|
||||
</Typography>
|
||||
</Text>
|
||||
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
<Text fw={600}>
|
||||
{value}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
||||
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
||||
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, ScrollAreaAutosize, Table, Text } from '@mantine/core'
|
||||
import { ActionIcon, Anchor, Breadcrumbs, Button, Divider, FileButton, Flex, Loader, MantineStyleProp, RingProgress, ScrollAreaAutosize, Table, Text } from '@mantine/core'
|
||||
import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react'
|
||||
|
||||
interface FolderProps {
|
||||
@ -21,7 +20,7 @@ interface DocumentProps {
|
||||
handleDocumentClick: (index: number) => void;
|
||||
}
|
||||
|
||||
const FileItemStyle: SxProps = {
|
||||
const FileItemStyle: MantineStyleProp = {
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
@ -36,13 +35,13 @@ function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
||||
<Flex
|
||||
onClick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<Box
|
||||
sx={FileItemStyle}
|
||||
<Flex
|
||||
style={FileItemStyle}
|
||||
{...props}
|
||||
>
|
||||
<Folder />
|
||||
{folder.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@ -72,15 +71,15 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
||||
|
||||
return (
|
||||
<Flex align='center'>
|
||||
<Box
|
||||
sx={FileItemStyle}
|
||||
<Flex
|
||||
style={FileItemStyle}
|
||||
onClick={() => handleDocumentClick(index)}
|
||||
{...props}
|
||||
>
|
||||
<InsertDriveFile />
|
||||
{doc.name}
|
||||
</Box>
|
||||
<Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
if (!isLoading) {
|
||||
@ -94,7 +93,7 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
||||
<IconDownload />
|
||||
}
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@ -171,7 +170,7 @@ export default function FolderViewer() {
|
||||
|
||||
if (foldersLoading || documentsLoading) {
|
||||
return (
|
||||
<CircularProgress />
|
||||
<Loader />
|
||||
)
|
||||
}
|
||||
|
||||
@ -205,16 +204,12 @@ export default function FolderViewer() {
|
||||
</Breadcrumbs>
|
||||
|
||||
{currentFolder &&
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
<Flex direction='column' gap='sm'>
|
||||
<Flex direction='column' gap='sm' p='sm' style={{
|
||||
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
|
||||
borderRadius: '8px',
|
||||
p: '16px'
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
||||
<Flex gap='sm'>
|
||||
<FileButton multiple onChange={handleFileInput}>
|
||||
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
|
||||
</FileButton>
|
||||
@ -240,7 +235,7 @@ export default function FolderViewer() {
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Divider />
|
||||
|
||||
@ -264,8 +259,8 @@ export default function FolderViewer() {
|
||||
))}
|
||||
</Flex>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
|
||||
<Table
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Box } from '@mui/material'
|
||||
import { IServer } from '../interfaces/servers'
|
||||
import { useServerIps } from '../hooks/swrHooks'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Table } from '@mantine/core'
|
||||
import { Flex, Table } from '@mantine/core'
|
||||
|
||||
function ServerData({ id }: IServer) {
|
||||
const { serverIps } = useServerIps(id, 0, 10)
|
||||
@ -17,7 +16,7 @@ function ServerData({ id }: IServer) {
|
||||
]
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
|
||||
<Flex direction='column' p='sm'>
|
||||
{serverIps &&
|
||||
// <FullFeaturedCrudGrid
|
||||
// initialRows={serverIps}
|
||||
@ -48,7 +47,7 @@ function ServerData({ id }: IServer) {
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
}
|
||||
</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useHardwares, useServers } from '../hooks/swrHooks'
|
||||
import ServerService from '../services/ServersService'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Close } from '@mui/icons-material'
|
||||
import ServerData from './ServerData'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AppBar, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
||||
import { useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useServerIps, useServers } from '../hooks/swrHooks'
|
||||
import ServerService from '../services/ServersService'
|
||||
@ -92,7 +92,7 @@ export default function ServerIpsView() {
|
||||
/>
|
||||
)
|
||||
}
|
||||
//value={search}
|
||||
//value={search}
|
||||
/>
|
||||
</form>
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
||||
import { Type } from 'ol/geom/Geometry'
|
||||
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
||||
import Feature from 'ol/Feature'
|
||||
import { SatelliteMapsProvider } from '../../interfaces/map'
|
||||
import { IGeometryType, SatelliteMapsProvider } from '../../interfaces/map'
|
||||
import { containsExtent, Extent } from 'ol/extent'
|
||||
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
|
||||
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
|
||||
@ -24,36 +24,28 @@ import { Stroke, Fill, Circle as CircleStyle, Style, Text } from 'ol/style'
|
||||
import { calculateCenter, calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
|
||||
import MapBrowserEvent from 'ol/MapBrowserEvent'
|
||||
import { get, transform } from 'ol/proj'
|
||||
import { useCities } from '../../hooks/swrHooks'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { Accordion, ActionIcon, Autocomplete, Box, Button, CloseButton, Flex, Grid, Select as MantineSelect, Text as MantineText, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Divider, Portal } from '@mantine/core'
|
||||
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
|
||||
import { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, Text as MantineText, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Divider, Portal, Tree, Group, TreeNodeData } from '@mantine/core'
|
||||
import { IconApi, IconArrowBackUp, IconArrowsMove, IconChevronDown, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
|
||||
import { getGridCellPosition } from './mapUtils'
|
||||
import { IFigure, ILine } from '../../interfaces/gis'
|
||||
import axios from 'axios'
|
||||
import ObjectParameter from './ObjectParameter'
|
||||
import { IObjectData, IObjectParam } from '../../interfaces/objects'
|
||||
import { IObjectData, IObjectList, IObjectParam } from '../../interfaces/objects'
|
||||
import ObjectData from './ObjectData'
|
||||
import { uploadCoordinates } from '../../actions/map'
|
||||
import MapToolbar from './MapToolbar/MapToolbar'
|
||||
import MapStatusbar from './MapStatusbar/MapStatusbar'
|
||||
|
||||
const MapComponent = () => {
|
||||
//const { cities } = useCities(100, 1)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (cities) {
|
||||
// cities.map((city: any) => {
|
||||
// citiesLayer.current?.getSource()?.addFeature(new Feature(new Point(fromLonLat([city.longitude, city.width]))))
|
||||
// })
|
||||
// }
|
||||
// }, [cities])
|
||||
|
||||
const [currentCoordinate, setCurrentCoordinate] = useState<Coordinate | null>(null)
|
||||
const [currentZ, setCurrentZ] = useState<number | undefined>(undefined)
|
||||
const [currentX, setCurrentX] = useState<number | undefined>(undefined)
|
||||
const [currentY, setCurrentY] = useState<number | undefined>(undefined)
|
||||
|
||||
const [file, setFile] = useState(null)
|
||||
const [file, setFile] = useState<File | null>(null)
|
||||
const [polygonExtent, setPolygonExtent] = useState<Extent | undefined>(undefined)
|
||||
const [bottomLeft, setBottomLeft] = useState<Coordinate | undefined>(undefined)
|
||||
const [topLeft, setTopLeft] = useState<Coordinate | undefined>(undefined)
|
||||
@ -70,7 +62,7 @@ const MapComponent = () => {
|
||||
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
|
||||
|
||||
const customMapSource = useRef<XYZ>(new XYZ({
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tile/custom/{z}/{x}/{y}`,
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/custom/{z}/{x}/{y}`,
|
||||
attributions: 'Custom map data'
|
||||
}))
|
||||
|
||||
@ -133,7 +125,7 @@ const MapComponent = () => {
|
||||
|
||||
draw.current.on('drawend', function (s) {
|
||||
console.log(s.feature.getGeometry()?.getType())
|
||||
let type = 'POLYGON'
|
||||
let type: IGeometryType = 'POLYGON'
|
||||
|
||||
switch (s.feature.getGeometry()?.getType()) {
|
||||
case 'LineString':
|
||||
@ -146,7 +138,7 @@ const MapComponent = () => {
|
||||
type = 'POLYGON'
|
||||
break
|
||||
}
|
||||
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates()
|
||||
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
|
||||
uploadCoordinates(coordinates, type)
|
||||
})
|
||||
|
||||
@ -222,10 +214,12 @@ const MapComponent = () => {
|
||||
});
|
||||
|
||||
// tile processing
|
||||
const handleImageDrop = useCallback((event: any) => {
|
||||
const handleImageDrop = useCallback((event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!event.dataTransfer?.files) return
|
||||
|
||||
const files = event.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
const file = files[0];
|
||||
@ -657,27 +651,6 @@ const MapComponent = () => {
|
||||
}
|
||||
}, [currentTool])
|
||||
|
||||
const uploadCoordinates = async (coordinates: any, type: any) => {
|
||||
try {
|
||||
const response = await fetch(`${import.meta.env.VITE_API_EMS_URL}/nodes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ coordinates, object_id: 1, type: type }) // Replace with actual object_id
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('Node created:', data);
|
||||
} else {
|
||||
console.error('Failed to upload coordinates');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(1)
|
||||
|
||||
const [statusText, setStatusText] = useState('')
|
||||
@ -721,7 +694,7 @@ const MapComponent = () => {
|
||||
formData.append('brX', bottomRight[0].toString())
|
||||
formData.append('brY', bottomRight[1].toString())
|
||||
|
||||
await fetch(`${import.meta.env.VITE_API_EMS_URL}/upload`, { method: 'POST', body: formData })
|
||||
await fetch(`${import.meta.env.VITE_API_EMS_URL}/tiles/upload`, { method: 'POST', body: formData })
|
||||
}
|
||||
}
|
||||
|
||||
@ -864,6 +837,122 @@ const MapComponent = () => {
|
||||
|
||||
const [searchCity, setSearchCity] = useState<string | undefined>("")
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedCity ? `/general/objects/list?year=${selectedYear}&city_id=${selectedCity}&planning=0` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
const { data: planningObjectsList } = useSWR(
|
||||
selectedYear && selectedCity ? `/general/objects/list?year=${selectedYear}&city_id=${selectedCity}&planning=1` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
const [objectsList, setObjectsList] = useState<TreeNodeData[] | null>(null)
|
||||
const [selectedObjectList, setSelectedObjectList] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedObjectList || !map.current) return;
|
||||
|
||||
// Define the highlight style
|
||||
const highlightStyle = new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'yellow',
|
||||
width: 3,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 255, 0, 0.3)',
|
||||
}),
|
||||
});
|
||||
|
||||
if (figuresLayer.current) {
|
||||
// Reset styles and apply highlight to matching features
|
||||
figuresLayer.current.getSource().getFeatures().forEach((feature) => {
|
||||
if (selectedObjectList == feature.get('type')) {
|
||||
feature.setStyle(highlightStyle);
|
||||
} else {
|
||||
feature.setStyle(null); // Reset to default style
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (linesLayer.current) {
|
||||
// Reset styles and apply highlight to matching features
|
||||
linesLayer.current.getSource().getFeatures().forEach((feature) => {
|
||||
if (selectedObjectList == feature.get('type')) {
|
||||
feature.setStyle(highlightStyle);
|
||||
} else {
|
||||
feature.setStyle(null); // Reset to default style
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [selectedObjectList])
|
||||
|
||||
useEffect(() => {
|
||||
if (existingObjectsList && planningObjectsList) {
|
||||
setObjectsList([
|
||||
{
|
||||
label: 'Существующие',
|
||||
value: 'existing',
|
||||
children: existingObjectsList.map((list: IObjectList) => ({
|
||||
label: `${list.name} (${list.count})`,
|
||||
value: list.id,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: 'Планируемые',
|
||||
value: 'planning',
|
||||
children: planningObjectsList.map((list: IObjectList) => ({
|
||||
label: `${list.name} (${list.count})`,
|
||||
value: list.id
|
||||
}))
|
||||
}
|
||||
])
|
||||
}
|
||||
}, [existingObjectsList, planningObjectsList])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentObjectId) {
|
||||
// Define the highlight style
|
||||
const highlightStyle = new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'red',
|
||||
width: 3,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 255, 0, 0.3)',
|
||||
}),
|
||||
});
|
||||
|
||||
if (figuresLayer.current) {
|
||||
// Reset styles and apply highlight to matching features
|
||||
figuresLayer.current.getSource().getFeatures().forEach((feature) => {
|
||||
if (currentObjectId == feature.get('object_id')) {
|
||||
feature.setStyle(highlightStyle);
|
||||
} else {
|
||||
feature.setStyle(null); // Reset to default style
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (linesLayer.current) {
|
||||
// Reset styles and apply highlight to matching features
|
||||
linesLayer.current.getSource().getFeatures().forEach((feature) => {
|
||||
if (currentObjectId == feature.get('object_id')) {
|
||||
feature.setStyle(highlightStyle);
|
||||
} else {
|
||||
feature.setStyle(null); // Reset to default style
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [currentObjectId])
|
||||
|
||||
const { data: currentObjectData } = useSWR(
|
||||
currentObjectId ? `/general/objects/${currentObjectId}` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
@ -939,7 +1028,9 @@ const MapComponent = () => {
|
||||
const feature = new Feature(ellipseGeom)
|
||||
|
||||
feature.setStyle(firstStyleFunction(feature))
|
||||
feature.set('type', figure.type)
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
}
|
||||
|
||||
@ -965,6 +1056,8 @@ const MapComponent = () => {
|
||||
})
|
||||
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
feature.set('type', figure.type)
|
||||
feature.setStyle(thirdStyleFunction(feature))
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
}
|
||||
@ -994,6 +1087,8 @@ const MapComponent = () => {
|
||||
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
|
||||
const feature1 = new Feature(geometry1)
|
||||
feature1.set('object_id', figure.object_id)
|
||||
feature1.set('planning', figure.planning)
|
||||
feature1.set('type', figure.type)
|
||||
feature1.set('angle', figure.angle)
|
||||
feature1.setStyle(fourthStyleFunction(feature1))
|
||||
figuresLayer.current?.getSource()?.addFeature(feature1)
|
||||
@ -1025,6 +1120,8 @@ const MapComponent = () => {
|
||||
|
||||
const feature = new Feature(new LineString(testCoords))
|
||||
feature.setStyle(styleFunction(feature))
|
||||
feature.set('type', line.type)
|
||||
feature.set('planning', line.planning)
|
||||
feature.set('object_id', line.object_id)
|
||||
|
||||
linesLayer.current?.getSource()?.addFeature(feature)
|
||||
@ -1037,6 +1134,12 @@ const MapComponent = () => {
|
||||
<Box w={'100%'} h='100%' pos={'relative'}>
|
||||
<Portal target='#header-portal'>
|
||||
<Flex gap={'sm'} direction={'row'}>
|
||||
<Flex align='center' direction='row' 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>
|
||||
|
||||
<form>
|
||||
<Autocomplete
|
||||
placeholder="Район"
|
||||
@ -1091,76 +1194,14 @@ const MapComponent = () => {
|
||||
</Portal>
|
||||
|
||||
<Flex w={'100%'} h={'100%'} pos={'absolute'}>
|
||||
<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))
|
||||
}}>
|
||||
<IconApi />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||
saveFeatures()
|
||||
}}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
||||
draw.current?.removeLastPoint()
|
||||
}}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant='transparent'
|
||||
onClick={() => map?.current?.addInteraction(new Translate())}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant='transparent'
|
||||
>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
<MapToolbar
|
||||
currentTool={currentTool}
|
||||
onSave={() => saveFeatures()}
|
||||
onRemove={() => draw.current?.removeLastPoint()}
|
||||
handleToolSelect={handleToolSelect}
|
||||
onMover={() => map?.current?.addInteraction(new Translate())}
|
||||
colorScheme={colorScheme}
|
||||
/>
|
||||
|
||||
<Flex direction='column' mah={'86%'} pl='sm' style={{
|
||||
...mapControlsStyle,
|
||||
@ -1188,73 +1229,76 @@ const MapComponent = () => {
|
||||
</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={'objects'} value={'Объекты'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
|
||||
{objectsList &&
|
||||
<Tree
|
||||
data={objectsList}
|
||||
selectOnClick
|
||||
levelOffset={23}
|
||||
renderNode={({ node, expanded, hasChildren, elementProps }) => (
|
||||
<Group gap={6} {...elementProps} onClick={(e) => {
|
||||
elementProps.onClick(e)
|
||||
if (node.value !== 'existing' && node.value !== 'planning') {
|
||||
setSelectedObjectList(Number(node.value))
|
||||
}
|
||||
}}>
|
||||
{hasChildren && (
|
||||
<IconChevronDown
|
||||
size={18}
|
||||
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MantineText size='sm'>{node.label}</MantineText>
|
||||
</Group>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
|
||||
<Accordion.Item key={'current_object'} value='current_object'>
|
||||
<Accordion.Control icon={<IconTable />}>
|
||||
{'Текущий объект'}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<ObjectData {...currentObjectData as IObjectData} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
{currentObjectId &&
|
||||
<Accordion.Item key={'current_object'} value={currentObjectId}>
|
||||
<Accordion.Control icon={<IconTable />}>
|
||||
{'Текущий объект'}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<ObjectData {...currentObjectData as IObjectData} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
}
|
||||
|
||||
{valuesData && <Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Flex gap={'sm'} direction={'column'}>
|
||||
{Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
|
||||
return (
|
||||
<ObjectParameter key={param.id_param} value={param.value} id_param={param.id_param} />
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>}
|
||||
{valuesData &&
|
||||
<Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Flex gap={'sm'} direction={'column'}>
|
||||
{Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
|
||||
return (
|
||||
<ObjectParameter key={param.id_param} value={param.value} id_param={param.id_param} />
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
}
|
||||
</Accordion>
|
||||
</ScrollAreaAutosize>
|
||||
|
||||
</Flex>
|
||||
|
||||
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
||||
<MantineText fz='xs' w={rem(130)}>
|
||||
x: {currentCoordinate?.[0]}
|
||||
</MantineText>
|
||||
|
||||
<MantineText fz='xs' w={rem(130)}>
|
||||
y: {currentCoordinate?.[1]}
|
||||
</MantineText>
|
||||
|
||||
<Divider orientation='vertical' />
|
||||
|
||||
<MantineText fz='xs'>
|
||||
Z={currentZ}
|
||||
</MantineText>
|
||||
|
||||
<MantineText fz='xs'>
|
||||
X={currentX}
|
||||
</MantineText>
|
||||
|
||||
<MantineText fz='xs'>
|
||||
Y={currentY}
|
||||
</MantineText>
|
||||
|
||||
<MantineText fz='xs' ml='auto'>
|
||||
{statusText}
|
||||
</MantineText>
|
||||
</Flex>
|
||||
<MapStatusbar
|
||||
mapControlsStyle={mapControlsStyle}
|
||||
currentCoordinate={currentCoordinate}
|
||||
currentX={currentX}
|
||||
currentY={currentY}
|
||||
currentZ={currentZ}
|
||||
statusText={statusText}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
|
||||
|
@ -10,12 +10,12 @@ register(proj4);
|
||||
const yandexProjection = get('EPSG:3395')?.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]) || 'EPSG:3395'
|
||||
|
||||
const googleMapsSatelliteSource = new XYZ({
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tile/google/{z}/{x}/{y}`,
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/google/{z}/{x}/{y}`,
|
||||
attributions: 'Map data © Google'
|
||||
})
|
||||
|
||||
const yandexMapsSatelliteSource = new XYZ({
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tile/yandex/{z}/{x}/{y}`,
|
||||
url: `${import.meta.env.VITE_API_EMS_URL}/tiles/tile/yandex/{z}/{x}/{y}`,
|
||||
attributions: 'Map data © Yandex',
|
||||
projection: yandexProjection,
|
||||
})
|
||||
|
53
client/src/components/map/MapStatusbar/MapStatusbar.tsx
Normal file
53
client/src/components/map/MapStatusbar/MapStatusbar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { Divider, Flex, rem, Text } from '@mantine/core'
|
||||
import { Coordinate } from 'ol/coordinate';
|
||||
import React, { CSSProperties } from 'react'
|
||||
|
||||
interface IMapStatusbarProps {
|
||||
mapControlsStyle: CSSProperties;
|
||||
currentCoordinate: Coordinate | null;
|
||||
currentX: number | undefined;
|
||||
currentY: number | undefined;
|
||||
currentZ: number | undefined;
|
||||
statusText: string;
|
||||
}
|
||||
|
||||
const MapStatusbar = ({
|
||||
mapControlsStyle,
|
||||
currentCoordinate,
|
||||
currentX,
|
||||
currentY,
|
||||
currentZ,
|
||||
statusText
|
||||
}: IMapStatusbarProps) => {
|
||||
return (
|
||||
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
||||
<Text fz='xs' w={rem(130)}>
|
||||
x: {currentCoordinate?.[0]}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' w={rem(130)}>
|
||||
y: {currentCoordinate?.[1]}
|
||||
</Text>
|
||||
|
||||
<Divider orientation='vertical' />
|
||||
|
||||
<Text fz='xs'>
|
||||
Z={currentZ}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
X={currentX}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
Y={currentY}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' ml='auto'>
|
||||
{statusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default MapStatusbar
|
93
client/src/components/map/MapToolbar/MapToolbar.tsx
Normal file
93
client/src/components/map/MapToolbar/MapToolbar.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import { ActionIcon, MantineColorScheme } from '@mantine/core'
|
||||
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler } from '@tabler/icons-react'
|
||||
import { Type } from 'ol/geom/Geometry'
|
||||
import React from 'react'
|
||||
|
||||
interface IToolbarProps {
|
||||
currentTool: Type | null;
|
||||
onSave: () => void;
|
||||
onRemove: () => void;
|
||||
handleToolSelect: (tool: Type) => void;
|
||||
onMover: () => void;
|
||||
colorScheme: MantineColorScheme;
|
||||
}
|
||||
|
||||
const MapToolbar = ({
|
||||
currentTool,
|
||||
onSave,
|
||||
onRemove,
|
||||
handleToolSelect,
|
||||
onMover,
|
||||
colorScheme
|
||||
}: IToolbarProps) => {
|
||||
return (
|
||||
<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))
|
||||
}}>
|
||||
<IconApi />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={onSave}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={onRemove}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
handleToolSelect('Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant='transparent'
|
||||
onClick={onMover}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant='transparent'
|
||||
>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
)
|
||||
}
|
||||
|
||||
export default MapToolbar
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Table } from '@mantine/core'
|
||||
import { Flex } from '@mantine/core'
|
||||
import { IObjectData, IObjectType } from '../../interfaces/objects'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
@ -17,7 +17,7 @@ const ObjectData = (object_data: IObjectData) => {
|
||||
<Flex gap='sm' direction='column'>
|
||||
{Array.isArray(typeData) && (typeData.find(type => Number(type.id) === Number(object_data.type)) as IObjectType).name}
|
||||
|
||||
|
||||
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { Checkbox, Flex, Grid } from '@mantine/core'
|
||||
import { Checkbox, Grid } from '@mantine/core'
|
||||
import { IObjectParam, IParam } from '../../interfaces/objects'
|
||||
|
||||
const ObjectParameter = ({
|
||||
|
@ -11,7 +11,9 @@ export interface IFigure {
|
||||
label_top: number | null,
|
||||
label_angle: number | null,
|
||||
label_size: number | null,
|
||||
year: number
|
||||
year: number,
|
||||
type: number,
|
||||
planning: boolean
|
||||
}
|
||||
|
||||
export interface ILine {
|
||||
@ -28,5 +30,7 @@ export interface ILine {
|
||||
label_sizes: string | null,
|
||||
label_angels: string | null,
|
||||
label_positions: string | null,
|
||||
year: number
|
||||
year: number,
|
||||
type: number,
|
||||
planning: boolean
|
||||
}
|
@ -3,4 +3,11 @@ export interface SatelliteMapsProviders {
|
||||
yandex: 'yandex';
|
||||
custom: 'custom';
|
||||
}
|
||||
export type SatelliteMapsProvider = SatelliteMapsProviders[keyof SatelliteMapsProviders]
|
||||
export type SatelliteMapsProvider = SatelliteMapsProviders[keyof SatelliteMapsProviders]
|
||||
|
||||
export interface IGeometryTypes {
|
||||
LINE: 'LINE'
|
||||
POLYGON: 'POLYGON'
|
||||
}
|
||||
|
||||
export type IGeometryType = IGeometryTypes[keyof IGeometryTypes]
|
||||
|
@ -1,3 +1,9 @@
|
||||
export interface IObjectList {
|
||||
id: number,
|
||||
name: string,
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface IObjectData {
|
||||
object_id: string,
|
||||
id_city: number,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Box } from "@mui/material"
|
||||
import { useCities } from "../hooks/swrHooks"
|
||||
import { useEffect, useState } from "react"
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
||||
import axiosInstance from "../http/axiosInstance"
|
||||
import { BASE_URL } from "../constants"
|
||||
import { Flex } from "@mantine/core"
|
||||
|
||||
|
||||
export default function ApiTest() {
|
||||
@ -36,7 +36,7 @@ export default function ApiTest() {
|
||||
]
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
||||
<Flex direction='column' gap='sm' h='100%'>
|
||||
<DataGrid
|
||||
rows={cities || []}
|
||||
columns={citiesColumns}
|
||||
@ -46,6 +46,6 @@ export default function ApiTest() {
|
||||
paginationModel={paginationModel}
|
||||
onPaginationModelChange={setPaginationModel}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
12
client/src/pages/ComponentTest.tsx
Normal file
12
client/src/pages/ComponentTest.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Flex } from '@mantine/core'
|
||||
import ServerHardware from '../components/ServerHardware'
|
||||
|
||||
const ComponentTest = () => {
|
||||
return (
|
||||
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
|
||||
<ServerHardware />
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComponentTest
|
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Card, Stack } from '@mui/material';
|
||||
import { Card, Flex } from '@mantine/core';
|
||||
|
||||
function CardComponent({
|
||||
url,
|
||||
@ -7,10 +7,10 @@ function CardComponent({
|
||||
}: { url: any, is_alive: any }) {
|
||||
return (
|
||||
<Card>
|
||||
<Stack p='24px' direction='column'>
|
||||
<Flex p='sm' direction='column'>
|
||||
<p>{url}</p>
|
||||
<p>{JSON.stringify(is_alive)}</p>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@ -38,11 +38,11 @@ export default function MonitorPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stack direction='column' spacing={1}>
|
||||
<Flex direction='column' gap='sm'>
|
||||
{servers.length > 0 && servers.map((server: any) => (
|
||||
<CardComponent url={server.name} is_alive={server.status} />
|
||||
))}
|
||||
</Stack>
|
||||
</Flex>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import { CircularProgress, Fade, Grow } from '@mui/material'
|
||||
import { useState } from 'react'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
import AuthService from '../../services/AuthService';
|
||||
import { CheckCircle } from '@mui/icons-material';
|
||||
import { Button, Flex, Paper, Text, TextInput } from '@mantine/core';
|
||||
import { Button, Flex, Loader, Paper, Text, TextInput, Transition } from '@mantine/core';
|
||||
|
||||
interface PasswordResetProps {
|
||||
email: string;
|
||||
@ -39,47 +38,54 @@ function PasswordReset() {
|
||||
</Text>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!success && <Fade in={!success}>
|
||||
<Flex direction='column' gap={'md'}>
|
||||
<Text>
|
||||
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label='E-mail'
|
||||
required
|
||||
{...register('email', { required: 'Введите E-mail' })}
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
|
||||
<Flex gap='sm'>
|
||||
<Button flex={1} type="submit" disabled={isSubmitting || watch('email').length == 0} variant='filled'>
|
||||
{isSubmitting ? <CircularProgress size={16} /> : 'Восстановить пароль'}
|
||||
</Button>
|
||||
|
||||
<Button flex={1} component='a' href="/auth/signin" type="button" variant='light'>
|
||||
Назад
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
</Fade>}
|
||||
{success &&
|
||||
<Grow in={success}>
|
||||
<Flex direction='column' gap='sm'>
|
||||
<Flex align='center' gap='sm'>
|
||||
<CheckCircle color='success' />
|
||||
{!success &&
|
||||
<Transition mounted={!success} transition='fade'>
|
||||
{(styles) =>
|
||||
<Flex style={styles} direction='column' gap={'md'}>
|
||||
<Text>
|
||||
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
||||
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label='E-mail'
|
||||
required
|
||||
{...register('email', { required: 'Введите E-mail' })}
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
|
||||
<Flex gap='sm'>
|
||||
<Button flex={1} type="submit" disabled={isSubmitting || watch('email').length == 0} variant='filled'>
|
||||
{isSubmitting ? <Loader size={16} /> : 'Восстановить пароль'}
|
||||
</Button>
|
||||
|
||||
<Button flex={1} component='a' href="/auth/signin" type="button" variant='light'>
|
||||
Назад
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
<Flex gap='sm'>
|
||||
<Button component='a' href="/auth/signin" type="button">
|
||||
Войти
|
||||
</Button>
|
||||
}
|
||||
|
||||
</Transition>
|
||||
}
|
||||
{success &&
|
||||
<Transition mounted={!success} transition='scale'>
|
||||
{(styles) =>
|
||||
<Flex style={styles} direction='column' gap='sm'>
|
||||
<Flex align='center' gap='sm'>
|
||||
<CheckCircle color='success' />
|
||||
<Text>
|
||||
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap='sm'>
|
||||
<Button component='a' href="/auth/signin" type="button">
|
||||
Войти
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Grow>
|
||||
}
|
||||
</Transition>
|
||||
}
|
||||
</form>
|
||||
</Flex>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||
import UserService from '../../services/UserService';
|
||||
import { IUser } from '../../interfaces/user';
|
||||
import { Button, Flex, Loader, Paper, Text, TextInput } from '@mantine/core';
|
||||
|
||||
const SignUp = () => {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm<IUser>({
|
||||
const { register, handleSubmit, formState: { errors, isValid, isSubmitting } } = useForm<IUser>({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
login: '',
|
||||
@ -26,77 +26,66 @@ const SignUp = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="sm">
|
||||
<Box my={4}>
|
||||
<Typography variant="h4" component="h1" gutterBottom>
|
||||
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
|
||||
<Flex direction='column' gap='sm'>
|
||||
<Text size="xl" fw={500}>
|
||||
Регистрация
|
||||
</Typography>
|
||||
</Text>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Email"
|
||||
required
|
||||
{...register('email', { required: 'Email обязателен' })}
|
||||
error={!!errors.email}
|
||||
helperText={errors.email?.message}
|
||||
/>
|
||||
<Flex direction='column' gap='sm'>
|
||||
<TextInput
|
||||
label='Email'
|
||||
required
|
||||
{...register('email', { required: 'Email обязателен' })}
|
||||
error={errors.email?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Логин"
|
||||
required
|
||||
{...register('login', { required: 'Логин обязателен' })}
|
||||
error={!!errors.login}
|
||||
helperText={errors.login?.message}
|
||||
/>
|
||||
<TextInput
|
||||
label='Логин'
|
||||
required
|
||||
{...register('login', { required: 'Логин обязателен' })}
|
||||
error={errors.login?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Телефон"
|
||||
{...register('phone')}
|
||||
error={!!errors.phone}
|
||||
helperText={errors.phone?.message}
|
||||
/>
|
||||
<TextInput
|
||||
label='Телефон'
|
||||
required
|
||||
{...register('phone')}
|
||||
error={errors.phone?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Имя"
|
||||
{...register('name')}
|
||||
error={!!errors.name}
|
||||
helperText={errors.name?.message}
|
||||
/>
|
||||
<TextInput
|
||||
label='Имя'
|
||||
required
|
||||
{...register('name')}
|
||||
error={errors.name?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Фамилия"
|
||||
{...register('surname')}
|
||||
error={!!errors.surname}
|
||||
helperText={errors.surname?.message}
|
||||
/>
|
||||
<TextInput
|
||||
label='Фамилия'
|
||||
required
|
||||
{...register('surname')}
|
||||
error={errors.surname?.message}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="password"
|
||||
label="Пароль"
|
||||
required
|
||||
{...register('password', { required: 'Пароль обязателен' })}
|
||||
error={!!errors.password}
|
||||
helperText={errors.password?.message}
|
||||
/>
|
||||
<TextInput
|
||||
label='Пароль'
|
||||
type="password"
|
||||
required
|
||||
{...register('password', { required: 'Пароль обязателен' })}
|
||||
error={errors.password?.message}
|
||||
/>
|
||||
|
||||
<Button type="submit" variant="contained" color="primary">
|
||||
Зарегистрироваться
|
||||
</Button>
|
||||
<Flex gap='sm'>
|
||||
<Button disabled={!isValid} type="submit" flex={1} variant='filled'>
|
||||
{isSubmitting ? <Loader size={16} /> : 'Зарегистрироваться'}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</form>
|
||||
</Box>
|
||||
</Container>
|
||||
</Flex>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user