diff --git a/client/src/App.tsx b/client/src/App.tsx
index bea8403..85176e9 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -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: ,
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: ,
+ component: ,
+ drawer: true,
+ dashboard: true,
+ enabled: true,
+ },
]
function App() {
diff --git a/client/src/actions/map.ts b/client/src/actions/map.ts
new file mode 100644
index 0000000..43834b3
--- /dev/null
+++ b/client/src/actions/map.ts
@@ -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);
+ }
+};
\ No newline at end of file
diff --git a/client/src/components/CardInfo/CardInfo.tsx b/client/src/components/CardInfo/CardInfo.tsx
index db6586a..dc8c579 100644
--- a/client/src/components/CardInfo/CardInfo.tsx
+++ b/client/src/components/CardInfo/CardInfo.tsx
@@ -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 (
-
-
+
+
{label}
-
+
{children}
-
+
)
}
\ No newline at end of file
diff --git a/client/src/components/CardInfo/CardInfoChip.tsx b/client/src/components/CardInfo/CardInfoChip.tsx
index 5c662ed..de1b7b0 100644
--- a/client/src/components/CardInfo/CardInfoChip.tsx
+++ b/client/src/components/CardInfo/CardInfoChip.tsx
@@ -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 (
+ variant='outline'
+ >
+ {label}
+
)
}
\ No newline at end of file
diff --git a/client/src/components/CardInfo/CardInfoLabel.tsx b/client/src/components/CardInfo/CardInfoLabel.tsx
index 0004ca4..fe3ea03 100644
--- a/client/src/components/CardInfo/CardInfoLabel.tsx
+++ b/client/src/components/CardInfo/CardInfoLabel.tsx
@@ -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 (
-
-
+
+
{label}
-
+
-
+
{value}
-
-
+
+
)
}
\ No newline at end of file
diff --git a/client/src/components/FolderViewer.tsx b/client/src/components/FolderViewer.tsx
index b3dc891..8d3a58f 100644
--- a/client/src/components/FolderViewer.tsx
+++ b/client/src/components/FolderViewer.tsx
@@ -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) {
handleFolderClick(folder)}
>
-
{folder.name}
-
+
)
}
@@ -72,15 +71,15 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
return (
- handleDocumentClick(index)}
{...props}
>
{doc.name}
-
-
+
+
{
if (!isLoading) {
@@ -94,7 +93,7 @@ function ItemDocument({ doc, index, handleDocumentClick, ...props }: DocumentPro
}
-
+
)
}
@@ -171,7 +170,7 @@ export default function FolderViewer() {
if (foldersLoading || documentsLoading) {
return (
-
+
)
}
@@ -205,16 +204,12 @@ export default function FolderViewer() {
{currentFolder &&
-
-
+ 0 ? '1px dashed gray' : 'none',
borderRadius: '8px',
- p: '16px'
}}>
-
+
{(props) => : } {...props}>Добавить}
@@ -240,7 +235,7 @@ export default function FolderViewer() {
>
}
-
+
@@ -264,8 +259,8 @@ export default function FolderViewer() {
))}
}
-
-
+
+
}
}
-
+
)
}
diff --git a/client/src/components/ServerHardware.tsx b/client/src/components/ServerHardware.tsx
index db7e898..c2abe7c 100644
--- a/client/src/components/ServerHardware.tsx
+++ b/client/src/components/ServerHardware.tsx
@@ -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'
diff --git a/client/src/components/ServerIpsView.tsx b/client/src/components/ServerIpsView.tsx
index b4994e6..80ec1c5 100644
--- a/client/src/components/ServerIpsView.tsx
+++ b/client/src/components/ServerIpsView.tsx
@@ -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}
/>
diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx
index db9f38b..43c14c3 100644
--- a/client/src/components/map/MapComponent.tsx
+++ b/client/src/components/map/MapComponent.tsx
@@ -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(null)
const [currentZ, setCurrentZ] = useState(undefined)
const [currentX, setCurrentX] = useState(undefined)
const [currentY, setCurrentY] = useState(undefined)
- const [file, setFile] = useState(null)
+ const [file, setFile] = useState(null)
const [polygonExtent, setPolygonExtent] = useState(undefined)
const [bottomLeft, setBottomLeft] = useState(undefined)
const [topLeft, setTopLeft] = useState(undefined)
@@ -70,7 +62,7 @@ const MapComponent = () => {
const gMapsSatSource = useRef(googleMapsSatelliteSource)
const customMapSource = useRef(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(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("")
+ 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(null)
+ const [selectedObjectList, setSelectedObjectList] = useState(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 = () => {
+
+ setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
+
+ setSatMapsProvider(value as SatelliteMapsProvider)} />
+
+
-
- {
- fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
- }}>
-
-
-
- {
- saveFeatures()
- }}>
-
-
-
- {
- draw.current?.removeLastPoint()
- }}>
-
-
-
- {
- handleToolSelect('Point')
- }}>
-
-
-
- {
- handleToolSelect('LineString')
- }}>
-
-
-
- {
- handleToolSelect('Polygon')
- }}>
-
-
-
- {
- handleToolSelect('Circle')
- }}>
-
-
-
- map?.current?.addInteraction(new Translate())}
- >
-
-
-
-
-
-
-
+ saveFeatures()}
+ onRemove={() => draw.current?.removeLastPoint()}
+ handleToolSelect={handleToolSelect}
+ onMover={() => map?.current?.addInteraction(new Translate())}
+ colorScheme={colorScheme}
+ />
{
-
- setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
-
- setSatMapsProvider(value as SatelliteMapsProvider)} />
-
-
}>{'Объекты'}
-
+ {objectsList &&
+ (
+ {
+ elementProps.onClick(e)
+ if (node.value !== 'existing' && node.value !== 'planning') {
+ setSelectedObjectList(Number(node.value))
+ }
+ }}>
+ {hasChildren && (
+
+ )}
+
+ {node.label}
+
+ )}
+ />
+ }
+
-
- }>
- {'Текущий объект'}
-
-
-
-
-
+ {currentObjectId &&
+
+ }>
+ {'Текущий объект'}
+
+
+
+
+
+ }
- {valuesData &&
- }>{'Параметры объекта'}
-
-
- {Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
- return (
-
- )
- })}
-
-
- }
+ {valuesData &&
+
+ }>{'Параметры объекта'}
+
+
+ {Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => {
+ return (
+
+ )
+ })}
+
+
+
+ }
-
-
- x: {currentCoordinate?.[0]}
-
-
-
- y: {currentCoordinate?.[1]}
-
-
-
-
-
- Z={currentZ}
-
-
-
- X={currentX}
-
-
-
- Y={currentY}
-
-
-
- {statusText}
-
-
+
diff --git a/client/src/components/map/MapSources.ts b/client/src/components/map/MapSources.ts
index 7403867..e398ac9 100644
--- a/client/src/components/map/MapSources.ts
+++ b/client/src/components/map/MapSources.ts
@@ -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,
})
diff --git a/client/src/components/map/MapStatusbar/MapStatusbar.tsx b/client/src/components/map/MapStatusbar/MapStatusbar.tsx
new file mode 100644
index 0000000..50ac8a5
--- /dev/null
+++ b/client/src/components/map/MapStatusbar/MapStatusbar.tsx
@@ -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 (
+
+
+ x: {currentCoordinate?.[0]}
+
+
+
+ y: {currentCoordinate?.[1]}
+
+
+
+
+
+ Z={currentZ}
+
+
+
+ X={currentX}
+
+
+
+ Y={currentY}
+
+
+
+ {statusText}
+
+
+ )
+}
+
+export default MapStatusbar
\ No newline at end of file
diff --git a/client/src/components/map/MapToolbar/MapToolbar.tsx b/client/src/components/map/MapToolbar/MapToolbar.tsx
new file mode 100644
index 0000000..173f245
--- /dev/null
+++ b/client/src/components/map/MapToolbar/MapToolbar.tsx
@@ -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 (
+
+ {
+ fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
+ }}>
+
+
+
+
+
+
+
+
+
+
+
+ {
+ handleToolSelect('Point')
+ }}>
+
+
+
+ {
+ handleToolSelect('LineString')
+ }}>
+
+
+
+ {
+ handleToolSelect('Polygon')
+ }}>
+
+
+
+ {
+ handleToolSelect('Circle')
+ }}>
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default MapToolbar
\ No newline at end of file
diff --git a/client/src/components/map/ObjectData.tsx b/client/src/components/map/ObjectData.tsx
index 7f03109..18e0b44 100644
--- a/client/src/components/map/ObjectData.tsx
+++ b/client/src/components/map/ObjectData.tsx
@@ -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) => {
{Array.isArray(typeData) && (typeData.find(type => Number(type.id) === Number(object_data.type)) as IObjectType).name}
-
+
)
}
diff --git a/client/src/components/map/ObjectParameter.tsx b/client/src/components/map/ObjectParameter.tsx
index 23473bd..04c818f 100644
--- a/client/src/components/map/ObjectParameter.tsx
+++ b/client/src/components/map/ObjectParameter.tsx
@@ -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 = ({
diff --git a/client/src/interfaces/gis.ts b/client/src/interfaces/gis.ts
index 8b1f863..7b47f33 100644
--- a/client/src/interfaces/gis.ts
+++ b/client/src/interfaces/gis.ts
@@ -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
}
\ No newline at end of file
diff --git a/client/src/interfaces/map.ts b/client/src/interfaces/map.ts
index e8c7e1a..042460a 100644
--- a/client/src/interfaces/map.ts
+++ b/client/src/interfaces/map.ts
@@ -3,4 +3,11 @@ export interface SatelliteMapsProviders {
yandex: 'yandex';
custom: 'custom';
}
-export type SatelliteMapsProvider = SatelliteMapsProviders[keyof SatelliteMapsProviders]
\ No newline at end of file
+export type SatelliteMapsProvider = SatelliteMapsProviders[keyof SatelliteMapsProviders]
+
+export interface IGeometryTypes {
+ LINE: 'LINE'
+ POLYGON: 'POLYGON'
+}
+
+export type IGeometryType = IGeometryTypes[keyof IGeometryTypes]
diff --git a/client/src/interfaces/objects.ts b/client/src/interfaces/objects.ts
index 19b5907..134a104 100644
--- a/client/src/interfaces/objects.ts
+++ b/client/src/interfaces/objects.ts
@@ -1,3 +1,9 @@
+export interface IObjectList {
+ id: number,
+ name: string,
+ count: number
+}
+
export interface IObjectData {
object_id: string,
id_city: number,
diff --git a/client/src/pages/ApiTest.tsx b/client/src/pages/ApiTest.tsx
index b3e8a96..3c108df 100644
--- a/client/src/pages/ApiTest.tsx
+++ b/client/src/pages/ApiTest.tsx
@@ -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 (
-
+
-
+
)
}
\ No newline at end of file
diff --git a/client/src/pages/ComponentTest.tsx b/client/src/pages/ComponentTest.tsx
new file mode 100644
index 0000000..6ea762a
--- /dev/null
+++ b/client/src/pages/ComponentTest.tsx
@@ -0,0 +1,12 @@
+import { Flex } from '@mantine/core'
+import ServerHardware from '../components/ServerHardware'
+
+const ComponentTest = () => {
+ return (
+
+
+
+ )
+}
+
+export default ComponentTest
\ No newline at end of file
diff --git a/client/src/pages/MonitorPage.tsx b/client/src/pages/MonitorPage.tsx
index c249bce..32f2cc9 100644
--- a/client/src/pages/MonitorPage.tsx
+++ b/client/src/pages/MonitorPage.tsx
@@ -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 (
-
+
{url}
{JSON.stringify(is_alive)}
-
+
)
}
@@ -38,11 +38,11 @@ export default function MonitorPage() {
return (
-
+
{servers.length > 0 && servers.map((server: any) => (
))}
-
+
)
}
\ No newline at end of file
diff --git a/client/src/pages/auth/PasswordReset.tsx b/client/src/pages/auth/PasswordReset.tsx
index b78e7aa..cd10866 100644
--- a/client/src/pages/auth/PasswordReset.tsx
+++ b/client/src/pages/auth/PasswordReset.tsx
@@ -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() {
diff --git a/client/src/pages/auth/SignUp.tsx b/client/src/pages/auth/SignUp.tsx
index 8813dc5..c6e45ba 100644
--- a/client/src/pages/auth/SignUp.tsx
+++ b/client/src/pages/auth/SignUp.tsx
@@ -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({
+ const { register, handleSubmit, formState: { errors, isValid, isSubmitting } } = useForm({
defaultValues: {
email: '',
login: '',
@@ -26,77 +26,66 @@ const SignUp = () => {
};
return (
-
-
-
+
+
+
Регистрация
-
+
-
-
+
+
);
};
diff --git a/ems/src/api/general/index.ts b/ems/src/api/general/index.ts
new file mode 100644
index 0000000..bb91559
--- /dev/null
+++ b/ems/src/api/general/index.ts
@@ -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
\ No newline at end of file
diff --git a/ems/src/api/gis/index.ts b/ems/src/api/gis/index.ts
new file mode 100644
index 0000000..11d4c80
--- /dev/null
+++ b/ems/src/api/gis/index.ts
@@ -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
\ No newline at end of file
diff --git a/ems/src/api/nodes/index.ts b/ems/src/api/nodes/index.ts
new file mode 100644
index 0000000..73f796c
--- /dev/null
+++ b/ems/src/api/nodes/index.ts
@@ -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
\ No newline at end of file
diff --git a/ems/src/api/tiles/index.ts b/ems/src/api/tiles/index.ts
new file mode 100644
index 0000000..5eff191
--- /dev/null
+++ b/ems/src/api/tiles/index.ts
@@ -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 => {
+ 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
\ No newline at end of file
diff --git a/ems/src/index.ts b/ems/src/index.ts
index 912d007..f8d7101 100644
--- a/ems/src/index.ts
+++ b/ems/src/index.ts
@@ -1,401 +1,23 @@
-import express, { Request, Response } from 'express'
-import { PrismaClient } from '@prisma/client'
-import fs from 'fs'
-import path from 'path'
-import axios from 'axios'
-import multer from 'multer'
+import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
-import { Coordinate } from './interfaces/map'
-import { generateTilesForZoomLevel } from './utils/tiles'
-import { query, validationResult } from 'express-validator'
-import { Connection, ConnectionConfiguration, Request as TediousRequest } from 'tedious'
+import generalRouter from './api/general'
+import gisRouter from './api/gis'
+import nodesRouter from './api/nodes'
+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 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(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
-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 })
-
-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 => {
- 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.use('/general', generalRouter)
+app.use('/gis', gisRouter)
+app.use('/nodes', nodesRouter)
+app.use('/tiles', tilesRouter)
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
\ No newline at end of file
diff --git a/ems/src/utils/tedious.ts b/ems/src/utils/tedious.ts
new file mode 100644
index 0000000..6dc2704
--- /dev/null
+++ b/ems/src/utils/tedious.ts
@@ -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()
+ });
+}
\ No newline at end of file