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 MapTest from "./pages/MapTest"
|
||||||
import MonitorPage from "./pages/MonitorPage"
|
import MonitorPage from "./pages/MonitorPage"
|
||||||
import DashboardLayout from "./layouts/DashboardLayout"
|
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 { Box, Loader } from "@mantine/core"
|
||||||
import TableTest from "./pages/TableTest"
|
import TableTest from "./pages/TableTest"
|
||||||
|
import ComponentTest from "./pages/ComponentTest"
|
||||||
|
|
||||||
// Определение страниц с путями и компонентом для рендера
|
// Определение страниц с путями и компонентом для рендера
|
||||||
export const pages = [
|
export const pages = [
|
||||||
@ -148,7 +149,7 @@ export const pages = [
|
|||||||
component: <MonitorPage />,
|
component: <MonitorPage />,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
dashboard: true,
|
dashboard: true,
|
||||||
enabled: false,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Table test",
|
label: "Table test",
|
||||||
@ -159,6 +160,15 @@ export const pages = [
|
|||||||
dashboard: true,
|
dashboard: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Component test",
|
||||||
|
path: "/component-test",
|
||||||
|
icon: <IconComponents />,
|
||||||
|
component: <ComponentTest />,
|
||||||
|
drawer: true,
|
||||||
|
dashboard: true,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function App() {
|
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'
|
import { PropsWithChildren } from 'react'
|
||||||
|
|
||||||
interface CardInfoProps extends PropsWithChildren {
|
interface CardInfoProps extends PropsWithChildren {
|
||||||
@ -10,14 +10,14 @@ export default function CardInfo({
|
|||||||
label
|
label
|
||||||
}: CardInfoProps) {
|
}: CardInfoProps) {
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
<Flex direction='column' gap='sm' p='sm'>
|
||||||
<Typography fontWeight={600}>
|
<Text fw={600}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</Paper>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Chip } from '@mui/material'
|
import { Chip } from '@mantine/core';
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
interface CardInfoChipProps {
|
interface CardInfoChipProps {
|
||||||
@ -17,9 +17,10 @@ export default function CardInfoChip({
|
|||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
icon={status ? iconOn : iconOff}
|
icon={status ? iconOn : iconOff}
|
||||||
variant="outlined"
|
|
||||||
label={label}
|
|
||||||
color={status ? "success" : "error"}
|
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 {
|
interface CardInfoLabelProps {
|
||||||
label: string;
|
label: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
@ -9,14 +9,14 @@ export default function CardInfoLabel({
|
|||||||
value
|
value
|
||||||
}: CardInfoLabelProps) {
|
}: CardInfoLabelProps) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<Flex justify='space-between' align='center'>
|
||||||
<Typography>
|
<Text>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<Typography variant="h6" fontWeight={600}>
|
<Text fw={600}>
|
||||||
{value}
|
{value}
|
||||||
</Typography>
|
</Text>
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,12 +1,11 @@
|
|||||||
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
import { useDocuments, useDownload, useFolders } from '../hooks/swrHooks'
|
||||||
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
import { IDocument, IDocumentFolder } from '../interfaces/documents'
|
||||||
import { Box, CircularProgress, Divider, SxProps } from '@mui/material'
|
|
||||||
import { Folder, InsertDriveFile } from '@mui/icons-material'
|
import { Folder, InsertDriveFile } from '@mui/icons-material'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import DocumentService from '../services/DocumentService'
|
import DocumentService from '../services/DocumentService'
|
||||||
import { mutate } from 'swr'
|
import { mutate } from 'swr'
|
||||||
import FileViewer from './modals/FileViewer'
|
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'
|
import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react'
|
||||||
|
|
||||||
interface FolderProps {
|
interface FolderProps {
|
||||||
@ -21,7 +20,7 @@ interface DocumentProps {
|
|||||||
handleDocumentClick: (index: number) => void;
|
handleDocumentClick: (index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileItemStyle: SxProps = {
|
const FileItemStyle: MantineStyleProp = {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -36,13 +35,13 @@ function ItemFolder({ folder, handleFolderClick, ...props }: FolderProps) {
|
|||||||
<Flex
|
<Flex
|
||||||
onClick={() => handleFolderClick(folder)}
|
onClick={() => handleFolderClick(folder)}
|
||||||
>
|
>
|
||||||
<Box
|
<Flex
|
||||||
sx={FileItemStyle}
|
style={FileItemStyle}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<Folder />
|
<Folder />
|
||||||
{folder.name}
|
{folder.name}
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,15 +71,15 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align='center'>
|
<Flex align='center'>
|
||||||
<Box
|
<Flex
|
||||||
sx={FileItemStyle}
|
style={FileItemStyle}
|
||||||
onClick={() => handleDocumentClick(index)}
|
onClick={() => handleDocumentClick(index)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<InsertDriveFile />
|
<InsertDriveFile />
|
||||||
{doc.name}
|
{doc.name}
|
||||||
</Box>
|
</Flex>
|
||||||
<Box>
|
<Flex>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
@ -94,7 +93,7 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
|
|||||||
<IconDownload />
|
<IconDownload />
|
||||||
}
|
}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Box>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -171,7 +170,7 @@ export default function FolderViewer() {
|
|||||||
|
|
||||||
if (foldersLoading || documentsLoading) {
|
if (foldersLoading || documentsLoading) {
|
||||||
return (
|
return (
|
||||||
<CircularProgress />
|
<Loader />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,16 +204,12 @@ export default function FolderViewer() {
|
|||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
|
|
||||||
{currentFolder &&
|
{currentFolder &&
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
<Flex direction='column' gap='sm'>
|
||||||
<Box sx={{
|
<Flex direction='column' gap='sm' p='sm' style={{
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '16px',
|
|
||||||
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
|
border: filesToUpload.length > 0 ? '1px dashed gray' : 'none',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
p: '16px'
|
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ display: 'flex', gap: '16px' }}>
|
<Flex gap='sm'>
|
||||||
<FileButton multiple onChange={handleFileInput}>
|
<FileButton multiple onChange={handleFileInput}>
|
||||||
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
|
{(props) => <Button variant='filled' leftSection={isUploading ? <Loader /> : <IconFilePlus />} {...props}>Добавить</Button>}
|
||||||
</FileButton>
|
</FileButton>
|
||||||
@ -240,7 +235,7 @@ export default function FolderViewer() {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Box>
|
</Flex>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
@ -264,8 +259,8 @@ export default function FolderViewer() {
|
|||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
</Box>
|
</Flex>
|
||||||
</Box>
|
</Flex>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Box } from '@mui/material'
|
|
||||||
import { IServer } from '../interfaces/servers'
|
import { IServer } from '../interfaces/servers'
|
||||||
import { useServerIps } from '../hooks/swrHooks'
|
import { useServerIps } from '../hooks/swrHooks'
|
||||||
import { GridColDef } from '@mui/x-data-grid'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { Table } from '@mantine/core'
|
import { Flex, Table } from '@mantine/core'
|
||||||
|
|
||||||
function ServerData({ id }: IServer) {
|
function ServerData({ id }: IServer) {
|
||||||
const { serverIps } = useServerIps(id, 0, 10)
|
const { serverIps } = useServerIps(id, 0, 10)
|
||||||
@ -17,7 +16,7 @@ function ServerData({ id }: IServer) {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
|
<Flex direction='column' p='sm'>
|
||||||
{serverIps &&
|
{serverIps &&
|
||||||
// <FullFeaturedCrudGrid
|
// <FullFeaturedCrudGrid
|
||||||
// initialRows={serverIps}
|
// initialRows={serverIps}
|
||||||
@ -48,7 +47,7 @@ function ServerData({ id }: IServer) {
|
|||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
}
|
}
|
||||||
</Box>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
||||||
import { Fragment, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { IRegion } from '../interfaces/fuel'
|
import { IRegion } from '../interfaces/fuel'
|
||||||
import { useHardwares, useServers } from '../hooks/swrHooks'
|
import { useHardwares, useServers } from '../hooks/swrHooks'
|
||||||
import ServerService from '../services/ServersService'
|
|
||||||
import { GridColDef } from '@mui/x-data-grid'
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
import { Close } from '@mui/icons-material'
|
import { Close } from '@mui/icons-material'
|
||||||
import ServerData from './ServerData'
|
import ServerData from './ServerData'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AppBar, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
import { AppBar, CircularProgress, Dialog, IconButton, Toolbar } from '@mui/material'
|
||||||
import { Fragment, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { IRegion } from '../interfaces/fuel'
|
import { IRegion } from '../interfaces/fuel'
|
||||||
import { useServerIps, useServers } from '../hooks/swrHooks'
|
import { useServerIps, useServers } from '../hooks/swrHooks'
|
||||||
import ServerService from '../services/ServersService'
|
import ServerService from '../services/ServersService'
|
||||||
@ -92,7 +92,7 @@ export default function ServerIpsView() {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//value={search}
|
//value={search}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
|
|||||||
import { Type } from 'ol/geom/Geometry'
|
import { Type } from 'ol/geom/Geometry'
|
||||||
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
|
||||||
import Feature from 'ol/Feature'
|
import Feature from 'ol/Feature'
|
||||||
import { SatelliteMapsProvider } from '../../interfaces/map'
|
import { IGeometryType, SatelliteMapsProvider } from '../../interfaces/map'
|
||||||
import { containsExtent, Extent } from 'ol/extent'
|
import { containsExtent, Extent } from 'ol/extent'
|
||||||
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
|
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
|
||||||
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
|
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 { calculateCenter, calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
|
||||||
import MapBrowserEvent from 'ol/MapBrowserEvent'
|
import MapBrowserEvent from 'ol/MapBrowserEvent'
|
||||||
import { get, transform } from 'ol/proj'
|
import { get, transform } from 'ol/proj'
|
||||||
import { useCities } from '../../hooks/swrHooks'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
import { BASE_URL } from '../../constants'
|
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 { 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, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
|
import { IconApi, IconArrowBackUp, IconArrowsMove, IconChevronDown, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
|
||||||
import { getGridCellPosition } from './mapUtils'
|
import { getGridCellPosition } from './mapUtils'
|
||||||
import { IFigure, ILine } from '../../interfaces/gis'
|
import { IFigure, ILine } from '../../interfaces/gis'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import ObjectParameter from './ObjectParameter'
|
import ObjectParameter from './ObjectParameter'
|
||||||
import { IObjectData, IObjectParam } from '../../interfaces/objects'
|
import { IObjectData, IObjectList, IObjectParam } from '../../interfaces/objects'
|
||||||
import ObjectData from './ObjectData'
|
import ObjectData from './ObjectData'
|
||||||
|
import { uploadCoordinates } from '../../actions/map'
|
||||||
|
import MapToolbar from './MapToolbar/MapToolbar'
|
||||||
|
import MapStatusbar from './MapStatusbar/MapStatusbar'
|
||||||
|
|
||||||
const MapComponent = () => {
|
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 [currentCoordinate, setCurrentCoordinate] = useState<Coordinate | null>(null)
|
||||||
const [currentZ, setCurrentZ] = useState<number | undefined>(undefined)
|
const [currentZ, setCurrentZ] = useState<number | undefined>(undefined)
|
||||||
const [currentX, setCurrentX] = useState<number | undefined>(undefined)
|
const [currentX, setCurrentX] = useState<number | undefined>(undefined)
|
||||||
const [currentY, setCurrentY] = 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 [polygonExtent, setPolygonExtent] = useState<Extent | undefined>(undefined)
|
||||||
const [bottomLeft, setBottomLeft] = useState<Coordinate | undefined>(undefined)
|
const [bottomLeft, setBottomLeft] = useState<Coordinate | undefined>(undefined)
|
||||||
const [topLeft, setTopLeft] = useState<Coordinate | undefined>(undefined)
|
const [topLeft, setTopLeft] = useState<Coordinate | undefined>(undefined)
|
||||||
@ -70,7 +62,7 @@ const MapComponent = () => {
|
|||||||
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
|
const gMapsSatSource = useRef<XYZ>(googleMapsSatelliteSource)
|
||||||
|
|
||||||
const customMapSource = useRef<XYZ>(new XYZ({
|
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'
|
attributions: 'Custom map data'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -133,7 +125,7 @@ const MapComponent = () => {
|
|||||||
|
|
||||||
draw.current.on('drawend', function (s) {
|
draw.current.on('drawend', function (s) {
|
||||||
console.log(s.feature.getGeometry()?.getType())
|
console.log(s.feature.getGeometry()?.getType())
|
||||||
let type = 'POLYGON'
|
let type: IGeometryType = 'POLYGON'
|
||||||
|
|
||||||
switch (s.feature.getGeometry()?.getType()) {
|
switch (s.feature.getGeometry()?.getType()) {
|
||||||
case 'LineString':
|
case 'LineString':
|
||||||
@ -146,7 +138,7 @@ const MapComponent = () => {
|
|||||||
type = 'POLYGON'
|
type = 'POLYGON'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates()
|
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
|
||||||
uploadCoordinates(coordinates, type)
|
uploadCoordinates(coordinates, type)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -222,10 +214,12 @@ const MapComponent = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// tile processing
|
// tile processing
|
||||||
const handleImageDrop = useCallback((event: any) => {
|
const handleImageDrop = useCallback((event: DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (!event.dataTransfer?.files) return
|
||||||
|
|
||||||
const files = event.dataTransfer.files;
|
const files = event.dataTransfer.files;
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
@ -657,27 +651,6 @@ const MapComponent = () => {
|
|||||||
}
|
}
|
||||||
}, [currentTool])
|
}, [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 [satelliteOpacity, setSatelliteOpacity] = useState<number>(1)
|
||||||
|
|
||||||
const [statusText, setStatusText] = useState('')
|
const [statusText, setStatusText] = useState('')
|
||||||
@ -721,7 +694,7 @@ const MapComponent = () => {
|
|||||||
formData.append('brX', bottomRight[0].toString())
|
formData.append('brX', bottomRight[0].toString())
|
||||||
formData.append('brY', bottomRight[1].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 [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(
|
const { data: currentObjectData } = useSWR(
|
||||||
currentObjectId ? `/general/objects/${currentObjectId}` : null,
|
currentObjectId ? `/general/objects/${currentObjectId}` : null,
|
||||||
(url) => fetcher(url, BASE_URL.ems),
|
(url) => fetcher(url, BASE_URL.ems),
|
||||||
@ -939,7 +1028,9 @@ const MapComponent = () => {
|
|||||||
const feature = new Feature(ellipseGeom)
|
const feature = new Feature(ellipseGeom)
|
||||||
|
|
||||||
feature.setStyle(firstStyleFunction(feature))
|
feature.setStyle(firstStyleFunction(feature))
|
||||||
|
feature.set('type', figure.type)
|
||||||
feature.set('object_id', figure.object_id)
|
feature.set('object_id', figure.object_id)
|
||||||
|
feature.set('planning', figure.planning)
|
||||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -965,6 +1056,8 @@ const MapComponent = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
feature.set('object_id', figure.object_id)
|
feature.set('object_id', figure.object_id)
|
||||||
|
feature.set('planning', figure.planning)
|
||||||
|
feature.set('type', figure.type)
|
||||||
feature.setStyle(thirdStyleFunction(feature))
|
feature.setStyle(thirdStyleFunction(feature))
|
||||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||||
}
|
}
|
||||||
@ -994,6 +1087,8 @@ const MapComponent = () => {
|
|||||||
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
|
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
|
||||||
const feature1 = new Feature(geometry1)
|
const feature1 = new Feature(geometry1)
|
||||||
feature1.set('object_id', figure.object_id)
|
feature1.set('object_id', figure.object_id)
|
||||||
|
feature1.set('planning', figure.planning)
|
||||||
|
feature1.set('type', figure.type)
|
||||||
feature1.set('angle', figure.angle)
|
feature1.set('angle', figure.angle)
|
||||||
feature1.setStyle(fourthStyleFunction(feature1))
|
feature1.setStyle(fourthStyleFunction(feature1))
|
||||||
figuresLayer.current?.getSource()?.addFeature(feature1)
|
figuresLayer.current?.getSource()?.addFeature(feature1)
|
||||||
@ -1025,6 +1120,8 @@ const MapComponent = () => {
|
|||||||
|
|
||||||
const feature = new Feature(new LineString(testCoords))
|
const feature = new Feature(new LineString(testCoords))
|
||||||
feature.setStyle(styleFunction(feature))
|
feature.setStyle(styleFunction(feature))
|
||||||
|
feature.set('type', line.type)
|
||||||
|
feature.set('planning', line.planning)
|
||||||
feature.set('object_id', line.object_id)
|
feature.set('object_id', line.object_id)
|
||||||
|
|
||||||
linesLayer.current?.getSource()?.addFeature(feature)
|
linesLayer.current?.getSource()?.addFeature(feature)
|
||||||
@ -1037,6 +1134,12 @@ const MapComponent = () => {
|
|||||||
<Box w={'100%'} h='100%' pos={'relative'}>
|
<Box w={'100%'} h='100%' pos={'relative'}>
|
||||||
<Portal target='#header-portal'>
|
<Portal target='#header-portal'>
|
||||||
<Flex gap={'sm'} direction={'row'}>
|
<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>
|
<form>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
placeholder="Район"
|
placeholder="Район"
|
||||||
@ -1091,76 +1194,14 @@ const MapComponent = () => {
|
|||||||
</Portal>
|
</Portal>
|
||||||
|
|
||||||
<Flex w={'100%'} h={'100%'} pos={'absolute'}>
|
<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' }}>
|
<MapToolbar
|
||||||
<ActionIcon size='lg' variant='transparent' onClick={() => {
|
currentTool={currentTool}
|
||||||
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
|
onSave={() => saveFeatures()}
|
||||||
}}>
|
onRemove={() => draw.current?.removeLastPoint()}
|
||||||
<IconApi />
|
handleToolSelect={handleToolSelect}
|
||||||
</ActionIcon>
|
onMover={() => map?.current?.addInteraction(new Translate())}
|
||||||
|
colorScheme={colorScheme}
|
||||||
<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>
|
|
||||||
|
|
||||||
<Flex direction='column' mah={'86%'} pl='sm' style={{
|
<Flex direction='column' mah={'86%'} pl='sm' style={{
|
||||||
...mapControlsStyle,
|
...mapControlsStyle,
|
||||||
@ -1188,73 +1229,76 @@ const MapComponent = () => {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Flex>
|
</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 variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
|
||||||
<Accordion.Item key={'objects'} value={'Объекты'}>
|
<Accordion.Item key={'objects'} value={'Объекты'}>
|
||||||
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
||||||
<Accordion.Panel>
|
<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.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|
||||||
<Accordion.Item key={'current_object'} value='current_object'>
|
{currentObjectId &&
|
||||||
<Accordion.Control icon={<IconTable />}>
|
<Accordion.Item key={'current_object'} value={currentObjectId}>
|
||||||
{'Текущий объект'}
|
<Accordion.Control icon={<IconTable />}>
|
||||||
</Accordion.Control>
|
{'Текущий объект'}
|
||||||
<Accordion.Panel>
|
</Accordion.Control>
|
||||||
<ObjectData {...currentObjectData as IObjectData} />
|
<Accordion.Panel>
|
||||||
</Accordion.Panel>
|
<ObjectData {...currentObjectData as IObjectData} />
|
||||||
</Accordion.Item>
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
}
|
||||||
|
|
||||||
{valuesData && <Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
{valuesData &&
|
||||||
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
<Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
||||||
<Accordion.Panel>
|
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
||||||
<Flex gap={'sm'} direction={'column'}>
|
<Accordion.Panel>
|
||||||
{Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
|
<Flex gap={'sm'} direction={'column'}>
|
||||||
return (
|
{Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
|
||||||
<ObjectParameter key={param.id_param} value={param.value} id_param={param.id_param} />
|
return (
|
||||||
)
|
<ObjectParameter key={param.id_param} value={param.value} id_param={param.id_param} />
|
||||||
})}
|
)
|
||||||
</Flex>
|
})}
|
||||||
</Accordion.Panel>
|
</Flex>
|
||||||
</Accordion.Item>}
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</ScrollAreaAutosize>
|
</ScrollAreaAutosize>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}>
|
<MapStatusbar
|
||||||
<MantineText fz='xs' w={rem(130)}>
|
mapControlsStyle={mapControlsStyle}
|
||||||
x: {currentCoordinate?.[0]}
|
currentCoordinate={currentCoordinate}
|
||||||
</MantineText>
|
currentX={currentX}
|
||||||
|
currentY={currentY}
|
||||||
<MantineText fz='xs' w={rem(130)}>
|
currentZ={currentZ}
|
||||||
y: {currentCoordinate?.[1]}
|
statusText={statusText}
|
||||||
</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>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ register(proj4);
|
|||||||
const yandexProjection = get('EPSG:3395')?.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]) || 'EPSG:3395'
|
const yandexProjection = get('EPSG:3395')?.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]) || 'EPSG:3395'
|
||||||
|
|
||||||
const googleMapsSatelliteSource = new XYZ({
|
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'
|
attributions: 'Map data © Google'
|
||||||
})
|
})
|
||||||
|
|
||||||
const yandexMapsSatelliteSource = new XYZ({
|
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',
|
attributions: 'Map data © Yandex',
|
||||||
projection: yandexProjection,
|
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 { IObjectData, IObjectType } from '../../interfaces/objects'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React from 'react'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from '../../http/axiosInstance'
|
import { fetcher } from '../../http/axiosInstance'
|
||||||
import { BASE_URL } from '../../constants'
|
import { BASE_URL } from '../../constants'
|
||||||
import { Checkbox, Flex, Grid } from '@mantine/core'
|
import { Checkbox, Grid } from '@mantine/core'
|
||||||
import { IObjectParam, IParam } from '../../interfaces/objects'
|
import { IObjectParam, IParam } from '../../interfaces/objects'
|
||||||
|
|
||||||
const ObjectParameter = ({
|
const ObjectParameter = ({
|
||||||
|
@ -11,7 +11,9 @@ export interface IFigure {
|
|||||||
label_top: number | null,
|
label_top: number | null,
|
||||||
label_angle: number | null,
|
label_angle: number | null,
|
||||||
label_size: number | null,
|
label_size: number | null,
|
||||||
year: number
|
year: number,
|
||||||
|
type: number,
|
||||||
|
planning: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILine {
|
export interface ILine {
|
||||||
@ -28,5 +30,7 @@ export interface ILine {
|
|||||||
label_sizes: string | null,
|
label_sizes: string | null,
|
||||||
label_angels: string | null,
|
label_angels: string | null,
|
||||||
label_positions: string | null,
|
label_positions: string | null,
|
||||||
year: number
|
year: number,
|
||||||
|
type: number,
|
||||||
|
planning: boolean
|
||||||
}
|
}
|
@ -4,3 +4,10 @@ export interface SatelliteMapsProviders {
|
|||||||
custom: 'custom';
|
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 {
|
export interface IObjectData {
|
||||||
object_id: string,
|
object_id: string,
|
||||||
id_city: number,
|
id_city: number,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Box } from "@mui/material"
|
|
||||||
import { useCities } from "../hooks/swrHooks"
|
import { useCities } from "../hooks/swrHooks"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
||||||
import axiosInstance from "../http/axiosInstance"
|
import axiosInstance from "../http/axiosInstance"
|
||||||
import { BASE_URL } from "../constants"
|
import { BASE_URL } from "../constants"
|
||||||
|
import { Flex } from "@mantine/core"
|
||||||
|
|
||||||
|
|
||||||
export default function ApiTest() {
|
export default function ApiTest() {
|
||||||
@ -36,7 +36,7 @@ export default function ApiTest() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
<Flex direction='column' gap='sm' h='100%'>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={cities || []}
|
rows={cities || []}
|
||||||
columns={citiesColumns}
|
columns={citiesColumns}
|
||||||
@ -46,6 +46,6 @@ export default function ApiTest() {
|
|||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
onPaginationModelChange={setPaginationModel}
|
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 { useEffect, useState } from 'react'
|
||||||
import { Card, Stack } from '@mui/material';
|
import { Card, Flex } from '@mantine/core';
|
||||||
|
|
||||||
function CardComponent({
|
function CardComponent({
|
||||||
url,
|
url,
|
||||||
@ -7,10 +7,10 @@ function CardComponent({
|
|||||||
}: { url: any, is_alive: any }) {
|
}: { url: any, is_alive: any }) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Stack p='24px' direction='column'>
|
<Flex p='sm' direction='column'>
|
||||||
<p>{url}</p>
|
<p>{url}</p>
|
||||||
<p>{JSON.stringify(is_alive)}</p>
|
<p>{JSON.stringify(is_alive)}</p>
|
||||||
</Stack>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -38,11 +38,11 @@ export default function MonitorPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Stack direction='column' spacing={1}>
|
<Flex direction='column' gap='sm'>
|
||||||
{servers.length > 0 && servers.map((server: any) => (
|
{servers.length > 0 && servers.map((server: any) => (
|
||||||
<CardComponent url={server.name} is_alive={server.status} />
|
<CardComponent url={server.name} is_alive={server.status} />
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import { CircularProgress, Fade, Grow } from '@mui/material'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import AuthService from '../../services/AuthService';
|
import AuthService from '../../services/AuthService';
|
||||||
import { CheckCircle } from '@mui/icons-material';
|
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 {
|
interface PasswordResetProps {
|
||||||
email: string;
|
email: string;
|
||||||
@ -39,47 +38,54 @@ function PasswordReset() {
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{!success && <Fade in={!success}>
|
{!success &&
|
||||||
<Flex direction='column' gap={'md'}>
|
<Transition mounted={!success} transition='fade'>
|
||||||
<Text>
|
{(styles) =>
|
||||||
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
<Flex style={styles} direction='column' gap={'md'}>
|
||||||
</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' />
|
|
||||||
<Text>
|
<Text>
|
||||||
На указанный адрес было отправлено письмо с новыми данными для авторизации.
|
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
|
||||||
</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>
|
||||||
<Flex gap='sm'>
|
}
|
||||||
<Button component='a' href="/auth/signin" type="button">
|
|
||||||
Войти
|
</Transition>
|
||||||
</Button>
|
}
|
||||||
|
{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>
|
||||||
</Flex>
|
}
|
||||||
</Grow>
|
</Transition>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
|
||||||
import UserService from '../../services/UserService';
|
import UserService from '../../services/UserService';
|
||||||
import { IUser } from '../../interfaces/user';
|
import { IUser } from '../../interfaces/user';
|
||||||
|
import { Button, Flex, Loader, Paper, Text, TextInput } from '@mantine/core';
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
const { register, handleSubmit, formState: { errors } } = useForm<IUser>({
|
const { register, handleSubmit, formState: { errors, isValid, isSubmitting } } = useForm<IUser>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: '',
|
email: '',
|
||||||
login: '',
|
login: '',
|
||||||
@ -26,77 +26,66 @@ const SignUp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="sm">
|
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
|
||||||
<Box my={4}>
|
<Flex direction='column' gap='sm'>
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Text size="xl" fw={500}>
|
||||||
Регистрация
|
Регистрация
|
||||||
</Typography>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<TextField
|
<Flex direction='column' gap='sm'>
|
||||||
fullWidth
|
<TextInput
|
||||||
margin="normal"
|
label='Email'
|
||||||
label="Email"
|
required
|
||||||
required
|
{...register('email', { required: 'Email обязателен' })}
|
||||||
{...register('email', { required: 'Email обязателен' })}
|
error={errors.email?.message}
|
||||||
error={!!errors.email}
|
/>
|
||||||
helperText={errors.email?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Логин'
|
||||||
margin="normal"
|
required
|
||||||
label="Логин"
|
{...register('login', { required: 'Логин обязателен' })}
|
||||||
required
|
error={errors.login?.message}
|
||||||
{...register('login', { required: 'Логин обязателен' })}
|
/>
|
||||||
error={!!errors.login}
|
|
||||||
helperText={errors.login?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Телефон'
|
||||||
margin="normal"
|
required
|
||||||
label="Телефон"
|
{...register('phone')}
|
||||||
{...register('phone')}
|
error={errors.phone?.message}
|
||||||
error={!!errors.phone}
|
/>
|
||||||
helperText={errors.phone?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Имя'
|
||||||
margin="normal"
|
required
|
||||||
label="Имя"
|
{...register('name')}
|
||||||
{...register('name')}
|
error={errors.name?.message}
|
||||||
error={!!errors.name}
|
/>
|
||||||
helperText={errors.name?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Фамилия'
|
||||||
margin="normal"
|
required
|
||||||
label="Фамилия"
|
{...register('surname')}
|
||||||
{...register('surname')}
|
error={errors.surname?.message}
|
||||||
error={!!errors.surname}
|
/>
|
||||||
helperText={errors.surname?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<TextInput
|
||||||
fullWidth
|
label='Пароль'
|
||||||
margin="normal"
|
type="password"
|
||||||
type="password"
|
required
|
||||||
label="Пароль"
|
{...register('password', { required: 'Пароль обязателен' })}
|
||||||
required
|
error={errors.password?.message}
|
||||||
{...register('password', { required: 'Пароль обязателен' })}
|
/>
|
||||||
error={!!errors.password}
|
|
||||||
helperText={errors.password?.message}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button type="submit" variant="contained" color="primary">
|
<Flex gap='sm'>
|
||||||
Зарегистрироваться
|
<Button disabled={!isValid} type="submit" flex={1} variant='filled'>
|
||||||
</Button>
|
{isSubmitting ? <Loader size={16} /> : 'Зарегистрироваться'}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Flex>
|
||||||
</Container>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
154
ems/src/api/general/index.ts
Normal file
154
ems/src/api/general/index.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import { tediousQuery } from '../../utils/tedious';
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
router.get('/cities/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { offset, limit, search, id } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..Cities
|
||||||
|
${id ? `WHERE id = '${id}'` : ''}
|
||||||
|
${search ? `WHERE name LIKE '%${search || ''}%'` : ''}
|
||||||
|
ORDER BY id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/types/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..tTypes
|
||||||
|
ORDER BY id
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/objects/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { offset, limit, city_id } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..vObjects
|
||||||
|
${city_id ? `WHERE id_city = ${city_id}` : ''}
|
||||||
|
ORDER BY object_id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/objects/list', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { city_id, year, planning } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
tTypes.id AS id,
|
||||||
|
tTypes.name AS name,
|
||||||
|
COUNT(vObjects.type) AS count
|
||||||
|
FROM
|
||||||
|
vObjects
|
||||||
|
JOIN
|
||||||
|
tTypes ON vObjects.type = tTypes.id
|
||||||
|
WHERE
|
||||||
|
vObjects.id_city = ${city_id} AND vObjects.year = ${year}
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN TRY_CAST(vObjects.planning AS BIT) IS NOT NULL THEN TRY_CAST(vObjects.planning AS BIT)
|
||||||
|
WHEN vObjects.planning = 'TRUE' THEN 1
|
||||||
|
WHEN vObjects.planning = 'FALSE' THEN 0
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) = ${planning}
|
||||||
|
GROUP BY
|
||||||
|
tTypes.id,
|
||||||
|
tTypes.name;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/objects/:id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..vObjects
|
||||||
|
${id ? `WHERE object_id = '${id}'` : ''}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
if (Array.isArray(result) && result.length > 0) {
|
||||||
|
res.status(200).json(result[0])
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/values/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { object_id } = req.query
|
||||||
|
|
||||||
|
if (!object_id) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..tValues
|
||||||
|
WHERE id_object = '${object_id}'
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/params/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { param_id } = req.query
|
||||||
|
|
||||||
|
if (!param_id) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM nGeneral..tParameters
|
||||||
|
WHERE id = '${param_id}'
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
65
ems/src/api/gis/index.ts
Normal file
65
ems/src/api/gis/index.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import { tediousQuery } from '../../utils/tedious';
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
router.get('/images/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { offset, limit, city_id } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM New_Gis..images
|
||||||
|
${city_id ? `WHERE city_id = ${city_id}` : ''}
|
||||||
|
ORDER BY city_id
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Get figures by year and city id
|
||||||
|
router.get('/figures/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { offset, limit, object_id, year, city_id } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM New_Gis..figures f
|
||||||
|
JOIN nGeneral..vObjects o ON f.object_id = o.object_id WHERE o.id_city = ${city_id} AND f.year = ${year}
|
||||||
|
ORDER BY f.year
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get lines by year and city id
|
||||||
|
router.get('/lines/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { offset, limit, object_id, year, city_id } = req.query
|
||||||
|
|
||||||
|
const result = await tediousQuery(
|
||||||
|
`
|
||||||
|
SELECT * FROM New_Gis..lines l
|
||||||
|
JOIN nGeneral..vObjects o ON l.object_id = o.object_id WHERE o.id_city = ${city_id} AND l.year = ${year}
|
||||||
|
ORDER BY l.year
|
||||||
|
OFFSET ${Number(offset) || 0} ROWS
|
||||||
|
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
res.status(200).json(result)
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
72
ems/src/api/nodes/index.ts
Normal file
72
ems/src/api/nodes/index.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import { query, validationResult } from 'express-validator';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
router.get('/all', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const nodes = await prisma.nodes.findMany()
|
||||||
|
|
||||||
|
res.json(nodes)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting node:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to get node' });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/', query('id').isString().isUUID(), async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const result = validationResult(req)
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
return res.send({ errors: result.array() })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const node = await prisma.nodes.findFirst({
|
||||||
|
where: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json(node)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting node:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to get node' });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { coordinates, object_id, type } = req.body;
|
||||||
|
|
||||||
|
// Convert the incoming array of coordinates into the shape structure
|
||||||
|
const shape = coordinates.map((point: number[]) => ({
|
||||||
|
object_id: object_id || null,
|
||||||
|
x: point[0],
|
||||||
|
y: point[1]
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(shape)
|
||||||
|
|
||||||
|
// Create a new node in the database
|
||||||
|
const node = await prisma.nodes.create({
|
||||||
|
data: {
|
||||||
|
object_id: object_id || null, // Nullable if object_id is not provided
|
||||||
|
shape_type: type, // You can adjust this dynamically
|
||||||
|
shape: shape, // Store the shape array as Json[]
|
||||||
|
label: 'Default'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json(node);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating node:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to create node' });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
83
ems/src/api/tiles/index.ts
Normal file
83
ems/src/api/tiles/index.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { Coordinate } from '../../interfaces/map';
|
||||||
|
import { generateTilesForZoomLevel } from '../../utils/tiles';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, path.join(__dirname, '..', 'public', 'temp'))
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
cb(null, Date.now() + path.extname(file.originalname))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const upload = multer({ storage: storage })
|
||||||
|
|
||||||
|
const tileFolder = path.join(__dirname, '..', '..', '..', 'public', 'tile_data')
|
||||||
|
const uploadDir = path.join(__dirname, '..', '..', '..', 'public', 'temp')
|
||||||
|
|
||||||
|
router.post('/upload', upload.single('file'), async (req: Request, res: Response) => {
|
||||||
|
const { extentMinX, extentMinY, extentMaxX, extentMaxY, blX, blY, tlX, tlY, trX, trY, brX, brY } = req.body
|
||||||
|
|
||||||
|
const bottomLeft: Coordinate = { x: blX, y: blY }
|
||||||
|
const topLeft: Coordinate = { x: tlX, y: tlY }
|
||||||
|
const topRight: Coordinate = { x: trX, y: trY }
|
||||||
|
const bottomRight: Coordinate = { x: brX, y: brY }
|
||||||
|
|
||||||
|
if (req.file) {
|
||||||
|
for (let z = 0; z <= 21; z++) {
|
||||||
|
await generateTilesForZoomLevel(uploadDir, tileFolder, req.file, [extentMinX, extentMinY, extentMaxX, extentMaxY], bottomLeft, topLeft, topRight, bottomRight, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/tile/:provider/:z/:x/:y', async (req: Request, res: Response) => {
|
||||||
|
const { provider, z, x, y } = req.params
|
||||||
|
|
||||||
|
if (!['google', 'yandex', 'custom'].includes(provider)) {
|
||||||
|
return res.status(400).send('Invalid provider')
|
||||||
|
}
|
||||||
|
|
||||||
|
const tilePath = provider === 'custom' ? path.join(tileFolder, provider, z, x, `${y}.png`) : path.join(tileFolder, provider, z, x, `${y}.jpg`)
|
||||||
|
|
||||||
|
if (fs.existsSync(tilePath)) {
|
||||||
|
return res.sendFile(tilePath)
|
||||||
|
} else {
|
||||||
|
if (provider !== 'custom') {
|
||||||
|
try {
|
||||||
|
const tileData = await fetchTileFromAPI(provider, z, x, y)
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(tilePath), { recursive: true })
|
||||||
|
|
||||||
|
fs.writeFileSync(tilePath, tileData)
|
||||||
|
|
||||||
|
res.contentType('image/jpeg')
|
||||||
|
res.send(tileData)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching tile from API:', error)
|
||||||
|
res.status(500).send('Error fetching tile from API')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404).send('Tile is not generated or not provided')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchTileFromAPI = async (provider: string, z: string, x: string, y: string): Promise<Buffer> => {
|
||||||
|
const url = provider === 'google'
|
||||||
|
? `https://khms2.google.com/kh/v=984?x=${x}&y=${y}&z=${z}`
|
||||||
|
: `https://core-sat.maps.yandex.net/tiles?l=sat&x=${x}&y=${y}&z=${z}&scale=1&lang=ru_RU`
|
||||||
|
|
||||||
|
const response = await axios.get(url, { responseType: 'arraybuffer' })
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router
|
396
ems/src/index.ts
396
ems/src/index.ts
@ -1,401 +1,23 @@
|
|||||||
import express, { Request, Response } from 'express'
|
import express from 'express'
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import axios from 'axios'
|
|
||||||
import multer from 'multer'
|
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import { Coordinate } from './interfaces/map'
|
import generalRouter from './api/general'
|
||||||
import { generateTilesForZoomLevel } from './utils/tiles'
|
import gisRouter from './api/gis'
|
||||||
import { query, validationResult } from 'express-validator'
|
import nodesRouter from './api/nodes'
|
||||||
import { Connection, ConnectionConfiguration, Request as TediousRequest } from 'tedious'
|
import tilesRouter from './api/tiles'
|
||||||
|
|
||||||
const tediousConfig: ConnectionConfiguration = {
|
|
||||||
server: 'localhost',
|
|
||||||
options: {
|
|
||||||
trustServerCertificate: true,
|
|
||||||
port: 1433,
|
|
||||||
database: 'nGeneral'
|
|
||||||
},
|
|
||||||
authentication: {
|
|
||||||
type: 'default',
|
|
||||||
options: {
|
|
||||||
userName: 'SA',
|
|
||||||
password: 'oMhthmsvbYHc'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const PORT = process.env.EMS_PORT || 5000
|
const PORT = process.env.EMS_PORT || 5000
|
||||||
|
|
||||||
const tileFolder = path.join(__dirname, '..', 'public', 'tile_data')
|
|
||||||
const uploadDir = path.join(__dirname, '..', 'public', 'temp')
|
|
||||||
|
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
|
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: true }))
|
app.use(bodyParser.urlencoded({ extended: true }))
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
app.use('/general', generalRouter)
|
||||||
destination: function (req, file, cb) {
|
app.use('/gis', gisRouter)
|
||||||
cb(null, path.join(__dirname, '..', 'public', 'temp'))
|
app.use('/nodes', nodesRouter)
|
||||||
},
|
app.use('/tiles', tilesRouter)
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, Date.now() + path.extname(file.originalname))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const upload = multer({ storage: storage })
|
|
||||||
|
|
||||||
app.get('/nodes/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const nodes = await prisma.nodes.findMany()
|
|
||||||
|
|
||||||
res.json(nodes)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting node:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to get node' });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/nodes', query('id').isString().isUUID(), async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const result = validationResult(req)
|
|
||||||
if (!result.isEmpty()) {
|
|
||||||
return res.send({ errors: result.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = req.params
|
|
||||||
|
|
||||||
const node = await prisma.nodes.findFirst({
|
|
||||||
where: {
|
|
||||||
id: id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json(node)
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting node:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to get node' });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/nodes', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { coordinates, object_id, type } = req.body;
|
|
||||||
|
|
||||||
// Convert the incoming array of coordinates into the shape structure
|
|
||||||
const shape = coordinates.map((point: number[]) => ({
|
|
||||||
object_id: object_id || null,
|
|
||||||
x: point[0],
|
|
||||||
y: point[1]
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log(shape)
|
|
||||||
|
|
||||||
// Create a new node in the database
|
|
||||||
const node = await prisma.nodes.create({
|
|
||||||
data: {
|
|
||||||
object_id: object_id || null, // Nullable if object_id is not provided
|
|
||||||
shape_type: type, // You can adjust this dynamically
|
|
||||||
shape: shape, // Store the shape array as Json[]
|
|
||||||
label: 'Default'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(201).json(node);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating node:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to create node' });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/upload', upload.single('file'), async (req: Request, res: Response) => {
|
|
||||||
const { extentMinX, extentMinY, extentMaxX, extentMaxY, blX, blY, tlX, tlY, trX, trY, brX, brY } = req.body
|
|
||||||
|
|
||||||
const bottomLeft: Coordinate = { x: blX, y: blY }
|
|
||||||
const topLeft: Coordinate = { x: tlX, y: tlY }
|
|
||||||
const topRight: Coordinate = { x: trX, y: trY }
|
|
||||||
const bottomRight: Coordinate = { x: brX, y: brY }
|
|
||||||
|
|
||||||
if (req.file) {
|
|
||||||
for (let z = 0; z <= 21; z++) {
|
|
||||||
await generateTilesForZoomLevel(uploadDir, tileFolder, req.file, [extentMinX, extentMinY, extentMaxX, extentMaxY], bottomLeft, topLeft, topRight, bottomRight, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchTileFromAPI = async (provider: string, z: string, x: string, y: string): Promise<Buffer> => {
|
|
||||||
const url = provider === 'google'
|
|
||||||
? `https://khms2.google.com/kh/v=984?x=${x}&y=${y}&z=${z}`
|
|
||||||
: `https://core-sat.maps.yandex.net/tiles?l=sat&x=${x}&y=${y}&z=${z}&scale=1&lang=ru_RU`
|
|
||||||
|
|
||||||
const response = await axios.get(url, { responseType: 'arraybuffer' })
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/tile/:provider/:z/:x/:y', async (req: Request, res: Response) => {
|
|
||||||
const { provider, z, x, y } = req.params
|
|
||||||
|
|
||||||
if (!['google', 'yandex', 'custom'].includes(provider)) {
|
|
||||||
return res.status(400).send('Invalid provider')
|
|
||||||
}
|
|
||||||
|
|
||||||
const tilePath = provider === 'custom' ? path.join(tileFolder, provider, z, x, `${y}.png`) : path.join(tileFolder, provider, z, x, `${y}.jpg`)
|
|
||||||
|
|
||||||
if (fs.existsSync(tilePath)) {
|
|
||||||
return res.sendFile(tilePath)
|
|
||||||
} else {
|
|
||||||
if (provider !== 'custom') {
|
|
||||||
try {
|
|
||||||
const tileData = await fetchTileFromAPI(provider, z, x, y)
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(tilePath), { recursive: true })
|
|
||||||
|
|
||||||
fs.writeFileSync(tilePath, tileData)
|
|
||||||
|
|
||||||
res.contentType('image/jpeg')
|
|
||||||
res.send(tileData)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching tile from API:', error)
|
|
||||||
res.status(500).send('Error fetching tile from API')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404).send('Tile is not generated or not provided')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function tediousQuery(query: string) {
|
|
||||||
// Read all rows from table
|
|
||||||
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const connection = new Connection(tediousConfig)
|
|
||||||
|
|
||||||
connection.on('connect', (err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: any = [];
|
|
||||||
|
|
||||||
const request = new TediousRequest(
|
|
||||||
query,
|
|
||||||
(err, rowCount) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(`Executing ${query}, ${rowCount} rows.`);
|
|
||||||
console.error(err.message);
|
|
||||||
} else {
|
|
||||||
console.log(`Executing ${query}, ${rowCount} rows.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
request.on("row", (columns) => {
|
|
||||||
const entry: any = {};
|
|
||||||
columns.forEach((column: any) => {
|
|
||||||
entry[column.metadata.colName] = column.value;
|
|
||||||
});
|
|
||||||
result.push(entry);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', error => reject(error));// some error happened, reject the promise
|
|
||||||
request.on('requestCompleted', () => {
|
|
||||||
connection.close();
|
|
||||||
resolve(result)
|
|
||||||
}); // resolve the promise with the result rows.
|
|
||||||
|
|
||||||
connection.execSql(request)
|
|
||||||
})
|
|
||||||
|
|
||||||
connection.on('error', (err) => {
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
connection.connect()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/general/cities/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { offset, limit, search, id } = req.query
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..Cities
|
|
||||||
${id ? `WHERE id = '${id}'` : ''}
|
|
||||||
${search ? `WHERE name LIKE '%${search || ''}%'` : ''}
|
|
||||||
ORDER BY id
|
|
||||||
OFFSET ${Number(offset) || 0} ROWS
|
|
||||||
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/general/types/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..tTypes
|
|
||||||
ORDER BY id
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/general/objects/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { offset, limit, city_id } = req.query
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..vObjects
|
|
||||||
${city_id ? `WHERE id_city = ${city_id}` : ''}
|
|
||||||
ORDER BY object_id
|
|
||||||
OFFSET ${Number(offset) || 0} ROWS
|
|
||||||
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/general/objects/:id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.params;
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..vObjects
|
|
||||||
${id ? `WHERE object_id = '${id}'` : ''}
|
|
||||||
`
|
|
||||||
)
|
|
||||||
if (Array.isArray(result) && result.length > 0) {
|
|
||||||
res.status(200).json(result[0])
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/general/values/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { object_id } = req.query
|
|
||||||
|
|
||||||
if (!object_id) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..tValues
|
|
||||||
WHERE id_object = '${object_id}'
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/general/params/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { param_id } = req.query
|
|
||||||
|
|
||||||
if (!param_id) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM nGeneral..tParameters
|
|
||||||
WHERE id = '${param_id}'
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/gis/images/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { offset, limit, city_id } = req.query
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM New_Gis..images
|
|
||||||
${city_id ? `WHERE city_id = ${city_id}` : ''}
|
|
||||||
ORDER BY city_id
|
|
||||||
OFFSET ${Number(offset) || 0} ROWS
|
|
||||||
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Get figures by year and city id
|
|
||||||
app.get('/gis/figures/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { offset, limit, object_id, year, city_id } = req.query
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM New_Gis..figures f
|
|
||||||
JOIN nGeneral..tObjects o ON f.object_id = o.id WHERE o.id_city = ${city_id} AND f.year = ${year}
|
|
||||||
ORDER BY f.year
|
|
||||||
OFFSET ${Number(offset) || 0} ROWS
|
|
||||||
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get lines by year and city id
|
|
||||||
app.get('/gis/lines/all', async (req: Request, res: Response) => {
|
|
||||||
try {
|
|
||||||
const { offset, limit, object_id, year, city_id } = req.query
|
|
||||||
|
|
||||||
const result = await tediousQuery(
|
|
||||||
`
|
|
||||||
SELECT * FROM New_Gis..lines l
|
|
||||||
JOIN nGeneral..tObjects o ON l.object_id = o.id WHERE o.id_city = ${city_id} AND l.year = ${year}
|
|
||||||
ORDER BY l.year
|
|
||||||
OFFSET ${Number(offset) || 0} ROWS
|
|
||||||
FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
res.status(200).json(result)
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
69
ems/src/utils/tedious.ts
Normal file
69
ems/src/utils/tedious.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Connection, ConnectionConfiguration, Request } from "tedious";
|
||||||
|
|
||||||
|
const tediousConfig: ConnectionConfiguration = {
|
||||||
|
server: 'localhost',
|
||||||
|
options: {
|
||||||
|
trustServerCertificate: true,
|
||||||
|
port: 1433,
|
||||||
|
database: 'nGeneral'
|
||||||
|
},
|
||||||
|
authentication: {
|
||||||
|
type: 'default',
|
||||||
|
options: {
|
||||||
|
userName: 'SA',
|
||||||
|
password: 'oMhthmsvbYHc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tediousQuery(query: string) {
|
||||||
|
// Read all rows from table
|
||||||
|
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const connection = new Connection(tediousConfig)
|
||||||
|
|
||||||
|
connection.on('connect', (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: any = [];
|
||||||
|
|
||||||
|
const request = new Request(
|
||||||
|
query,
|
||||||
|
(err, rowCount) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(`Executing ${query}, ${rowCount} rows.`);
|
||||||
|
console.error(err.message);
|
||||||
|
} else {
|
||||||
|
console.log(`Executing ${query}, ${rowCount} rows.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
request.on("row", (columns) => {
|
||||||
|
const entry: any = {};
|
||||||
|
columns.forEach((column: any) => {
|
||||||
|
entry[column.metadata.colName] = column.value;
|
||||||
|
});
|
||||||
|
result.push(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', error => reject(error));// some error happened, reject the promise
|
||||||
|
request.on('requestCompleted', () => {
|
||||||
|
connection.close();
|
||||||
|
resolve(result)
|
||||||
|
}); // resolve the promise with the result rows.
|
||||||
|
|
||||||
|
connection.execSql(request)
|
||||||
|
})
|
||||||
|
|
||||||
|
connection.on('error', (err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
connection.connect()
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user