forked from VinokurovVE/tests
Update
This commit is contained in:
@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo2.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dashboard</title>
|
||||
<title>ИС</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
34
client/public/logo1.svg
Normal file
34
client/public/logo1.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<svg width="252" height="252" viewBox="0 0 252 252" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61 36C47.1929 36 36 47.1929 36 61V191C36 204.807 47.1929 216 61 216H191C204.807 216 216 204.807 216 191V61C216 47.1929 204.807 36 191 36H61ZM73 54C61.9543 54 53 62.9543 53 74V179C53 190.046 61.9543 199 73 199H178C189.046 199 198 190.046 198 179V74C198 62.9543 189.046 54 178 54H73Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61 36C47.1929 36 36 47.1929 36 61V191C36 204.807 47.1929 216 61 216H191C204.807 216 216 204.807 216 191V61C216 47.1929 204.807 36 191 36H61ZM73 54C61.9543 54 53 62.9543 53 74V179C53 190.046 61.9543 199 73 199H178C189.046 199 198 190.046 198 179V74C198 62.9543 189.046 54 178 54H73Z" fill="url(#paint0_linear_2_2)"/>
|
||||
<path d="M67.4996 81.0781L66.5525 172H115.5V126C115.5 119.096 121.096 113.5 128 113.5H236V80H67.434C67.481 80.3523 67.5034 80.7123 67.4996 81.0781Z" fill="#D9D9D9"/>
|
||||
<path d="M67.4996 81.0781L66.5525 172H115.5V126C115.5 119.096 121.096 113.5 128 113.5H236V80H67.434C67.481 80.3523 67.5034 80.7123 67.4996 81.0781Z" fill="url(#paint1_linear_2_2)"/>
|
||||
<path d="M236 138.5H158.178L191.678 172H236V138.5Z" fill="#D9D9D9"/>
|
||||
<path d="M236 138.5H158.178L191.678 172H236V138.5Z" fill="url(#paint2_linear_2_2)"/>
|
||||
<path d="M156.322 172L140.5 156.178V172H156.322Z" fill="#D9D9D9"/>
|
||||
<path d="M156.322 172L140.5 156.178V172H156.322Z" fill="url(#paint3_linear_2_2)"/>
|
||||
<path d="M15 172H51.5517L52.5004 80.9219C52.5037 80.6096 52.526 80.3019 52.5662 80H15V172Z" fill="#D9D9D9"/>
|
||||
<path d="M15 172H51.5517L52.5004 80.9219C52.5037 80.6096 52.526 80.3019 52.5662 80H15V172Z" fill="url(#paint4_linear_2_2)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#007B91"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#007B91"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#007B91"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#007B91"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#007B91"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
34
client/public/logo2.svg
Normal file
34
client/public/logo2.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<svg width="252" height="252" viewBox="0 0 252 252" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61 36C47.1929 36 36 47.1929 36 61V191C36 204.807 47.1929 216 61 216H191C204.807 216 216 204.807 216 191V61C216 47.1929 204.807 36 191 36H61ZM73 54C61.9543 54 53 62.9543 53 74V179C53 190.046 61.9543 199 73 199H178C189.046 199 198 190.046 198 179V74C198 62.9543 189.046 54 178 54H73Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M61 36C47.1929 36 36 47.1929 36 61V191C36 204.807 47.1929 216 61 216H191C204.807 216 216 204.807 216 191V61C216 47.1929 204.807 36 191 36H61ZM73 54C61.9543 54 53 62.9543 53 74V179C53 190.046 61.9543 199 73 199H178C189.046 199 198 190.046 198 179V74C198 62.9543 189.046 54 178 54H73Z" fill="url(#paint0_linear_2_2)"/>
|
||||
<path d="M67.4996 81.0781L66.5525 172H115.5V126C115.5 119.096 121.096 113.5 128 113.5H236V80H67.434C67.481 80.3523 67.5034 80.7123 67.4996 81.0781Z" fill="#D9D9D9"/>
|
||||
<path d="M67.4996 81.0781L66.5525 172H115.5V126C115.5 119.096 121.096 113.5 128 113.5H236V80H67.434C67.481 80.3523 67.5034 80.7123 67.4996 81.0781Z" fill="url(#paint1_linear_2_2)"/>
|
||||
<path d="M236 138.5H158.178L191.678 172H236V138.5Z" fill="#D9D9D9"/>
|
||||
<path d="M236 138.5H158.178L191.678 172H236V138.5Z" fill="url(#paint2_linear_2_2)"/>
|
||||
<path d="M156.322 172L140.5 156.178V172H156.322Z" fill="#D9D9D9"/>
|
||||
<path d="M156.322 172L140.5 156.178V172H156.322Z" fill="url(#paint3_linear_2_2)"/>
|
||||
<path d="M15 172H51.5517L52.5004 80.9219C52.5037 80.6096 52.526 80.3019 52.5662 80H15V172Z" fill="#D9D9D9"/>
|
||||
<path d="M15 172H51.5517L52.5004 80.9219C52.5037 80.6096 52.526 80.3019 52.5662 80H15V172Z" fill="url(#paint4_linear_2_2)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00D9FF" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00D9FF" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00D9FF" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00D9FF" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_2_2" x1="236" y1="36" x2="15" y2="216" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#00D9FF" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#00D9FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -1,21 +0,0 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import axiosInstance from '../http/axiosInstance'
|
||||
export function useDataFetching<T>(url: string, initData: T): T {
|
||||
const [data, setData] = useState<T>(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<T>(() => data, [data])
|
||||
return memoizedData
|
||||
}
|
||||
|
||||
export default useDataFetching;
|
110
client/src/components/Tree/ObjectTree.tsx
Normal file
110
client/src/components/Tree/ObjectTree.tsx
Normal file
@ -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 (
|
||||
<Accordion multiple chevronPosition='left'>
|
||||
<TypeTree label='Существующие' value={'existing'} count={existingCount} objectList={existingObjectsList} planning={0} />
|
||||
<TypeTree label='Планируемые' value={'planning'} count={planningCount} objectList={planningObjectsList} planning={1} />
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
interface TypeTreeProps {
|
||||
label: string;
|
||||
value: string;
|
||||
count: number;
|
||||
objectList: unknown;
|
||||
planning: number;
|
||||
}
|
||||
|
||||
const TypeTree = ({
|
||||
label,
|
||||
objectList,
|
||||
count,
|
||||
planning
|
||||
}: TypeTreeProps) => {
|
||||
|
||||
return (
|
||||
<NavLink p={0} label={`${label} ${count ? `(${count})` : ''}`}>
|
||||
{Array.isArray(objectList) && objectList.map(list => (
|
||||
<ObjectList key={list.id} label={list.name} id={list.id} planning={planning} count={list.count} />
|
||||
))}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<NavLink p={0} label={`${label} ${count ? `(${count})` : ''}`}>
|
||||
{Array.isArray(data) && data.map((type) => (
|
||||
<NavLink key={type.object_id} label={type.object_id} p={0} onClick={() => setCurrentObjectId(type.object_id)} />
|
||||
))}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
export default ObjectTree
|
@ -1,18 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import UserService from "../services/UserService";
|
||||
|
||||
export default function useUserData<T>(token: string, initData: T): T {
|
||||
const [userData, setUserData] = useState<T>(initData)
|
||||
|
||||
useEffect(()=> {
|
||||
const fetchUserData = async (token: string) => {
|
||||
const response = await UserService.getCurrentUser(token)
|
||||
setUserData(response.data)
|
||||
}
|
||||
|
||||
fetchUserData(token)
|
||||
}, [token])
|
||||
|
||||
const memoizedData = useMemo<T>(() => userData, [userData])
|
||||
return memoizedData
|
||||
}
|
@ -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<Coordinate | null>(null)
|
||||
@ -121,24 +134,27 @@ const MapComponent = () => {
|
||||
}
|
||||
}))
|
||||
|
||||
const figuresLayer = useRef<VectorLayer>(new VectorLayer({
|
||||
const figuresLayer = useRef<VectorImage>(new VectorImage({
|
||||
source: new VectorSource(),
|
||||
declutter: true,
|
||||
properties: {
|
||||
id: uuidv4(),
|
||||
name: 'Фигуры'
|
||||
}
|
||||
}))
|
||||
|
||||
const linesLayer = useRef<VectorLayer>(new VectorLayer({
|
||||
const linesLayer = useRef<VectorImage>(new VectorImage({
|
||||
source: new VectorSource(),
|
||||
declutter: true,
|
||||
properties: {
|
||||
id: uuidv4(),
|
||||
name: 'Линии'
|
||||
}
|
||||
}))
|
||||
|
||||
const regionsLayer = useRef<VectorImageLayer>(new VectorImageLayer({
|
||||
const regionsLayer = useRef<VectorImage>(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<any>) {
|
||||
map.current.on('pointermove', function (e: MapBrowserEvent<UIEvent>) {
|
||||
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<any>) {
|
||||
map.current.on('click', function (e: MapBrowserEvent<UIEvent>) {
|
||||
const pixel = map.current?.getEventPixel(e.originalEvent)
|
||||
|
||||
if (pixel) {
|
||||
@ -430,7 +446,7 @@ const MapComponent = () => {
|
||||
}
|
||||
}, [mapState.currentTool])
|
||||
|
||||
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(1)
|
||||
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(0)
|
||||
|
||||
const [statusText, setStatusText] = useState('')
|
||||
|
||||
@ -513,31 +529,14 @@ const MapComponent = () => {
|
||||
}
|
||||
}, [nodes])
|
||||
|
||||
const [currentObjectId, setCurrentObjectId] = useState<string | null>(null)
|
||||
|
||||
const [selectedCity, setSelectedCity] = useState<number | null>(null)
|
||||
const [selectedYear, setSelectedYear] = useState<number | null>(2023)
|
||||
const [citiesPage, setCitiesPage] = useState<number>(0)
|
||||
|
||||
const [searchCity, setSearchCity] = useState<string | undefined>("")
|
||||
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<string | undefined>("")
|
||||
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<TreeNodeData[] | null>(null)
|
||||
const [selectedObjectList, setSelectedObjectList] = useState<number | null>(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 (
|
||||
<Box w={'100%'} h='100%' pos={'relative'}>
|
||||
<Portal target='#header-portal'>
|
||||
@ -703,11 +757,37 @@ const MapComponent = () => {
|
||||
<MantineSelect variant='filled' value={satMapsProvider} data={[{ label: 'Google', value: 'google' }, { label: 'Yandex', value: 'yandex' }, { label: 'Custom', value: 'custom' }]} onChange={(value) => setSatMapsProvider(value as SatelliteMapsProvider)} />
|
||||
</Flex>
|
||||
|
||||
<form>
|
||||
<Autocomplete
|
||||
placeholder="Поиск"
|
||||
flex={'1'}
|
||||
data={searchData ? searchData.map((item: { value: string, id_object: string }) => ({ 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 !== '' && (
|
||||
<CloseButton
|
||||
size="sm"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
setSearchObject('')
|
||||
//setSelectedCity(null)
|
||||
}}
|
||||
aria-label="Clear value"
|
||||
/>
|
||||
)
|
||||
}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
value={searchObject}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<Autocomplete
|
||||
placeholder="Район"
|
||||
flex={'1'}
|
||||
data={citiesData ? citiesData.map((item: any) => ({ 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 = () => {
|
||||
</form>
|
||||
|
||||
<MantineSelect
|
||||
data={[
|
||||
{ label: '2018', value: '2018' },
|
||||
{ label: '2019', value: '2019' },
|
||||
{ label: '2020', value: '2020' },
|
||||
{ label: '2021', value: '2021' },
|
||||
{ label: '2022', value: '2022' },
|
||||
{ label: '2023', value: '2023' },
|
||||
{ label: '2024', value: '2024' },
|
||||
]}
|
||||
w='84px'
|
||||
data={['2018', '2019', '2020', '2021', '2022', '2023', '2024'].map(el => ({ label: el, value: el }))}
|
||||
onChange={(e) => {
|
||||
setSelectedYear(Number(e))
|
||||
}}
|
||||
defaultValue={selectedYear?.toString()}
|
||||
//variant="unstyled"
|
||||
allowDeselect={false}
|
||||
/>
|
||||
|
||||
@ -766,7 +838,7 @@ const MapComponent = () => {
|
||||
|
||||
<Flex direction='column' mah={'86%'} pl='sm' style={{
|
||||
...mapControlsStyle,
|
||||
maxWidth: '300px',
|
||||
maxWidth: '340px',
|
||||
width: '100%',
|
||||
top: '8px',
|
||||
left: '8px',
|
||||
@ -791,165 +863,87 @@ const MapComponent = () => {
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
|
||||
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
|
||||
<Accordion.Item key={'objects'} value={'Объекты'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{objectsList &&
|
||||
<Tree
|
||||
data={objectsList}
|
||||
selectOnClick
|
||||
levelOffset={23}
|
||||
renderNode={({ node, expanded, hasChildren, elementProps }) => (
|
||||
<Group gap={6} {...elementProps} onClick={async (e) => {
|
||||
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) && (
|
||||
<IconChevronDown
|
||||
size={18}
|
||||
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
/>
|
||||
)}
|
||||
<MantineText size='sm'>{`${node.label} ${node?.count ? `(${node.count})` : ''}`}</MantineText>
|
||||
</Group>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
|
||||
{/* {currentObjectId &&
|
||||
<Accordion.Item key={'current_object'} value={currentObjectId}>
|
||||
<Accordion.Control icon={<IconTable />}>
|
||||
{'Текущий объект'}
|
||||
</Accordion.Control>
|
||||
{selectedCity &&
|
||||
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
|
||||
<Accordion.Item key={'objects'} value={'Объекты'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<ObjectData {...currentObjectData as IObjectData} />
|
||||
<ObjectTree />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
} */}
|
||||
|
||||
{valuesData &&
|
||||
<Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Flex gap={'sm'} direction={'column'}>
|
||||
{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<string, IObjectParam[]>)
|
||||
).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 &&
|
||||
<Accordion.Item key={'parameters'} value={'Параметры объекта'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Параметры объекта'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Flex gap={'sm'} direction={'column'}>
|
||||
{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<string, IObjectParam[]>)
|
||||
).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`
|
||||
<Timeline
|
||||
key={id_param}
|
||||
active={0}
|
||||
bulletSize={18}
|
||||
>
|
||||
{sortedParams.map((param, index) => (
|
||||
<Timeline.Item
|
||||
key={index}
|
||||
style={{
|
||||
filter: index !== 0 ? 'grayscale(100%) opacity(50%)' : 'none'
|
||||
}}
|
||||
>
|
||||
<ObjectParameter param={param} showLabel={false} />
|
||||
<Text size='xs'>
|
||||
{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('.')}
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
) : (
|
||||
// Step 3: Render ObjectParameter directly if there's only one entry
|
||||
<ObjectParameter key={id_param} param={sortedParams[0]} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</Flex>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
}
|
||||
</Accordion>
|
||||
return sortedParams.length > 1 ? (
|
||||
// Step 2: Render Mantine Timeline for multiple entries with the same `id_param`
|
||||
<Timeline
|
||||
key={id_param}
|
||||
active={0}
|
||||
bulletSize={18}
|
||||
>
|
||||
{sortedParams.map((param: IObjectParam, index: number) => (
|
||||
<Timeline.Item
|
||||
key={index}
|
||||
style={{
|
||||
filter: index !== 0 ? 'grayscale(100%) opacity(50%)' : 'none'
|
||||
}}
|
||||
>
|
||||
<ObjectParameter param={param} showLabel={false} />
|
||||
<Text size='xs'>
|
||||
{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('.')}
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
) : (
|
||||
<ObjectParameter key={id_param} param={sortedParams[0]} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</Flex>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
}
|
||||
</Accordion>
|
||||
}
|
||||
|
||||
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Слои'>
|
||||
<Accordion.Item key={'objects'} value={'Слои'}>
|
||||
<Accordion.Control icon={<IconTable />}>{'Слои'}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{map.current?.getLayers().getArray() &&
|
||||
<Tree
|
||||
data={map.current?.getLayers().getArray().map(layer => {
|
||||
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) => (
|
||||
<Flex key={`layer-${index}`} gap='xs' align='center'>
|
||||
<Checkbox.Indicator
|
||||
checked={layer.getLayerState().visible}
|
||||
onClick={() => layer.getLayerState().visible ? layer.setVisible(false) : layer.setVisible(true)}
|
||||
/>
|
||||
<NavLink p={0} label={layer.get('name')} onClick={() => { console.log(layer.getLayerState()) }} />
|
||||
</Flex>
|
||||
))}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
|
||||
</ScrollAreaAutosize>
|
||||
</Flex>
|
||||
|
||||
|
@ -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 (
|
||||
<Group gap="xs" {...elementProps}>
|
||||
<Checkbox.Indicator
|
||||
@ -25,7 +20,7 @@ export const MapTreeCheckbox = ({
|
||||
/>
|
||||
|
||||
<Group gap={5} onClick={() => tree.toggleExpanded(node.value)}>
|
||||
<span>{node.label}</span>
|
||||
<Text size="xs">{node.label}</Text>
|
||||
|
||||
{hasChildren && (
|
||||
<IconChevronDown
|
||||
|
@ -1,10 +1,46 @@
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { Checkbox, Divider, Flex, Grid, Stack, Text } from '@mantine/core'
|
||||
import { IObjectParam, IParam } from '../../interfaces/objects'
|
||||
import { decodeDoubleEncodedString } from '../../utils/format'
|
||||
import TCBParameter from './TCBParameter'
|
||||
import TableValue from './TableValue'
|
||||
|
||||
export function formatNumericValue(format: string, value: string) {
|
||||
// Extract precision and scale from the format string
|
||||
const regex = /numeric\((\d+),(\d+)\)/;
|
||||
const match = format.match(regex);
|
||||
if (!match) return value; // return original if format is not correct
|
||||
|
||||
const precision = parseInt(match[1], 10); // Total number of digits
|
||||
const scale = parseInt(match[2], 10); // Number of digits after the decimal point
|
||||
|
||||
// Convert value to a number and handle cases like empty value or invalid input
|
||||
const numericValue = parseFloat(value);
|
||||
|
||||
if (isNaN(numericValue)) {
|
||||
return '0'.padStart(precision - scale, '0') + '.' + '0'.repeat(scale); // fallback in case of invalid value
|
||||
}
|
||||
|
||||
// Ensure the value has the correct number of digits after the decimal point (scale)
|
||||
let formattedValue = numericValue.toFixed(scale);
|
||||
|
||||
// Ensure the total length doesn't exceed the precision
|
||||
const totalDigits = formattedValue.replace('.', '').length;
|
||||
|
||||
if (totalDigits > 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 (
|
||||
<Flex direction='row' align='center' gap='sm'>
|
||||
<Checkbox defaultChecked={value as boolean} />
|
||||
<Text>{name}</Text>
|
||||
</Flex>
|
||||
)
|
||||
case 'varchar(200)':
|
||||
return (
|
||||
<Text>
|
||||
{decodeDoubleEncodedString(value as string)}
|
||||
</Text>
|
||||
)
|
||||
case 'varchar(5)':
|
||||
return (
|
||||
<Text>
|
||||
{decodeDoubleEncodedString(value as string)}
|
||||
</Text>
|
||||
<TableValue value={value} name={name} type='boolean' />
|
||||
)
|
||||
case 'bigint':
|
||||
return (
|
||||
<Text>
|
||||
{(value as string)}
|
||||
</Text>
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
)
|
||||
case 'tinyint':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
)
|
||||
// TODO: Calculate from calc procedures
|
||||
case 'calculate':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='value' />
|
||||
)
|
||||
case 'GTCB':
|
||||
return (
|
||||
<TCBParameter value={value as string} vtable={vtable} />
|
||||
<TCBParameter value={value as string} vtable={vtable} name={name} />
|
||||
)
|
||||
case 'TCB':
|
||||
return (
|
||||
<TCBParameter value={value as string} vtable={vtable} />
|
||||
<TCBParameter value={value as string} vtable={vtable} name={name} />
|
||||
)
|
||||
case type.match(/varchar\((\d+)\)/)?.input:
|
||||
return (
|
||||
<TableValue value={value} name={name} type='string' />
|
||||
)
|
||||
case type.match(/numeric\((\d+),(\d+)\)/)?.input:
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' unit={unit} />
|
||||
)
|
||||
case 'year':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div>
|
||||
{type}
|
||||
{value as string}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -70,12 +111,7 @@ const ObjectParameter = ({
|
||||
return (
|
||||
<>
|
||||
{paramData &&
|
||||
<Stack gap={0}>
|
||||
{showLabel &&
|
||||
<Divider my="xs" label={paramData.name} labelPosition="left" />
|
||||
}
|
||||
{Parameter(paramData.format, paramData.name, param.value, paramData.vtable)}
|
||||
</Stack>
|
||||
Parameter(paramData.format, paramData.name, param.value, paramData.vtable, paramData.unit)
|
||||
}
|
||||
</>
|
||||
)
|
||||
|
@ -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 (
|
||||
<Text>
|
||||
{JSON.stringify(tcbValue)}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'tTypes':
|
||||
return (
|
||||
<Text>
|
||||
{(tcbValue as tType)?.name}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vPipesGround':
|
||||
return (
|
||||
<Text>
|
||||
{(tcbValue)?.name}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vRepairEvent':
|
||||
return (
|
||||
<Text>
|
||||
{(tcbValue)?.name}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vPipesMaterial':
|
||||
return (
|
||||
<Text>
|
||||
{(tcbValue)?.name}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vBoilers':
|
||||
return (
|
||||
<Text>
|
||||
{(tcbValue)?.name}
|
||||
</Text>
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vHotWaterTypes':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vHeatingTypes':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vColdWaterTypes':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vCanalization':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vElectroSupplyTypes':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vGasSupplyTypes':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vFoundation':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vMaterialsWall':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vCovering':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vRoof':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vTechStatus':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vPipeOutDiameters':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
case 'vPipeDiameters':
|
||||
return (
|
||||
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} />
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Text>
|
||||
{JSON.stringify(name)}
|
||||
{JSON.stringify(tcbValue)}
|
||||
</Text>
|
||||
)
|
||||
|
71
client/src/components/map/TableValue.tsx
Normal file
71
client/src/components/map/TableValue.tsx
Normal file
@ -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 (
|
||||
<Grid>
|
||||
<Grid.Col span={4} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Text size='xs' style={{ textWrap: 'wrap' }}>{name as string}</Text>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8}>
|
||||
{type === 'boolean' ?
|
||||
<Checkbox defaultChecked={value as boolean} />
|
||||
:
|
||||
type === 'number' ?
|
||||
<NumberInput
|
||||
size='xs'
|
||||
value={value as number}
|
||||
onChange={() => { }}
|
||||
suffix={unit ? ` ${unit}` : ''}
|
||||
/>
|
||||
:
|
||||
type === 'select' && !isValidating && tcbAll ?
|
||||
<Select size='xs' data={tcbAll} defaultValue={JSON.stringify(value)} />
|
||||
:
|
||||
type === 'string' ?
|
||||
<Textarea size='xs' defaultValue={value as string} autosize minRows={1} />
|
||||
:
|
||||
<Text size='xs'>{value as string}</Text>
|
||||
}
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableValue
|
@ -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<VectorLayer<VectorSource<any>, any>>
|
||||
linesLayer: React.MutableRefObject<VectorImage<Feature<Geometry>, VectorSource<Feature<Geometry>>>>
|
||||
) {
|
||||
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<VectorLayer<VectorSource<any>, any>>
|
||||
figuresLayer: React.MutableRefObject<VectorImage<Feature<Geometry>, VectorSource<Feature<Geometry>>>>
|
||||
) {
|
||||
if (figure.figure_type_id == 1) {
|
||||
const width = figure.width * scaling.w
|
||||
@ -186,7 +188,6 @@ export const addInteractions = (
|
||||
measureModify: React.MutableRefObject<Modify>,
|
||||
) => {
|
||||
const currentTool = getCurrentTool()
|
||||
const showSegments = getMeasureShowSegments()
|
||||
const clearPrevious = getMeasureClearPrevious()
|
||||
const measureType = getMeasureType()
|
||||
const tipPoint = getTipPoint()
|
||||
@ -338,7 +339,7 @@ const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature
|
||||
}
|
||||
|
||||
// Function to save features to localStorage
|
||||
export const saveFeatures = (layerRef: React.MutableRefObject<VectorLayer<VectorSource<any>, any> | null>) => {
|
||||
export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource> | null>) => {
|
||||
const features = layerRef.current?.getSource()?.getFeatures()
|
||||
if (features && features.length > 0) {
|
||||
const geoJSON = new GeoJSON()
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AppShell, Avatar, Burger, Button, Flex, Group, Menu, NavLink, rem, Text, useMantineColorScheme } from '@mantine/core';
|
||||
import { AppShell, Avatar, Burger, Button, Flex, Group, Image, Menu, NavLink, rem, Text, useMantineColorScheme } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { pages } from '../App';
|
||||
@ -102,6 +102,13 @@ function DashboardLayout() {
|
||||
>
|
||||
Выход
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item>
|
||||
<Flex gap='sm' align='center'>
|
||||
<Image src={'/logo2.svg'} w={32} />
|
||||
<Text>0.1.0</Text>
|
||||
</Flex>
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Group>
|
||||
|
46
client/src/store/objects.ts
Normal file
46
client/src/store/objects.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface ObjectsState {
|
||||
selectedCity: number | null;
|
||||
selectedYear: number | null;
|
||||
currentObjectId: string | null;
|
||||
}
|
||||
|
||||
export const useObjectsStore = create<ObjectsState>(() => ({
|
||||
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
|
||||
}
|
3
client/src/utils/math.ts
Normal file
3
client/src/utils/math.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function deg2rad(degrees: number) {
|
||||
return degrees * (Math.PI / 180)
|
||||
}
|
@ -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
|
Reference in New Issue
Block a user