diff --git a/client/public/template.docx b/client/public/template.docx
new file mode 100644
index 0000000..dec638c
Binary files /dev/null and b/client/public/template.docx differ
diff --git a/client/public/template_table.docx b/client/public/template_table.docx
new file mode 100644
index 0000000..b88ddad
Binary files /dev/null and b/client/public/template_table.docx differ
diff --git a/client/public/test.png b/client/public/test.png
new file mode 100644
index 0000000..1b0be22
Binary files /dev/null and b/client/public/test.png differ
diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx
index 9a539b5..45aa24a 100644
--- a/client/src/components/map/MapComponent.tsx
+++ b/client/src/components/map/MapComponent.tsx
@@ -14,13 +14,13 @@ import { addInteractions, handleImageDrop, loadFeatures, processFigure, processL
import useSWR, { SWRConfiguration } from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
-import { ActionIcon, Autocomplete, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay, Stack, Container } from '@mantine/core'
+import { ActionIcon, Autocomplete, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay, Stack, Container, Modal, Transition } from '@mantine/core'
import { IconBoxMultiple, IconBoxPadding, IconChevronDown, IconPlus, IconSearch, IconUpload } from '@tabler/icons-react'
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
import axios from 'axios'
import MapToolbar from './MapToolbar/MapToolbar'
import MapStatusbar from './MapStatusbar/MapStatusbar'
-import { setAlignMode, setSatMapsProvider, setTypeRoles, useMapStore, setMapLabel } from '../../store/map'
+import { setAlignMode, setSatMapsProvider, setTypeRoles, useMapStore, setMapLabel, clearPrintArea } from '../../store/map'
import { useThrottle } from '@uidotdev/usehooks'
import ObjectTree from '../Tree/ObjectTree'
import { setCurrentObjectId, setSelectedDistrict, setSelectedRegion, setSelectedYear, useObjectsStore } from '../../store/objects'
@@ -73,6 +73,7 @@ const MapComponent = ({
nodeLayerSource, drawingLayerSource,
satLayer, staticMapLayer, figuresLayer, linesLayer,
regionsLayer, districtBoundLayer, baseLayer,
+ printArea, printSource, printAreaDraw
} = useMapStore().id[id]
// Tab settings
@@ -383,6 +384,17 @@ const MapComponent = ({
// }
// }, [searchParams, figuresData, linesData])
+ useEffect(() => {
+ if (selectedDistrict === null) {
+ setSelectedYear(id, null)
+ }
+
+ if (selectedRegion === null) {
+ setSelectedYear(id, null)
+ setSelectedDistrict(id, null)
+ }
+ }, [selectedDistrict, selectedRegion, id])
+
useEffect(() => {
const districtBoundSource = districtBoundLayer.getSource()
@@ -505,8 +517,42 @@ const MapComponent = ({
}
}, [colorScheme])
+ useEffect(() => {
+ if (map) {
+ if (mode === 'print') {
+ map.addInteraction(printAreaDraw)
+ } else {
+ map.removeInteraction(printAreaDraw)
+ }
+ }
+
+ }, [mode, map, printAreaDraw])
+
+ useEffect(() => {
+ if (printArea) {
+ map?.setTarget('print-portal')
+ // map?.setView(new View({
+ // extent: printArea
+ // }))
+ printSource.clear()
+ map?.getView().setCenter(getCenter(printArea))
+ map?.getView().fit(printArea, {
+ size: [640, 320]
+ })
+ }
+ }, [printArea, map])
+
return (
<>
+ {
+ clearPrintArea(id)
+ map?.setTarget(mapElement.current as HTMLDivElement)
+ }} title="Предпросмотр области">
+
+
+
+
+
{active &&
@@ -636,22 +682,40 @@ const MapComponent = ({
}
+
+
+ {(styles) =>
+
+
+
+ }
+
-
+
-
+
{selectedRegion && selectedDistrict && selectedYear && mode === 'edit' &&
}
-
- {selectedRegion && selectedDistrict && selectedYear &&
-
- }
+
+
+ {(styles) => }
+
diff --git a/client/src/components/map/MapLegend/MapLegend.tsx b/client/src/components/map/MapLegend/MapLegend.tsx
index 135a7ff..cc5a05d 100644
--- a/client/src/components/map/MapLegend/MapLegend.tsx
+++ b/client/src/components/map/MapLegend/MapLegend.tsx
@@ -1,14 +1,16 @@
-import { Accordion, ColorSwatch, Flex, ScrollAreaAutosize, Stack, Text, useMantineColorScheme } from '@mantine/core'
+import { Accordion, ColorSwatch, Flex, MantineStyleProp, 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
+ selectedYear,
+ style
}: {
selectedDistrict: number | null,
- selectedYear: number | null
+ selectedYear: number | null,
+ style: MantineStyleProp
}) => {
const { colorScheme } = useMantineColorScheme();
@@ -29,7 +31,7 @@ const MapLegend = ({
)
return (
-
+
Легенда
diff --git a/client/src/components/map/MapMode.tsx b/client/src/components/map/MapMode.tsx
index a772ac4..6d8fc4a 100644
--- a/client/src/components/map/MapMode.tsx
+++ b/client/src/components/map/MapMode.tsx
@@ -1,6 +1,6 @@
import { Center, SegmentedControl } from '@mantine/core'
import { getMode, Mode, setMode } from '../../store/map'
-import { IconEdit, IconEye } from '@tabler/icons-react'
+import { IconEdit, IconEye, IconPrinter } from '@tabler/icons-react'
const MapMode = ({
map_id
@@ -25,6 +25,15 @@ const MapMode = ({
),
},
+ {
+ value: 'print',
+ label: (
+
+
+ Печать
+
+ ),
+ },
]} />
)
}
diff --git a/client/src/components/map/mapUtils.ts b/client/src/components/map/mapUtils.ts
index 94ec564..f3c526c 100644
--- a/client/src/components/map/mapUtils.ts
+++ b/client/src/components/map/mapUtils.ts
@@ -17,6 +17,7 @@ import { fromCircle, fromExtent } from "ol/geom/Polygon";
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
import { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
import Collection from "ol/Collection";
+import { GeometryFunction, SketchCoordType } from "ol/interaction/Draw";
const calculateAngle = (coords: [number, number][]) => {
const [start, end] = coords;
@@ -341,9 +342,47 @@ export const updateImageSource = (
}
};
+export const fixedAspectRatioBox: GeometryFunction = (
+ coordinates: SketchCoordType,
+ geometry: SimpleGeometry | undefined,
+): SimpleGeometry => {
+ // Ensure coordinates is an array of at least two points
+ if (!Array.isArray(coordinates) || coordinates.length < 2) {
+ return geometry ?? new Polygon([]);
+ }
+
+ const [start, end] = coordinates as Coordinate[]; // Ensure it's a Coordinate array
+ const minX = start[0];
+ const minY = start[1];
+ const maxX = end[0];
+ let maxY = end[1];
+
+ const width = maxX - minX;
+ const height = width / 2; // Enforce 2:1 aspect ratio
+ maxY = minY + height;
+
+ // Define the rectangle's coordinates
+ const boxCoords: Coordinate[][] = [[
+ [minX, minY],
+ [maxX, minY],
+ [maxX, maxY],
+ [minX, maxY],
+ [minX, minY], // Close the polygon
+ ]];
+
+ if (geometry) {
+ geometry.setCoordinates(boxCoords);
+ return geometry;
+ }
+ return new Polygon(boxCoords);
+};
+
+
+
export const addInteractions = (
map_id: string
) => {
+ console.log("Adding interactions")
const currentTool = getCurrentTool(map_id)
const clearPrevious = getMeasureClearPrevious(map_id)
const measureType = getMeasureType(map_id)
diff --git a/client/src/constants/app.tsx b/client/src/constants/app.tsx
index afd5e49..a880d7b 100644
--- a/client/src/constants/app.tsx
+++ b/client/src/constants/app.tsx
@@ -152,10 +152,10 @@ const pages = [
component: ,
drawer: true,
dashboard: true,
- enabled: true,
+ enabled: false,
},
{
- label: "DB Manager",
+ label: "Тест БД",
path: "/db-manager",
icon: ,
component: ,
diff --git a/client/src/layouts/DashboardLayout.tsx b/client/src/layouts/DashboardLayout.tsx
index 1647bcb..7535dad 100644
--- a/client/src/layouts/DashboardLayout.tsx
+++ b/client/src/layouts/DashboardLayout.tsx
@@ -9,7 +9,7 @@ import { pages } from '../constants/app';
function DashboardLayout() {
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure()
- const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true)
+ const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(false)
const navigate = useNavigate()
const getPageTitle = () => {
diff --git a/client/src/pages/MapTest.tsx b/client/src/pages/MapTest.tsx
index 6fa2862..e6d61a6 100644
--- a/client/src/pages/MapTest.tsx
+++ b/client/src/pages/MapTest.tsx
@@ -17,12 +17,12 @@ function MapTest() {
region: 11,
district: 145,
},
- {
- id: uuidv4(),
- year: 2023,
- region: 11,
- district: 146,
- },
+ // {
+ // id: uuidv4(),
+ // year: 2023,
+ // region: 11,
+ // district: 146,
+ // },
]
useEffect(() => {
diff --git a/client/src/pages/PrintReport.tsx b/client/src/pages/PrintReport.tsx
index a64a68e..5b658e0 100644
--- a/client/src/pages/PrintReport.tsx
+++ b/client/src/pages/PrintReport.tsx
@@ -1,4 +1,6 @@
import { Button, Flex } from "@mantine/core";
+import { useState } from "react";
+import createReport from 'docx-templates'
const xslTemplate = `
@@ -904,9 +906,109 @@ const xslTemplate = `
`
const PrintReport = () => {
- const handleGenerateExcel = () => {
- // Define the example XML data
- const xmlData = `
+ const [loading, setLoading] = useState(false);
+
+ const generateDocx = async () => {
+ setLoading(true);
+
+ try {
+ // Fetch the DOCX template from the public folder
+ const response = await fetch("/template.docx");
+ const response_table = await fetch("/template_table.docx");
+ const templateArrayBuffer = await response.arrayBuffer();
+ const templateArrayBuffer_table = await response_table.arrayBuffer();
+
+ // Convert ArrayBuffer to Uint8Array (Fix TypeScript error)
+ const templateUint8Array = new Uint8Array(templateArrayBuffer);
+ const templateUint8Array_table = new Uint8Array(templateArrayBuffer_table);
+
+ // Fetch the image (Example: Load from public folder)
+ const imageResponse = await fetch("/test.png"); // Change this to your image path
+ const imageBlob = await imageResponse.blob();
+ const imageArrayBuffer = await imageBlob.arrayBuffer();
+ const imageUint8Array = new Uint8Array(imageArrayBuffer);
+
+ // Generate the DOCX file with the replacement
+ const report = await createReport({
+ template: templateUint8Array, // Ensure it's Uint8Array
+ data: {
+ test: "Hello World",
+ myImage: {
+ width: 6, // Width in cm
+ height: 6, // Height in cm
+ data: imageUint8Array, // Image binary data
+ extension: ".png", // Specify the image format
+ },
+ },
+ });
+
+ const report_table = await createReport({
+ template: templateUint8Array_table, // Ensure it's Uint8Array
+ data: {
+ test: "Hello World",
+ rows: [
+ {
+ first: 'A',
+ second: 'B',
+ third: 'C',
+ fourth: 'D',
+ },
+ {
+ first: 'E',
+ second: 'F',
+ third: 'G',
+ fourth: 'H',
+ },
+ {
+ first: 'I',
+ second: 'J',
+ third: 'K',
+ fourth: 'L',
+ }
+ ]
+ },
+ });
+
+ // Convert Uint8Array to a Blob
+ const blob = new Blob([report], {
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ });
+
+ const blob_table = new Blob([report_table], {
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ });
+
+ // Create a download link and trigger the download
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = "report.docx";
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ // Create a download link and trigger the download
+ const url_table = URL.createObjectURL(blob_table);
+ const a_table = document.createElement("a");
+ a_table.href = url_table;
+ a_table.download = "report_table.docx";
+ document.body.appendChild(a_table);
+ a_table.click();
+ document.body.removeChild(a_table);
+
+ // Revoke the object URL after download
+ URL.revokeObjectURL(url);
+ URL.revokeObjectURL(url_table);
+ } catch (error) {
+ console.error("Error generating DOCX:", error);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ const handleGenerateExcel = () => {
+ // Define the example XML data
+ const xmlData = `
1
@@ -928,40 +1030,41 @@ const PrintReport = () => {
`;
- // Parse the XSL template and XML data
- const parser = new DOMParser();
- const xslDoc = parser.parseFromString(xslTemplate, "application/xml");
- const xmlDoc = parser.parseFromString(xmlData, "application/xml");
+ // Parse the XSL template and XML data
+ const parser = new DOMParser();
+ const xslDoc = parser.parseFromString(xslTemplate, "application/xml");
+ const xmlDoc = parser.parseFromString(xmlData, "application/xml");
- // Apply the transformation
- const xsltProcessor = new XSLTProcessor();
- xsltProcessor.importStylesheet(xslDoc);
- const resultDocument = xsltProcessor.transformToDocument(xmlDoc);
+ // Apply the transformation
+ const xsltProcessor = new XSLTProcessor();
+ xsltProcessor.importStylesheet(xslDoc);
+ const resultDocument = xsltProcessor.transformToDocument(xmlDoc);
- // Serialize the result to a string
- const serializer = new XMLSerializer();
- const resultXml = serializer.serializeToString(resultDocument);
+ // Serialize the result to a string
+ const serializer = new XMLSerializer();
+ const resultXml = serializer.serializeToString(resultDocument);
- // Add missing Excel-specific headers if needed
- const correctedXml = `\n` + resultXml
+ // Add missing Excel-specific headers if needed
+ const correctedXml = `\n` + resultXml
- // Convert to Blob and trigger download
- const blob = new Blob([correctedXml], { type: "application/vnd.ms-excel" });
- const url = URL.createObjectURL(blob);
- const link = document.createElement("a");
- link.href = url;
- link.download = "template.xls";
- link.click();
+ // Convert to Blob and trigger download
+ const blob = new Blob([correctedXml], { type: "application/vnd.ms-excel" });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = "template.xls";
+ link.click();
- // Clean up
- URL.revokeObjectURL(url);
- }
+ // Clean up
+ URL.revokeObjectURL(url);
+ }
- return (
-
-
-
- )
+ return (
+
+
+
+
+ )
}
export default PrintReport
\ No newline at end of file
diff --git a/client/src/store/map.ts b/client/src/store/map.ts
index 927e2ab..07b21c8 100644
--- a/client/src/store/map.ts
+++ b/client/src/store/map.ts
@@ -22,11 +22,11 @@ import { click, pointerMove } from 'ol/events/condition';
import { measureStyleFunction, modifyStyle } from '../components/map/Measure/MeasureStyles';
import MapBrowserEvent from 'ol/MapBrowserEvent';
import { transform } from 'ol/proj';
-import { applyTransformations, calculateTransformations, zoomToFeature } from '../components/map/mapUtils';
+import { applyTransformations, calculateTransformations, fixedAspectRatioBox, zoomToFeature } from '../components/map/mapUtils';
import { setCurrentObjectId, setSelectedRegion } from './objects';
import View from 'ol/View';
-export type Mode = 'edit' | 'view'
+export type Mode = 'edit' | 'view' | 'print'
interface MapState {
id: Record;
}
@@ -206,6 +211,24 @@ export const initializeMapState = (
const alignModeLayer = new VectorLayer({ source: new VectorSource(), properties: { id: uuidv4(), type: 'align', name: 'Подгонка' } })
+
+ const printSource = new VectorSource()
+ const printLayer = new VectorLayer({
+ source: printSource
+ })
+ const printAreaDraw = new Draw({
+ source: printSource,
+ type: 'Circle',
+ geometryFunction: fixedAspectRatioBox
+ })
+
+ printAreaDraw.on('drawend', (e) => {
+ const extent = e.feature.getGeometry()?.getExtent()
+ if (extent) {
+ setPrintArea(id, extent)
+ }
+ })
+
const map = new Map({
controls: [],
layers: [
@@ -222,7 +245,8 @@ export const initializeMapState = (
overlayLayer,
nodeLayer,
measureLayer,
- alignModeLayer
+ alignModeLayer,
+ printLayer
]
})
@@ -364,13 +388,38 @@ export const initializeMapState = (
nodeLayer: nodeLayer,
overlayLayer: overlayLayer,
regionSelect: regionSelect,
- lineSelect: lineSelect
+ lineSelect: lineSelect,
+ printArea: null,
+ printLayer: printLayer,
+ printSource: printSource,
+ printAreaDraw: printAreaDraw,
+ printPreviewSize: [640, 320]
}
}
}
})
}
+export const clearPrintArea = (id: string) => useMapStore.setState((state) => {
+ state.id[id].printSource.clear()
+
+ return {
+ id: {
+ ...state.id,
+ [id]: { ...state.id[id], printArea: null }
+ }
+ }
+})
+
+export const setPrintArea = (id: string, extent: Extent | null) => useMapStore.setState((state) => {
+ return {
+ id: {
+ ...state.id,
+ [id]: { ...state.id[id], printArea: extent }
+ }
+ }
+})
+
export const getFiguresLayer = (id: string) => useMapStore.getState().id[id].figuresLayer
export const getLinesLayer = (id: string) => useMapStore.getState().id[id].linesLayer
export const getMeasureModify = (id: string) => useMapStore.getState().id[id].measureModify