diff --git a/client/src/App.tsx b/client/src/App.tsx index 2c41ef5..bea8403 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -17,9 +17,8 @@ import Settings from "./pages/Settings" import PasswordReset from "./pages/auth/PasswordReset" import MapTest from "./pages/MapTest" import MonitorPage from "./pages/MonitorPage" -import ChunkedUpload from "./components/map/ChunkedUpload" import DashboardLayout from "./layouts/DashboardLayout" -import { IconApi, IconBuildingFactory2, IconDeviceDesktopAnalytics, IconFiles, IconFlag2, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconTable, IconUsers } from "@tabler/icons-react" +import { IconApi, IconBuildingFactory2, 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" @@ -142,15 +141,6 @@ export const pages = [ dashboard: true, enabled: true, }, - { - label: "Chunk test", - path: "/chunk-test", - icon: , - component: , - drawer: true, - dashboard: true, - enabled: false, - }, { label: "Монитор", path: "/monitor", diff --git a/client/src/components/CustomTable.tsx b/client/src/components/CustomTable.tsx index 8507921..e81497a 100644 --- a/client/src/components/CustomTable.tsx +++ b/client/src/components/CustomTable.tsx @@ -1,4 +1,4 @@ -import { Button, Flex, Input, Table } from '@mantine/core'; +import { Input, Table } from '@mantine/core'; import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { useMemo, useState } from 'react'; import styles from './CustomTable.module.scss' diff --git a/client/src/components/FolderViewer.tsx b/client/src/components/FolderViewer.tsx index 2ef136b..b3dc891 100644 --- a/client/src/components/FolderViewer.tsx +++ b/client/src/components/FolderViewer.tsx @@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react' import DocumentService from '../services/DocumentService' import { mutate } from 'swr' import FileViewer from './modals/FileViewer' -import { ActionIcon, Anchor, Breadcrumbs, Button, FileButton, Flex, Loader, RingProgress, Table, Text } from '@mantine/core' +import { ActionIcon, Anchor, Breadcrumbs, Button, FileButton, Flex, Loader, RingProgress, ScrollAreaAutosize, Table, Text } from '@mantine/core' import { IconCancel, IconDownload, IconFile, IconFilePlus, IconFileUpload, IconX } from '@tabler/icons-react' interface FolderProps { @@ -176,12 +176,7 @@ export default function FolderViewer() { } return ( - + - + ) } \ No newline at end of file diff --git a/client/src/components/ServersView.tsx b/client/src/components/ServersView.tsx index e806a70..bfea7e0 100644 --- a/client/src/components/ServersView.tsx +++ b/client/src/components/ServersView.tsx @@ -1,9 +1,8 @@ -import { AppBar, Box, CircularProgress, Dialog, Grid, IconButton, TextField, Toolbar } from '@mui/material' -import { Fragment, useState } from 'react' +import { AppBar, Box, Dialog, Grid, IconButton, Toolbar } from '@mui/material' +import { useState } from 'react' import { IRegion } from '../interfaces/fuel' import { useRegions, useServers, useServersInfo } from '../hooks/swrHooks' -import ServerService from '../services/ServersService' -import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid' +import { GridColDef } from '@mui/x-data-grid' import { Close, Cloud, CloudOff } from '@mui/icons-material' import ServerData from './ServerData' import { IServersInfo } from '../interfaces/servers' @@ -20,14 +19,14 @@ export default function ServersView() { const [selectedOption, setSelectedOption] = useState(null) - const { regions, isLoading } = useRegions(10, 1, debouncedSearch) + const { regions } = useRegions(10, 1, debouncedSearch) const { serversInfo } = useServersInfo(selectedOption) const [serverDataOpen, setServerDataOpen] = useState(false) const [currentServerData, setCurrentServerData] = useState(null) - const { servers, isLoading: serversLoading } = useServers(selectedOption, 0, 10) + const { servers } = useServers(selectedOption, 0, 10) const serversColumns: GridColDef[] = [ //{ field: 'id', headerName: 'ID', type: "number" }, diff --git a/client/src/components/map/ChunkedUpload.tsx b/client/src/components/map/ChunkedUpload.tsx deleted file mode 100644 index 01ca68d..0000000 --- a/client/src/components/map/ChunkedUpload.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState } from 'react'; -import axios from 'axios'; - -const ChunkedUpload = () => { - const [file, setFile] = useState(null); - const [uploadProgress, setUploadProgress] = useState(0); - - // Handle file selection - const handleFileChange = (event: React.ChangeEvent) => { - if (event.target.files) { - setFile(event.target.files[0]); - } - }; - - // Upload the file in chunks - const uploadFile = async () => { - if (!file) return; - - const chunkSize = 1024 * 1024; // 1MB per chunk - const totalChunks = Math.ceil(file.size / chunkSize); - const fileId = `${file.name}-${Date.now()}`; // Unique file identifier - let uploadedChunks = 0; - - for (let start = 0; start < file.size; start += chunkSize) { - const chunk = file.slice(start, start + chunkSize); - const chunkNumber = Math.ceil(start / chunkSize) + 1; - - try { - await axios.post(`${import.meta.env.VITE_API_EMS_URL}/upload`, chunk, { - headers: { - 'Content-Type': 'application/octet-stream', - 'X-Chunk-Number': chunkNumber.toString(), - 'X-Total-Chunks': totalChunks.toString(), - 'X-File-Id': fileId, - }, - }); - uploadedChunks++; - setUploadProgress((uploadedChunks / totalChunks) * 100); - } catch (error) { - console.error('Chunk upload failed', error); - // Implement retry logic if needed - break; - } - } - }; - - return ( - - - - Upload File - - Upload Progress: {uploadProgress.toFixed(2)}% - - ); -}; - -export default ChunkedUpload; \ No newline at end of file diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx index 8e982f2..db9f38b 100644 --- a/client/src/components/map/MapComponent.tsx +++ b/client/src/components/map/MapComponent.tsx @@ -6,7 +6,6 @@ import View from 'ol/View' import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction' import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source' import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer' -import { Stack, Typography } from '@mui/material' import { Type } from 'ol/geom/Geometry' import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition' import Feature from 'ol/Feature' @@ -24,27 +23,30 @@ import { Coordinate } from 'ol/coordinate' import { Stroke, Fill, Circle as CircleStyle, Style, Text } from 'ol/style' import { calculateCenter, calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils' import MapBrowserEvent from 'ol/MapBrowserEvent' -import { fromLonLat, get, transform } from 'ol/proj' +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, Box, Button, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme } from '@mantine/core' -import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconTable, IconUpload } from '@tabler/icons-react' +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 { getGridCellPosition } from './mapUtils' import { IFigure, ILine } from '../../interfaces/gis' -import { Height } from '@mui/icons-material' +import axios from 'axios' +import ObjectParameter from './ObjectParameter' +import { IObjectData, IObjectParam } from '../../interfaces/objects' +import ObjectData from './ObjectData' const MapComponent = () => { - const { cities } = useCities(100, 1) + //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]) + // 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) @@ -104,6 +106,10 @@ const MapComponent = () => { source: new VectorSource() })) + const linesLayer = useRef(new VectorLayer({ + source: new VectorSource() + })) + const regionsLayer = useRef(new VectorImageLayer({ source: regionsLayerSource, style: regionsLayerStyle @@ -576,7 +582,7 @@ const MapComponent = () => { map.current = new Map({ controls: [], - layers: [baseLayer.current, satLayer.current, regionsLayer.current, citiesLayer.current, figuresLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current, nodeLayer.current], + layers: [baseLayer.current, satLayer.current, regionsLayer.current, citiesLayer.current, linesLayer.current, figuresLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current, nodeLayer.current], target: mapElement.current as HTMLDivElement, view: new View({ center: transform([129.7659541, 62.009504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]), @@ -596,6 +602,14 @@ const MapComponent = () => { setCurrentY(tileY) }) + map.current.on('click', function (e: MapBrowserEvent) { + const pixel = map.current?.getEventPixel(e.originalEvent) + + map.current?.forEachFeatureAtPixel(pixel, function (feature) { + setCurrentObjectId(feature.get('object_id')) + }) + }) + const modify = new Modify({ source: drawingLayerSource.current }) map.current.addInteraction(modify) @@ -842,291 +856,407 @@ const MapComponent = () => { ]; } + const [currentObjectId, setCurrentObjectId] = useState(null) + + const [selectedCity, setSelectedCity] = useState(null) + const [selectedYear, setSelectedYear] = useState(2023) + const [citiesPage, setCitiesPage] = useState(0) + + const [searchCity, setSearchCity] = useState("") + + const { data: currentObjectData } = useSWR( + currentObjectId ? `/general/objects/${currentObjectId}` : null, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + const { data: valuesData } = useSWR( + currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + const { data: citiesData } = useSWR( + `/general/cities/all?limit=${10}&offset=${citiesPage || 0}${searchCity ? `&search=${searchCity}` : ''}`, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + const { data: figuresData, isValidating: figuresValidating } = useSWR( + selectedCity && selectedYear ? `/gis/figures/all?city_id=${selectedCity}&year=${selectedYear}&offset=0&limit=${10000}` : null, + (url) => axios.get(url, { + baseURL: BASE_URL.ems + }).then((res) => res.data), + { + revalidateOnFocus: false + } + ) + + const { data: linesData } = useSWR( + !figuresValidating && selectedCity && selectedYear ? `/gis/lines/all?city_id=${selectedCity}&year=${selectedYear}&offset=0&limit=${10000}` : null, + (url) => axios.get(url, { + baseURL: BASE_URL.ems + }).then((res) => { + return res.data + }), + { + revalidateOnFocus: false + } + ) + + useEffect(() => { + if (Array.isArray(figuresData)) { + figuresLayer.current.getSource()?.clear() + if (figuresData.length > 0) { + const scaling = { + w: 10000, // responseData[0].width + h: 10000 // responseData[0].width + } + + figuresData.map((figure: IFigure) => { + if (figure.figure_type_id == 1) { + const width = figure.width * scaling.w + const height = figure.height * scaling.h + + const left = figure.left * scaling.w + const top = figure.top * scaling.h + + const centerX = mapCenter[0] + left + (width / 2) + const centerY = mapCenter[1] - top - (height / 2) + + const radius = width / 2; + const circleGeom = new Circle([centerX, centerY], radius) + + const ellipseGeom = fromCircle(circleGeom, 64) + ellipseGeom.scale(1, height / width) + + const feature = new Feature(ellipseGeom) + + feature.setStyle(firstStyleFunction(feature)) + feature.set('object_id', figure.object_id) + figuresLayer.current?.getSource()?.addFeature(feature) + } + + if (figure.figure_type_id == 3) { + const x = figure.left * scaling.w + const y = figure.top * scaling.h + + const center = [mapCenter[0] + x, mapCenter[1] - y] + + const coords = figure.points?.split(' ').map(pair => { + const [x, y] = pair.split(';').map(Number) + return [ + center[0] + (x * scaling.w), + center[1] - (y * scaling.h) + ] + }) + + if (coords) { + const polygon = new Polygon([coords]) + + const feature = new Feature({ + geometry: polygon + }) + + feature.set('object_id', figure.object_id) + feature.setStyle(thirdStyleFunction(feature)) + figuresLayer.current?.getSource()?.addFeature(feature) + } + } + + if (figure.figure_type_id == 4) { + const width = figure.width * scaling.w + const height = figure.height * scaling.h + const left = figure.left * scaling.w + const top = figure.top * scaling.h + + const halfWidth = width / 2 + const halfHeight = height / 2 + + const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight] + + const testCoords = [ + [center[0] - halfWidth, center[1] - halfHeight], + [center[0] - halfWidth, center[1] + halfHeight], + [center[0] + halfWidth, center[1] + halfHeight], + [center[0] + halfWidth, center[1] - halfHeight], + [center[0] - halfWidth, center[1] - halfHeight] + ] + + const geometry1 = new Polygon([testCoords]) + const anchor1 = center + geometry1.rotate(-figure.angle * Math.PI / 180, anchor1) + const feature1 = new Feature(geometry1) + feature1.set('object_id', figure.object_id) + feature1.set('angle', figure.angle) + feature1.setStyle(fourthStyleFunction(feature1)) + figuresLayer.current?.getSource()?.addFeature(feature1) + } + }) + } + } + + if (Array.isArray(linesData)) { + linesLayer.current.getSource()?.clear() + if (linesData.length > 0) { + const scaling = { + w: 10000, // responseData[0].width + h: 10000 // responseData[0].width + } + + linesData.map((line: ILine) => { + const x1 = line.x1 * scaling.w + const y1 = line.y1 * scaling.h + const x2 = line.x2 * scaling.w + const y2 = line.y2 * scaling.h + + const center = [mapCenter[0], mapCenter[1]] + + const testCoords = [ + [center[0] + x1, center[1] - y1], + [center[0] + x2, center[1] - y2], + ] + + const feature = new Feature(new LineString(testCoords)) + feature.setStyle(styleFunction(feature)) + feature.set('object_id', line.object_id) + + linesLayer.current?.getSource()?.addFeature(feature) + }) + } + } + }, [figuresData, linesData, selectedCity, selectedYear]) + return ( - - - { - fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res)) - }}> - - + + + + + ({ label: item.name, value: item.id.toString() })) : []} + //onSelect={(e) => console.log(e.currentTarget.value)} + onChange={(value) => setSearchCity(value)} + onOptionSubmit={(value) => setSelectedCity(Number(value))} + rightSection={ + searchCity !== '' && ( + event.preventDefault()} + onClick={() => { + setSearchCity('') + setSelectedCity(null) + }} + aria-label="Clear value" + /> + ) + } + value={searchCity} + /> + - { - saveFeatures() - }}> - - + { + setSelectedYear(Number(e)) + }} + defaultValue={selectedYear?.toString()} + //variant="unstyled" + allowDeselect={false} + /> - { - draw.current?.removeLastPoint() - }}> - - - - { - handleToolSelect('Point') - }}> - - - - { - handleToolSelect('LineString') - }}> - - - - { - handleToolSelect('Polygon') - }}> - - - - { - handleToolSelect('Circle') - }}> - - - - map?.current?.addInteraction(new Translate())} - > - - - - - - - - - - submitOverlay()} + title='Настройки ИКС' > - + + + + + + + + { + 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())} > - + - - - setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} /> + + + + - setSatMapsProvider(value as SatelliteMapsProvider)} /> - - - - - }>{'Объекты'} - - { - //open() - - const city_id = 145 - const year = 2023 - - const figuresLimit = 10000 - const linesLimit = 10000 - - figuresLayer.current.getSource()?.clear() - - try { - // const response = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/images/all?city_id=${city_id}`, { - // method: 'GET', - // headers: { - // 'Content-Type': 'application/json', - // }, - // }) - - //const responseData = await response.json() - - const scaling = { - w: 10000, // responseData[0].width - h: 10000 // responseData[0].width - } - - const figuresResponse = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/figures/all?city_id=${city_id}&year=${year}&offset=0&limit=${figuresLimit}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }) - - const linesResponse = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/lines/all?city_id=${city_id}&year=${year}&offset=0&limit=${linesLimit}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }) - - await linesResponse.json().then(linesData => { - linesData.map((line: ILine) => { - const x1 = line.x1 * scaling.w - const y1 = line.y1 * scaling.h - const x2 = line.x2 * scaling.w - const y2 = line.y2 * scaling.h - - const center = [mapCenter[0], mapCenter[1]] - - const testCoords = [ - [center[0] + x1, center[1] - y1], - [center[0] + x2, center[1] - y2], - ] - - const feature = new Feature(new LineString(testCoords)) - feature.setStyle(styleFunction(feature)) - feature.set('object_id', line.object_id) - - figuresLayer.current?.getSource()?.addFeature(feature) - }) - }) - - await figuresResponse.json().then(figuresData => { - figuresData.map((figure: IFigure) => { - if (figure.figure_type_id == 1) { - const width = figure.width * scaling.w - const height = figure.height * scaling.h - - const left = figure.left * scaling.w - const top = figure.top * scaling.h - - const centerX = mapCenter[0] + left + (width / 2) - const centerY = mapCenter[1] - top - (height / 2) - - const radius = width / 2; - const circleGeom = new Circle([centerX, centerY], radius) - - const ellipseGeom = fromCircle(circleGeom, 64) - ellipseGeom.scale(1, height / width) - - const feature = new Feature(ellipseGeom) - - feature.setStyle(firstStyleFunction(feature)) - feature.set('object_id', figure.object_id) - figuresLayer.current?.getSource()?.addFeature(feature) - } - - if (figure.figure_type_id == 3) { - const x = figure.left * scaling.w - const y = figure.top * scaling.h - - const center = [mapCenter[0] + x, mapCenter[1] - y] - - const coords = figure.points?.split(' ').map(pair => { - const [x, y] = pair.split(';').map(Number) - return [ - center[0] + (x * scaling.w), - center[1] - (y * scaling.h) - ] - }) - - if (coords) { - const polygon = new Polygon([coords]) - - const feature = new Feature({ - geometry: polygon - }) - - feature.set('object_id', figure.object_id) - feature.setStyle(thirdStyleFunction(feature)) - figuresLayer.current?.getSource()?.addFeature(feature) - } - } - - if (figure.figure_type_id == 4) { - const width = figure.width * scaling.w - const height = figure.height * scaling.h - const left = figure.left * scaling.w - const top = figure.top * scaling.h - - const halfWidth = width / 2 - const halfHeight = height / 2 - - const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight] - - const testCoords = [ - [center[0] - halfWidth, center[1] - halfHeight], - [center[0] - halfWidth, center[1] + halfHeight], - [center[0] + halfWidth, center[1] + halfHeight], - [center[0] + halfWidth, center[1] - halfHeight], - [center[0] - halfWidth, center[1] - halfHeight] - ] - - const geometry1 = new Polygon([testCoords]) - const anchor1 = center - geometry1.rotate(-figure.angle * Math.PI / 180, anchor1) - const feature1 = new Feature(geometry1) - feature1.set('object_id', figure.object_id) - feature1.set('angle', figure.angle) - feature1.setStyle(fourthStyleFunction(feature1)) - figuresLayer.current?.getSource()?.addFeature(feature1) - } - }) - }) - } catch (err) { - console.error('No data') - } - }} + + + + submitOverlay()} > - Test - - - - - + + - - - + + + + + + + setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} /> + + setSatMapsProvider(value as SatelliteMapsProvider)} /> + + + + + }>{'Объекты'} + + + + + + + }> + {'Текущий объект'} + + + + + + + {valuesData && + }>{'Параметры объекта'} + + + {Array.isArray(valuesData) && valuesData.map((param: IObjectParam) => { + return ( + + ) + })} + + + } + + + + + + + x: {currentCoordinate?.[0]} - - + + + y: {currentCoordinate?.[1]} - - + - - Z={currentZ} - X={currentX} - Y={currentY} - + + + + Z={currentZ} + + + + X={currentX} + + + + Y={currentY} + + + + {statusText} + + - - {statusText} - { + const { data: typeData } = useSWR( + object_data.type ? `/general/types/all` : null, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + return ( + + {Array.isArray(typeData) && (typeData.find(type => Number(type.id) === Number(object_data.type)) as IObjectType).name} + + + + ) +} + +export default ObjectData \ No newline at end of file diff --git a/client/src/components/map/ObjectParameter.tsx b/client/src/components/map/ObjectParameter.tsx new file mode 100644 index 0000000..23473bd --- /dev/null +++ b/client/src/components/map/ObjectParameter.tsx @@ -0,0 +1,51 @@ +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 { IObjectParam, IParam } from '../../interfaces/objects' + +const ObjectParameter = ({ + id_param, + value +}: IObjectParam) => { + const { data: paramData } = useSWR( + `/general/params/all?param_id=${id_param}`, + (url) => fetcher(url, BASE_URL.ems).then(res => res[0] as IParam), + { + revalidateOnFocus: false + } + ) + + const Parameter = (type: string, name: string, value: unknown) => { + switch (type) { + case 'bit': + return ( + + + + + + {name} + + + ) + default: + return ( + + Неподдерживаемый параметр + + ) + } + } + + return ( + + {paramData && + Parameter(paramData.format, paramData.name, value) + } + + ) +} + +export default ObjectParameter \ No newline at end of file diff --git a/client/src/interfaces/objects.ts b/client/src/interfaces/objects.ts new file mode 100644 index 0000000..19b5907 --- /dev/null +++ b/client/src/interfaces/objects.ts @@ -0,0 +1,38 @@ +export interface IObjectData { + object_id: string, + id_city: number, + year: number, + id_parent: number | null, + type: number, + planning: boolean, + activity: boolean, + kvr: string | null, + jur: string | null, + fuel: string | null, + boiler_id: string | null +} + +export interface IObjectParam { + id_object: string, + id_param: number, + value: string, + date_s: string | null, + date_po: string | null, + id_user: number +} + +export interface IParam { + id: number, + id_group: number | null, + name: string, + format: string, + vtable: string, + unit: string | null, + exact_format: string | null, + inHistory: string | null +} + +export interface IObjectType { + id: number, + name: string +} \ No newline at end of file diff --git a/client/src/layouts/DashboardLayout.tsx b/client/src/layouts/DashboardLayout.tsx index 46f080d..5e4c784 100644 --- a/client/src/layouts/DashboardLayout.tsx +++ b/client/src/layouts/DashboardLayout.tsx @@ -49,10 +49,14 @@ function DashboardLayout() { - + {getPageTitle()} + + + + ))} - + + + ) diff --git a/client/src/pages/Boilers.tsx b/client/src/pages/Boilers.tsx index b9c27e6..63a078a 100644 --- a/client/src/pages/Boilers.tsx +++ b/client/src/pages/Boilers.tsx @@ -1,7 +1,7 @@ import { GridColDef } from '@mui/x-data-grid' import { useEffect, useState } from 'react' import { useBoilers } from '../hooks/swrHooks' -import { Badge, Flex, Table, Text } from '@mantine/core' +import { Badge, ScrollAreaAutosize, Table, Text } from '@mantine/core' function Boilers() { const [boilersPage, setBoilersPage] = useState(1) @@ -33,7 +33,7 @@ function Boilers() { ] return ( - + Котельные @@ -78,7 +78,7 @@ function Boilers() { } - + ) } diff --git a/client/src/pages/Main.tsx b/client/src/pages/Main.tsx index 4f3e3e0..4af343e 100644 --- a/client/src/pages/Main.tsx +++ b/client/src/pages/Main.tsx @@ -34,7 +34,7 @@ export default function Main() { } return ( - + Главная diff --git a/client/src/pages/NotFound.tsx b/client/src/pages/NotFound.tsx index 59dea02..bf4bee4 100644 --- a/client/src/pages/NotFound.tsx +++ b/client/src/pages/NotFound.tsx @@ -3,14 +3,13 @@ import { IconError404 } from "@tabler/icons-react"; export default function NotFound() { return ( - + Запрашиваемая страница не найдена. - ) } \ No newline at end of file diff --git a/client/src/pages/Reports.tsx b/client/src/pages/Reports.tsx index b9bbc35..4baffdd 100644 --- a/client/src/pages/Reports.tsx +++ b/client/src/pages/Reports.tsx @@ -3,7 +3,7 @@ import { useCities, useReport, useReportExport } from "../hooks/swrHooks" import { useDebounce } from "@uidotdev/usehooks" import { ICity } from "../interfaces/fuel" import { mutate } from "swr" -import { ActionIcon, Autocomplete, Badge, Button, CloseButton, Flex, Table } from "@mantine/core" +import { ActionIcon, Autocomplete, Badge, Button, CloseButton, Flex, ScrollAreaAutosize, Table } from "@mantine/core" import { IconRefresh } from "@tabler/icons-react" export default function Reports() { @@ -40,7 +40,7 @@ export default function Reports() { } return ( - + {/* */} { }} /> */} - + ) } \ No newline at end of file diff --git a/client/src/pages/Roles.tsx b/client/src/pages/Roles.tsx index e14e892..8ae7e0e 100644 --- a/client/src/pages/Roles.tsx +++ b/client/src/pages/Roles.tsx @@ -3,7 +3,7 @@ import { useRoles } from '../hooks/swrHooks' import { CreateField } from '../interfaces/create' import RoleService from '../services/RoleService' import FormFields from '../components/FormFields' -import { Button, Flex, Loader, Modal, Table } from '@mantine/core' +import { Button, Loader, Modal, ScrollAreaAutosize, Table } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' export default function Roles() { @@ -26,7 +26,7 @@ export default function Roles() { if (isLoading) return return ( - + Добавить роль @@ -78,6 +78,6 @@ export default function Roles() { onProcessRowUpdateError={() => { }} /> */} - + ) } \ No newline at end of file diff --git a/client/src/pages/Servers.tsx b/client/src/pages/Servers.tsx index e1b46f5..7de35c6 100644 --- a/client/src/pages/Servers.tsx +++ b/client/src/pages/Servers.tsx @@ -3,13 +3,13 @@ import ServersView from "../components/ServersView" import ServerIpsView from "../components/ServerIpsView" import ServerHardware from "../components/ServerHardware" import ServerStorage from "../components/ServerStorages" -import { Flex, Tabs } from "@mantine/core" +import { Flex, ScrollAreaAutosize, Tabs } from "@mantine/core" export default function Servers() { const [currentTab, setCurrentTab] = useState('0') return ( - + @@ -43,6 +43,6 @@ export default function Servers() { width={500} height={300} /> */} - + ) } \ No newline at end of file diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx index 95a647c..1a471d0 100644 --- a/client/src/pages/Settings.tsx +++ b/client/src/pages/Settings.tsx @@ -5,7 +5,7 @@ import { CreateField } from "../interfaces/create" import { IUser } from "../interfaces/user" import FormFields from "../components/FormFields" import AuthService from "../services/AuthService" -import { Flex } from "@mantine/core" +import { Flex, ScrollAreaAutosize } from "@mantine/core" export default function Settings() { const { token } = useAuthStore() @@ -39,10 +39,9 @@ export default function Settings() { ] return ( - {currentUser && @@ -64,6 +63,6 @@ export default function Settings() { /> } - + ) } \ No newline at end of file diff --git a/client/src/pages/TableTest.tsx b/client/src/pages/TableTest.tsx index f84b447..3ad733e 100644 --- a/client/src/pages/TableTest.tsx +++ b/client/src/pages/TableTest.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { Flex } from '@mantine/core'; import CustomTable from '../components/CustomTable'; @@ -6,24 +5,6 @@ function TableTest() { return ( - {/* ({ - column1: `column:1 row:${index}`, - column2: `column:2 row:${index}`, - column3: `column:3 row:${index}`, - column4: `column:4 row:${index}`, - id: index, - }), - )} - rowKeyField={'id'} - /> */} ) diff --git a/client/src/pages/Users.tsx b/client/src/pages/Users.tsx index c318c65..ca2266d 100644 --- a/client/src/pages/Users.tsx +++ b/client/src/pages/Users.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react" import { CreateField } from "../interfaces/create" import UserService from "../services/UserService" import FormFields from "../components/FormFields" -import { Badge, Button, Flex, Loader, Modal, Pagination, Select, Table } from "@mantine/core" +import { Badge, Button, Flex, Loader, Modal, Pagination, ScrollAreaAutosize, Select, Table } from "@mantine/core" import { useDisclosure } from "@mantine/hooks" export default function Users() { @@ -65,7 +65,7 @@ export default function Users() { } return ( - + Добавить пользователя @@ -157,6 +157,6 @@ export default function Users() { onProcessRowUpdateError={() => { }} /> */} - + ) } \ No newline at end of file diff --git a/ems/src/index.ts b/ems/src/index.ts index c8ee13c..912d007 100644 --- a/ems/src/index.ts +++ b/ems/src/index.ts @@ -9,9 +9,9 @@ import cors from 'cors' import { Coordinate } from './interfaces/map' import { generateTilesForZoomLevel } from './utils/tiles' import { query, validationResult } from 'express-validator' -import { Connection, Request as TediousRequest } from 'tedious' +import { Connection, ConnectionConfiguration, Request as TediousRequest } from 'tedious' -const tedious = new Connection({ +const tediousConfig: ConnectionConfiguration = { server: 'localhost', options: { trustServerCertificate: true, @@ -25,17 +25,7 @@ const tedious = new Connection({ password: 'oMhthmsvbYHc' } } -}) - -tedious.on('connect', function (err) { - if (err) { - console.log('Error: ', err) - } - - console.log('Connected to General') -}) - -tedious.connect() +} const prisma = new PrismaClient() const app = express() @@ -185,33 +175,53 @@ app.get('/tile/:provider/:z/:x/:y', async (req: Request, res: Response) => { function tediousQuery(query: string) { // Read all rows from table - 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.`); - } - } - ); + return new Promise((resolve, reject) => { - const result: any = []; + const connection = new Connection(tediousConfig) - request.on("row", (columns) => { - const entry: any = {}; - columns.forEach((column: any) => { - entry[column.metadata.colName] = column.value; + 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); }); - result.push(entry); - }); - request.on('error', error => reject(error));// some error happened, reject the promise - request.on('requestCompleted', () => resolve(result)); // resolve the promise with the result rows. + 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. - tedious.execSql(request); + connection.execSql(request) + }) + + connection.on('error', (err) => { + reject(err) + }) + + connection.connect() }); } @@ -235,16 +245,29 @@ app.get('/general/cities/all', async (req: Request, res: Response) => { } }) +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, id } = req.query + const { offset, limit, city_id } = req.query const result = await tediousQuery( ` - SELECT * FROM nGeneral..tObjects + SELECT * FROM nGeneral..vObjects ${city_id ? `WHERE id_city = ${city_id}` : ''} - ${id ? `WHERE id = '${id}'` : ''} - ORDER BY id + ORDER BY object_id OFFSET ${Number(offset) || 0} ROWS FETCH NEXT ${Number(limit) || 10} ROWS ONLY; ` @@ -255,6 +278,66 @@ app.get('/general/objects/all', async (req: Request, res: Response) => { } }) +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
{name}