From e9595f97036366447d2e837d72420dc3503992f3 Mon Sep 17 00:00:00 2001 From: cracklesparkle Date: Fri, 6 Dec 2024 12:42:34 +0900 Subject: [PATCH] Update --- client/index.html | 4 +- client/public/logo1.svg | 34 ++ client/public/logo2.svg | 34 ++ client/src/components/FetchingData.ts | 21 - client/src/components/Tree/ObjectTree.tsx | 110 ++++ client/src/components/UserData.ts | 18 - client/src/components/map/MapComponent.tsx | 476 +++++++++--------- .../map/MapTree/MapTreeCheckbox.tsx | 9 +- client/src/components/map/ObjectParameter.tsx | 102 ++-- client/src/components/map/TCBParameter.tsx | 109 ++-- client/src/components/map/TableValue.tsx | 71 +++ client/src/components/map/mapUtils.ts | 13 +- client/src/layouts/DashboardLayout.tsx | 9 +- client/src/store/objects.ts | 46 ++ client/src/utils/math.ts | 3 + ems/src/api/general/index.ts | 101 +++- 16 files changed, 770 insertions(+), 390 deletions(-) create mode 100644 client/public/logo1.svg create mode 100644 client/public/logo2.svg delete mode 100644 client/src/components/FetchingData.ts create mode 100644 client/src/components/Tree/ObjectTree.tsx delete mode 100644 client/src/components/UserData.ts create mode 100644 client/src/components/map/TableValue.tsx create mode 100644 client/src/store/objects.ts create mode 100644 client/src/utils/math.ts 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: item.value, value: item.id_object.toString() })) : []} + //onSelect={(e) => console.log(e.currentTarget.value)} + onChange={(value) => setSearchObject(value)} + onOptionSubmit={(value) => setCurrentObjectId(value)} + rightSection={ + searchObject !== '' && ( + event.preventDefault()} + onClick={() => { + setSearchObject('') + //setSelectedCity(null) + }} + aria-label="Clear value" + /> + ) + } + leftSection={} + value={searchObject} + /> + +
({ label: item.name, value: item.id.toString() })) : []} + data={citiesData ? citiesData.map((item: { name: string, id: number }) => ({ label: item.name, value: item.id.toString() })) : []} //onSelect={(e) => console.log(e.currentTarget.value)} onChange={(value) => setSearchCity(value)} onOptionSubmit={(value) => setSelectedCity(Number(value))} @@ -729,20 +809,12 @@ const MapComponent = () => { ({ 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 ? +