diff --git a/client/index.html b/client/index.html
index 4a8433d..fa76fe8 100644
--- a/client/index.html
+++ b/client/index.html
@@ -2,9 +2,9 @@
-
+
- Dashboard
+ ИС
diff --git a/client/public/logo1.svg b/client/public/logo1.svg
new file mode 100644
index 0000000..349d9e8
--- /dev/null
+++ b/client/public/logo1.svg
@@ -0,0 +1,34 @@
+
diff --git a/client/public/logo2.svg b/client/public/logo2.svg
new file mode 100644
index 0000000..911e6f7
--- /dev/null
+++ b/client/public/logo2.svg
@@ -0,0 +1,34 @@
+
diff --git a/client/src/components/FetchingData.ts b/client/src/components/FetchingData.ts
deleted file mode 100644
index b8101c7..0000000
--- a/client/src/components/FetchingData.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useState, useEffect, useMemo } from 'react'
-import axiosInstance from '../http/axiosInstance'
-export function useDataFetching(url: string, initData: T): T {
- const [data, setData] = useState(initData)
-
- useEffect(() => {
- const fetchData = async () => {
- const response = await axiosInstance.get(url)
- const result = await response.data
- setData(result)
- }
-
- fetchData()
- }, [url])
-
- // Memoize the data value
- const memoizedData = useMemo(() => data, [data])
- return memoizedData
-}
-
-export default useDataFetching;
\ No newline at end of file
diff --git a/client/src/components/Tree/ObjectTree.tsx b/client/src/components/Tree/ObjectTree.tsx
new file mode 100644
index 0000000..6f89de3
--- /dev/null
+++ b/client/src/components/Tree/ObjectTree.tsx
@@ -0,0 +1,110 @@
+import { useState } from 'react'
+import useSWR from 'swr'
+import { fetcher } from '../../http/axiosInstance'
+import { BASE_URL } from '../../constants'
+import { Accordion, NavLink } from '@mantine/core';
+import { setCurrentObjectId, useObjectsStore } from '../../store/objects';
+
+const ObjectTree = () => {
+ const { selectedCity, selectedYear } = useObjectsStore()
+
+ const [existingCount, setExistingCount] = useState(0)
+ const [planningCount, setPlanningCount] = useState(0)
+
+ const { data: existingObjectsList } = useSWR(
+ selectedYear && selectedCity ? `/general/objects/list?year=${selectedYear}&city_id=${selectedCity}&planning=0` : null,
+ (url) => fetcher(url, BASE_URL.ems).then(res => {
+ if (Array.isArray(res)) {
+ let count = 0
+ res.forEach(el => {
+ count = count + el.count
+ })
+ setExistingCount(count)
+ }
+ return res
+ }),
+ {
+ 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).then(res => {
+ if (Array.isArray(res)) {
+ let count = 0
+ res.forEach(el => {
+ count = count + el.count
+ })
+ setPlanningCount(count)
+ }
+ return res
+ }),
+ {
+ revalidateOnFocus: false
+ }
+ )
+
+ return (
+
+
+
+
+ )
+}
+
+interface TypeTreeProps {
+ label: string;
+ value: string;
+ count: number;
+ objectList: unknown;
+ planning: number;
+}
+
+const TypeTree = ({
+ label,
+ objectList,
+ count,
+ planning
+}: TypeTreeProps) => {
+
+ return (
+
+ {Array.isArray(objectList) && objectList.map(list => (
+
+ ))}
+
+ )
+}
+
+interface IObjectList {
+ label: string;
+ id: number;
+ planning: number;
+ count: number;
+}
+
+const ObjectList = ({
+ label,
+ id,
+ planning,
+ count
+}: IObjectList) => {
+ const { selectedCity, selectedYear } = useObjectsStore()
+
+ const { data } = useSWR(
+ selectedCity && selectedYear ? `/general/objects/list?type=${id}&city_id=${selectedCity}&year=${selectedYear}&planning=${planning}` : null,
+ (url) => fetcher(url, BASE_URL.ems),
+ { revalidateOnFocus: false }
+ )
+
+ return (
+
+ {Array.isArray(data) && data.map((type) => (
+ setCurrentObjectId(type.object_id)} />
+ ))}
+
+ )
+}
+
+export default ObjectTree
\ No newline at end of file
diff --git a/client/src/components/UserData.ts b/client/src/components/UserData.ts
deleted file mode 100644
index 227281e..0000000
--- a/client/src/components/UserData.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-import UserService from "../services/UserService";
-
-export default function useUserData(token: string, initData: T): T {
- const [userData, setUserData] = useState(initData)
-
- useEffect(()=> {
- const fetchUserData = async (token: string) => {
- const response = await UserService.getCurrentUser(token)
- setUserData(response.data)
- }
-
- fetchUserData(token)
- }, [token])
-
- const memoizedData = useMemo(() => userData, [userData])
- return memoizedData
-}
\ No newline at end of file
diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx
index b0baf5e..ce28c07 100644
--- a/client/src/components/map/MapComponent.tsx
+++ b/client/src/components/map/MapComponent.tsx
@@ -4,17 +4,15 @@ import Map from 'ol/Map'
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 { Tile as TileLayer, VectorImage, Vector as VectorLayer } from 'ol/layer'
import { click, never, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
import Feature from 'ol/Feature'
import { IRectCoords, SatelliteMapsProvider } from '../../interfaces/map'
import { Extent } from 'ol/extent'
import { drawingLayerStyle, highlightStyleRed, highlightStyleYellow, overlayStyle, regionsLayerStyle } from './MapStyles'
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
-import { mapCenter } from './MapConstants'
import ImageLayer from 'ol/layer/Image'
-import VectorImageLayer from 'ol/layer/VectorImage'
-import { LineString, Point } from 'ol/geom'
+import { LineString, Point, SimpleGeometry } from 'ol/geom'
import { fromExtent } from 'ol/geom/Polygon'
import Collection from 'ol/Collection'
import { Coordinate } from 'ol/coordinate'
@@ -24,22 +22,37 @@ import { get, transform } from 'ol/proj'
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
-import { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, Text as MantineText, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Portal, Tree, Group, TreeNodeData, Button, useTree, Timeline, Text, Stack, Overlay } from '@mantine/core'
-import { IconChevronDown, IconPlus, IconSettings, IconTable, IconUpload } from '@tabler/icons-react'
+import { Accordion, ActionIcon, Autocomplete, Box, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, ScrollAreaAutosize, Slider, useMantineColorScheme, Portal, Timeline, Text, Stack, NavLink, Grid, Checkbox } from '@mantine/core'
+import { IconPlus, IconSearch, 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, IObjectList, IObjectParam } from '../../interfaces/objects'
-import ObjectData from './ObjectData'
+import { IObjectParam } from '../../interfaces/objects'
import MapToolbar from './MapToolbar/MapToolbar'
import MapStatusbar from './MapStatusbar/MapStatusbar'
import { measureStyleFunction, modifyStyle } from './Measure/MeasureStyles'
import { useMapStore } from '../../store/map'
-import { MapTreeCheckbox } from './MapTree/MapTreeCheckbox'
import { v4 as uuidv4 } from 'uuid'
+import { useThrottle } from '@uidotdev/usehooks'
+import ObjectTree from '../Tree/ObjectTree'
+import { setCurrentObjectId, setSelectedCity, setSelectedYear, useObjectsStore } from '../../store/objects'
+
+// Settings for cities
+const citySettings = [
+ {
+ city_id: 145,
+ // scale: 10000,
+ scale: 9000,
+ offset_x: 14442665.697619518,
+ offset_y: 8884520.63524492,
+ rotation: 0
+ }
+]
const MapComponent = () => {
+ const { selectedCity, selectedYear, currentObjectId } = useObjectsStore()
+
const mapState = useMapStore()
const [currentCoordinate, setCurrentCoordinate] = useState(null)
@@ -121,24 +134,27 @@ const MapComponent = () => {
}
}))
- const figuresLayer = useRef(new VectorLayer({
+ const figuresLayer = useRef(new VectorImage({
source: new VectorSource(),
+ declutter: true,
properties: {
id: uuidv4(),
name: 'Фигуры'
}
}))
- const linesLayer = useRef(new VectorLayer({
+ const linesLayer = useRef(new VectorImage({
source: new VectorSource(),
+ declutter: true,
properties: {
id: uuidv4(),
name: 'Линии'
}
}))
- const regionsLayer = useRef(new VectorImageLayer({
+ const regionsLayer = useRef(new VectorImage({
source: regionsLayerSource,
+ declutter: true,
style: regionsLayerStyle,
properties: {
id: uuidv4(),
@@ -356,14 +372,14 @@ const MapComponent = () => {
],
target: mapElement.current as HTMLDivElement,
view: new View({
- center: transform([129.7659541, 62.009504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
- zoom: 17,
+ center: transform([129.7466541, 62.083504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
+ zoom: 16,
maxZoom: 21,
//extent: mapExtent,
}),
})
- map.current.on('pointermove', function (e: MapBrowserEvent) {
+ map.current.on('pointermove', function (e: MapBrowserEvent) {
setCurrentCoordinate(e.coordinate)
const currentExtent = get('EPSG:3857')?.getExtent() as Extent
const { tileX, tileY } = getGridCellPosition(e.coordinate[0], e.coordinate[1], currentExtent, Number(map.current?.getView().getZoom()?.toFixed(0)))
@@ -372,7 +388,7 @@ const MapComponent = () => {
setCurrentY(tileY)
})
- map.current.on('click', function (e: MapBrowserEvent) {
+ map.current.on('click', function (e: MapBrowserEvent) {
const pixel = map.current?.getEventPixel(e.originalEvent)
if (pixel) {
@@ -430,7 +446,7 @@ const MapComponent = () => {
}
}, [mapState.currentTool])
- const [satelliteOpacity, setSatelliteOpacity] = useState(1)
+ const [satelliteOpacity, setSatelliteOpacity] = useState(0)
const [statusText, setStatusText] = useState('')
@@ -513,31 +529,14 @@ const MapComponent = () => {
}
}, [nodes])
- 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 throttledSearchCity = useThrottle(searchCity, 500);
- 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 [searchObject, setSearchObject] = useState("")
+ const throttledSearchObject = useThrottle(searchObject, 500);
- 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(() => {
@@ -549,7 +548,7 @@ const MapComponent = () => {
if (selectedObjectList == feature.get('type')) {
feature.setStyle(highlightStyleYellow);
} else {
- feature.setStyle(null); // Reset to default style
+ feature.setStyle(undefined); // Reset to default style
}
})
}
@@ -560,81 +559,70 @@ const MapComponent = () => {
if (selectedObjectList == feature.get('type')) {
feature.setStyle(highlightStyleYellow);
} else {
- feature.setStyle(null); // Reset to default style
+ feature.setStyle(undefined); // Reset to default style
}
})
}
}, [selectedObjectList])
- useEffect(() => {
- if (existingObjectsList && planningObjectsList) {
- setObjectsList([
- {
- label: 'Существующие',
- value: 'existing',
- children: existingObjectsList.map((list: IObjectList) => ({
- label:list.name,
- count: list.count,
- value: list.id,
- })),
- },
- {
- label: 'Планируемые',
- value: 'planning',
- children: planningObjectsList.map((list: IObjectList) => ({
- label: list.name,
- count: list.count,
- value: list.id
- }))
- }
- ])
- }
- }, [existingObjectsList, planningObjectsList])
-
useEffect(() => {
if (currentObjectId) {
if (figuresLayer.current) {
// Reset styles and apply highlight to matching features
- figuresLayer.current.getSource()?.getFeatures().forEach((feature) => {
+ figuresLayer.current.getSource()?.getFeatures().forEach((feature: Feature) => {
if (currentObjectId == feature.get('object_id')) {
feature.setStyle(highlightStyleRed);
+
+ const geometry = feature.getGeometry()
+
+ if (geometry) {
+ map.current?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 })
+ //map.current?.getView().animate({ center: calculateCenter(geometry as SimpleGeometry).center })
+ }
+
} else {
- feature.setStyle(null); // Reset to default style
+ feature.setStyle(undefined); // Reset to default style
}
})
}
if (linesLayer.current) {
// Reset styles and apply highlight to matching features
- linesLayer.current.getSource()?.getFeatures().forEach((feature) => {
+ linesLayer.current.getSource()?.getFeatures().forEach((feature: Feature) => {
if (currentObjectId == feature.get('object_id')) {
feature.setStyle(highlightStyleRed);
+
+ const geometry = feature.getGeometry()
+ if (geometry) {
+ map.current?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 })
+ }
} else {
- feature.setStyle(null); // Reset to default style
+ feature.setStyle(undefined); // Reset to default style
}
})
}
}
}, [currentObjectId])
- 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
+ revalidateOnFocus: false,
+ revalidateIfStale: false
}
)
const { data: citiesData } = useSWR(
- `/general/cities/all?limit=${10}&offset=${citiesPage || 0}${searchCity ? `&search=${searchCity}` : ''}`,
+ `/general/cities/all?limit=${10}&offset=${citiesPage || 0}${throttledSearchCity ? `&search=${throttledSearchCity}` : ''}`,
+ (url) => fetcher(url, BASE_URL.ems),
+ {
+ revalidateOnFocus: false
+ }
+ )
+
+ const { data: searchData } = useSWR(
+ throttledSearchObject !== "" && selectedCity && selectedYear ? `/general/search/objects?q=${throttledSearchObject}&id_city=${selectedCity}&year=${selectedYear}` : null,
(url) => fetcher(url, BASE_URL.ems),
{
revalidateOnFocus: false
@@ -664,16 +652,45 @@ const MapComponent = () => {
)
useEffect(() => {
+ let offset_x = 14444582.697619518
+ let offset_y = 8866450.63524492
+ let scale = {
+ w: 100000,
+ h: 100000
+ }
+ let rotation = 0
+
+ if (citySettings.find(el => el.city_id === selectedCity)) {
+ console.log("City settings found")
+
+ if (citySettings.find(el => el.city_id === selectedCity)?.scale) {
+ scale = {
+ w: citySettings.find(el => el.city_id === selectedCity).scale,
+ h: citySettings.find(el => el.city_id === selectedCity).scale
+ }
+ }
+ if (citySettings.find(el => el.city_id === selectedCity)?.offset_x && citySettings.find(el => el.city_id === selectedCity)?.offset_y) {
+ offset_x = citySettings.find(el => el.city_id === selectedCity).offset_x
+ offset_y = citySettings.find(el => el.city_id === selectedCity).offset_y
+ }
+
+ if (citySettings.find(el => el.city_id === selectedCity)?.rotation) {
+ rotation = citySettings.find(el => el.city_id === selectedCity)?.rotation
+ }
+ } else {
+ console.log("City settings NOT found")
+ }
+
if (Array.isArray(figuresData)) {
figuresLayer.current.getSource()?.clear()
if (figuresData.length > 0) {
const scaling = {
- w: 10000, // responseData[0].width
- h: 10000 // responseData[0].width
+ w: scale.w, // responseData[0].width
+ h: scale.h // responseData[0].width
}
figuresData.map((figure: IFigure) => {
- processFigure(figure, scaling, mapCenter, figuresLayer)
+ processFigure(figure, scaling, [offset_x, offset_y], figuresLayer)
})
}
}
@@ -682,17 +699,54 @@ const MapComponent = () => {
linesLayer.current.getSource()?.clear()
if (linesData.length > 0) {
const scaling = {
- w: 10000, // responseData[0].width
- h: 10000 // responseData[0].width
+ w: scale.w, // responseData[0].width
+ h: scale.h // responseData[0].width
}
linesData.map((line: ILine) => {
- processLine(line, scaling, mapCenter, linesLayer)
+ processLine(line, scaling, [offset_x, offset_y], linesLayer)
})
}
}
}, [figuresData, linesData, selectedCity, selectedYear])
+
+ useEffect(() => {
+ // if (map.current) {
+ // map.current.on('postcompose', function () {
+ // if (colorScheme === 'dark') {
+ // document.querySelector('canvas').style.filter = 'grayscale(80%) invert(100%) hue-rotate(180deg)'
+ // } else {
+ // document.querySelector('canvas').style.filter = 'grayscale(0%) invert(0%) hue-rotate(0deg)'
+ // }
+ // })
+ // }
+
+ if (baseLayer.current) {
+ baseLayer.current.on('prerender', function (e) {
+ if (colorScheme === 'dark') {
+ if (e.context) {
+ const context = e.context as CanvasRenderingContext2D;
+ context.filter = 'grayscale(80%) invert(100%) hue-rotate(180deg) ';
+ context.globalCompositeOperation = 'source-over';
+ }
+ } else {
+ if (e.context) {
+ const context = e.context as CanvasRenderingContext2D;
+ context.filter = 'none';
+ }
+ }
+ })
+
+ baseLayer.current.on('postrender', function (e) {
+ if (e.context) {
+ const context = e.context as CanvasRenderingContext2D;
+ context.filter = 'none';
+ }
+ })
+ }
+ }, [colorScheme])
+
return (
@@ -703,11 +757,37 @@ const MapComponent = () => {
setSatMapsProvider(value as SatelliteMapsProvider)} />
+
+
({ label: el, value: el }))}
onChange={(e) => {
setSelectedYear(Number(e))
}}
defaultValue={selectedYear?.toString()}
- //variant="unstyled"
allowDeselect={false}
/>
@@ -766,7 +838,7 @@ const MapComponent = () => {
{
-
-
- }>{'Объекты'}
-
- {objectsList &&
- (
- {
- elementProps.onClick(e)
-
- if (node.value !== 'existing' && node.value !== 'planning') {
- setSelectedObjectList(Number(node.value))
-
- try {
- // Fetch data from the API based on the node's value
- await fetcher(`/general/objects/list?type=${node.value}&city_id=${selectedCity}&year=${selectedYear}&planning=0`, BASE_URL.ems).then(res => {
- setObjectsList((prevList) => {
- return prevList.map((item) => {
- return {
- ...item,
- children: [
- ...(item.children.map(child => {
- if (child.value == node.value) {
- return {
- ...child,
- children: res.map(object => {
- return {
- label: object.object_id,
- value: object.object_id
- }
- })
- }
- } else {
- return { ...child }
- }
- }) || []),
- ],
- }
- }
- )
- }
- );
- })
-
-
- } catch (error) {
- console.error('Error fetching data:', error);
- }
- }
- }}>
- {(hasChildren || node?.count) && (
-
- )}
- {`${node.label} ${node?.count ? `(${node.count})` : ''}`}
-
- )}
- />
- }
-
-
-
-
- {/* {currentObjectId &&
-
- }>
- {'Текущий объект'}
-
+ {selectedCity &&
+
+
+ }>{'Объекты'}
-
+
- } */}
- {valuesData &&
-
- }>{'Параметры объекта'}
-
-
- {Array.isArray(valuesData) &&
- // Group objects by `id_param`
- Object.entries(
- valuesData.reduce((acc, param) => {
- if (!acc[param.id_param]) {
- acc[param.id_param] = [];
- }
- acc[param.id_param].push(param);
- return acc;
- }, {} as Record)
- ).map(([id_param, params]) => {
- // Step 1: Sort the parameters by date_s (start date) and date_po (end date)
- const sortedParams = params.sort((b, a) => {
- const dateA = new Date(a.date_s || 0);
- const dateB = new Date(b.date_s || 0);
- return dateA.getTime() - dateB.getTime();
- });
+ {valuesData &&
+
+ }>{'Параметры объекта'}
+
+
+ {Array.isArray(valuesData) &&
+ Object.entries(
+ valuesData.reduce((acc, param) => {
+ if (!acc[param.id_param]) {
+ acc[param.id_param] = [];
+ }
+ acc[param.id_param].push(param);
+ return acc;
+ }, {} as Record)
+ ).map(([id_param, params]) => {
+ // Step 1: Sort the parameters by date_s (start date) and date_po (end date)
+ const sortedParams = (params as IObjectParam[]).sort((b, a) => {
+ const dateA = new Date(a.date_s || 0);
+ const dateB = new Date(b.date_s || 0);
+ return dateA.getTime() - dateB.getTime();
+ });
- return sortedParams.length > 1 ? (
- // Step 2: Render Mantine Timeline for multiple entries with the same `id_param`
-
- {sortedParams.map((param, index) => (
-
-
-
- {new Date(param.date_s).toLocaleDateString('en-GB').split('/').join('.')} - {new Date(param.date_po).toLocaleDateString('en-GB') === '01/01/1970' ? 'По сей день' : new Date(param.date_po).toLocaleDateString('en-GB').split('/').join('.')}
-
-
- ))}
-
- ) : (
- // Step 3: Render ObjectParameter directly if there's only one entry
-
- );
- })
- }
-
-
-
- }
-
+ return sortedParams.length > 1 ? (
+ // Step 2: Render Mantine Timeline for multiple entries with the same `id_param`
+
+ {sortedParams.map((param: IObjectParam, index: number) => (
+
+
+
+ {new Date(param.date_s).toLocaleDateString('en-GB').split('/').join('.')} - {new Date(param.date_po).toLocaleDateString('en-GB') === '01/01/1970' ? 'По сей день' : new Date(param.date_po).toLocaleDateString('en-GB').split('/').join('.')}
+
+
+ ))}
+
+ ) : (
+
+ );
+ })
+ }
+
+
+
+ }
+
+ }
}>{'Слои'}
- {map.current?.getLayers().getArray() &&
- {
- return {
- label: layer.get('name'),
- value: layer.get('id')
- }
- }) as TreeNodeData[]}
- //selectOnClick
- expandOnClick={false}
- levelOffset={23}
- renderNode={MapTreeCheckbox}
- />
- }
-
+ {map.current?.getLayers().getArray() && map.current?.getLayers().getArray().map((layer, index) => (
+
+ layer.getLayerState().visible ? layer.setVisible(false) : layer.setVisible(true)}
+ />
+ { console.log(layer.getLayerState()) }} />
+
+ ))}
-
diff --git a/client/src/components/map/MapTree/MapTreeCheckbox.tsx b/client/src/components/map/MapTree/MapTreeCheckbox.tsx
index c7318bf..67fee8b 100644
--- a/client/src/components/map/MapTree/MapTreeCheckbox.tsx
+++ b/client/src/components/map/MapTree/MapTreeCheckbox.tsx
@@ -1,6 +1,5 @@
-import { Checkbox, Group, RenderTreeNodePayload } from "@mantine/core";
+import { Checkbox, Group, RenderTreeNodePayload, Text } from "@mantine/core";
import { IconChevronDown } from "@tabler/icons-react";
-import { useEffect } from "react";
export const MapTreeCheckbox = ({
node,
@@ -12,10 +11,6 @@ export const MapTreeCheckbox = ({
const checked = tree.isNodeChecked(node.value);
const indeterminate = tree.isNodeIndeterminate(node.value);
- useEffect(() => {
- console.log(node.value)
- }, [checked])
-
return (
tree.toggleExpanded(node.value)}>
- {node.label}
+ {node.label}
{hasChildren && (
precision) {
+ // Truncate the value if it exceeds the total precision
+ formattedValue = numericValue.toPrecision(precision);
+ }
+
+ // Pad with leading zeros if necessary (if it's an integer and the precision is greater than the scale)
+ const [integerPart, decimalPart] = formattedValue.split('.');
+
+ // Ensure the integer part doesn't exceed the precision
+ const paddedInteger = integerPart.padStart(precision - scale, '0');
+
+ // Reassemble the number
+ return `${paddedInteger}.${decimalPart.padEnd(scale, '0')}`;
+}
interface ObjectParameterProps {
showLabel?: boolean,
@@ -12,56 +48,61 @@ interface ObjectParameterProps {
}
const ObjectParameter = ({
- param,
- showLabel = true
+ param
}: ObjectParameterProps) => {
const { data: paramData } = useSWR(
`/general/params/all?param_id=${param.id_param}`,
(url) => fetcher(url, BASE_URL.ems).then(res => res[0] as IParam),
{
- revalidateOnFocus: false
+ revalidateOnFocus: false,
+ revalidateIfStale: false
}
)
- const Parameter = (type: string, name: string, value: unknown, vtable: string) => {
+ const Parameter = (type: string, name: string, value: unknown, vtable: string, unit: string | null) => {
switch (type) {
case 'bit':
return (
-
-
- {name}
-
- )
- case 'varchar(200)':
- return (
-
- {decodeDoubleEncodedString(value as string)}
-
- )
- case 'varchar(5)':
- return (
-
- {decodeDoubleEncodedString(value as string)}
-
+
)
case 'bigint':
return (
-
- {(value as string)}
-
+
+ )
+ case 'tinyint':
+ return (
+
+ )
+ // TODO: Calculate from calc procedures
+ case 'calculate':
+ return (
+
)
case 'GTCB':
return (
-
+
)
case 'TCB':
return (
-
+
+ )
+ case type.match(/varchar\((\d+)\)/)?.input:
+ return (
+
+ )
+ case type.match(/numeric\((\d+),(\d+)\)/)?.input:
+ return (
+
+ )
+ case 'year':
+ return (
+
)
default:
return (
{type}
+ {value as string}
)
}
@@ -70,12 +111,7 @@ const ObjectParameter = ({
return (
<>
{paramData &&
-
- {showLabel &&
-
- }
- {Parameter(paramData.format, paramData.name, param.value, paramData.vtable)}
-
+ Parameter(paramData.format, paramData.name, param.value, paramData.vtable, paramData.unit)
}
>
)
diff --git a/client/src/components/map/TCBParameter.tsx b/client/src/components/map/TCBParameter.tsx
index 9f49fab..40127be 100644
--- a/client/src/components/map/TCBParameter.tsx
+++ b/client/src/components/map/TCBParameter.tsx
@@ -1,30 +1,20 @@
-import React from 'react'
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { Text } from '@mantine/core'
+import TableValue from './TableValue'
interface ITCBParameterProps {
value: string,
vtable: string,
- inactive?: boolean
-}
-
-interface vStreet {
- id: number,
- id_city: number,
- name: string,
- kv: number
-}
-
-interface tType {
- id: number,
- name: string,
+ inactive?: boolean,
+ name: string
}
const TCBParameter = ({
value,
- vtable
+ vtable,
+ name
}: ITCBParameterProps) => {
//Get value
@@ -32,16 +22,8 @@ const TCBParameter = ({
`/general/params/tcb?id=${value}&vtable=${vtable}`,
(url) => fetcher(url, BASE_URL.ems).then(res => res[0]),
{
- revalidateOnFocus: false
- }
- )
-
- //Get available values
- const { data: tcbAll } = useSWR(
- `/general/params/tcb?vtable=${vtable}`,
- (url) => fetcher(url, BASE_URL.ems).then(res => res),
- {
- revalidateOnFocus: false
+ revalidateOnFocus: false,
+ revalidateIfStale: false
}
)
@@ -49,43 +31,84 @@ const TCBParameter = ({
switch (vtable) {
case 'vStreets':
return (
-
- {JSON.stringify(tcbValue)}
-
+
)
case 'tTypes':
return (
-
- {(tcbValue as tType)?.name}
-
+
)
case 'vPipesGround':
return (
-
- {(tcbValue)?.name}
-
+
)
case 'vRepairEvent':
return (
-
- {(tcbValue)?.name}
-
+
)
case 'vPipesMaterial':
return (
-
- {(tcbValue)?.name}
-
+
)
case 'vBoilers':
return (
-
- {(tcbValue)?.name}
-
+
+ )
+ case 'vHotWaterTypes':
+ return (
+
+ )
+ case 'vHeatingTypes':
+ return (
+
+ )
+ case 'vColdWaterTypes':
+ return (
+
+ )
+ case 'vCanalization':
+ return (
+
+ )
+ case 'vElectroSupplyTypes':
+ return (
+
+ )
+ case 'vGasSupplyTypes':
+ return (
+
+ )
+ case 'vFoundation':
+ return (
+
+ )
+ case 'vMaterialsWall':
+ return (
+
+ )
+ case 'vCovering':
+ return (
+
+ )
+ case 'vRoof':
+ return (
+
+ )
+ case 'vTechStatus':
+ return (
+
+ )
+ case 'vPipeOutDiameters':
+ return (
+
+ )
+ case 'vPipeDiameters':
+ return (
+
)
default:
return (
+ {JSON.stringify(name)}
{JSON.stringify(tcbValue)}
)
diff --git a/client/src/components/map/TableValue.tsx b/client/src/components/map/TableValue.tsx
new file mode 100644
index 0000000..af818b6
--- /dev/null
+++ b/client/src/components/map/TableValue.tsx
@@ -0,0 +1,71 @@
+import { Checkbox, ComboboxData, Grid, NumberInput, Select, Text, Textarea } from '@mantine/core';
+import useSWR from 'swr';
+import { fetcher } from '../../http/axiosInstance';
+import { BASE_URL } from '../../constants';
+import { useObjectsStore } from '../../store/objects';
+
+interface TableValueProps {
+ name: string;
+ value: unknown;
+ type: 'value' | 'boolean' | 'number' | 'select' | 'string';
+ unit?: string | null | undefined;
+ vtable?: string;
+}
+
+const TableValue = ({
+ name,
+ value,
+ type,
+ unit,
+ vtable
+}: TableValueProps) => {
+ const { selectedCity } = useObjectsStore()
+
+ //Get available values
+ const { data: tcbAll, isValidating } = useSWR(
+ type === 'select' && selectedCity ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedCity}` : null,
+ (url) => fetcher(url, BASE_URL.ems).then(res => {
+ if (Array.isArray(res)) {
+ return res.map((el) => ({
+ label: el.name || "",
+ value: JSON.stringify(el.id)
+ })) as ComboboxData
+ }
+ }),
+ {
+ revalidateOnFocus: false,
+ revalidateIfStale: false
+ }
+ )
+
+ return (
+
+
+ {name as string}
+
+
+ {type === 'boolean' ?
+
+ :
+ type === 'number' ?
+ { }}
+ suffix={unit ? ` ${unit}` : ''}
+ />
+ :
+ type === 'select' && !isValidating && tcbAll ?
+
+ :
+ type === 'string' ?
+
+ :
+ {value as string}
+ }
+
+
+ )
+}
+
+export default TableValue
\ No newline at end of file
diff --git a/client/src/components/map/mapUtils.ts b/client/src/components/map/mapUtils.ts
index 0479b11..0dc44c4 100644
--- a/client/src/components/map/mapUtils.ts
+++ b/client/src/components/map/mapUtils.ts
@@ -2,7 +2,7 @@ import { Coordinate, distance, rotate } from "ol/coordinate";
import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent";
import Feature from "ol/Feature";
import GeoJSON from "ol/format/GeoJSON";
-import { Circle, Geometry, LineString, Point, Polygon, SimpleGeometry } from "ol/geom";
+import { Circle, Geometry, LineString, Polygon, SimpleGeometry } from "ol/geom";
import VectorLayer from "ol/layer/Vector";
import VectorImageLayer from "ol/layer/VectorImage";
import Map from "ol/Map";
@@ -20,13 +20,15 @@ import ImageLayer from "ol/layer/Image";
import { IFigure, ILine } from "../../interfaces/gis";
import { fromCircle } from "ol/geom/Polygon";
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
-import { getCurrentTool, getMeasureClearPrevious, getMeasureShowSegments, getMeasureType, getTipPoint } from "../../store/map";
+import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint } from "../../store/map";
+import { VectorImage } from "ol/layer";
+import { MutableRefObject } from "react";
export function processLine(
line: ILine,
scaling: { w: number, h: number },
mapCenter: Coordinate,
- linesLayer: React.MutableRefObject, any>>
+ linesLayer: React.MutableRefObject, VectorSource>>>
) {
const x1 = line.x1 * scaling.w
const y1 = line.y1 * scaling.h
@@ -53,7 +55,7 @@ export function processFigure(
figure: IFigure,
scaling: { w: number, h: number },
mapCenter: Coordinate,
- figuresLayer: React.MutableRefObject, any>>
+ figuresLayer: React.MutableRefObject, VectorSource>>>
) {
if (figure.figure_type_id == 1) {
const width = figure.width * scaling.w
@@ -186,7 +188,6 @@ export const addInteractions = (
measureModify: React.MutableRefObject,
) => {
const currentTool = getCurrentTool()
- const showSegments = getMeasureShowSegments()
const clearPrevious = getMeasureClearPrevious()
const measureType = getMeasureType()
const tipPoint = getTipPoint()
@@ -338,7 +339,7 @@ const zoomToFeature = (map: React.MutableRefObject
diff --git a/client/src/store/objects.ts b/client/src/store/objects.ts
new file mode 100644
index 0000000..eeac8e8
--- /dev/null
+++ b/client/src/store/objects.ts
@@ -0,0 +1,46 @@
+import { create } from 'zustand';
+
+interface ObjectsState {
+ selectedCity: number | null;
+ selectedYear: number | null;
+ currentObjectId: string | null;
+}
+
+export const useObjectsStore = create(() => ({
+ selectedCity: null,
+ selectedYear: 2023,
+ currentObjectId: null
+}));
+
+const getSelectedCity = () => {
+ return useObjectsStore.getState().selectedCity
+}
+
+const setSelectedCity = (city: number | null) => {
+ useObjectsStore.setState(() => ({ selectedCity: city }))
+}
+
+const getSelectedYear = () => {
+ return useObjectsStore.getState().selectedYear
+}
+
+const setSelectedYear = (year: number | null) => {
+ useObjectsStore.setState(() => ({ selectedYear: year }))
+}
+
+const getCurrentObjectId = () => {
+ return useObjectsStore.getState().currentObjectId
+}
+
+const setCurrentObjectId = (objectId: string | null) => {
+ useObjectsStore.setState(() => ({ currentObjectId: objectId }))
+}
+
+export {
+ getSelectedCity,
+ setSelectedCity,
+ getSelectedYear,
+ setSelectedYear,
+ getCurrentObjectId,
+ setCurrentObjectId
+}
\ No newline at end of file
diff --git a/client/src/utils/math.ts b/client/src/utils/math.ts
new file mode 100644
index 0000000..dce8b76
--- /dev/null
+++ b/client/src/utils/math.ts
@@ -0,0 +1,3 @@
+export function deg2rad(degrees: number) {
+ return degrees * (Math.PI / 180)
+}
\ No newline at end of file
diff --git a/ems/src/api/general/index.ts b/ems/src/api/general/index.ts
index 010b694..0cf0463 100644
--- a/ems/src/api/general/index.ts
+++ b/ems/src/api/general/index.ts
@@ -62,17 +62,17 @@ router.get('/objects/list', async (req: Request, res: Response) => {
if (type) {
const result = await tediousQuery(
`
- SELECT
+ SELECT
*
- FROM
+ FROM
vObjects
- WHERE
- vObjects.id_city = ${city_id}
+ WHERE
+ vObjects.id_city = ${city_id}
AND vObjects.year = ${year}
AND type = ${type}
- AND
- (
- CASE
+ 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
@@ -91,12 +91,12 @@ router.get('/objects/list', async (req: Request, res: Response) => {
COUNT(vObjects.type) AS count
FROM
vObjects
- JOIN
+ JOIN
tTypes ON vObjects.type = tTypes.id
WHERE
vObjects.id_city = ${city_id} AND vObjects.year = ${year}
- AND
- (
+ 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
@@ -144,7 +144,17 @@ router.get('/values/all', async (req: Request, res: Response) => {
const result = await tediousQuery(
`
- SELECT * FROM nGeneral..tValues
+ SELECT
+ id_object,
+ id_param,
+ CAST(v.value AS varchar(max)) AS value,
+ date_s,
+ date_po,
+ id_user
+ FROM
+ nGeneral..tValues v
+ JOIN
+ nGeneral..tParameters p ON v.id_param = p.id
WHERE id_object = '${object_id}'
`
)
@@ -176,10 +186,21 @@ router.get('/params/all', async (req: Request, res: Response) => {
}
})
+const tcbParamQuery = (vtable: string, id_city: string) => {
+ switch (vtable) {
+ case 'vStreets':
+ return `SELECT * FROM nGeneral..${vtable} WHERE id_city = ${id_city};`
+ case 'vBoilers':
+ return `SELECT * FROM nGeneral..${vtable} WHERE id_city = ${id_city};`
+ default:
+ return `SELECT * FROM nGeneral..${vtable};`
+ }
+}
+
// Get value from TCB parameter
router.get('/params/tcb', async (req: Request, res: Response) => {
try {
- const { vtable, id, offset, limit } = req.query
+ const { vtable, id, offset, limit, id_city } = req.query
if (!vtable) {
res.status(500)
@@ -195,12 +216,13 @@ router.get('/params/tcb', async (req: Request, res: Response) => {
res.status(200).json(result)
} else {
const result = await tediousQuery(
- `
- SELECT * FROM nGeneral..${vtable}
- ORDER BY object_id
- OFFSET ${Number(offset) || 0} ROWS
- FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
- `
+ // `
+ // SELECT * FROM nGeneral..${vtable}
+ // ORDER BY id
+ // OFFSET ${Number(offset) || 0} ROWS
+ // FETCH NEXT ${Number(limit) || 10} ROWS ONLY;
+ // `
+ tcbParamQuery(vtable as string, id_city as string)
)
res.status(200).json(result)
}
@@ -209,4 +231,47 @@ router.get('/params/tcb', async (req: Request, res: Response) => {
}
})
+router.get('/search/objects', async (req: Request, res: Response) => {
+ try {
+ const { q, id_city, year } = req.query
+
+ if (q && id_city && year) {
+ const result = await tediousQuery(
+ `
+ WITH RankedValues AS (
+ SELECT
+ id_object,
+ date_s,
+ CAST(value AS varchar(max)) AS value,
+ ROW_NUMBER() OVER (PARTITION BY id_object ORDER BY date_s DESC) AS rn,
+ o.id_city AS id_city,
+ o.year AS year
+ FROM nGeneral..tValues
+ JOIN nGeneral..tObjects o ON o.id = id_object
+ WHERE CAST(value AS varchar(max)) LIKE '%${q}%'
+ )
+ SELECT
+ id_object,
+ date_s,
+ value,
+ id_city,
+ year
+ FROM RankedValues
+ WHERE rn = 1 AND id_city = ${id_city} AND year = ${year};
+ `
+ )
+
+ res.status(200).json(result)
+ } else {
+ res.status(400).json("Bad request")
+ }
+
+ } catch (err) {
+ res.status(500).json({
+ message: "Error",
+ error: err
+ })
+ }
+})
+
export default router
\ No newline at end of file