From 0788a401ca842fa83c3d9e2d994b256d0c7ad707 Mon Sep 17 00:00:00 2001 From: cracklesparkle Date: Thu, 30 Jan 2025 12:36:39 +0900 Subject: [PATCH] Update --- client/package-lock.json | 86 +- client/package.json | 1 + client/src/components/CustomTable.module.scss | 53 +- client/src/components/CustomTable.tsx | 276 ++-- client/src/components/Tree/ObjectTree.tsx | 39 +- client/src/components/map/MapComponent.tsx | 1282 +++++------------ .../components/map/MapLayers/MapLayers.tsx | 6 +- .../components/map/MapLegend/MapLegend.tsx | 90 ++ client/src/components/map/MapMode.tsx | 32 + client/src/components/map/MapSources.ts | 2 +- .../map/MapStatusbar/MapStatusbar.tsx | 18 +- client/src/components/map/MapStyles.ts | 7 +- .../components/map/MapToolbar/MapToolbar.tsx | 149 +- .../components/map/Measure/MeasureStyles.ts | 3 +- client/src/components/map/ObjectParameter.tsx | 28 +- .../map/ObjectParameters/ObjectParameters.tsx | 13 +- client/src/components/map/TCBParameter.tsx | 6 +- client/src/components/map/TableValue.tsx | 11 +- .../src/components/map/TabsPane/TabsPane.tsx | 4 +- client/src/components/map/mapUtils.ts | 533 ++++--- client/src/components/modals/FileViewer.tsx | 6 +- client/src/constants/app.tsx | 32 +- client/src/interfaces/gis.ts | 61 + client/src/layouts/DashboardLayout.tsx | 16 +- client/src/pages/Boilers.tsx | 48 +- client/src/pages/DBManager.tsx | 78 + client/src/pages/MapTest.tsx | 75 +- client/src/pages/PrintReport.tsx | 967 +++++++++++++ client/src/pages/Reports.tsx | 6 +- client/src/pages/Roles.tsx | 69 +- client/src/pages/TableTest.tsx | 13 - client/src/pages/Users.tsx | 159 +- client/src/services/GisService.ts | 39 + client/src/store/app.ts | 43 + client/src/store/map.ts | 718 +++++++-- client/src/store/objects.ts | 119 +- client/yarn.lock | 59 +- ems/package-lock.json | 98 +- ems/src/api/db/index.ts | 143 ++ ems/src/api/general/index.ts | 29 +- ems/src/api/gis/index.ts | 13 + ems/src/api/tiles/index.ts | 2 +- ems/src/index.ts | 2 + 43 files changed, 3710 insertions(+), 1724 deletions(-) create mode 100644 client/src/components/map/MapLegend/MapLegend.tsx create mode 100644 client/src/components/map/MapMode.tsx create mode 100644 client/src/pages/DBManager.tsx create mode 100644 client/src/pages/PrintReport.tsx delete mode 100644 client/src/pages/TableTest.tsx create mode 100644 client/src/services/GisService.ts create mode 100644 client/src/store/app.ts create mode 100644 ems/src/api/db/index.ts diff --git a/client/package-lock.json b/client/package-lock.json index 9047477..30b439d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -41,6 +41,7 @@ "axios": "^1.7.2", "buffer": "^6.0.3", "dayjs": "^1.11.13", + "docx-templates": "^4.13.0", "embla-carousel-react": "^8.3.0", "file-type": "^19.0.0", "jspdf": "^2.5.2", @@ -5446,8 +5447,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/create-ecdh": { "version": "4.0.4", @@ -5913,6 +5913,18 @@ "node": ">=6.0.0" } }, + "node_modules/docx-templates": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/docx-templates/-/docx-templates-4.13.0.tgz", + "integrity": "sha512-tTmR3WhROYctuyVReQ+PfCU3zprmC45/VuSVzn8EjovzpRkXYUdXiDatB9M8pasj0V+wuuOyY8bcSHvlQ2GNag==", + "dependencies": { + "jszip": "^3.10.1", + "sax": "1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -7196,6 +7208,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -7880,6 +7897,49 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7924,6 +7984,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -8537,8 +8605,7 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -8982,8 +9049,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/proj4": { "version": "2.12.0", @@ -10425,6 +10491,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -10607,8 +10678,7 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "node_modules/sha.js": { "version": "2.4.11", diff --git a/client/package.json b/client/package.json index 5244eb7..f14d041 100644 --- a/client/package.json +++ b/client/package.json @@ -44,6 +44,7 @@ "axios": "^1.7.2", "buffer": "^6.0.3", "dayjs": "^1.11.13", + "docx-templates": "^4.13.0", "embla-carousel-react": "^8.3.0", "file-type": "^19.0.0", "jspdf": "^2.5.2", diff --git a/client/src/components/CustomTable.module.scss b/client/src/components/CustomTable.module.scss index edbb22a..98d2aff 100644 --- a/client/src/components/CustomTable.module.scss +++ b/client/src/components/CustomTable.module.scss @@ -5,47 +5,48 @@ right: 0; height: 100%; width: 5px; - background: #27bbff; + background: var(--mantine-color-anchor); cursor: col-resize; user-select: none; touch-action: none; border-radius: 6px; + transition: opacity .2s ease; } .resize_handler:hover { opacity: 1; } -.tr { - display: flex; - //width: 100%; - //max-width: 100%; - width: fit-content; -} +// .tr { +// display: flex; +// //width: 100%; +// //max-width: 100%; +// width: fit-content; +// } .th { position: relative; } -.th, -.td { - display: flex; - width: auto; -} +// .th, +// .td { +// display: flex; +// width: auto; +// } -.thead { - display: flex; - width: 100%; -} +// .thead { +// display: flex; +// width: 100%; +// } -.table { - display: flex; - flex-direction: column; - width: 100%; -} +// .table { +// display: flex; +// flex-direction: column; +// width: 100%; +// } -.tbody { - display: flex; - flex-direction: column; - width: 100%; -} \ No newline at end of file +// .tbody { +// display: flex; +// flex-direction: column; +// width: 100%; +// } \ No newline at end of file diff --git a/client/src/components/CustomTable.tsx b/client/src/components/CustomTable.tsx index 944758e..2c72172 100644 --- a/client/src/components/CustomTable.tsx +++ b/client/src/components/CustomTable.tsx @@ -1,111 +1,205 @@ -import { Input, Table } from '@mantine/core'; -import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; -import { useMemo, useState } from 'react'; +import { Badge, Button, Flex, Input, Modal, ScrollAreaAutosize, Select, Stack, Table, TextInput } from '@mantine/core'; +import { Cell, ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; +import { useEffect, useMemo, useState } from 'react'; import styles from './CustomTable.module.scss' +import { useRoles } from '../hooks/swrHooks'; +import { IRole } from '../interfaces/role'; +import { IconPlus } from '@tabler/icons-react'; +import { CreateField } from '../interfaces/create'; +import { AxiosResponse } from 'axios'; +import FormFields from './FormFields'; +import { useDisclosure } from '@mantine/hooks'; -// Sample data - -type DataType = { - id: number, - name: string, - age: number +type CustomTableProps = { + data: T[]; + columns: ColumnDef[]; + createFields?: CreateField[]; + submitHandler?: (data: T) => Promise } -// Define columns -const columns: ColumnDef[] = [ - { - accessorKey: 'name', - header: 'Name', - cell: (info) => info.getValue(), - maxSize: Number.MAX_SAFE_INTEGER, - }, - { - accessorKey: 'age', - header: 'Age', - cell: (info) => info.getValue(), - }, -]; - -const CustomTable = () => { - const [data, setData] = useState([ - { id: 1, name: 'John Doe', age: 25 }, - { id: 2, name: 'Jane Smith', age: 30 }, - { id: 3, name: 'Sam Green', age: 22 }, - ]); +const CustomTable = ({ + data: initialData, + columns, + createFields, + submitHandler +}: CustomTableProps) => { + const [data, setData] = useState(initialData); + const [searchText, setSearchText] = useState(''); const [editingCell, setEditingCell] = useState<{ rowIndex: string | number | null, columnId: string | number | null }>({ rowIndex: null, columnId: null }); - const tableColumns = useMemo[]>(() => columns, []); + const tableColumns = useMemo(() => columns, [columns]); + + // Function to handle cell edit + const handleEditCell = ( + rowIndex: number, + columnId: keyof T, + value: T[keyof T] + ) => { + const updatedData = [...data]; + updatedData[rowIndex][columnId] = value; + setData(updatedData); + //setEditingCell({ rowIndex: null, columnId: null }); + }; + + const filteredData = useMemo(() => { + if (!searchText) return data; + + return data.filter((row) => + Object.values(row).some((value) => + value?.toString().toLowerCase().includes(searchText.toLowerCase()) + ) + ); + }, [data, searchText]) const table = useReactTable({ - data, + data: filteredData, columns: tableColumns, getCoreRowModel: getCoreRowModel(), columnResizeMode: "onChange", }); - // Function to handle cell edit - const handleEditCell = ( - rowIndex: number, - columnId: keyof DataType, - value: DataType[keyof DataType] - ) => { - const updatedData = [...data]; - (updatedData[rowIndex][columnId] as DataType[keyof DataType]) = value; - setData(updatedData); - //setEditingCell({ rowIndex: null, columnId: null }); - }; + const [opened, { open, close }] = useDisclosure(false); return ( - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map((header) => ( - - {flexRender(header.column.columnDef.header, header.getContext())} -
-
-
- ))} -
- ))} -
- - {table.getRowModel().rows.map((row, rowIndex) => ( - - {row.getVisibleCells().map(cell => { - const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id; + + {createFields && submitHandler && + + + + } - return ( - setEditingCell({ rowIndex, columnId: cell.column.id })} - style={{ width: cell.column.getSize() }} - className={styles.td} - > - {isEditing ? ( - handleEditCell(rowIndex, (cell.column.id as keyof DataType), e.target.value)} - onBlur={() => setEditingCell({ rowIndex: null, columnId: null })} - autoFocus - /> - ) : ( - flexRender(cell.column.columnDef.cell, cell.getContext()) - )} - - ); - })} - - ))} - -
+ + setSearchText(e.target.value)} + w='100%' + /> + {createFields && submitHandler && + + } + + + + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map((header) => ( + + {flexRender(header.column.columnDef.header, header.getContext())} +
+
+
+ ))} +
+ ))} +
+ + {table.getRowModel().rows.map((row, rowIndex) => ( + + {row.getVisibleCells().map(cell => { + const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id; + + return ( + setEditingCell({ rowIndex, columnId: cell.column.id })} + style={{ width: cell.column.getSize() }} + className={styles.td} + > + {isEditing ? ( + handleEditCell(rowIndex, (cell.column.id as keyof T), e.target.value as T[keyof T])} + onBlur={() => setEditingCell({ rowIndex: null, columnId: null })} + autoFocus + /> + ) : ( + + )} + + ); + })} + + ))} + +
+
+ ); }; +type CellDisplayProps = { + cell: Cell; +} + +const CellDisplay = ({ + cell +}: CellDisplayProps) => { + const { roles } = useRoles() + + const [roleOptions, setRoleOptions] = useState<{ label: string, value: string }[]>() + + useEffect(() => { + if (Array.isArray(roles)) { + setRoleOptions(roles.map((role: IRole) => ({ label: role.name, value: role.id.toString() }))) + } + }, [roles]) + + switch (cell.column.id) { + case 'activity': + return ( + cell.getValue() ? ( + + Активен + + ) : ( + + Отключен + + ) + ) + case 'is_active': + return ( + cell.getValue() ? ( + + Активен + + ) : ( + + Отключен + + ) + ) + case 'role_id': + return ( + (new Select({ - condition: function (mapBrowserEvent) { - return click(mapBrowserEvent) && shiftKeyOnly(mapBrowserEvent) - }, - })) + useEffect(() => { + if (regionsLayer && regionBoundsData) { + if (Array.isArray(regionBoundsData)) { + regionBoundsData.map(bound => { + const geoJson = new GeoJSON({ featureProjection: 'EPSG:3857' }) + const geometry = geoJson.readGeometry(bound) as Geometry - const nodeLayer = useRef(null) - const nodeLayerSource = useRef(new VectorSource()) + const feature = new Feature(geometry) + feature.setProperties(bound.properties) - const overlayLayer = useRef(null) - const overlayLayerSource = useRef(new VectorSource()) - - const drawingLayer = useRef(null) - const drawingLayerSource = useRef(new VectorSource()) - - const citiesLayer = useRef(new VectorLayer({ - source: new VectorSource(), - properties: { - id: uuidv4(), - name: 'Города' + regionsLayer.getSource()?.addFeature(feature) + }) + } + //regionsLayer.current.getSource()?.addFeature() } - })) - - const figuresLayer = useRef(new VectorLayer({ - source: new VectorSource(), - declutter: true, - properties: { - id: uuidv4(), - type: 'figures', - name: 'Фигуры' - }, - style: function (feature) { - figureStyle.getText()?.setText(feature.get('object_id')) - return figureStyle - } - })) - - const linesLayer = useRef(new VectorLayer({ - source: new VectorSource(), - declutter: true, - properties: { - id: uuidv4(), - type: 'lines', - name: 'Линии' - }, - style: function (feature) { - //lineStyle.getText()?.setText('11,4')//(feature.get('object_id')) - lineStyle.getText()?.setRotation(feature.get('rotation')) - lineStyle.getText()?.setOffsetY(-8) - return lineStyle - } - })) - - const regionsLayer = useRef(new VectorImage({ - source: regionsLayerSource, - declutter: true, - style: regionsLayerStyle, - properties: { - id: uuidv4(), - name: 'Регион' - } - })) - - const districtBoundLayer = useRef(new VectorImage({ - style: new Style({ - stroke: new Stroke({ - color: 'red', - width: 2, - }), - }) - })) + }, [regionBoundsData]) useEffect(() => { if (selectedDistrict && selectedYear) { const bounds = new VectorSource({ - url: `bounds/cities/${selectedDistrict}.geojson`, + url: `${BASE_URL.ems}/gis/bounds/city/${selectedDistrict}`, format: new GeoJSON(), }) - districtBoundLayer.current.setSource(bounds) + districtBoundLayer.setSource(bounds) bounds.on('featuresloadend', function () { // map.current?.setView(new View({ @@ -339,376 +127,55 @@ const MapComponent = () => { } }, [selectedDistrict, selectedYear]) - const selectedArea = useRef(null) - - const baseLayer = useRef(new TileLayer({ - source: new OSM(), - properties: { - id: uuidv4(), - name: 'OpenStreetMap' - } - })) - - const imageLayer = useRef>(new ImageLayer({ - properties: { - id: uuidv4(), - name: 'Изображение' - } - })) - - const staticMapLayer = useRef>(new ImageLayer({ - properties: { - id: uuidv4(), - name: 'Подложка' - } - })) - - // tile processing - const handleImageDrop = useCallback((event: DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - - if (!event.dataTransfer?.files) return - - const files = event.dataTransfer.files; - if (files.length > 0) { - const file = files[0]; - setFile(file) - - if (file.type.startsWith('image/')) { - const reader = new FileReader(); - - reader.onload = () => { - const imageUrl = reader.result as string; - const img = new Image(); - img.src = imageUrl; - img.onload = () => { - if (map.current) { - const view = map.current.getView(); - const center = view.getCenter() || [0, 0]; - - const width = img.naturalWidth; - const height = img.naturalHeight; - const resolution = view.getResolution() || 0; - - const extent = [ - center[0] - (width * resolution) / 20, - center[1] - (height * resolution) / 20, - center[0] + (width * resolution) / 20, - center[1] + (height * resolution) / 20, - ]; - - // Create a polygon feature with the same extent as the image - const polygonFeature = new Feature({ - geometry: fromExtent(extent), - }); - - // Add the polygon feature to the drawing layer source - overlayLayerSource.current?.addFeature(polygonFeature); - - // Set up the initial image layer with the extent - const imageSource = new ImageStatic({ - url: imageUrl, - imageExtent: extent, - }); - imageLayer.current.setSource(imageSource); - - //map.current.addLayer(imageLayer.current); - - // Add interactions for translation and scaling - const translate = new Translate({ - layers: [imageLayer.current], - features: new Collection([polygonFeature]), - }); - - const defaultStyle = new Modify({ source: overlayLayerSource.current }) - .getOverlay() - .getStyleFunction(); - - const modify = new Modify({ - insertVertexCondition: never, - source: overlayLayerSource.current, - condition: function (event) { - return primaryAction(event) && !platformModifierKeyOnly(event); - }, - deleteCondition: never, - features: new Collection([polygonFeature]), - style: function (feature) { - feature.get('features').forEach(function (modifyFeature: Feature) { - const modifyGeometry = modifyFeature.get('modifyGeometry') - if (modifyGeometry) { - const point = (feature.getGeometry() as Point).getCoordinates() - let modifyPoint = modifyGeometry.point - if (!modifyPoint) { - // save the initial geometry and vertex position - modifyPoint = point; - modifyGeometry.point = modifyPoint; - modifyGeometry.geometry0 = modifyGeometry.geometry; - // get anchor and minimum radius of vertices to be used - const result = calculateCenter(modifyGeometry.geometry0); - modifyGeometry.center = result.center; - modifyGeometry.minRadius = result.minRadius; - } - const center = modifyGeometry.center; - const minRadius = modifyGeometry.minRadius; - let dx, dy; - dx = modifyPoint[0] - center[0]; - dy = modifyPoint[1] - center[1]; - const initialRadius = Math.sqrt(dx * dx + dy * dy); - if (initialRadius > minRadius) { - const initialAngle = Math.atan2(dy, dx); - dx = point[0] - center[0]; - dy = point[1] - center[1]; - const currentRadius = Math.sqrt(dx * dx + dy * dy); - if (currentRadius > 0) { - const currentAngle = Math.atan2(dy, dx); - const geometry = modifyGeometry.geometry0.clone(); - geometry.scale(currentRadius / initialRadius, undefined, center); - geometry.rotate(currentAngle - initialAngle, center); - modifyGeometry.geometry = geometry; - } - } - } - }) - const res = map?.current?.getView()?.getResolution() - if (typeof res === 'number' && feature && defaultStyle) { - return defaultStyle(feature, res) - } - } - }); - - translate.on('translateend', () => updateImageSource(imageUrl, imageLayer, polygonFeature, setPolygonExtent, setRectCoords)); - //modify.on('modifyend', updateImageSource); - - modify.on('modifystart', function (event) { - event.features.forEach(function (feature) { - feature.set( - 'modifyGeometry', - { geometry: feature.getGeometry()?.clone() }, - true, - ); - }); - }); - - modify.on('modifyend', function (event) { - event.features.forEach(function (feature) { - const modifyGeometry = feature.get('modifyGeometry'); - if (modifyGeometry) { - feature.setGeometry(modifyGeometry.geometry); - feature.unset('modifyGeometry', true); - } - }) - updateImageSource(imageUrl, imageLayer, polygonFeature, setPolygonExtent, setRectCoords) - }) - - map.current.addInteraction(translate); - map.current.addInteraction(modify); - } - }; - }; - reader.readAsDataURL(file); - } - } - }, []) - useEffect(() => { - drawingLayer.current = new VectorLayer({ - source: drawingLayerSource.current, - style: drawingLayerStyle, - properties: { - id: uuidv4(), - name: 'Чертеж' - } - }) + map?.setTarget(mapElement.current as HTMLDivElement) - overlayLayer.current = new VectorLayer({ - source: overlayLayerSource.current, - style: overlayStyle, - properties: { - id: uuidv4(), - name: 'Наложения' - } - }) - - nodeLayer.current = new VectorLayer({ - source: nodeLayerSource.current, - style: drawingLayerStyle, - properties: { - id: uuidv4(), - name: 'Узлы' - } - }) - - map.current = new Map({ - controls: [], - layers: [ - baseLayer.current, - satLayer.current, - staticMapLayer.current, - regionsLayer.current, - districtBoundLayer.current, - citiesLayer.current, - linesLayer.current, - figuresLayer.current, - drawingLayer.current, - imageLayer.current, - overlayLayer.current, - nodeLayer.current, - measureLayer.current, - alignModeLayer.current - ], - target: mapElement.current as HTMLDivElement, - view: new View({ - center: transform([129.7466541, 62.083504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]), - //zoom: 16, - zoom: 5, - maxZoom: 21, - //extent: mapExtent, - }), - }) - - 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))) - setCurrentZ(Number(map.current?.getView().getZoom()?.toFixed(0))) - setCurrentX(tileX) - setCurrentY(tileY) - - const pixel = map.current?.getEventPixel(e.originalEvent) - if (pixel) { - map.current?.forEachFeatureAtPixel(pixel, function (feature) { - if (feature.get('geometry_type') === 'line') { - //console.log(feature.getProperties()) - } - }) - } - }) - - map.current.on('click', function (e: MapBrowserEvent) { - if (getAlignMode()) { - if (alignPoints.current.length < 4) { - alignPoints.current.push(e.coordinate) - alignModeLayer.current.getSource()?.addFeature(new Feature(new Point(e.coordinate))) - if (alignPoints.current.length === 4) { - console.log("collected 4 points, now can align") - console.log(alignPoints.current) - const transformations = calculateTransformations(alignPoints.current); - console.log(transformations) - - // Use the first map point (P3) as the origin for scaling and rotation - const origin = alignPoints.current[2]; - console.log(origin) - - applyTransformations(figuresLayer, transformations, origin); - applyTransformations(linesLayer, transformations, origin); - console.log("Figures layer aligned!") - - alignPoints.current = [] - alignModeLayer.current.getSource()?.clear() - } - } - } else { - const pixel = map.current?.getEventPixel(e.originalEvent) - - if (pixel) { - map.current?.forEachFeatureAtPixel(pixel, function (feature) { - setCurrentObjectId(feature.get('object_id')) - }) - } - } - }) - - const modify = new Modify({ source: drawingLayerSource.current }) - map.current.addInteraction(modify) - - map.current.addInteraction(selectFeature.current) - - selectFeature.current.on('select', (e) => { - const selectedFeatures = e.selected - - if (selectedFeatures.length > 0) { - selectedFeatures.forEach((feature) => { - drawingLayerSource.current?.removeFeature(feature) - }) - } - }) - - loadFeatures(drawingLayerSource) - - regionsInit(map, selectedArea, regionsLayer) - - if (mapElement.current) { - mapElement.current.addEventListener('dragover', (e) => { - e.preventDefault() - }) - - mapElement.current.addEventListener('drop', handleImageDrop) + if (drawingLayerSource) { + const modify = new Modify({ source: drawingLayerSource }) + map?.addInteraction(modify) } - return () => { - map?.current?.setTarget(undefined) - - if (mapElement.current) { - mapElement.current.removeEventListener('drop', handleImageDrop) - } - } + loadFeatures(id) }, []) useEffect(() => { if (currentTool) { - if (draw.current) map?.current?.removeInteraction(draw.current) + if (draw) map?.removeInteraction(draw) //if (snap.current) map?.current?.removeInteraction(snap.current) - addInteractions(drawingLayerSource, translate, draw, map, snap, measureDraw, measureSource, measureModify) + addInteractions(id) } else { - if (translate.current) map?.current?.removeInteraction(translate.current) - if (draw.current) map?.current?.removeInteraction(draw.current) - if (snap.current) map?.current?.removeInteraction(snap.current) - if (measureDraw.current) map?.current?.removeInteraction(measureDraw.current) + if (translate) map?.removeInteraction(translate) + if (draw) map?.removeInteraction(draw) + if (snap) map?.removeInteraction(snap) + if (measureDraw) map?.removeInteraction(measureDraw) } }, [currentTool]) // Satellite tiles setting useEffect(() => { - if (satLayer.current) { + if (satLayer) { if (satMapsProvider === 'google') { - satLayer.current.setSource(googleMapsSatelliteSource) + satLayer.setSource(googleMapsSatelliteSource) } if (satMapsProvider === 'yandex') { - satLayer.current.setSource(yandexMapsSatelliteSource) + satLayer.setSource(yandexMapsSatelliteSource) } if (satMapsProvider === 'custom') { - satLayer.current.setSource(customMapSource) + satLayer.setSource(customMapSource) } } }, [satMapsProvider, satLayer]) - const submitOverlay = async () => { - if (file && polygonExtent && rectCoords?.bl && rectCoords?.tl && rectCoords?.tr && rectCoords?.br) { - const formData = new FormData() - formData.append('file', file) - formData.append('extentMinX', polygonExtent[0].toString()) - formData.append('extentMinY', polygonExtent[1].toString()) - formData.append('extentMaxX', polygonExtent[2].toString()) - formData.append('extentMaxY', polygonExtent[3].toString()) - formData.append('blX', rectCoords?.bl[0].toString()) - formData.append('blY', rectCoords?.bl[1].toString()) - formData.append('tlX', rectCoords?.tl[0].toString()) - formData.append('tlY', rectCoords?.tl[1].toString()) - formData.append('trX', rectCoords?.tr[0].toString()) - formData.append('trY', rectCoords?.tr[1].toString()) - formData.append('brX', rectCoords?.br[0].toString()) - formData.append('brY', rectCoords?.br[1].toString()) - - await fetch(`${import.meta.env.VITE_API_EMS_URL}/tiles/upload`, { method: 'POST', body: formData }) - } + const submitOverlay = async (file: File | null, polygonExtent: Extent | undefined, rectCoords: IRectCoords | undefined) => { + await GisService.uploadOverlay(file, polygonExtent, rectCoords).then(res => { + console.log(res) + }) } const mapControlsStyle: MantineStyleProp = { borderRadius: '4px', - position: 'absolute', zIndex: '1', backgroundColor: colorScheme === 'light' ? '#F0F0F0CC' : '#000000CC', backdropFilter: 'blur(8px)', @@ -730,21 +197,21 @@ const MapComponent = () => { }) } //console.log(coordinates) - nodeLayerSource.current.addFeature(new Feature({ geometry: new LineString(coordinates) })) + nodeLayerSource.addFeature(new Feature({ geometry: new LineString(coordinates) })) } }) } - }, [nodes]) + }, [nodes, nodeLayerSource]) const [searchObject, setSearchObject] = useState("") const throttledSearchObject = useThrottle(searchObject, 500); useEffect(() => { - if (!selectedObjectType || !map.current) return; + if (!selectedObjectType || !map) return; - if (figuresLayer.current) { + if (figuresLayer) { // Reset styles and apply highlight to matching features - figuresLayer.current.getSource()?.getFeatures().forEach((feature) => { + figuresLayer.getSource()?.getFeatures().forEach((feature) => { if (selectedObjectType == feature.get('type')) { feature.setStyle(highlightStyleYellow); } else { @@ -753,9 +220,9 @@ const MapComponent = () => { }) } - if (linesLayer.current) { + if (linesLayer) { // Reset styles and apply highlight to matching features - linesLayer.current.getSource()?.getFeatures().forEach((feature) => { + linesLayer.getSource()?.getFeatures().forEach((feature) => { if (selectedObjectType == feature.get('type')) { feature.setStyle(highlightStyleYellow); } else { @@ -767,17 +234,16 @@ const MapComponent = () => { useEffect(() => { if (currentObjectId) { - if (figuresLayer.current) { + if (figuresLayer) { // Reset styles and apply highlight to matching features - figuresLayer.current.getSource()?.getFeatures().forEach((feature: Feature) => { + figuresLayer.getSource()?.getFeatures().forEach((feature: Feature) => { if (currentObjectId == feature.get('object_id')) { - feature.setStyle(highlightStyleRed); + 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 }) + map?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 }) } } else { @@ -786,15 +252,15 @@ const MapComponent = () => { }) } - if (linesLayer.current) { + if (linesLayer) { // Reset styles and apply highlight to matching features - linesLayer.current.getSource()?.getFeatures().forEach((feature: Feature) => { + linesLayer.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?.getView().fit(geometry as SimpleGeometry, { duration: 500, maxZoom: 18 }) } } else { feature.setStyle(undefined); // Reset to default style @@ -822,9 +288,7 @@ const MapComponent = () => { !figuresValidating && selectedDistrict && selectedYear ? `/gis/lines/all?city_id=${selectedDistrict}&year=${selectedYear}&offset=0&limit=${10000}` : null, (url) => axios.get(url, { baseURL: BASE_URL.ems - }).then((res) => { - return res.data - }), + }).then((res) => res.data), swrOptions ) @@ -836,114 +300,133 @@ const MapComponent = () => { swrOptions ) - const [searchParams, setSearchParams] = useSearchParams() - useEffect(() => { - if (selectedRegion) { - setSearchParams((params) => { - params.set('r', selectedRegion.toString()); - return params - }) - } - - if (selectedDistrict) { - setSearchParams((params) => { - params.set('d', selectedDistrict?.toString()); - return params - }) - } - - if (selectedYear) { - setSearchParams((params) => { - params.set('y', selectedYear?.toString()); - return params - }) - } - - if (currentObjectId) { - setSearchParams((params) => { - params.set('o', currentObjectId?.toString()); - return params - }) - } - }, [selectedRegion, selectedDistrict, selectedYear, currentObjectId, setSearchParams]) - - useEffect(() => { - if (Array.isArray(regionsData)) { - const region = searchParams.get('r') - - if (searchParams.get('r')) { - setSelectedRegion(Number(region)) - } - } - }, [searchParams, regionsData]) - - useEffect(() => { - if (Array.isArray(regionsData)) { - const district = searchParams.get('d') - - if (Array.isArray(districtsData)) { - if (district) { - setSelectedDistrict(Number(district)) + if (Array.isArray(districtsData)) { + districtsData.map((district) => { + if (district.id === selectedDistrict) { + setMapLabel(id, [district.name, district.district_name, selectedYear].join(' - ')) } - } + }) } - }, [searchParams, regionsData, districtsData]) + }, [districtsData, id, selectedDistrict, selectedYear]) + //const [searchParams, setSearchParams] = useSearchParams() + + // useEffect(() => { + // if (selectedRegion) { + // setSearchParams((params) => { + // params.set('r', selectedRegion.toString()); + // return params + // }) + // } + + // if (selectedDistrict) { + // setSearchParams((params) => { + // params.set('d', selectedDistrict?.toString()); + // return params + // }) + // } + + // if (selectedYear) { + // setSearchParams((params) => { + // params.set('y', selectedYear?.toString()); + // return params + // }) + // } + + // if (currentObjectId) { + // setSearchParams((params) => { + // params.set('o', currentObjectId?.toString()); + // return params + // }) + // } + // }, [selectedRegion, selectedDistrict, selectedYear, currentObjectId, setSearchParams]) + + // useEffect(() => { + // if (Array.isArray(regionsData)) { + // const region = searchParams.get('r') + + // if (searchParams.get('r')) { + // setSelectedRegion(Number(region)) + // } + // } + // }, [searchParams, regionsData]) + + // useEffect(() => { + // if (Array.isArray(regionsData)) { + // const district = searchParams.get('d') + + // if (Array.isArray(districtsData)) { + // if (district) { + // setSelectedDistrict(Number(district)) + // } + // } + // } + // }, [searchParams, regionsData, districtsData]) + + + // useEffect(() => { + // if (Array.isArray(regionsData)) { + // const year = searchParams.get('y') + + // if (year) { + // setSelectedYear(Number(year)) + // } + // } + // }, [searchParams, regionsData]) + + // useEffect(() => { + // const object = searchParams.get('o') + + // if (figuresData && linesData && object) { + // setCurrentObjectId(object) + // } + // }, [searchParams, figuresData, linesData]) useEffect(() => { - if (Array.isArray(regionsData)) { - const year = searchParams.get('y') - - if (year) { - setSelectedYear(Number(year)) - } - } - }, [searchParams, regionsData]) - - useEffect(() => { - const object = searchParams.get('o') - - if (figuresData && linesData && object) { - setCurrentObjectId(object) - } - }, [searchParams, figuresData, linesData]) - - useEffect(() => { - const districtBoundSource: VectorSource> | null = districtBoundLayer.current.getSource() + const districtBoundSource = districtBoundLayer.getSource() if (selectedDistrict && districtBoundSource && districtBoundSource.getFeatures().length > 0) { const center: Coordinate = getCenter(districtBoundSource.getExtent()) - const settings = getCitySettings(selectedDistrict) + const settings = getCitySettings() if (Array.isArray(figuresData)) { - figuresLayer.current.getSource()?.clear() + figuresLayer.getSource()?.clear() if (figuresData.length > 0) { - figuresData.map((figure: IFigure) => { - processFigure( + const geoJsonObject = { + type: "FeatureCollection", + features: figuresData.map((figure: IFigure) => processFigure( figure, settings.scale, - [center[0], center[1]], - figuresLayer - ) - }) + [center[0], center[1]] + )), + } - if (map.current) { - const extent = figuresLayer.current.getSource()?.getExtent() + const features = new GeoJSON().readFeatures(geoJsonObject) + + figuresLayer.getSource()?.addFeatures(features) + + if (map) { + const extent = figuresLayer.getSource()?.getExtent() if (extent) { - map.current.getView().fit(fromExtent(extent), { duration: 500 }) + map.getView().fit(fromExtent(extent), { duration: 500 }) } } } } if (Array.isArray(linesData)) { - linesLayer.current.getSource()?.clear() + linesLayer.getSource()?.clear() if (linesData.length > 0) { - linesData.map((line: ILine) => { - processLine(line, settings.scale, [center[0], center[1]], linesLayer) - }) + const geoJsonObject = { + type: "FeatureCollection", + features: linesData.map((line: ILine) => processLine(line, settings.scale, [center[0], center[1]])), + } + + const features = new GeoJSON().readFeatures(geoJsonObject) + + linesLayer.getSource()?.addFeatures(features) } } } @@ -952,22 +435,20 @@ const MapComponent = () => { useEffect(() => { if (!selectedRegion) { - setSelectedDistrict(null) - setSelectedYear(null) + setSelectedRegion(id, null) + setSelectedYear(id, null) } - }, [selectedRegion, selectedDistrict]) + }, [selectedRegion, selectedDistrict, id]) useEffect(() => { if (selectedDistrict && districtData) { - const settings = getCitySettings(selectedDistrict) + const settings = getCitySettings() const imageUrl = `${import.meta.env.VITE_API_EMS_URL}/static/${selectedDistrict}`; const img = new Image(); img.src = imageUrl; img.onload = () => { - if (map.current) { - console.log(districtData) - + if (map) { const width = img.naturalWidth const height = img.naturalHeight //const k = (width < height ? width / height : height / width) @@ -990,17 +471,17 @@ const MapComponent = () => { url: imageUrl, imageExtent: extent, }); - staticMapLayer.current.setSource(imageSource); + staticMapLayer.setSource(imageSource); //map.current.addLayer(imageLayer.current); } }; } - }, [selectedDistrict, districtData]) + }, [selectedDistrict, districtData, staticMapLayer]) useEffect(() => { - if (baseLayer.current) { - baseLayer.current.on('prerender', function (e) { + if (baseLayer) { + baseLayer.on('prerender', function (e) { if (colorScheme === 'dark') { if (e.context) { const context = e.context as CanvasRenderingContext2D; @@ -1015,7 +496,7 @@ const MapComponent = () => { } }) - baseLayer.current.on('postrender', function (e) { + baseLayer.on('postrender', function (e) { if (e.context) { const context = e.context as CanvasRenderingContext2D; context.filter = 'none'; @@ -1025,184 +506,171 @@ const MapComponent = () => { }, [colorScheme]) return ( - - - - ({ 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() })) : []} - onChange={(value) => setSelectedRegion(Number(value))} - clearable - onClear={() => { - setSelectedRegion(null) - setSearchParams((params) => { - params.delete('r') - return params - }) - }} - searchable - value={selectedRegion ? selectedRegion.toString() : null} - /> - - ({ label: [item.name, item.district_name].join(' - '), value: item.id.toString() })) : []} - onChange={(value) => setSelectedDistrict(Number(value))} - clearable - onClear={() => { - setSelectedDistrict(null) - setSearchParams((params) => { - params.delete('d') - return params - }) - }} - searchable - value={selectedDistrict ? selectedDistrict.toString() : null} - /> - - ({ label: el, value: el }))} - onChange={(e) => { - if (e) { - setSelectedYear(Number(e)) - } else { - setSelectedYear(null) - } - }} - onClear={() => { - setSelectedYear(null) - setSearchParams((params) => { - params.delete('y') - return params - }) - }} - value={selectedYear ? selectedYear?.toString() : null} - clearable - /> - - - - - - - - - {'Настройка видимости слоёв'} - - - - setSatMapsProvider(value as SatelliteMapsProvider)} + <> + {active && + + + ({ label: item.value, value: item.id_object.toString() })) : []} + //onSelect={(e) => console.log(e.currentTarget.value)} + onChange={(value) => setSearchObject(value)} + onOptionSubmit={(value) => setCurrentObjectId(id, value)} + rightSection={ + searchObject !== '' && ( + event.preventDefault()} + onClick={() => { + setSearchObject('') + }} + aria-label="Clear value" /> - - - submitOverlay()}> - - + ) + } + leftSection={} + value={searchObject} + /> - - - + ({ label: item.name, value: item.id.toString() })) : []} + onChange={(value) => setSelectedRegion(id, Number(value))} + clearable + onClear={() => { + setSelectedRegion(id, null) + // setSearchParams((params) => { + // params.delete('r') + // return params + // }) + }} + searchable + value={selectedRegion ? selectedRegion.toString() : null} + /> + + ({ label: [item.name, item.district_name].join(' - '), value: item.id.toString() })) : []} + onChange={(value) => setSelectedDistrict(id, Number(value))} + clearable + onClear={() => { + setSelectedDistrict(id, null) + // setSearchParams((params) => { + // params.delete('d') + // return params + // }) + }} + searchable + value={selectedDistrict ? selectedDistrict.toString() : null} + /> + + ({ label: el, value: el }))} + onChange={(e) => { + if (e) { + setSelectedYear(id, Number(e)) + } else { + setSelectedYear(id, null) + } + }} + onClear={() => { + setSelectedYear(id, null) + // setSearchParams((params) => { + // params.delete('y') + // return params + // }) + }} + value={selectedYear ? selectedYear?.toString() : null} + clearable + /> + + + + + + + + + {'Настройка видимости слоёв'} + + + + setSatMapsProvider(id, value as SatelliteMapsProvider)} /> + + + submitOverlay(file, polygonExtent, rectCoords)}> + + + + + + + + - + + + + + } + + + + + + {selectedRegion && selectedDistrict && selectedYear && + + + + + + } + + + + + - - + + + + {selectedRegion && selectedDistrict && selectedYear && mode === 'edit' && + + } + + {selectedRegion && selectedDistrict && selectedYear && + + } + + + + + + - + + + e.preventDefault()} onDrop={(e) => handleImageDrop(e, id)}> +
+
- - - {selectedRegion && selectedDistrict && selectedYear && - saveFeatures(drawingLayer)} - onRemove={() => draw.current?.removeLastPoint()} - /> - } - - {selectedRegion && selectedDistrict && selectedYear && - - - - - - } - - - - - -
-
-
- ); + + ) }; export default MapComponent diff --git a/client/src/components/map/MapLayers/MapLayers.tsx b/client/src/components/map/MapLayers/MapLayers.tsx index e495f6c..ea0bba4 100644 --- a/client/src/components/map/MapLayers/MapLayers.tsx +++ b/client/src/components/map/MapLayers/MapLayers.tsx @@ -1,10 +1,10 @@ import { Checkbox, Flex, NavLink, Slider, Stack } from '@mantine/core' import BaseLayer from 'ol/layer/Base' import Map from 'ol/Map' -import React, { useEffect, useState } from 'react' +import { useEffect, useState } from 'react' interface MapLayersProps { - map: React.MutableRefObject + map: Map | null } const MapLayers = ({ @@ -12,7 +12,7 @@ const MapLayers = ({ }: MapLayersProps) => { return ( - {map.current?.getLayers().getArray() && map.current?.getLayers().getArray().map((layer, index) => ( + {map?.getLayers().getArray() && map?.getLayers().getArray().map((layer, index) => ( ))} diff --git a/client/src/components/map/MapLegend/MapLegend.tsx b/client/src/components/map/MapLegend/MapLegend.tsx new file mode 100644 index 0000000..135a7ff --- /dev/null +++ b/client/src/components/map/MapLegend/MapLegend.tsx @@ -0,0 +1,90 @@ +import { Accordion, ColorSwatch, Flex, ScrollAreaAutosize, Stack, Text, useMantineColorScheme } from '@mantine/core' +import useSWR from 'swr'; +import { fetcher } from '../../../http/axiosInstance'; +import { BASE_URL } from '../../../constants'; + +const MapLegend = ({ + selectedDistrict, + selectedYear +}: { + selectedDistrict: number | null, + selectedYear: number | null +}) => { + const { colorScheme } = useMantineColorScheme(); + + const { data: existingObjectsList } = useSWR( + selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + const { data: planningObjectsList } = useSWR( + selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=1` : null, + (url) => fetcher(url, BASE_URL.ems), + { + revalidateOnFocus: false + } + ) + + return ( + + + + Легенда + + + + + Существующие + + {existingObjectsList && } + + + + + Планируемые + + {planningObjectsList && } + + + + + + ) +} + +const LegendGroup = ({ + objectsList, + border +}: { + objectsList: { id: number, name: string, count: number, r: number | null, g: number | null, b: number | null }[], + border: 'solid' | 'dotted' +}) => { + + const borderStyle = () => { + switch (border) { + case 'solid': + return '2px solid black' + case 'dotted': + return '2px dotted black' + default: + return 'none' + } + } + + return ( + + {objectsList.map(object => ( + + + - + {object.name} + + ))} + + ) +} + +export default MapLegend \ No newline at end of file diff --git a/client/src/components/map/MapMode.tsx b/client/src/components/map/MapMode.tsx new file mode 100644 index 0000000..a772ac4 --- /dev/null +++ b/client/src/components/map/MapMode.tsx @@ -0,0 +1,32 @@ +import { Center, SegmentedControl } from '@mantine/core' +import { getMode, Mode, setMode } from '../../store/map' +import { IconEdit, IconEye } from '@tabler/icons-react' + +const MapMode = ({ + map_id +}: { map_id: string }) => { + return ( + setMode(map_id, value as Mode)} color="blue" w='auto' data={[ + { + value: 'view', + label: ( +
+ + Просмотр +
+ ), + }, + { + value: 'edit', + label: ( +
+ + Редактирование +
+ ), + }, + ]} /> + ) +} + +export default MapMode \ No newline at end of file diff --git a/client/src/components/map/MapSources.ts b/client/src/components/map/MapSources.ts index 9e261dc..4f0d7aa 100644 --- a/client/src/components/map/MapSources.ts +++ b/client/src/components/map/MapSources.ts @@ -26,7 +26,7 @@ const customMapSource = new XYZ({ }) const regionsLayerSource = new VectorSource({ - url: 'sakha_republic.geojson', + url: 'http://localhost:5000/gis/bounds/region', format: new GeoJSON(), }) diff --git a/client/src/components/map/MapStatusbar/MapStatusbar.tsx b/client/src/components/map/MapStatusbar/MapStatusbar.tsx index d40ff55..4401b17 100644 --- a/client/src/components/map/MapStatusbar/MapStatusbar.tsx +++ b/client/src/components/map/MapStatusbar/MapStatusbar.tsx @@ -4,39 +4,41 @@ import { useMapStore } from '../../../store/map'; interface IMapStatusbarProps { mapControlsStyle: CSSProperties; + map_id: string; } const MapStatusbar = ({ mapControlsStyle, + map_id }: IMapStatusbarProps) => { - const mapState = useMapStore() + const { currentCoordinate, currentX, currentY, currentZ, statusText } = useMapStore().id[map_id] return ( - + - x: {mapState.currentCoordinate?.[0]} + x: {currentCoordinate?.[0]} - y: {mapState.currentCoordinate?.[1]} + y: {currentCoordinate?.[1]} - Z={mapState.currentZ} + Z={currentZ} - X={mapState.currentX} + X={currentX} - Y={mapState.currentY} + Y={currentY} - {mapState.statusText} + {statusText} ) diff --git a/client/src/components/map/MapStyles.ts b/client/src/components/map/MapStyles.ts index 7a6a521..3453e09 100644 --- a/client/src/components/map/MapStyles.ts +++ b/client/src/components/map/MapStyles.ts @@ -95,7 +95,7 @@ const figureStyle = new Style({ color: 'rgba(255,255,255,0.4)' }), stroke: new Stroke({ - color: '#3399CC', + color: 'black', width: 1.25 }), text: new Text({ @@ -108,12 +108,9 @@ const figureStyle = new Style({ }) const lineStyle = new Style({ - fill: new Fill({ - color: 'rgba(255,255,255,0.4)' - }), stroke: new Stroke({ color: '#3399CC', - width: 1.25 + width: 1 }), text: new Text({ font: '12px Calibri,sans-serif', diff --git a/client/src/components/map/MapToolbar/MapToolbar.tsx b/client/src/components/map/MapToolbar/MapToolbar.tsx index 0386970..4b3b51e 100644 --- a/client/src/components/map/MapToolbar/MapToolbar.tsx +++ b/client/src/components/map/MapToolbar/MapToolbar.tsx @@ -1,93 +1,90 @@ -import { ActionIcon, useMantineColorScheme } from '@mantine/core' +import { ActionIcon, Flex, useMantineColorScheme } from '@mantine/core' import { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react' -import { setCurrentTool, useMapStore } from '../../../store/map'; - -interface IToolbarProps { - onSave: () => void; - onRemove: () => void; -} +import { getDraw, setCurrentTool, useMapStore } from '../../../store/map'; +import { saveFeatures } from '../mapUtils'; const MapToolbar = ({ - onSave, - onRemove, -}: IToolbarProps) => { - const mapState = useMapStore() + map_id +}: { map_id: string }) => { + const { currentTool } = useMapStore().id[map_id] const { colorScheme } = useMantineColorScheme(); return ( - - - - + + + saveFeatures(map_id)}> + + - - - + getDraw(map_id)?.removeLastPoint()}> + + - { - setCurrentTool('Edit') - }}> - - + { + setCurrentTool(map_id, 'Edit') + }}> + + - { - setCurrentTool('Point') - }}> - - + { + setCurrentTool(map_id, 'Point') + }}> + + - { - setCurrentTool('LineString') - }}> - - + { + setCurrentTool(map_id, 'LineString') + }}> + + - { - setCurrentTool('Polygon') - }}> - - + { + setCurrentTool(map_id, 'Polygon') + }}> + + - { - setCurrentTool('Circle') - }}> - - + { + setCurrentTool(map_id, 'Circle') + }}> + + - { - setCurrentTool('Mover') - }} - > - - + { + setCurrentTool(map_id, 'Mover') + }} + > + + - { - setCurrentTool('Measure') - }}> - - - + { + setCurrentTool(map_id, 'Measure') + }}> + + + + ) } diff --git a/client/src/components/map/Measure/MeasureStyles.ts b/client/src/components/map/Measure/MeasureStyles.ts index 50dcdb4..009face 100644 --- a/client/src/components/map/Measure/MeasureStyles.ts +++ b/client/src/components/map/Measure/MeasureStyles.ts @@ -138,6 +138,7 @@ const formatArea = function (polygon: Geometry) { }; export function measureStyleFunction( + map_id: string, feature: FeatureLike, drawType?: Type, tip?: string, @@ -149,7 +150,7 @@ export function measureStyleFunction( const type = geometry?.getType(); const segmentStyles = [segmentStyle]; - const segments = getMeasureShowSegments() + const segments = getMeasureShowSegments(map_id) if (!geometry) return diff --git a/client/src/components/map/ObjectParameter.tsx b/client/src/components/map/ObjectParameter.tsx index 22a2dfc..58dcc6f 100644 --- a/client/src/components/map/ObjectParameter.tsx +++ b/client/src/components/map/ObjectParameter.tsx @@ -6,12 +6,14 @@ import TCBParameter from './TCBParameter' import TableValue from './TableValue' interface ObjectParameterProps { - showLabel?: boolean, - param: IObjectParam, + showLabel?: boolean; + param: IObjectParam; + map_id: string; } const ObjectParameter = ({ - param + param, + map_id }: ObjectParameterProps) => { const { data: paramData } = useSWR( `/general/params/all?param_id=${param.id_param}`, @@ -26,44 +28,44 @@ const ObjectParameter = ({ switch (type) { case 'bit': return ( - + ) case 'bigint': return ( - + ) 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 ( - + ) case 'uniqueidentifier': return ( - + ) default: return ( diff --git a/client/src/components/map/ObjectParameters/ObjectParameters.tsx b/client/src/components/map/ObjectParameters/ObjectParameters.tsx index 973afe6..dc900c3 100644 --- a/client/src/components/map/ObjectParameters/ObjectParameters.tsx +++ b/client/src/components/map/ObjectParameters/ObjectParameters.tsx @@ -6,8 +6,13 @@ import { BASE_URL } from '../../../constants'; import { fetcher } from '../../../http/axiosInstance'; import { useObjectsStore } from '../../../store/objects'; -const ObjectParameters = () => { - const { currentObjectId } = useObjectsStore() +const ObjectParameters = ({ + map_id +}: { + map_id: string +}) => { + + const { currentObjectId } = useObjectsStore().id[map_id] const { data: valuesData, isValidating: valuesValidating } = useSWR( currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null, @@ -42,13 +47,13 @@ const ObjectParameters = () => { sortedParams.map((param: IObjectParam) => { if (param.date_po == null) { return ( - + ) } } ) ) : ( - + ); }) } diff --git a/client/src/components/map/TCBParameter.tsx b/client/src/components/map/TCBParameter.tsx index 3a623fe..c244b8b 100644 --- a/client/src/components/map/TCBParameter.tsx +++ b/client/src/components/map/TCBParameter.tsx @@ -9,12 +9,14 @@ interface ITCBParameterProps { vtable: string; inactive?: boolean; name: string; + map_id: string; } const TCBParameter = ({ value, vtable, - name + name, + map_id }: ITCBParameterProps) => { //Get value @@ -80,7 +82,7 @@ const TCBParameter = ({ const TCBValue = (vtable: string) => { if (tables.includes(vtable)) { return ( - + ) } else { return ( diff --git a/client/src/components/map/TableValue.tsx b/client/src/components/map/TableValue.tsx index 20db1c9..0cc5461 100644 --- a/client/src/components/map/TableValue.tsx +++ b/client/src/components/map/TableValue.tsx @@ -10,6 +10,7 @@ interface TableValueProps { type: 'value' | 'boolean' | 'number' | 'select' | 'string'; unit?: string | null | undefined; vtable?: string; + map_id: string; } const TableValue = ({ @@ -17,10 +18,10 @@ const TableValue = ({ value, type, unit, - vtable + vtable, + map_id }: TableValueProps) => { - const { selectedDistrict } = useObjectsStore() - + const { selectedDistrict } = useObjectsStore().id[map_id] //Get available values const { data: tcbAll, isValidating } = useSWR( type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null, @@ -56,10 +57,10 @@ const TableValue = ({ /> : type === 'select' && !isValidating && tcbAll ? - : type === 'string' ? -