Compare commits

...

20 Commits

Author SHA1 Message Date
1f9a3a8e03 tools: bounds importer 2025-04-04 14:53:32 +09:00
6015218d59 docker-compose: add PostGIS db 2025-03-31 10:53:47 +09:00
442255ebaf Form from template 2025-03-26 10:31:00 +09:00
3bcea3f1ac ObjectTree: add padding 2025-03-07 16:53:39 +09:00
ada3b63b8d pass aspect ratio to fixedAspectRatio; remove printAreaDraw after printArea is defined 2025-03-07 16:50:54 +09:00
0ca6c136e3 ObjectTree: Memoize ObjectList 2025-03-06 15:37:49 +09:00
ec622da773 Remove duplication 2025-02-26 15:07:19 +09:00
300921751a lines/figures: hitTolerance for hover/click 2025-02-26 14:58:51 +09:00
4cc3a919ed MapPrint: fixedAspectRatioBox direction fix 2025-02-25 16:09:05 +09:00
349d7449f0 Add missing TCB types 2025-02-25 16:01:31 +09:00
8438e05301 MapComponent: Print button 2025-02-24 13:23:33 +09:00
85bafea7c8 ESLint 2025-01-31 15:56:10 +09:00
c08f839b70 Report test; Map printing test 2025-01-31 15:53:58 +09:00
0788a401ca Update 2025-01-30 12:36:39 +09:00
e6b3dc05d3 Region/district/city bounds routes 2025-01-14 14:32:41 +09:00
2bf657e8ed Update 2025-01-10 11:38:00 +09:00
59fded5cab Temporary disable nodes (prisma) 2024-12-20 10:15:56 +09:00
242ed1aee2 Rename client_app -> web_client 2024-12-20 10:01:46 +09:00
75d6420d6b Remove unused setter 2024-12-20 09:45:38 +09:00
7e8d1f50c8 Add CLIENT_PORT env 2024-12-20 09:39:12 +09:00
63 changed files with 14521 additions and 1838 deletions

View File

@ -6,5 +6,6 @@ POSTGRES_DB=ems
POSTGRES_USER=ems POSTGRES_USER=ems
POSTGRES_PASSWORD= POSTGRES_PASSWORD=
POSTGRES_PORT=5432 POSTGRES_PORT=5432
CLIENT_PORT=5173
EMS_PORT=5000 EMS_PORT=5000
MONITOR_PORT=1234 MONITOR_PORT=1234

1
client/.gitignore vendored
View File

@ -11,6 +11,7 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
stats.html
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

486
client/package-lock.json generated
View File

@ -9,8 +9,10 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"-": "^0.0.1", "-": "^0.0.1",
"@dnd-kit/core": "^6.3.1",
"@fontsource/inter": "^5.0.19", "@fontsource/inter": "^5.0.19",
"@fontsource/open-sans": "^5.0.28", "@fontsource/open-sans": "^5.0.28",
"@hello-pangea/dnd": "^17.0.0",
"@js-preview/docx": "^1.6.2", "@js-preview/docx": "^1.6.2",
"@js-preview/excel": "^1.7.8", "@js-preview/excel": "^1.7.8",
"@js-preview/pdf": "^2.0.2", "@js-preview/pdf": "^2.0.2",
@ -29,6 +31,7 @@
"@mantine/tiptap": "^7.13.0", "@mantine/tiptap": "^7.13.0",
"@tabler/icons-react": "^3.17.0", "@tabler/icons-react": "^3.17.0",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@techstark/opencv-js": "^4.10.0-release.1",
"@tiptap/extension-link": "^2.7.3", "@tiptap/extension-link": "^2.7.3",
"@tiptap/react": "^2.7.3", "@tiptap/react": "^2.7.3",
"@tiptap/starter-kit": "^2.7.3", "@tiptap/starter-kit": "^2.7.3",
@ -38,8 +41,12 @@
"axios": "^1.7.2", "axios": "^1.7.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"docx-templates": "^4.13.0",
"easy-template-x": "^5.1.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"file-type": "^19.0.0", "file-type": "^19.0.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"ol": "^10.0.0", "ol": "^10.0.0",
"ol-ext": "^4.0.23", "ol-ext": "^4.0.23",
"proj4": "^2.12.0", "proj4": "^2.12.0",
@ -65,6 +72,7 @@
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-preset-mantine": "^1.17.0", "postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"rollup-plugin-visualizer": "^5.12.0",
"sass-embedded": "^1.79.5", "sass-embedded": "^1.79.5",
"serve": "^14.2.3", "serve": "^14.2.3",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",
@ -1863,9 +1871,9 @@
"dev": true "dev": true
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.24.7", "version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@ -1937,6 +1945,42 @@
"integrity": "sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==", "integrity": "sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==",
"dev": true "dev": true
}, },
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@dnd-kit/utilities": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -2441,6 +2485,24 @@
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.28.tgz", "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.28.tgz",
"integrity": "sha512-hBvJHY76pJT/JynGUB5EXWhnzjYfLdcMn655J5p1v9lTT9HdQSy+keq2KPVXO2Htlg998BBa3p6u/jlrZ6w0kg==" "integrity": "sha512-hBvJHY76pJT/JynGUB5EXWhnzjYfLdcMn655J5p1v9lTT9HdQSy+keq2KPVXO2Htlg998BBa3p6u/jlrZ6w0kg=="
}, },
"node_modules/@hello-pangea/dnd": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz",
"integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==",
"dependencies": {
"@babel/runtime": "^7.25.6",
"css-box-model": "^1.2.1",
"memoize-one": "^6.0.0",
"raf-schd": "^4.0.3",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"use-memo-one": "^1.1.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -3467,6 +3529,11 @@
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
} }
}, },
"node_modules/@techstark/opencv-js": {
"version": "4.10.0-release.1",
"resolved": "https://registry.npmjs.org/@techstark/opencv-js/-/opencv-js-4.10.0-release.1.tgz",
"integrity": "sha512-S4XELidRiQeA0q1s9VQLo540wCxUo24r1O4C+LqZ6llX+sPCXvZCPv3Ice8dEIr0uavyZ8YZeKXSBdDgMXSXjw=="
},
"node_modules/@tiptap/core": { "node_modules/@tiptap/core": {
"version": "2.7.3", "version": "2.7.3",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.3.tgz", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.7.3.tgz",
@ -3939,8 +4006,7 @@
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.3", "version": "18.3.3",
@ -4193,6 +4259,15 @@
"vite": "^4 || ^5" "vite": "^4 || ^5"
} }
}, },
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@zeit/schemas": { "node_modules/@zeit/schemas": {
"version": "2.36.0", "version": "2.36.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
@ -4453,7 +4528,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"peer": true,
"bin": { "bin": {
"atob": "bin/atob.js" "atob": "bin/atob.js"
}, },
@ -4580,8 +4654,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">= 0.6.0"
} }
@ -4859,7 +4931,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"peer": true,
"bin": { "bin": {
"btoa": "bin/btoa.js" "btoa": "bin/btoa.js"
}, },
@ -5008,7 +5079,6 @@
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0", "@types/raf": "^3.4.0",
@ -5027,8 +5097,7 @@
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.2", "version": "4.1.2",
@ -5141,6 +5210,57 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/cliui/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@ -5316,7 +5436,6 @@
"integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==",
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
@ -5338,8 +5457,7 @@
"node_modules/core-util-is": { "node_modules/core-util-is": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
"dev": true
}, },
"node_modules/create-ecdh": { "node_modules/create-ecdh": {
"version": "4.0.4", "version": "4.0.4",
@ -5440,12 +5558,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"dependencies": {
"tiny-invariant": "^1.0.6"
}
},
"node_modules/css-line-break": { "node_modules/css-line-break": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"utrie": "^1.0.2" "utrie": "^1.0.2"
} }
@ -5696,6 +5820,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/define-properties": { "node_modules/define-properties": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@ -5789,6 +5922,18 @@
"node": ">=6.0.0" "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": { "node_modules/dom-helpers": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@ -5814,8 +5959,7 @@
"version": "2.5.6", "version": "2.5.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz",
"integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==", "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/earcut": { "node_modules/earcut": {
"version": "3.0.0", "version": "3.0.0",
@ -5828,6 +5972,18 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true "dev": true
}, },
"node_modules/easy-template-x": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/easy-template-x/-/easy-template-x-5.1.0.tgz",
"integrity": "sha512-vypMbIMLWLXoooA9rsL3SVN2oQtZwmmx1m4H8gi6JfbEXQQ5VLHGOUHYi9APbvN9R8Gx93r1fphdSFRHxozeYw==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "0.8.10",
"json5": "2.2.3",
"jszip": "3.10.1",
"lodash.get": "4.4.2"
}
},
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@ -6414,10 +6570,9 @@
} }
}, },
"node_modules/fflate": { "node_modules/fflate": {
"version": "0.4.8", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
"peer": true
}, },
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
@ -6688,6 +6843,15 @@
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@ -7007,8 +7171,7 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"optional": true, "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"css-line-break": "^2.1.0", "css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3" "text-segmentation": "^1.0.3"
@ -7066,6 +7229,11 @@
"node": ">= 4" "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": { "node_modules/immutable": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
@ -7704,7 +7872,6 @@
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": { "bin": {
"json5": "lib/cli.js" "json5": "lib/cli.js"
}, },
@ -7734,23 +7901,66 @@
} }
}, },
"node_modules/jspdf": { "node_modules/jspdf": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
"integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
"peer": true, "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.14.0", "@babel/runtime": "^7.23.2",
"atob": "^2.1.2", "atob": "^2.1.2",
"btoa": "^1.2.1", "btoa": "^1.2.1",
"fflate": "^0.4.8" "fflate": "^0.8.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"canvg": "^3.0.6", "canvg": "^3.0.6",
"core-js": "^3.6.0", "core-js": "^3.6.0",
"dompurify": "^2.2.0", "dompurify": "^2.5.4",
"html2canvas": "^1.0.0-rc.5" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -7795,6 +8005,14 @@
"node": ">= 0.8.0" "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": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@ -7849,6 +8067,13 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true "dev": true
}, },
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -7922,6 +8147,11 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
}, },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@ -8325,6 +8555,23 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"dev": true,
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -8386,8 +8633,7 @@
"node_modules/pako": { "node_modules/pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
"dev": true
}, },
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
@ -8542,8 +8788,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true, "optional": true
"peer": true
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.0", "version": "1.1.0",
@ -8832,8 +9077,7 @@
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"dev": true
}, },
"node_modules/proj4": { "node_modules/proj4": {
"version": "2.12.0", "version": "2.12.0",
@ -9147,11 +9391,15 @@
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"performance-now": "^2.1.0" "performance-now": "^2.1.0"
} }
}, },
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -9273,6 +9521,36 @@
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
@ -9499,6 +9777,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"node_modules/regenerate": { "node_modules/regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -9609,6 +9892,15 @@
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": { "node_modules/require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@ -9667,7 +9959,6 @@
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 0.8.15" "node": ">= 0.8.15"
} }
@ -9733,6 +10024,32 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup-plugin-visualizer": {
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz",
"integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==",
"dev": true,
"dependencies": {
"open": "^8.4.0",
"picomatch": "^2.3.1",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/rope-sequence": { "node_modules/rope-sequence": {
"version": "1.3.4", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
@ -10202,6 +10519,11 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "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": { "node_modules/scheduler": {
"version": "0.23.2", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@ -10384,8 +10706,7 @@
"node_modules/setimmediate": { "node_modules/setimmediate": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
"dev": true
}, },
"node_modules/sha.js": { "node_modules/sha.js": {
"version": "2.4.11", "version": "2.4.11",
@ -10466,6 +10787,15 @@
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
"dev": true "dev": true
}, },
"node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -10505,7 +10835,6 @@
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.1.14" "node": ">=0.1.14"
} }
@ -10854,7 +11183,6 @@
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
} }
@ -10980,8 +11308,6 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"utrie": "^1.0.2" "utrie": "^1.0.2"
} }
@ -11443,6 +11769,14 @@
} }
} }
}, },
"node_modules/use-memo-one": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
"integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/use-sidecar": { "node_modules/use-sidecar": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
@ -11494,8 +11828,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"base64-arraybuffer": "^1.0.2" "base64-arraybuffer": "^1.0.2"
} }
@ -12213,6 +12545,15 @@
"node": ">=0.4" "node": ">=0.4"
} }
}, },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@ -12231,6 +12572,53 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/yargs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -12,8 +12,10 @@
}, },
"dependencies": { "dependencies": {
"-": "^0.0.1", "-": "^0.0.1",
"@dnd-kit/core": "^6.3.1",
"@fontsource/inter": "^5.0.19", "@fontsource/inter": "^5.0.19",
"@fontsource/open-sans": "^5.0.28", "@fontsource/open-sans": "^5.0.28",
"@hello-pangea/dnd": "^17.0.0",
"@js-preview/docx": "^1.6.2", "@js-preview/docx": "^1.6.2",
"@js-preview/excel": "^1.7.8", "@js-preview/excel": "^1.7.8",
"@js-preview/pdf": "^2.0.2", "@js-preview/pdf": "^2.0.2",
@ -32,6 +34,7 @@
"@mantine/tiptap": "^7.13.0", "@mantine/tiptap": "^7.13.0",
"@tabler/icons-react": "^3.17.0", "@tabler/icons-react": "^3.17.0",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@techstark/opencv-js": "^4.10.0-release.1",
"@tiptap/extension-link": "^2.7.3", "@tiptap/extension-link": "^2.7.3",
"@tiptap/react": "^2.7.3", "@tiptap/react": "^2.7.3",
"@tiptap/starter-kit": "^2.7.3", "@tiptap/starter-kit": "^2.7.3",
@ -41,8 +44,12 @@
"axios": "^1.7.2", "axios": "^1.7.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"docx-templates": "^4.13.0",
"easy-template-x": "^5.1.0",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.0",
"file-type": "^19.0.0", "file-type": "^19.0.0",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.2",
"ol": "^10.0.0", "ol": "^10.0.0",
"ol-ext": "^4.0.23", "ol-ext": "^4.0.23",
"proj4": "^2.12.0", "proj4": "^2.12.0",
@ -68,6 +75,7 @@
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-preset-mantine": "^1.17.0", "postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"rollup-plugin-visualizer": "^5.12.0",
"sass-embedded": "^1.79.5", "sass-embedded": "^1.79.5",
"serve": "^14.2.3", "serve": "^14.2.3",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",

BIN
client/public/template.docx Normal file

Binary file not shown.

Binary file not shown.

BIN
client/public/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@ -5,47 +5,48 @@
right: 0; right: 0;
height: 100%; height: 100%;
width: 5px; width: 5px;
background: #27bbff; background: var(--mantine-color-anchor);
cursor: col-resize; cursor: col-resize;
user-select: none; user-select: none;
touch-action: none; touch-action: none;
border-radius: 6px; border-radius: 6px;
transition: opacity .2s ease;
} }
.resize_handler:hover { .resize_handler:hover {
opacity: 1; opacity: 1;
} }
.tr { // .tr {
display: flex; // display: flex;
//width: 100%; // //width: 100%;
//max-width: 100%; // //max-width: 100%;
width: fit-content; // width: fit-content;
} // }
.th { .th {
position: relative; position: relative;
} }
.th, // .th,
.td { // .td {
display: flex; // display: flex;
width: auto; // width: auto;
} // }
.thead { // .thead {
display: flex; // display: flex;
width: 100%; // width: 100%;
} // }
.table { // .table {
display: flex; // display: flex;
flex-direction: column; // flex-direction: column;
width: 100%; // width: 100%;
} // }
.tbody { // .tbody {
display: flex; // display: flex;
flex-direction: column; // flex-direction: column;
width: 100%; // width: 100%;
} // }

View File

@ -1,111 +1,205 @@
import { Input, Table } from '@mantine/core'; import { Badge, Button, Flex, Input, Modal, ScrollAreaAutosize, Select, Stack, Table, TextInput } from '@mantine/core';
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { Cell, ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import styles from './CustomTable.module.scss' 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 CustomTableProps<T> = {
data: T[];
type DataType = { columns: ColumnDef<T>[];
id: number, createFields?: CreateField[];
name: string, submitHandler?: (data: T) => Promise<AxiosResponse>
age: number
} }
// Define columns const CustomTable = <T extends object>({
const columns: ColumnDef<DataType>[] = [ data: initialData,
{ columns,
accessorKey: 'name', createFields,
header: 'Name', submitHandler
cell: (info) => info.getValue(), }: CustomTableProps<T>) => {
maxSize: Number.MAX_SAFE_INTEGER, const [data, setData] = useState<T[]>(initialData);
}, const [searchText, setSearchText] = useState('');
{
accessorKey: 'age',
header: 'Age',
cell: (info) => info.getValue(),
},
];
const CustomTable = () => {
const [data, setData] = useState<DataType[]>([
{ id: 1, name: 'John Doe', age: 25 },
{ id: 2, name: 'Jane Smith', age: 30 },
{ id: 3, name: 'Sam Green', age: 22 },
]);
const [editingCell, setEditingCell] = useState<{ rowIndex: string | number | null, columnId: string | number | null }>({ rowIndex: null, columnId: null }); const [editingCell, setEditingCell] = useState<{ rowIndex: string | number | null, columnId: string | number | null }>({ rowIndex: null, columnId: null });
const tableColumns = useMemo<ColumnDef<typeof data[0]>[]>(() => 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({ const table = useReactTable({
data, data: filteredData,
columns: tableColumns, columns: tableColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange", columnResizeMode: "onChange",
}); });
// Function to handle cell edit const [opened, { open, close }] = useDisclosure(false);
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 });
};
return ( return (
<Table striped withColumnBorders highlightOnHover className={styles.table}> <Stack h='100%'>
<Table.Thead className={styles.thead}> {createFields && submitHandler &&
{table.getHeaderGroups().map(headerGroup => ( <Modal opened={opened} onClose={close} title="Добавление объекта" centered>
<Table.Tr key={headerGroup.id} className={styles.tr}> <FormFields
{headerGroup.headers.map((header) => ( fields={createFields}
<Table.Th key={header.id} className={styles.th} w={header.getSize()}> submitHandler={submitHandler}
{flexRender(header.column.columnDef.header, header.getContext())} />
<div </Modal>
className={styles.resize_handler} }
onMouseDown={header.getResizeHandler()} //for desktop
onTouchStart={header.getResizeHandler()}
>
</div>
</Table.Th>
))}
</Table.Tr>
))}
</Table.Thead>
<Table.Tbody className={styles.tbody}>
{table.getRowModel().rows.map((row, rowIndex) => (
<Table.Tr key={row.id} className={styles.tr}>
{row.getVisibleCells().map(cell => {
const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id;
return ( <Flex w='100%' gap='sm'>
<Table.Td <TextInput
key={cell.id} placeholder="Поиск"
onDoubleClick={() => setEditingCell({ rowIndex, columnId: cell.column.id })} value={searchText}
style={{ width: cell.column.getSize() }} onChange={(e) => setSearchText(e.target.value)}
className={styles.td} w='100%'
> />
{isEditing ? ( {createFields && submitHandler &&
<Input <Button
type='text' leftSection={<IconPlus />}
value={data[rowIndex][cell.column.id as keyof DataType]} onClick={open}
onChange={(e) => handleEditCell(rowIndex, (cell.column.id as keyof DataType), e.target.value)} style={{ flexShrink: 0 }}
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })} >
autoFocus Добавить
/> </Button>
) : ( }
flexRender(cell.column.columnDef.cell, cell.getContext()) </Flex>
)}
</Table.Td> <ScrollAreaAutosize offsetScrollbars style={{ borderRadius: '4px' }}>
); <Table stickyHeader striped withColumnBorders highlightOnHover className={styles.table}>
})} <Table.Thead className={styles.thead}>
</Table.Tr> {table.getHeaderGroups().map(headerGroup => (
))} <Table.Tr key={headerGroup.id} className={styles.tr}>
</Table.Tbody> {headerGroup.headers.map((header) => (
</Table> <Table.Th key={header.id} className={styles.th} w={header.getSize()}>
{flexRender(header.column.columnDef.header, header.getContext())}
<div
className={styles.resize_handler}
onMouseDown={header.getResizeHandler()} //for desktop
onTouchStart={header.getResizeHandler()}
>
</div>
</Table.Th>
))}
</Table.Tr>
))}
</Table.Thead>
<Table.Tbody className={styles.tbody}>
{table.getRowModel().rows.map((row, rowIndex) => (
<Table.Tr key={row.id} className={styles.tr}>
{row.getVisibleCells().map(cell => {
const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id;
return (
<Table.Td
key={cell.id}
onDoubleClick={() => setEditingCell({ rowIndex, columnId: cell.column.id })}
style={{ width: cell.column.getSize() }}
className={styles.td}
>
{isEditing ? (
<Input
type='text'
value={(data[rowIndex][cell.column.id as keyof T] as string)}
onChange={(e) => handleEditCell(rowIndex, (cell.column.id as keyof T), e.target.value as T[keyof T])}
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })}
autoFocus
/>
) : (
<CellDisplay cell={cell} />
)}
</Table.Td>
);
})}
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollAreaAutosize>
</Stack>
); );
}; };
type CellDisplayProps<T> = {
cell: Cell<T, unknown>;
}
const CellDisplay = <T extends object>({
cell
}: CellDisplayProps<T>) => {
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() ? (
<Badge fullWidth variant="light">
Активен
</Badge>
) : (
<Badge color="gray" fullWidth variant="light">
Отключен
</Badge>
)
)
case 'is_active':
return (
cell.getValue() ? (
<Badge fullWidth variant="light">
Активен
</Badge>
) : (
<Badge color="gray" fullWidth variant="light">
Отключен
</Badge>
)
)
case 'role_id':
return (
<Select
data={roleOptions}
value={Number(cell.getValue()).toString()}
variant="unstyled"
allowDeselect={false}
/>
)
default:
return (
flexRender(cell.column.columnDef.cell, cell.getContext())
)
}
}
export default CustomTable; export default CustomTable;

View File

@ -4,7 +4,7 @@ import { useStorages } from '../hooks/swrHooks'
import { Loader, Table } from '@mantine/core' import { Loader, Table } from '@mantine/core'
export default function ServerStorage() { export default function ServerStorage() {
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null) const [selectedOption] = useState<IRegion | null>(null)
const { storages, isLoading: serversLoading } = useStorages(selectedOption?.id, 0, 10) const { storages, isLoading: serversLoading } = useStorages(selectedOption?.id, 0, 10)

View File

@ -1,15 +1,18 @@
import { useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance' import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants' import { BASE_URL } from '../../constants'
import { Accordion, NavLink, Text } from '@mantine/core'; import { NavLink, Stack, Text } from '@mantine/core';
import { setCurrentObjectId, useObjectsStore } from '../../store/objects';
import { IconChevronDown } from '@tabler/icons-react'; import { IconChevronDown } from '@tabler/icons-react';
import { setSelectedObjectType } from '../../store/map'; import { setSelectedObjectType } from '../../store/map';
import { setCurrentObjectId, useObjectsStore } from '../../store/objects';
const ObjectTree = () => { const ObjectTree = ({
const { selectedDistrict, selectedYear } = useObjectsStore() map_id
}: {
map_id: string,
}) => {
const { selectedYear, selectedDistrict } = useObjectsStore().id[map_id]
const [existingCount, setExistingCount] = useState(0) const [existingCount, setExistingCount] = useState(0)
const [planningCount, setPlanningCount] = useState(0) const [planningCount, setPlanningCount] = useState(0)
@ -49,10 +52,10 @@ const ObjectTree = () => {
if (selectedDistrict) { if (selectedDistrict) {
return ( return (
<Accordion multiple chevronPosition='left'> <Stack gap={0}>
<TypeTree label='Существующие' value={'existing'} count={existingCount} objectList={existingObjectsList} planning={0} /> <TypeTree map_id={map_id} label='Существующие' value={'existing'} count={existingCount} objectList={existingObjectsList} planning={0} />
<TypeTree label='Планируемые' value={'planning'} count={planningCount} objectList={planningObjectsList} planning={1} /> <TypeTree map_id={map_id} label='Планируемые' value={'planning'} count={planningCount} objectList={planningObjectsList} planning={1} />
</Accordion> </Stack>
) )
} else { } else {
return ( return (
@ -69,19 +72,21 @@ interface TypeTreeProps {
count: number; count: number;
objectList: unknown; objectList: unknown;
planning: number; planning: number;
map_id: string;
} }
const TypeTree = ({ const TypeTree = ({
label, label,
objectList, objectList,
count, count,
planning planning,
map_id
}: TypeTreeProps) => { }: TypeTreeProps) => {
return ( return (
<NavLink p={0} label={`${label} ${count ? `(${count})` : ''}`}> <NavLink px='xs' py={0} label={`${label} ${count ? `(${count})` : ''}`}>
{Array.isArray(objectList) && objectList.map(list => ( {Array.isArray(objectList) && objectList.map(list => (
<ObjectList key={list.id} label={list.name} id={list.id} planning={planning} count={list.count} /> <ObjectList map_id={map_id} key={list.id} label={list.name} id={list.id} planning={planning} count={list.count} />
))} ))}
</NavLink> </NavLink>
) )
@ -92,15 +97,17 @@ interface IObjectList {
id: number; id: number;
planning: number; planning: number;
count: number; count: number;
map_id: string;
} }
const ObjectList = ({ const ObjectList = ({
label, label,
id, id,
planning, planning,
count count,
map_id
}: IObjectList) => { }: IObjectList) => {
const { selectedDistrict, selectedYear } = useObjectsStore() const { selectedDistrict, selectedYear } = useObjectsStore().id[map_id]
const { data } = useSWR( const { data } = useSWR(
selectedDistrict && selectedYear ? `/general/objects/list?type=${id}&city_id=${selectedDistrict}&year=${selectedYear}&planning=${planning}` : null, selectedDistrict && selectedYear ? `/general/objects/list?type=${id}&city_id=${selectedDistrict}&year=${selectedYear}&planning=${planning}` : null,
@ -111,15 +118,25 @@ const ObjectList = ({
} }
) )
const navLinks = useMemo(() => (
Array.isArray(data) ? data.map((type) => (
<NavLink key={type.object_id} label={type.caption ? type.caption : 'Без названия'} px='xs' py={0} onClick={() => setCurrentObjectId(map_id, type.object_id)} />
)) : null
), [data, map_id]);
return ( return (
<NavLink onClick={() => { <NavLink onClick={() => setSelectedObjectType(map_id, id)} rightSection={<IconChevronDown size={14} />} px='xs' py={0} label={`${label} ${count ? `(${count})` : ''}`}>
setSelectedObjectType(id) {navLinks}
}} rightSection={<IconChevronDown size={14} />} p={0} label={`${label} ${count ? `(${count})` : ''}`}>
{Array.isArray(data) && data.map((type) => (
<NavLink key={type.object_id} label={type.caption ? type.caption : 'Без названия'} p={0} onClick={() => setCurrentObjectId(type.object_id)} />
))}
</NavLink> </NavLink>
) );
// return (
// <NavLink onClick={() => { setSelectedObjectType(map_id, id) }} rightSection={<IconChevronDown size={14} />} p={0} label={`${label} ${count ? `(${count})` : ''}`}>
// {Array.isArray(data) && data.map((type) => (
// <NavLink key={type.object_id} label={type.caption ? type.caption : 'Без названия'} p={0} onClick={() => setCurrentObjectId(map_id, type.object_id)} />
// ))}
// </NavLink>
// )
} }
export default ObjectTree export default ObjectTree

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
import { Checkbox, Flex, NavLink, Slider, Stack } from '@mantine/core' import { Checkbox, Flex, NavLink, Slider, Stack } from '@mantine/core'
import BaseLayer from 'ol/layer/Base' import BaseLayer from 'ol/layer/Base'
import Map from 'ol/Map' import Map from 'ol/Map'
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
interface MapLayersProps { interface MapLayersProps {
map: React.MutableRefObject<Map | null> map: Map | null
} }
const MapLayers = ({ const MapLayers = ({
@ -12,7 +12,7 @@ const MapLayers = ({
}: MapLayersProps) => { }: MapLayersProps) => {
return ( return (
<Stack gap='0'> <Stack gap='0'>
{map.current?.getLayers().getArray() && map.current?.getLayers().getArray().map((layer, index) => ( {map?.getLayers().getArray() && map?.getLayers().getArray().map((layer, index) => (
<LayerSetting key={index} index={index} layer={layer} /> <LayerSetting key={index} index={index} layer={layer} />
))} ))}
</Stack> </Stack>

View File

@ -0,0 +1,92 @@
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,
style
}: {
selectedDistrict: number | null,
selectedYear: number | null,
style: MantineStyleProp
}) => {
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 (
<ScrollAreaAutosize offsetScrollbars maw='300px' w='100%' fz='xs' mt='auto' style={{...style, zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
<Stack gap='sm' p='sm'>
<Text fz='xs'>
Легенда
</Text>
<Accordion defaultValue={['existing', 'planning']} multiple>
<Accordion.Item value='existing' key='existing'>
<Accordion.Control>Существующие</Accordion.Control>
<Accordion.Panel>
{existingObjectsList && <LegendGroup objectsList={existingObjectsList} border='solid' />}
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value='planning' key='planning'>
<Accordion.Control>Планируемые</Accordion.Control>
<Accordion.Panel>
{planningObjectsList && <LegendGroup objectsList={planningObjectsList} border='dotted' />}
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Stack>
</ScrollAreaAutosize>
)
}
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 (
<Stack gap={4}>
{objectsList.map(object => (
<Flex gap='xs' align='center' key={object.id}>
<ColorSwatch style={{ border: borderStyle() }} radius={0} size={16} color={`rgb(${object.r},${object.g},${object.b})`} />
-
<Text fz='xs'>{object.name}</Text>
</Flex>
))}
</Stack>
)
}
export default MapLegend

View File

@ -0,0 +1,107 @@
import { Button, Flex, FloatingIndicator, Popover, SegmentedControl } from '@mantine/core'
import { Mode, setMode, useMapStore } from '../../store/map'
import { IconChevronDown, IconCropLandscape, IconCropPortrait, IconEdit, IconEye, IconPrinter } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
import { PrintOrientation, setPrintOrientation, usePrintStore } from '../../store/print'
const MapMode = ({
map_id
}: { map_id: string }) => {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const { mode } = useMapStore().id[map_id]
const setControlRef = (item: Mode) => (node: HTMLButtonElement) => {
controlsRefs[item] = node;
setControlsRefs(controlsRefs);
}
const { printOrientation } = usePrintStore()
useEffect(() => {
}, [printOrientation])
return (
<Flex ref={setRootRef} p={4} gap={4}>
<Button
variant={mode === 'view' ? 'filled' : 'subtle'}
key={'view'}
ref={setControlRef('view' as Mode)}
onClick={() => {
setMode(map_id, 'view' as Mode)
}}
leftSection={<IconEye size={16} />}
mod={{ active: mode === 'view' as Mode }}
>
Просмотр
</Button>
<Button
variant={mode === 'edit' ? 'filled' : 'subtle'}
key={'edit'}
ref={setControlRef('edit' as Mode)}
onClick={() => {
setMode(map_id, 'edit' as Mode)
}}
leftSection={<IconEdit size={16} />}
mod={{ active: mode === 'edit' as Mode }}
>
Редактирование
</Button>
<Popover width='auto' position='bottom-end' >
<Popover.Target>
<Button.Group>
<Button
variant={mode === 'print' ? 'filled' : 'subtle'}
key={'print'}
ref={setControlRef('print' as Mode)}
onClick={(e) => {
e.stopPropagation()
setMode(map_id, 'print' as Mode)
}}
leftSection={<IconPrinter size={16} />}
mod={{ active: mode === 'print' as Mode }}
>
Печать
</Button>
<Button variant={mode === 'print' ? 'filled' : 'subtle'} w='auto' p={8} title='Ориентация'>
<IconChevronDown size={16} />
</Button>
</Button.Group>
</Popover.Target>
<Popover.Dropdown p={0} style={{ display: 'flex' }}>
<SegmentedControl
color='blue'
value={printOrientation}
onChange={(value) => {
setPrintOrientation(value as PrintOrientation)
setMode(map_id, 'print' as Mode)
}}
data={[
{
value: 'horizontal',
label: (
<IconCropLandscape title='Горизонтальная' style={{ display: 'block' }} size={20} />
),
},
{
value: 'vertical',
label: (
<IconCropPortrait title='Вертикальная' style={{ display: 'block' }} size={20} />
),
},
]}
/>
</Popover.Dropdown>
</Popover>
<FloatingIndicator target={controlsRefs[mode]} parent={rootRef} />
</Flex >
)
}
export default MapMode

View File

@ -26,7 +26,7 @@ const customMapSource = new XYZ({
}) })
const regionsLayerSource = new VectorSource({ const regionsLayerSource = new VectorSource({
url: 'sakha_republic.geojson', url: 'http://localhost:5000/gis/bounds/region',
format: new GeoJSON(), format: new GeoJSON(),
}) })

View File

@ -4,39 +4,41 @@ import { useMapStore } from '../../../store/map';
interface IMapStatusbarProps { interface IMapStatusbarProps {
mapControlsStyle: CSSProperties; mapControlsStyle: CSSProperties;
map_id: string;
} }
const MapStatusbar = ({ const MapStatusbar = ({
mapControlsStyle, mapControlsStyle,
map_id
}: IMapStatusbarProps) => { }: IMapStatusbarProps) => {
const mapState = useMapStore() const { currentCoordinate, currentX, currentY, currentZ, statusText } = useMapStore().id[map_id]
return ( return (
<Flex gap='sm' p={'4px'} miw={'100%'} fz={'xs'} pos='absolute' bottom='0px' left='0px' style={{ ...mapControlsStyle, borderRadius: 0 }}> <Flex gap='sm' p={'4px'} w={'100%'} fz={'xs'} style={{ ...mapControlsStyle, borderRadius: 0 }}>
<Text fz='xs' w={rem(130)}> <Text fz='xs' w={rem(130)}>
x: {mapState.currentCoordinate?.[0]} x: {currentCoordinate?.[0]}
</Text> </Text>
<Text fz='xs' w={rem(130)}> <Text fz='xs' w={rem(130)}>
y: {mapState.currentCoordinate?.[1]} y: {currentCoordinate?.[1]}
</Text> </Text>
<Divider orientation='vertical' /> <Divider orientation='vertical' />
<Text fz='xs'> <Text fz='xs'>
Z={mapState.currentZ} Z={currentZ}
</Text> </Text>
<Text fz='xs'> <Text fz='xs'>
X={mapState.currentX} X={currentX}
</Text> </Text>
<Text fz='xs'> <Text fz='xs'>
Y={mapState.currentY} Y={currentY}
</Text> </Text>
<Text fz='xs' ml='auto'> <Text fz='xs' ml='auto'>
{mapState.statusText} {statusText}
</Text> </Text>
</Flex> </Flex>
) )

View File

@ -1,4 +1,4 @@
import Feature, { FeatureLike } from "ol/Feature"; import { FeatureLike } from "ol/Feature";
import { Text } from "ol/style"; import { Text } from "ol/style";
import Fill from "ol/style/Fill"; import Fill from "ol/style/Fill";
import { FlatStyleLike } from "ol/style/flat"; import { FlatStyleLike } from "ol/style/flat";
@ -90,101 +90,39 @@ export function overlayStyle(feature: FeatureLike) {
return styles return styles
} }
export function styleFunction(feature: Feature) { const figureStyle = new Style({
return [ fill: new Fill({
new Style({ color: 'rgba(255,255,255,0.4)'
fill: new Fill({ }),
color: 'rgba(255,255,255,0.4)' stroke: new Stroke({
}), color: 'black',
stroke: new Stroke({ width: 1.25
color: '#3399CC', }),
width: 1.25 text: new Text({
}), font: '12px Calibri,sans-serif',
text: new Text({ fill: new Fill({ color: '#000' }),
font: '12px Calibri,sans-serif', stroke: new Stroke({
fill: new Fill({ color: '#000' }), color: '#fff', width: 2
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
}) })
]; })
} })
export function firstStyleFunction(feature: Feature) { const lineStyle = new Style({
return [ stroke: new Stroke({
new Style({ color: '#3399CC',
fill: new Fill({ width: 1
color: 'rgba(255,255,255,0.4)' }),
}), text: new Text({
stroke: new Stroke({ font: '12px Calibri,sans-serif',
color: 'red', fill: new Fill({ color: '#000' }),
width: 1.25 stroke: new Stroke({
}), color: '#fff', width: 2
text: new Text({ }),
font: '12px Calibri,sans-serif', placement: 'line',
fill: new Fill({ color: '#000' }), overflow: true,
stroke: new Stroke({ //declutterMode: 'obstacle'
color: '#fff', width: 2 })
}), })
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
})
];
}
export function thirdStyleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: '#33ccb3',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
})
];
}
export function fourthStyleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: '#3399CC',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: `${feature.get('object_id')}\n ${feature.get('angle')}`
})
})
];
}
const drawingLayerStyle: FlatStyleLike = { const drawingLayerStyle: FlatStyleLike = {
'fill-color': 'rgba(255, 255, 255, 0.2)', 'fill-color': 'rgba(255, 255, 255, 0.2)',
@ -218,5 +156,7 @@ const regionsLayerStyle = new Style({
export { export {
drawingLayerStyle, drawingLayerStyle,
selectStyle, selectStyle,
regionsLayerStyle regionsLayerStyle,
lineStyle,
figureStyle
} }

View File

@ -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 { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react'
import { setCurrentTool, useMapStore } from '../../../store/map'; import { getDraw, setCurrentTool, useMapStore } from '../../../store/map';
import { saveFeatures } from '../mapUtils';
interface IToolbarProps {
onSave: () => void;
onRemove: () => void;
}
const MapToolbar = ({ const MapToolbar = ({
onSave, map_id
onRemove, }: { map_id: string }) => {
}: IToolbarProps) => { const { currentTool } = useMapStore().id[map_id]
const mapState = useMapStore()
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
return ( return (
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}> <Flex>
<ActionIcon size='lg' variant='transparent' onClick={onSave}> <ActionIcon.Group orientation='vertical' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
<IconExclamationCircle /> <ActionIcon size='lg' variant='transparent' onClick={() => saveFeatures(map_id)}>
</ActionIcon> <IconExclamationCircle />
</ActionIcon>
<ActionIcon size='lg' variant='transparent' onClick={onRemove}> <ActionIcon size='lg' variant='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()}>
<IconArrowBackUp /> <IconArrowBackUp />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Edit' ? 'filled' : 'transparent'} variant={currentTool === 'Edit' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Edit') setCurrentTool(map_id, 'Edit')
}}> }}>
<IconTransformPoint /> <IconTransformPoint />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Point' ? 'filled' : 'transparent'} variant={currentTool === 'Point' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Point') setCurrentTool(map_id, 'Point')
}}> }}>
<IconPoint /> <IconPoint />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'LineString' ? 'filled' : 'transparent'} variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('LineString') setCurrentTool(map_id, 'LineString')
}}> }}>
<IconLine /> <IconLine />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Polygon' ? 'filled' : 'transparent'} variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Polygon') setCurrentTool(map_id, 'Polygon')
}}> }}>
<IconPolygon /> <IconPolygon />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Circle' ? 'filled' : 'transparent'} variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Circle') setCurrentTool(map_id, 'Circle')
}}> }}>
<IconCircle /> <IconCircle />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Mover' ? 'filled' : 'transparent'} variant={currentTool === 'Mover' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Mover') setCurrentTool(map_id, 'Mover')
}} }}
> >
<IconArrowsMove /> <IconArrowsMove />
</ActionIcon> </ActionIcon>
<ActionIcon <ActionIcon
size='lg' size='lg'
variant={mapState.currentTool === 'Measure' ? 'filled' : 'transparent'} variant={currentTool === 'Measure' ? 'filled' : 'transparent'}
onClick={() => { onClick={() => {
setCurrentTool('Measure') setCurrentTool(map_id, 'Measure')
}}> }}>
<IconRuler /> <IconRuler />
</ActionIcon> </ActionIcon>
</ActionIcon.Group> </ActionIcon.Group>
</Flex>
) )
} }

View File

@ -138,6 +138,7 @@ const formatArea = function (polygon: Geometry) {
}; };
export function measureStyleFunction( export function measureStyleFunction(
map_id: string,
feature: FeatureLike, feature: FeatureLike,
drawType?: Type, drawType?: Type,
tip?: string, tip?: string,
@ -149,7 +150,7 @@ export function measureStyleFunction(
const type = geometry?.getType(); const type = geometry?.getType();
const segmentStyles = [segmentStyle]; const segmentStyles = [segmentStyle];
const segments = getMeasureShowSegments() const segments = getMeasureShowSegments(map_id)
if (!geometry) return if (!geometry) return

View File

@ -6,12 +6,14 @@ import TCBParameter from './TCBParameter'
import TableValue from './TableValue' import TableValue from './TableValue'
interface ObjectParameterProps { interface ObjectParameterProps {
showLabel?: boolean, showLabel?: boolean;
param: IObjectParam, param: IObjectParam;
map_id: string;
} }
const ObjectParameter = ({ const ObjectParameter = ({
param param,
map_id
}: ObjectParameterProps) => { }: ObjectParameterProps) => {
const { data: paramData } = useSWR( const { data: paramData } = useSWR(
`/general/params/all?param_id=${param.id_param}`, `/general/params/all?param_id=${param.id_param}`,
@ -26,44 +28,48 @@ const ObjectParameter = ({
switch (type) { switch (type) {
case 'bit': case 'bit':
return ( return (
<TableValue value={value} name={name} type='boolean' /> <TableValue map_id={map_id} value={value} name={name} type='boolean' />
)
case 'int':
return (
<TableValue map_id={map_id} value={value} name={name} type='number' />
) )
case 'bigint': case 'bigint':
return ( return (
<TableValue value={value} name={name} type='number' /> <TableValue map_id={map_id} value={value} name={name} type='number' />
) )
case 'tinyint': case 'tinyint':
return ( return (
<TableValue value={value} name={name} type='number' /> <TableValue map_id={map_id} value={value} name={name} type='number' />
) )
// TODO: Calculate from calc procedures // TODO: Calculate from calc procedures
case 'calculate': case 'calculate':
return ( return (
<TableValue value={value} name={name} type='value' /> <TableValue map_id={map_id} value={value} name={name} type='value' />
) )
case 'GTCB': case 'GTCB':
return ( return (
<TCBParameter value={value as string} vtable={vtable} name={name} /> <TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
) )
case 'TCB': case 'TCB':
return ( return (
<TCBParameter value={value as string} vtable={vtable} name={name} /> <TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
) )
case type.match(/varchar\((\d+)\)/)?.input: case type.match(/varchar\((\d+)\)/)?.input:
return ( return (
<TableValue value={value} name={name} type='string' /> <TableValue map_id={map_id} value={value} name={name} type='string' />
) )
case type.match(/numeric\((\d+),(\d+)\)/)?.input: case type.match(/numeric\((\d+),(\d+)\)/)?.input:
return ( return (
<TableValue value={value} name={name} type='number' unit={unit} /> <TableValue map_id={map_id} value={value} name={name} type='number' unit={unit} />
) )
case 'year': case 'year':
return ( return (
<TableValue value={value} name={name} type='number' /> <TableValue map_id={map_id} value={value} name={name} type='number' />
) )
case 'uniqueidentifier': case 'uniqueidentifier':
return ( return (
<TableValue value={value} name={name} type='value'/> <TableValue map_id={map_id} value={value} name={name} type='value' />
) )
default: default:
return ( return (

View File

@ -6,8 +6,13 @@ import { BASE_URL } from '../../../constants';
import { fetcher } from '../../../http/axiosInstance'; import { fetcher } from '../../../http/axiosInstance';
import { useObjectsStore } from '../../../store/objects'; import { useObjectsStore } from '../../../store/objects';
const ObjectParameters = () => { const ObjectParameters = ({
const { currentObjectId } = useObjectsStore() map_id
}: {
map_id: string
}) => {
const { currentObjectId } = useObjectsStore().id[map_id]
const { data: valuesData, isValidating: valuesValidating } = useSWR( const { data: valuesData, isValidating: valuesValidating } = useSWR(
currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null, currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null,
@ -42,13 +47,13 @@ const ObjectParameters = () => {
sortedParams.map((param: IObjectParam) => { sortedParams.map((param: IObjectParam) => {
if (param.date_po == null) { if (param.date_po == null) {
return ( return (
<ObjectParameter key={id_param} param={param} showLabel={false} /> <ObjectParameter map_id={map_id} key={id_param} param={param} showLabel={false} />
) )
} }
} }
) )
) : ( ) : (
<ObjectParameter key={id_param} param={sortedParams[0]} /> <ObjectParameter map_id={map_id} key={id_param} param={sortedParams[0]} />
); );
}) })
} }

View File

@ -9,12 +9,14 @@ interface ITCBParameterProps {
vtable: string; vtable: string;
inactive?: boolean; inactive?: boolean;
name: string; name: string;
map_id: string;
} }
const TCBParameter = ({ const TCBParameter = ({
value, value,
vtable, vtable,
name name,
map_id
}: ITCBParameterProps) => { }: ITCBParameterProps) => {
//Get value //Get value
@ -28,6 +30,8 @@ const TCBParameter = ({
) )
const tables = [ const tables = [
'BoilersTemper',
'FuelsParametrs',
'PipesTypes', 'PipesTypes',
'vAddRepairEvent', 'vAddRepairEvent',
'vBoilers', 'vBoilers',
@ -80,7 +84,7 @@ const TCBParameter = ({
const TCBValue = (vtable: string) => { const TCBValue = (vtable: string) => {
if (tables.includes(vtable)) { if (tables.includes(vtable)) {
return ( return (
<TableValue value={tcbValue?.id} name={name} type='select' vtable={vtable} /> <TableValue map_id={map_id} value={tcbValue?.id} name={name} type='select' vtable={vtable} />
) )
} else { } else {
return ( return (

View File

@ -10,6 +10,7 @@ interface TableValueProps {
type: 'value' | 'boolean' | 'number' | 'select' | 'string'; type: 'value' | 'boolean' | 'number' | 'select' | 'string';
unit?: string | null | undefined; unit?: string | null | undefined;
vtable?: string; vtable?: string;
map_id: string;
} }
const TableValue = ({ const TableValue = ({
@ -17,10 +18,10 @@ const TableValue = ({
value, value,
type, type,
unit, unit,
vtable vtable,
map_id
}: TableValueProps) => { }: TableValueProps) => {
const { selectedDistrict } = useObjectsStore() const { selectedDistrict } = useObjectsStore().id[map_id]
//Get available values //Get available values
const { data: tcbAll, isValidating } = useSWR( const { data: tcbAll, isValidating } = useSWR(
type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null, type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null,
@ -56,10 +57,10 @@ const TableValue = ({
/> />
: :
type === 'select' && !isValidating && tcbAll ? type === 'select' && !isValidating && tcbAll ?
<Select size='xs' data={tcbAll} defaultValue={JSON.stringify(value)} /> <Select size='xs' data={tcbAll} value={JSON.stringify(value)} />
: :
type === 'string' ? type === 'string' ?
<Textarea size='xs' defaultValue={value as string} autosize minRows={1} /> <Textarea size='xs' value={value as string} autosize minRows={1} />
: :
<Text size='xs'>{value as string}</Text> <Text size='xs'>{value as string}</Text>
} }

View File

@ -31,9 +31,9 @@ const TabsPane = ({
</ScrollAreaAutosize> </ScrollAreaAutosize>
<ScrollAreaAutosize h='100%' offsetScrollbars p='xs'> <ScrollAreaAutosize h='100%' offsetScrollbars>
{tabs.map(tab => ( {tabs.map(tab => (
<Tabs.Panel key={tab.value} value={tab.value}> <Tabs.Panel p='xs' key={tab.value} value={tab.value}>
{tab.view} {tab.view}
</Tabs.Panel> </Tabs.Panel>
))} ))}

View File

@ -1,34 +1,35 @@
import { Coordinate, distance, rotate } from "ol/coordinate"; import { Coordinate, distance, rotate } from "ol/coordinate";
import { containsExtent, Extent, getCenter, getHeight, getWidth } from "ol/extent"; import { Extent, getCenter, getHeight, getWidth } from "ol/extent";
import Feature from "ol/Feature"; import Feature from "ol/Feature";
import GeoJSON from "ol/format/GeoJSON"; import GeoJSON from "ol/format/GeoJSON";
import { Circle, Geometry, LineString, Polygon, SimpleGeometry } from "ol/geom"; import { Circle, Geometry, LineString, Point, Polygon, SimpleGeometry } from "ol/geom";
import VectorLayer from "ol/layer/Vector"; import VectorLayer from "ol/layer/Vector";
import VectorImageLayer from "ol/layer/VectorImage";
import Map from "ol/Map";
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj"; import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
import VectorSource from "ol/source/Vector";
import proj4 from "proj4"; import proj4 from "proj4";
import { firstStyleFunction, fourthStyleFunction, selectStyle, styleFunction, thirdStyleFunction } from "./MapStyles";
import { Type } from "ol/geom/Geometry"; import { Type } from "ol/geom/Geometry";
import { Draw, Modify, Snap, Translate } from "ol/interaction"; import { Draw, Modify, Snap, Translate } from "ol/interaction";
import { noModifierKeys } from "ol/events/condition"; import { never, noModifierKeys, platformModifierKeyOnly, primaryAction } from "ol/events/condition";
import { IGeometryType, IRectCoords } from "../../interfaces/map"; import { IGeometryType } from "../../interfaces/map";
import { uploadCoordinates } from "../../actions/map"; import { uploadCoordinates } from "../../actions/map";
import { ImageStatic } from "ol/source"; import { ImageStatic } from "ol/source";
import ImageLayer from "ol/layer/Image";
import { IFigure, ILine } from "../../interfaces/gis"; import { IFigure, ILine } from "../../interfaces/gis";
import { fromCircle, fromExtent } from "ol/geom/Polygon"; import { fromCircle, fromExtent } from "ol/geom/Polygon";
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles"; import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint, setStatusText } from "../../store/map"; import { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, PrintOrientation, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
import { MutableRefObject } from "react"; import Collection from "ol/Collection";
import { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects"; import { SketchCoordType } from "ol/interaction/Draw";
const calculateAngle = (coords: [number, number][]) => {
const [start, end] = coords;
const dx = end[0] - start[0];
const dy = end[1] - start[1];
return Math.atan2(dy, dx); // Angle in radians
}
export function processLine( export function processLine(
line: ILine, line: ILine,
scaling: number, scaling: number,
mapCenter: Coordinate, mapCenter: Coordinate
linesLayer: MutableRefObject<VectorLayer<VectorSource>>
) { ) {
const x1 = line.x1 * scaling const x1 = line.x1 * scaling
const y1 = line.y1 * scaling const y1 = line.y1 * scaling
@ -37,27 +38,30 @@ export function processLine(
const center = [mapCenter[0], mapCenter[1]] const center = [mapCenter[0], mapCenter[1]]
const testCoords = [ const testCoords: [number, number][] = [
[center[0] + x1, center[1] - y1], [center[0] + x1, center[1] - y1],
[center[0] + x2, center[1] - y2], [center[0] + x2, center[1] - y2],
] ]
const feature = new Feature(new LineString(testCoords)) const geometry = new LineString(testCoords)
feature.setStyle(styleFunction(feature))
feature.set('type', line.type) return {
feature.set('geometry_type', 'line') type: "Feature",
feature.set('planning', line.planning) geometry: new GeoJSON().writeGeometryObject(geometry),
feature.set('object_id', line.object_id) properties: {
type: line.type,
linesLayer.current?.getSource()?.addFeature(feature) geometry_type: 'line',
object_id: line.object_id,
planning: line.planning,
rotation: calculateAngle(testCoords)
}
}
} }
export function processFigure( export function processFigure(
figure: IFigure, figure: IFigure,
scaling: number, scaling: number,
mapCenter: Coordinate, mapCenter: Coordinate,
figuresLayer: MutableRefObject<VectorLayer<VectorSource>>
) { ) {
if (figure.figure_type_id == 1) { if (figure.figure_type_id == 1) {
const width = figure.width * scaling const width = figure.width * scaling
@ -75,13 +79,15 @@ export function processFigure(
const ellipseGeom = fromCircle(circleGeom, 64) const ellipseGeom = fromCircle(circleGeom, 64)
ellipseGeom.scale(1, height / width) ellipseGeom.scale(1, height / width)
const feature = new Feature(ellipseGeom) return {
type: "Feature",
feature.setStyle(firstStyleFunction(feature)) geometry: new GeoJSON().writeGeometryObject(ellipseGeom),
feature.set('type', figure.type) properties: {
feature.set('object_id', figure.object_id) type: figure.type,
feature.set('planning', figure.planning) object_id: figure.object_id,
figuresLayer.current?.getSource()?.addFeature(feature) planning: figure.planning
}
}
} }
if (figure.figure_type_id == 3) { if (figure.figure_type_id == 3) {
@ -101,15 +107,15 @@ export function processFigure(
if (coords) { if (coords) {
const polygon = new Polygon([coords]) const polygon = new Polygon([coords])
const feature = new Feature({ return {
geometry: polygon type: "Feature",
}) geometry: new GeoJSON().writeGeometryObject(polygon),
properties: {
feature.set('object_id', figure.object_id) type: figure.type,
feature.set('planning', figure.planning) object_id: figure.object_id,
feature.set('type', figure.type) planning: figure.planning
feature.setStyle(thirdStyleFunction(feature)) }
figuresLayer.current?.getSource()?.addFeature(feature) }
} }
} }
@ -135,23 +141,179 @@ export function processFigure(
const geometry1 = new Polygon([testCoords]) const geometry1 = new Polygon([testCoords])
const anchor1 = center const anchor1 = center
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1) geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
const feature1 = new Feature(geometry1)
feature1.set('object_id', figure.object_id) return {
feature1.set('planning', figure.planning) type: "Feature",
feature1.set('type', figure.type) geometry: new GeoJSON().writeGeometryObject(geometry1),
feature1.set('angle', figure.angle) properties: {
feature1.setStyle(fourthStyleFunction(feature1)) type: figure.type,
figuresLayer.current?.getSource()?.addFeature(feature1) object_id: figure.object_id,
planning: figure.planning,
angle: figure.angle
}
}
}
}
export const handleImageDrop = (
event: React.DragEvent<HTMLDivElement>,
map_id: string,
) => {
event.preventDefault()
event.stopPropagation()
if (!event.dataTransfer?.files) return
const files = event.dataTransfer.files
if (files.length > 0) {
const file = files[0]
setFile(map_id, 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 = () => {
const map = getMap(map_id)
if (map) {
const center = map.getView().getCenter() || [0, 0];
const width = img.naturalWidth;
const height = img.naturalHeight;
const resolution = map.getView().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), });
const overlayLayerSource = getOverlayLayerSource(map_id)
// Add the polygon feature to the drawing layer source
overlayLayerSource.addFeature(polygonFeature);
const imageLayer = getImageLayer(map_id)
// Set up the initial image layer with the extent
imageLayer.setSource(new ImageStatic({ url: imageUrl, imageExtent: extent, }));
updateImageSource(map_id, imageUrl, polygonFeature)
//map.current.addLayer(imageLayer.current);
// Add interactions for translation and scaling
setTranslate(map_id, new Translate({
layers: [imageLayer],
features: new Collection([polygonFeature]),
}))
const defaultStyle = new Modify({ source: overlayLayerSource })
.getOverlay()
.getStyleFunction();
const modify = new Modify({
insertVertexCondition: never,
source: overlayLayerSource,
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?.getView()?.getResolution()
if (typeof res === 'number' && feature && defaultStyle) {
return defaultStyle(feature, res)
}
}
});
const translate = getTranslate(map_id)
if (translate) {
translate.on('translateend', () => updateImageSource(map_id, imageUrl, polygonFeature));
map.addInteraction(translate);
}
//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(map_id, imageUrl, polygonFeature)
})
map.addInteraction(modify);
}
};
};
reader.readAsDataURL(file);
}
} }
} }
// Function to update the image layer with a new source when extent changes // Function to update the image layer with a new source when extent changes
export const updateImageSource = ( export const updateImageSource = (
map_id: string,
imageUrl: string, imageUrl: string,
imageLayer: React.MutableRefObject<ImageLayer<ImageStatic>>, polygonFeature: Feature<Polygon>
polygonFeature: Feature<Polygon>,
setPolygonExtent: (value: React.SetStateAction<Extent | undefined>) => void,
setRectCoords: React.Dispatch<React.SetStateAction<IRectCoords | undefined>>
) => { ) => {
const newExtent = polygonFeature.getGeometry()?.getExtent(); const newExtent = polygonFeature.getGeometry()?.getExtent();
@ -160,14 +322,14 @@ export const updateImageSource = (
const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2] const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2]
const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3] const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3]
setRectCoords({ setRectCoords(map_id, {
bl: bottomLeft, bl: bottomLeft,
tl: topLeft, tl: topLeft,
tr: topRight, tr: topRight,
br: bottomRight br: bottomRight
}) })
setPolygonExtent(newExtent) setPolygonExtent(map_id, newExtent)
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) { if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight) const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
@ -175,55 +337,94 @@ export const updateImageSource = (
url: imageUrl, url: imageUrl,
imageExtent: originalExtent, imageExtent: originalExtent,
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent) projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
}); })
imageLayer.current.setSource(newImageSource); getImageLayer(map_id).setSource(newImageSource)
} }
}; };
export const fixedAspectRatioBox = (
coordinates: SketchCoordType,
geometry: SimpleGeometry | undefined,
orientation?: PrintOrientation
): SimpleGeometry => {
if (!Array.isArray(coordinates) || coordinates.length < 2) {
return geometry ?? new Polygon([]);
}
const [start, end] = coordinates as Coordinate[];
const minX = start[0];
const minY = start[1];
const maxX = end[0];
const width = maxX - minX;
const height = orientation === 'horizontal' ? Math.abs(width / Math.sqrt(2)) : Math.abs(width * Math.sqrt(2)); // Maintain 1:√2 ratio
const maxY = end[1] > minY ? minY + height : minY - height;
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 = ( export const addInteractions = (
drawingLayerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>, map_id: string
translate: React.MutableRefObject<Translate | null>,
draw: React.MutableRefObject<Draw | null>,
map: React.MutableRefObject<Map | null>,
snap: React.MutableRefObject<Snap | null>,
measureDraw: React.MutableRefObject<Draw | null>,
measureSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
measureModify: React.MutableRefObject<Modify>,
) => { ) => {
const currentTool = getCurrentTool() console.log("Adding interactions")
const clearPrevious = getMeasureClearPrevious() const currentTool = getCurrentTool(map_id)
const measureType = getMeasureType() const clearPrevious = getMeasureClearPrevious(map_id)
const tipPoint = getTipPoint() const measureType = getMeasureType(map_id)
const tipPoint = getTipPoint(map_id)
const map = getMap(map_id)
const measureModify = getMeasureModify(map_id)
if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') { if (currentTool !== 'Measure' && currentTool !== 'Mover' && currentTool !== 'Edit') {
draw.current = new Draw({ setDraw(map_id, new Draw({
source: drawingLayerSource.current, source: getDrawingLayerSource(map_id),
type: currentTool as Type, type: currentTool as Type,
condition: noModifierKeys condition: noModifierKeys
}) }))
draw.current.on('drawend', function (s) { const draw = getDraw(map_id)
console.log(s.feature.getGeometry()?.getType())
let type: IGeometryType = 'POLYGON'
switch (s.feature.getGeometry()?.getType()) { if (draw) {
case 'LineString': draw.on('drawend', function (s) {
type = 'LINE' console.log(s.feature.getGeometry()?.getType())
break let type: IGeometryType = 'POLYGON'
case 'Polygon':
type = 'POLYGON' switch (s.feature.getGeometry()?.getType()) {
break case 'LineString':
default: type = 'LINE'
type = 'POLYGON' break
break case 'Polygon':
type = 'POLYGON'
break
default:
type = 'POLYGON'
break
}
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[]
uploadCoordinates(coordinates, type)
})
map?.addInteraction(draw)
setSnap(map_id, new Snap({ source: getDrawingLayerSource(map_id) }))
const snap = getSnap(map_id)
if (snap) {
map?.addInteraction(snap)
} }
const coordinates = (s.feature.getGeometry() as SimpleGeometry).getCoordinates() as Coordinate[] }
uploadCoordinates(coordinates, type)
})
map?.current?.addInteraction(draw.current)
snap.current = new Snap({ source: drawingLayerSource.current })
map?.current?.addInteraction(snap.current)
} }
if (currentTool == 'Measure') { if (currentTool == 'Measure') {
@ -234,35 +435,45 @@ export const addInteractions = (
const idleTip = 'Кликните, чтобы начать измерение'; const idleTip = 'Кликните, чтобы начать измерение';
let tip = idleTip; let tip = idleTip;
measureDraw.current = new Draw({ setMeasureDraw(map_id, new Draw({
source: measureSource.current, source: getMeasureSource(map_id),
type: drawType, type: drawType,
style: function (feature) { style: function (feature) {
return measureStyleFunction(feature, drawType, tip); return measureStyleFunction(map_id, feature, drawType, tip);
}, },
}); }))
measureDraw.current.on('drawstart', function () {
if (clearPrevious) { const measureDraw = getMeasureDraw(map_id)
measureSource.current.clear();
} if (measureDraw) {
measureModify.current.setActive(false); measureDraw.on('drawstart', function () {
tip = activeTip; if (clearPrevious) {
}); getMeasureSource(map_id).clear();
measureDraw.current.on('drawend', function () { }
modifyStyle.setGeometry(tipPoint as Geometry); measureModify.setActive(false);
measureModify.current.setActive(true); tip = activeTip;
map.current?.once('pointermove', function () {
modifyStyle.setGeometry('');
}); });
tip = idleTip; measureDraw.on('drawend', function () {
}); modifyStyle.setGeometry(tipPoint as Geometry);
measureModify.current.setActive(true); measureModify.setActive(true);
map.current?.addInteraction(measureDraw.current); map?.once('pointermove', function () {
modifyStyle.setGeometry('');
});
tip = idleTip;
});
measureModify.setActive(true)
map?.addInteraction(measureDraw)
}
} }
if (currentTool == 'Mover') { if (currentTool == 'Mover') {
translate.current = new Translate() setTranslate(map_id, new Translate())
map?.current?.addInteraction(translate.current)
const translate = getTranslate(map_id)
if (translate) {
map?.addInteraction(translate)
}
} }
if (currentTool == 'Edit') { if (currentTool == 'Edit') {
@ -271,91 +482,12 @@ export const addInteractions = (
} }
} }
export function regionsInit( export const zoomToFeature = (map_id: string, feature: Feature) => {
map: React.MutableRefObject<Map | null>,
selectedRegion: React.MutableRefObject<Feature<Geometry> | null>,
regionsLayer: React.MutableRefObject<VectorImageLayer<Feature<Geometry>, VectorSource<Feature<Geometry>>>>,
) {
regionsLayer.current.once('change', function () {
if (getSelectedCity() === null || getSelectedYear() === null) {
const extent = regionsLayer.current.getSource()?.getExtent()
if (extent && !extent?.every(val => Math.abs(val) === Infinity)) {
map.current?.getView().fit(fromExtent(extent) as SimpleGeometry, { duration: 500, maxZoom: 18, padding: [60, 60, 60, 60] })
}
}
})
map.current?.on('click', function (e) {
if (selectedRegion.current !== null) {
selectedRegion.current = null
}
if (map.current) {
map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
if (layer === regionsLayer.current) {
selectedRegion.current = feature as Feature
// Zoom to the selected feature
zoomToFeature(map, selectedRegion.current)
if (feature.get('id')) {
setSelectedRegion(feature.get('id'))
}
return true
} else return false
});
}
})
// Show current selected region
map.current?.on('pointermove', function (e) {
if (selectedRegion.current !== null) {
selectedRegion.current.setStyle(undefined)
selectedRegion.current = null
}
if (map.current) {
map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
if (layer === regionsLayer.current) {
selectedRegion.current = feature as Feature
selectedRegion.current.setStyle(selectStyle)
if (feature.get('district')) {
setStatusText(feature.get('district'))
}
return true
} else return false
})
}
})
// Hide regions layer when fully visible
map.current?.on('moveend', function () {
const viewExtent = map.current?.getView().calculateExtent(map.current.getSize())
const features = regionsLayer.current.getSource()?.getFeatures()
let isViewCovered = false
features?.forEach((feature: Feature) => {
const featureExtent = feature?.getGeometry()?.getExtent()
if (viewExtent && featureExtent) {
if (containsExtent(featureExtent, viewExtent)) {
isViewCovered = true
}
}
})
regionsLayer.current.setVisible(!isViewCovered)
})
}
const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature) => {
const geometry = feature.getGeometry() const geometry = feature.getGeometry()
const extent = geometry?.getExtent() const extent = geometry?.getExtent()
if (map.current && extent) { if (getMap(map_id) && extent) {
map.current.getView().fit(extent, { getMap(map_id)?.getView().fit(extent, {
duration: 300, duration: 300,
maxZoom: 19, maxZoom: 19,
}) })
@ -363,8 +495,8 @@ const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature
} }
// Function to save features to localStorage // Function to save features to localStorage
export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource> | null>) => { export const saveFeatures = (map_id: string) => {
const features = layerRef.current?.getSource()?.getFeatures() const features = getDrawingLayerSource(map_id).getFeatures()
if (features && features.length > 0) { if (features && features.length > 0) {
const geoJSON = new GeoJSON() const geoJSON = new GeoJSON()
const featuresJSON = geoJSON.writeFeatures(features) const featuresJSON = geoJSON.writeFeatures(features)
@ -373,14 +505,14 @@ export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource
} }
// Function to load features from localStorage // Function to load features from localStorage
export const loadFeatures = (layerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>) => { export const loadFeatures = (map_id: string) => {
const savedFeatures = localStorage.getItem('savedFeatures') const savedFeatures = localStorage.getItem('savedFeatures')
if (savedFeatures) { if (savedFeatures) {
const geoJSON = new GeoJSON() const geoJSON = new GeoJSON()
const features = geoJSON.readFeatures(savedFeatures, { const features = geoJSON.readFeatures(savedFeatures, {
featureProjection: 'EPSG:4326', // Ensure the projection is correct featureProjection: 'EPSG:4326', // Ensure the projection is correct
}) })
layerSource.current?.addFeatures(features) // Add features to the vector source getDrawingLayerSource(map_id).addFeatures(features)
//drawingLayer.current?.getSource()?.changed() //drawingLayer.current?.getSource()?.changed()
} }
} }
@ -531,6 +663,25 @@ function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number)
return { tileX, tileY }; return { tileX, tileY };
} }
function calculateTransformations(alignPoints: Coordinate[]) {
const [P1, P2, P3, P4] = alignPoints;
// Translation vector (move P1 to P3)
const translation = [P3[0] - P1[0], P3[1] - P1[1]];
// Scaling factor (distance between P3 and P4 divided by P1 and P2)
const distanceLayer = Math.sqrt((P2[0] - P1[0]) ** 2 + (P2[1] - P1[1]) ** 2);
const distanceMap = Math.sqrt((P4[0] - P3[0]) ** 2 + (P4[1] - P3[1]) ** 2);
const scale = distanceMap / distanceLayer;
// Rotation angle (difference in angles between the two lines)
const angleLayer = Math.atan2(P2[1] - P1[1], P2[0] - P1[0]);
const angleMap = Math.atan2(P4[1] - P3[1], P4[0] - P3[0]);
const rotation = angleMap - angleLayer;
return { translation, scale, rotation };
}
function calculateCenter(geometry: SimpleGeometry) { function calculateCenter(geometry: SimpleGeometry) {
let center, coordinates, minRadius; let center, coordinates, minRadius;
const type = geometry.getType(); const type = geometry.getType();
@ -574,8 +725,41 @@ function calculateCenter(geometry: SimpleGeometry) {
}; };
} }
function applyTransformations(
layer: VectorLayer,
transformations: {
translation: number[];
scale: number;
rotation: number;
},
origin: Coordinate
) {
const { translation, scale, rotation } = transformations;
const source = layer.getSource();
if (!source) return;
source.getFeatures().forEach((feature) => {
const geometry = feature.getGeometry();
if (geometry) {
// Translate
geometry.translate(translation[0], translation[1]);
// Scale (around the origin)
geometry.scale(scale, scale, origin);
// Rotate (around the origin)
geometry.rotate(rotation, origin);
}
});
console.log("Transformations applied to figuresLayer");
}
export { export {
rotateProjection, rotateProjection,
calculateTransformations,
calculateRotationAngle, calculateRotationAngle,
calculateExtent, calculateExtent,
calculateCentroid, calculateCentroid,
@ -583,5 +767,6 @@ export {
normalize, normalize,
getTileIndex, getTileIndex,
getGridCellPosition, getGridCellPosition,
calculateCenter calculateCenter,
applyTransformations
} }

View File

@ -21,7 +21,7 @@ interface Props {
} }
interface ViewerProps { interface ViewerProps {
url: string url: string | ArrayBuffer | Blob
} }
function PdfViewer({ function PdfViewer({
@ -82,7 +82,7 @@ function DocxViewer({
) )
} }
function ExcelViewer({ export function ExcelViewer({
url url
}: ViewerProps) { }: ViewerProps) {
const previewContainerRef = useRef(null) const previewContainerRef = useRef(null)
@ -120,7 +120,7 @@ function ImageViewer({
width: '100%', width: '100%',
height: '100%' height: '100%'
}}> }}>
<img alt='image-preview' src={url} style={{ <img alt='image-preview' src={url as string} style={{
display: 'flex', display: 'flex',
maxWidth: '100%', maxWidth: '100%',
maxHeight: '100%' maxHeight: '100%'

View File

@ -1,8 +1,7 @@
import { IconBuildingFactory2, IconComponents, IconDeviceDesktopAnalytics, IconFiles, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconTable, IconUsers } from "@tabler/icons-react"; import { IconBuildingFactory2, IconComponents, IconDeviceDesktopAnalytics, IconFiles, IconHome, IconLogin, IconLogin2, IconMap, IconPassword, IconReport, IconServer, IconSettings, IconShield, IconUsers } from "@tabler/icons-react";
import SignIn from "../pages/auth/SignIn"; import SignIn from "../pages/auth/SignIn";
import SignUp from "../pages/auth/SignUp"; import SignUp from "../pages/auth/SignUp";
import PasswordReset from "../pages/auth/PasswordReset"; import PasswordReset from "../pages/auth/PasswordReset";
import TableTest from "../pages/TableTest";
import ComponentTest from "../pages/ComponentTest"; import ComponentTest from "../pages/ComponentTest";
import MonitorPage from "../pages/MonitorPage"; import MonitorPage from "../pages/MonitorPage";
import Settings from "../pages/Settings"; import Settings from "../pages/Settings";
@ -14,6 +13,8 @@ import Reports from "../pages/Reports";
import Servers from "../pages/Servers"; import Servers from "../pages/Servers";
import Boilers from "../pages/Boilers"; import Boilers from "../pages/Boilers";
import MapTest from "../pages/MapTest"; import MapTest from "../pages/MapTest";
import PrintReport from "../pages/PrintReport";
import DBManager from "../pages/DBManager";
// Определение страниц с путями и компонентом для рендера // Определение страниц с путями и компонентом для рендера
@ -135,15 +136,6 @@ const pages = [
dashboard: true, dashboard: true,
enabled: false, enabled: false,
}, },
{
label: "Table test",
path: "/table-test",
icon: <IconTable />,
component: <TableTest />,
drawer: true,
dashboard: true,
enabled: false,
},
{ {
label: "Component test", label: "Component test",
path: "/component-test", path: "/component-test",
@ -153,6 +145,24 @@ const pages = [
dashboard: true, dashboard: true,
enabled: false, enabled: false,
}, },
{
label: "Print report test",
path: "/print-report-test",
icon: <IconComponents />,
component: <PrintReport />,
drawer: true,
dashboard: true,
enabled: false,
},
{
label: "Тест БД",
path: "/db-manager",
icon: <IconComponents />,
component: <DBManager />,
drawer: true,
dashboard: true,
enabled: true,
},
] ]
export { export {

View File

@ -0,0 +1,48 @@
export const schemas = [
'2018',
'2019',
'2020',
'2021',
'2022',
'2023',
'2024',
]
export const scaleOptions = [
{
label: '1:500000',
value: '500'
},
{
label: '1:100000',
value: '250'
},
{
label: '1:50000',
value: '50'
},
{
label: '1:25000',
value: '25'
},
{
label: '1:10000',
value: '10'
},
]
export const satMapsProviders = [
{ label: 'Google', value: 'google' },
{ label: 'Яндекс', value: 'yandex' },
{ label: 'Подложка', value: 'custom' },
{ label: 'Static', value: 'static' }
]
export const printDimensions = {
a0: [1189, 841],
a1: [841, 594],
a2: [594, 420],
a3: [420, 297],
a4: [297, 210],
a5: [210, 148],
}

View File

@ -1,3 +1,10 @@
import { Point } from "ol/geom";
import { ToolType } from "../types/tools";
import { SatelliteMapsProvider } from "./map";
import Map from "ol/Map";
import { Coordinate } from "ol/coordinate";
import { Mode } from "../store/map";
export interface IFigure { export interface IFigure {
object_id: string, object_id: string,
figure_type_id: number, figure_type_id: number,
@ -34,3 +41,57 @@ export interface ILine {
type: number, type: number,
planning: boolean planning: boolean
} }
export interface ICitySettings {
city_id: number;
image_width: number;
image_height: number;
scale: number;
offset_x: number;
offset_y: number;
rotation: number;
image_scale: number;
}
export interface TypeRole {
id: number;
sname: string;
r: number | null;
g: number | null;
b: number | null;
object_type_id: number;
object_role_id: number;
parent_type_id: string | null;
owner_role_ids: string | null;
necessary_params_ids: string | null;
vis_params_ids: string | null;
read_only_params_ids: string | null;
inactive_r: number | null;
inactive_g: number | null;
inactive_b: number | null;
color_group: number | null;
}
export interface CreateMapState {
currentTool: ToolType;
measureType: "LineString" | "Polygon";
measureShowSegments: boolean;
measureClearPrevious: boolean;
tipPoint: Point | null;
map: Map | null;
currentZ: number | undefined;
currentX: number | undefined;
currentY: number | undefined;
currentCoordinate: Coordinate | null;
statusText: string;
satMapsProvider: SatelliteMapsProvider;
selectedObjectType: number | null;
alignMode: boolean;
mode: Mode;
typeRoles: TypeRole[] | null;
setCurrentCoordinate: (currentCoordinate: Coordinate | null) => void;
setCurrentZ: (currentZ: number | undefined) => void;
setCurrentX: (currentX: number | undefined) => void;
setCurrentY: (currentY: number | undefined) => void;
setStatusText: (statusText: string) => void;
}

View File

@ -9,7 +9,7 @@ import { pages } from '../constants/app';
function DashboardLayout() { function DashboardLayout() {
const [mobileOpened, { toggle: toggleMobile }] = useDisclosure() const [mobileOpened, { toggle: toggleMobile }] = useDisclosure()
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true) const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(false)
const navigate = useNavigate() const navigate = useNavigate()
const getPageTitle = () => { const getPageTitle = () => {
@ -50,7 +50,7 @@ function DashboardLayout() {
</Group> </Group>
<Group w='auto'> <Group w='auto'>
{getPageTitle()} <Text fw='600'>{getPageTitle()}</Text>
</Group> </Group>
<Group id='header-portal' w='auto' ml='auto'> <Group id='header-portal' w='auto' ml='auto'>
@ -123,14 +123,20 @@ function DashboardLayout() {
leftSection={item.icon} leftSection={item.icon}
active={location.pathname === item.path} active={location.pathname === item.path}
style={{ textWrap: 'nowrap' }} style={{ textWrap: 'nowrap' }}
// styles={(theme, { active }) => ({
// root: {
// color: active ? theme.colors.blue[6] : theme.colors.dark[5],
// fontWeight: active ? "bold" : "normal",
// },
// leftSection: {
// color: active ? theme.colors.blue[6] : theme.colors.dark[5], // Icon color
// }
// })}
/> />
))} ))}
</AppShell.Navbar> </AppShell.Navbar>
<AppShell.Main> <AppShell.Main>
<Flex w={{ <Flex bg={colorScheme === 'dark' ? undefined : '#E8E8E8'} w={{ sm: desktopOpened ? 'calc(100% - 200px)' : 'calc(100% - 50px)', base: '100%' }} h={'calc(100% - 60px)'} style={{ transition: "width 0.2s ease" }} pos={'fixed'}>
sm: desktopOpened ? 'calc(100% - 200px)' : 'calc(100% - 50px)',
base: '100%'
}} h={'calc(100% - 60px)'} style={{ transition: "width 0.2s ease" }} pos={'fixed'}>
<Outlet /> <Outlet />
</Flex> </Flex>
</AppShell.Main> </AppShell.Main>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useBoilers } from '../hooks/swrHooks' import { useBoilers } from '../hooks/swrHooks'
import { Badge, ScrollAreaAutosize, Table, Text } from '@mantine/core' import { Stack, Text } from '@mantine/core'
import { IBoiler } from '../interfaces/fuel' import CustomTable from '../components/CustomTable'
function Boilers() { function Boilers() {
const [boilersPage, setBoilersPage] = useState(1) const [boilersPage, setBoilersPage] = useState(1)
@ -24,21 +24,43 @@ function Boilers() {
setBoilerSearch("") setBoilerSearch("")
}, []) }, [])
const boilersColumns = [
{ field: 'id_object', headerName: 'ID', type: "number" },
{ field: 'boiler_name', headerName: 'Название', type: "string", flex: 1 },
{ field: 'boiler_code', headerName: 'Код', type: "string", flex: 1 },
{ field: 'id_city', headerName: 'Город', type: "string", flex: 1 },
{ field: 'activity', headerName: 'Активен', type: "boolean", flex: 1 },
]
return ( return (
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'> <Stack w={'100%'} h={'100%'} p='sm'>
<Text size="xl" fw={600}> <Text size="xl" fw={600}>
Котельные Котельные
</Text> </Text>
{boilers && {boilers &&
<CustomTable data={boilers} columns={[
{
accessorKey: 'id_object',
header: 'ID',
cell: (info) => info.getValue(),
},
{
accessorKey: 'boiler_name',
header: 'Название',
cell: (info) => info.getValue(),
},
{
accessorKey: 'boiler_code',
header: 'Код',
cell: (info) => info.getValue(),
},
{
accessorKey: 'id_city',
header: 'Город',
cell: (info) => info.getValue(),
},
{
accessorKey: 'activity',
header: 'Активен',
cell: (info) => info.getValue(),
},
]} />
}
{/* {boilers &&
<Table highlightOnHover> <Table highlightOnHover>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
@ -77,8 +99,8 @@ function Boilers() {
))} ))}
</Table.Tbody> </Table.Tbody>
</Table> </Table>
} } */}
</ScrollAreaAutosize> </Stack>
) )
} }

View File

@ -0,0 +1,78 @@
import { Stack, Tabs } from '@mantine/core'
import useSWR from 'swr'
import { BASE_URL } from '../constants'
import { fetcher } from '../http/axiosInstance'
import { useState } from 'react'
import CustomTable from '../components/CustomTable'
const DBManager = () => {
const { data: tablesData } = useSWR(`/db/tables`, (key) => fetcher(key, BASE_URL.ems), {
revalidateOnFocus: false
})
return (
<Stack w={'100%'} h={'100%'} p='xs'>
{tablesData && Array.isArray(tablesData) && tablesData.length > 0 &&
<Tabs w='100%' h='80%'>
<Stack h='100%'>
<Tabs.List>
{tablesData.map(table => (
<Tabs.Tab key={table.tablename} value={table.tablename}>
{table.tablename}
</Tabs.Tab>
))}
</Tabs.List>
{tablesData.map(table => (
<Tabs.Panel h='100%' key={table.tablename} value={table.tablename} w='100%'>
<TableData tablename={table.tablename} />
</Tabs.Panel>
))}
</Stack>
</Tabs>
}
{/* <Card withBorder radius='sm'>
<Stack>
<Group justify='flex-start'>
<Text fw='bold'>Figures</Text>
</Group>
<Grid>
<Button>Import from New_Gis (erases data)</Button>
</Grid>
</Stack>
</Card> */}
</Stack>
)
}
const TableData = ({
tablename
}: { tablename: string }) => {
const [offset] = useState(0)
const [limit] = useState(20)
const { data: columnsData } = useSWR(`/db/columns/${tablename}`, (key) => fetcher(key, BASE_URL.ems), {
revalidateOnFocus: false
})
const { data: rowsData } = useSWR(columnsData ? `/db/rows/${tablename}?offset=${offset}&limit=${limit}` : null, (key) => fetcher(key, BASE_URL.ems), {
revalidateOnFocus: false
})
return (
<>
{columnsData && rowsData && Array.isArray(columnsData) && Array.isArray(rowsData) && columnsData.length > 0 &&
<CustomTable data={rowsData} columns={columnsData.map(column => (
{
accessorKey: column.column_name,
header: column.column_name,
cell: (info) => JSON.stringify(info.getValue()).length > 30 ? [JSON.stringify(info.getValue()).substring(0, 30), '...'].join('') : JSON.stringify(info.getValue()),
}
))} />
}
</>
)
}
export default DBManager

View File

@ -1,8 +1,81 @@
import { Container, Stack, Tabs } from '@mantine/core'
import MapComponent from '../components/map/MapComponent' import MapComponent from '../components/map/MapComponent'
import { useEffect } from 'react'
import { initializeObjectsState } from '../store/objects'
import { deleteMapTab, setCurrentTab, useAppStore } from '../store/app'
import { initializeMapState, useMapStore } from '../store/map'
import { v4 as uuidv4 } from 'uuid'
function MapTest() { function MapTest() {
const { mapTab, currentTab } = useAppStore()
const { id } = useMapStore()
const tabs = [
{
id: uuidv4(),
year: 2023,
region: 11,
district: 145,
},
// {
// id: uuidv4(),
// year: 2023,
// region: 11,
// district: 146,
// },
]
useEffect(() => {
tabs.map(tab => useAppStore.setState((state) => {
initializeObjectsState(tab.id, tab.region, tab.district, null, tab.year)
initializeMapState(tab.id)
return {
mapTab: {
...state.mapTab,
[tab.id]: {
year: tab.year,
region: tab.region,
district: tab.district
}
}
}
}))
setCurrentTab(tabs[0].id)
return () => {
tabs.map(tab => deleteMapTab(tab.id))
}
}, [])
return ( return (
<MapComponent /> <Container fluid w='100%' pos='relative' p={0}>
<Tabs h='100%' variant='default' value={currentTab} onChange={setCurrentTab} keepMounted={true}>
<Stack gap={0} h='100%'>
<Tabs.List>
{Object.entries(mapTab).map(([key]) => (
<Tabs.Tab value={key} key={key}>
{id[key]?.mapLabel}
</Tabs.Tab>
))}
</Tabs.List>
{Object.entries(mapTab).map(([key]) => (
<Tabs.Panel value={key} key={key} h='100%' pos='relative'>
<MapComponent
key={key}
id={key}
active={currentTab === key}
/>
</Tabs.Panel>
))}
</Stack>
</Tabs>
</Container>
) )
} }

File diff suppressed because it is too large Load Diff

View File

@ -93,11 +93,11 @@ export default function Reports() {
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{[...new Set(Object.keys(report).flatMap(key => Object.keys(report[key])))].map(id => { {[...new Set(Object.keys(report).flatMap(key => Object.keys(report[key])))].map(id => {
const row: any = { id: Number(id) }; const row: Record<string, unknown> = { id: Number(id) };
Object.keys(report).forEach(key => { Object.keys(report).forEach(key => {
row[key] = report[key][id]; row[key] = report[key][id];
}); });
return (<Table.Tr key={row.id}> return (<Table.Tr key={row.id as number}>
{[ {[
{ field: 'id', headerName: '№', width: 70 }, { field: 'id', headerName: '№', width: 70 },
...Object.keys(report).map(key => ({ ...Object.keys(report).map(key => ({
@ -125,7 +125,7 @@ export default function Reports() {
) )
} }
return ( return (
<Table.Td key={`${row.id}-${column.headerName}`}>{row[column.field]}</Table.Td> <Table.Td key={`${row.id}-${column.headerName}`}>{row[column.field] as string}</Table.Td>
) )
})} })}
</Table.Tr>) </Table.Tr>)

View File

@ -1,63 +1,42 @@
import { useRoles } from '../hooks/swrHooks' import { useRoles } from '../hooks/swrHooks'
import { CreateField } from '../interfaces/create' import { CreateField } from '../interfaces/create'
import RoleService from '../services/RoleService' import RoleService from '../services/RoleService'
import FormFields from '../components/FormFields' import { Loader, Stack } from '@mantine/core'
import { Button, Loader, Modal, ScrollAreaAutosize, Table } from '@mantine/core' import CustomTable from '../components/CustomTable'
import { useDisclosure } from '@mantine/hooks'
import { IRole } from '../interfaces/role'
export default function Roles() { export default function Roles() {
const { roles, isError, isLoading } = useRoles() const { roles, isError, isLoading } = useRoles()
const [opened, { open, close }] = useDisclosure(false);
const createFields: CreateField[] = [ const createFields: CreateField[] = [
{ key: 'name', headerName: 'Название', type: 'string', required: true, defaultValue: '' }, { key: 'name', headerName: 'Название', type: 'string', required: true, defaultValue: '' },
{ key: 'description', headerName: 'Описание', type: 'string', required: false, defaultValue: '' }, { key: 'description', headerName: 'Описание', type: 'string', required: false, defaultValue: '' },
] ]
const columns = [
{ field: 'id', headerName: 'ID', type: "number" },
{ field: 'name', headerName: 'Название', flex: 1, editable: true },
{ field: 'description', headerName: 'Описание', flex: 1, editable: true },
];
if (isError) return <div>Произошла ошибка при получении данных.</div> if (isError) return <div>Произошла ошибка при получении данных.</div>
if (isLoading) return <Loader /> if (isLoading) return <Loader />
return ( return (
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'> <Stack w={'100%'} h={'100%'} p='sm'>
<Button onClick={open}> <CustomTable
Добавить роль createFields={createFields}
</Button> submitHandler={RoleService.createRole}
data={roles} columns={[
<Modal opened={opened} onClose={close} title="Создание роли" centered> {
<FormFields accessorKey: 'id',
fields={createFields} header: 'id',
submitHandler={RoleService.createRole} cell: (info) => info.getValue(),
/> },
</Modal> {
accessorKey: 'name',
<Table highlightOnHover> header: 'Название',
<Table.Thead> cell: (info) => info.getValue(),
<Table.Tr> },
{columns.map(column => ( {
<Table.Th key={column.field}>{column.headerName}</Table.Th> accessorKey: 'description',
))} header: 'Описание',
</Table.Tr> cell: (info) => info.getValue(),
</Table.Thead> },
<Table.Tbody> ]} />
{roles.map((role: IRole) => ( </Stack>
<Table.Tr
key={role.id}
>
{columns.map(column => (
<Table.Td key={column.field}>{role[column.field as keyof IRole]}</Table.Td>
))}
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollAreaAutosize>
) )
} }

View File

@ -1,13 +0,0 @@
import { Flex } from '@mantine/core';
import CustomTable from '../components/CustomTable';
function TableTest() {
return (
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
<CustomTable />
</Flex>
)
}
export default TableTest

View File

@ -3,10 +3,8 @@ import { IRole } from "../interfaces/role"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { CreateField } from "../interfaces/create" import { CreateField } from "../interfaces/create"
import UserService from "../services/UserService" import UserService from "../services/UserService"
import FormFields from "../components/FormFields" import { Flex, Loader, Stack } from "@mantine/core"
import { Badge, Button, Flex, Loader, Modal, Pagination, ScrollAreaAutosize, Select, Table } from "@mantine/core" import CustomTable from "../components/CustomTable"
import { useDisclosure } from "@mantine/hooks"
import { IUser } from "../interfaces/user"
export default function Users() { export default function Users() {
const { users, isError, isLoading } = useUsers() const { users, isError, isLoading } = useUsers()
@ -21,8 +19,6 @@ export default function Users() {
} }
}, [roles]) }, [roles])
const [opened, { open, close }] = useDisclosure(false);
const createFields: CreateField[] = [ const createFields: CreateField[] = [
{ key: 'email', headerName: 'E-mail', type: 'string', required: true, defaultValue: '' }, { key: 'email', headerName: 'E-mail', type: 'string', required: true, defaultValue: '' },
{ key: 'login', headerName: 'Логин', type: 'string', required: true, defaultValue: '' }, { key: 'login', headerName: 'Логин', type: 'string', required: true, defaultValue: '' },
@ -32,24 +28,6 @@ export default function Users() {
{ key: 'password', headerName: 'Пароль', type: 'string', required: true, defaultValue: '' }, { key: 'password', headerName: 'Пароль', type: 'string', required: true, defaultValue: '' },
] ]
const columns = [
{ field: 'id', headerName: 'ID', type: "number", flex: 1 },
{ field: 'email', headerName: 'Email', flex: 1, editable: true },
{ field: 'login', headerName: 'Логин', flex: 1, editable: true },
{ field: 'phone', headerName: 'Телефон', flex: 1, editable: true },
{ field: 'name', headerName: 'Имя', flex: 1, editable: true },
{ field: 'surname', headerName: 'Фамилия', flex: 1, editable: true },
{ field: 'is_active', headerName: 'Статус', type: "boolean", flex: 1, editable: true },
{
field: 'role_id',
headerName: 'Роль',
valueOptions: roles ? roles.map((role: IRole) => ({ label: role.name, value: role.id })) : [],
type: 'singleSelect',
flex: 1,
editable: true
},
];
if (isError) return ( if (isError) return (
<div> <div>
Произошла ошибка при получении данных. Произошла ошибка при получении данных.
@ -65,97 +43,50 @@ export default function Users() {
} }
return ( return (
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'> <Stack w={'100%'} h={'100%'} p='xs'>
<Button onClick={open}>
Добавить пользователя
</Button>
<Modal opened={opened} onClose={close} title="Регистрация пользователя" centered>
<FormFields
fields={createFields}
submitHandler={UserService.createUser}
/>
</Modal>
{Array.isArray(roleOptions) && {Array.isArray(roleOptions) &&
<Table highlightOnHover> <CustomTable
<Table.Thead> createFields={createFields}
<Table.Tr> submitHandler={UserService.createUser}
{columns.map(column => ( data={users}
<Table.Th key={column.field}>{column.headerName}</Table.Th> columns={[
))} {
</Table.Tr> accessorKey: 'email',
</Table.Thead> header: 'E-mail',
<Table.Tbody> cell: (info) => info.getValue(),
{users.map((user: IUser) => ( },
<Table.Tr {
key={user.id} accessorKey: 'login',
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined} header: 'Логин',
> cell: (info) => info.getValue(),
{columns.map(column => { },
if (column.field === 'is_active') { {
return ( accessorKey: 'phone',
user.is_active ? ( header: 'Телефон',
<Table.Td key={column.field}> cell: (info) => info.getValue(),
<Badge fullWidth variant="light"> },
Активен {
</Badge> accessorKey: 'name',
</Table.Td> header: 'Имя',
) : ( cell: (info) => info.getValue(),
<Table.Td key={column.field}> },
<Badge color="gray" fullWidth variant="light"> {
Отключен accessorKey: 'surname',
</Badge> header: 'Фамилия',
</Table.Td> cell: (info) => info.getValue(),
) },
) {
} accessorKey: 'is_active',
else if (column.field === 'role_id') { header: 'Активен',
return ( cell: (info) => info.getValue(),
<Table.Td key={column.field}> },
<Select {
data={roleOptions} accessorKey: 'role_id',
defaultValue={user.role_id.toString()} header: 'Роль',
variant="unstyled" cell: (info) => info.getValue(),
allowDeselect={false} }
/> ]} />
</Table.Td>
)
}
else return (
<Table.Td key={column.field}>{user[column.field as keyof IUser]}</Table.Td>
)
})}
</Table.Tr>
))}
</Table.Tbody>
</Table>
} }
</Stack>
<Pagination total={10} />
{/* <DataGrid
density="compact"
autoHeight
style={{ width: "100%" }}
rows={users}
columns={columns}
initialState={{
pagination: {
paginationModel: { page: 0, pageSize: 10 },
},
}}
pageSizeOptions={[10, 20, 50, 100]}
checkboxSelection
disableRowSelectionOnClick
processRowUpdate={(updatedRow) => {
return updatedRow
}}
onProcessRowUpdateError={() => {
}}
/> */}
</ScrollAreaAutosize>
) )
} }

View File

@ -0,0 +1,39 @@
import { AxiosRequestConfig } from "axios";
import axiosInstance from "../http/axiosInstance";
import { BASE_URL } from "../constants";
import { Extent } from "ol/extent";
import { IRectCoords } from "../interfaces/map";
const config: AxiosRequestConfig = {
baseURL: BASE_URL.ems
}
export default class GisService {
static async getAddress(limit?: number, page?: number) {
return await axiosInstance.get(`/general/address?limit=${limit || 10}&page=${page || 1}`, config)
}
static async uploadOverlay(file: File | null, polygonExtent: Extent | undefined, rectCoords: IRectCoords | undefined) {
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())
return await axiosInstance.post(`/tiles/upload`, formData, config)
}
}
//static async updateFigure(object_id: string, )
}

43
client/src/store/app.ts Normal file
View File

@ -0,0 +1,43 @@
import { create } from 'zustand';
export type Mode = 'edit' | 'view'
export interface AppState {
mapTab: Record<string, {
year: number | null,
region: number | null,
district: number | null
}>,
currentTab: string | null;
}
export const useAppStore = create<AppState>(() => ({
currentTab: null,
mapTab: {}
}))
const getCurrentTab = () => useAppStore.getState().currentTab
const setCurrentTab = (id: string | null) => useAppStore.setState(() => ({ currentTab: id }))
const setMapTabYear = (id: string, year: number | null) =>
useAppStore.setState((state) => {
return {
mapTab: {
...state.mapTab,
[id]: { ...state.mapTab[id], year: year }
}
}
})
const deleteMapTab = (id: string) =>
useAppStore.setState((state) => {
const { [id]: _, ...remainingTabs } = state.mapTab;
return { mapTab: remainingTabs };
})
export {
deleteMapTab,
getCurrentTab,
setCurrentTab,
setMapTabYear
}

View File

@ -1,115 +1,782 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { ToolType } from '../types/tools'; import { ToolType } from '../types/tools';
import { Point } from 'ol/geom'; import { Geometry, Point } from 'ol/geom';
import Map from 'ol/Map'; import Map from 'ol/Map';
import { Coordinate } from 'ol/coordinate'; import { Coordinate } from 'ol/coordinate';
import { SatelliteMapsProvider } from '../interfaces/map'; import { IRectCoords, SatelliteMapsProvider } from '../interfaces/map';
import { TypeRole } from '../interfaces/gis';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import { containsExtent, Extent } from 'ol/extent';
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction';
import TileLayer from 'ol/layer/Tile';
import { v4 as uuidv4 } from 'uuid';
import { googleMapsSatelliteSource } from '../components/map/MapSources';
import VectorLayer from 'ol/layer/Vector';
import { ImageStatic, OSM } from 'ol/source';
import ImageLayer from 'ol/layer/Image';
import { drawingLayerStyle, figureStyle, highlightStyleRed, highlightStyleYellow, lineStyle, overlayStyle, regionsLayerStyle, selectStyle } from '../components/map/MapStyles';
import { Fill, Stroke, Style } from 'ol/style';
import { VectorImage } from 'ol/layer';
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, fixedAspectRatioBox, zoomToFeature } from '../components/map/mapUtils';
import { setCurrentObjectId, setSelectedRegion } from './objects';
import View from 'ol/View';
import { getPrintOrientation } from './print';
export type Mode = 'edit' | 'view' | 'print'
export type PrintScale = '500' | '250' | '50' | '25' | '10'
export type PrintOrientation = 'horizontal' | 'vertical'
interface MapState { interface MapState {
currentTool: ToolType; id: Record<string, {
measureType: "LineString" | "Polygon"; currentTool: ToolType;
measureShowSegments: boolean; measureType: "LineString" | "Polygon";
measureClearPrevious: boolean; measureShowSegments: boolean;
tipPoint: Point | null; measureClearPrevious: boolean;
map: Map | null; tipPoint: Point | null;
currentZ: number | undefined; map: Map | null;
currentX: number | undefined; currentZ: number | undefined;
currentY: number | undefined; currentX: number | undefined;
currentCoordinate: Coordinate | null; currentY: number | undefined;
statusText: string; currentCoordinate: Coordinate | null;
satMapsProvider: SatelliteMapsProvider; statusText: string;
selectedObjectType: number | null; satMapsProvider: SatelliteMapsProvider;
selectedObjectType: number | null;
alignMode: boolean;
mode: Mode;
typeRoles: TypeRole[] | null;
mapLabel: string;
file: File | null;
measureSource: VectorSource<Feature<Geometry>>;
measureDraw: Draw | null;
polygonExtent: Extent | undefined;
rectCoords: IRectCoords | undefined;
alignPoints: Coordinate[];
alignModeLayer: VectorLayer;
drawingLayer: VectorLayer;
drawingLayerSource: VectorSource;
draw: Draw | null;
snap: Snap | null;
translate: Translate | null;
overlayLayerSource: VectorSource;
satLayer: TileLayer;
nodeLayer: VectorLayer;
nodeLayerSource: VectorSource;
staticMapLayer: ImageLayer<ImageStatic>;
figuresLayer: VectorLayer<VectorSource>;
linesLayer: VectorLayer<VectorSource>;
regionsLayer: VectorImage;
citiesLayer: VectorLayer;
districtBoundLayer: VectorImage;
imageLayer: ImageLayer<ImageStatic>;
selectedArea: Feature | null;
baseLayer: TileLayer;
measureLayer: VectorLayer;
measureModify: Modify;
overlayLayer: VectorLayer;
regionSelect: Select;
lineSelect: Select;
previousView: View | undefined | null;
printArea: Extent | null;
printLayer: VectorLayer;
printSource: VectorSource;
printAreaDraw: Draw;
printPreviewSize: number[];
printDim: 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5';
printRes: number;
printOrientation: PrintOrientation;
printScale: PrintScale;
printScaleLine: boolean;
}>;
} }
export const useMapStore = create<MapState>(() => ({ export const useMapStore = create<MapState>(() => ({
currentTool: null, id: {}
measureType: "LineString", }))
measureShowSegments: true,
measureClearPrevious: true,
tipPoint: null,
map: null,
currentZ: undefined,
currentX: undefined,
currentY: undefined,
currentCoordinate: null,
statusText: '',
satMapsProvider: 'google',
selectedObjectType: null,
}));
const setCurrentZ = (z: number | undefined) => useMapStore.setState(() => ({ currentZ: z })) export const initializeMapState = (
const setCurrentX = (x: number | undefined) => useMapStore.setState(() => ({ currentX: x })) id: string
const setCurrentY = (y: number | undefined) => useMapStore.setState(() => ({ currentY: y })) ) => {
const setCurrentCoordinate = (c: Coordinate | null) => useMapStore.setState(() => ({ currentCoordinate: c })) useMapStore.setState((state) => {
const setStatusText = (t: string) => useMapStore.setState(() => ({ statusText: t })) const hitTolerance = 10
const setSatMapsProvider = (p: SatelliteMapsProvider) => useMapStore.setState(() => ({ satMapsProvider: p }))
const setSelectedObjectType = (t: number | null) => useMapStore.setState(() => ({ selectedObjectType: t }))
const setMap = (m: Map | null) => useMapStore.setState(() => ({ map: m }))
const setTipPoint = (tipPoint: Point | null) => { const baseLayer = new TileLayer({ source: new OSM(), properties: { id: uuidv4(), name: 'OpenStreetMap' } })
useMapStore.setState(() => ({ tipPoint: tipPoint }))
const satLayer = new TileLayer({ source: googleMapsSatelliteSource, visible: false, properties: { id: uuidv4(), name: 'Спутник' } })
const staticMapLayer = new ImageLayer<ImageStatic>({ properties: { id: uuidv4(), name: 'Подложка' } })
// Region select
const regionSelect = new Select({ condition: pointerMove, style: selectStyle, layers: (layer) => layer.get('type') === 'region' })
// Line select
const lineSelect = new Select({ condition: click, style: highlightStyleRed, hitTolerance: hitTolerance, layers: (layer) => layer.get('type') === 'line', })
lineSelect.on('select', (e) => {
if (e.selected[0]) {
setCurrentObjectId(id, e.selected[0].get('object_id'))
}
})
const lineHover = new Select({ condition: pointerMove, style: highlightStyleYellow, hitTolerance: hitTolerance, layers: (layer) => layer.get('type') === 'line', })
// Figure select
const figureSelect = new Select({ condition: click, style: highlightStyleRed, hitTolerance: hitTolerance, layers: (layer) => layer.get('type') === 'figure' })
figureSelect.on('select', (e) => {
if (e.selected[0]) {
setCurrentObjectId(id, e.selected[0].get('object_id'))
}
})
const figureHover = new Select({ condition: pointerMove, style: highlightStyleYellow, hitTolerance: hitTolerance, layers: (layer) => layer.get('type') === 'figure', })
const measureSource = new VectorSource()
const measureLayer = new VectorLayer({
source: measureSource,
style: (feature) => measureStyleFunction(id, feature),
properties: { id: uuidv4(), type: 'measure', name: 'Линейка' }
})
const measureModify = new Modify({
source: measureSource,
style: modifyStyle
})
const nodeLayerSource = new VectorSource()
const nodeLayer = new VectorLayer({
source: nodeLayerSource,
style: drawingLayerStyle,
properties: {
id: uuidv4(),
name: 'Узлы'
}
})
const overlayLayerSource = new VectorSource()
const overlayLayer = new VectorLayer({
source: overlayLayerSource,
style: overlayStyle,
properties: {
id: uuidv4(),
name: 'Наложения'
}
})
const drawingLayerSource = new VectorSource()
const drawingLayer = new VectorLayer({
source: drawingLayerSource
})
const regionsLayer = new VectorImage({
source: new VectorSource(),
declutter: true,
style: regionsLayerStyle,
properties: {
id: uuidv4(),
name: 'Регион',
type: 'region'
}
})
const districtBoundLayer = new VectorImage({ style: new Style({ stroke: new Stroke({ color: 'red', width: 2 }), }) })
const citiesLayer = new VectorLayer({ source: new VectorSource(), properties: { id: uuidv4(), name: 'Города' } })
const linesLayer = new VectorLayer({
source: new VectorSource(),
declutter: true,
properties: { id: uuidv4(), type: 'line', name: 'Линии' },
style: function (feature) {
const objectType = feature.get('type')
const typeRole = getTypeRoles(id)?.find((el) => el.id.toString() === String(objectType))
if (typeRole) {
lineStyle.setStroke(new Stroke({
color: `rgb(${typeRole.r},${typeRole.g},${typeRole.b})`
}))
}
//lineStyle.getText()?.setText('11,4')//(feature.get('object_id'))
lineStyle.getText()?.setRotation(feature.get('rotation'))
lineStyle.getText()?.setOffsetY(-8)
return lineStyle
},
})
const figuresLayer = new VectorLayer({
source: new VectorSource(),
declutter: true,
properties: { id: uuidv4(), type: 'figure', name: 'Фигуры' },
style: function (feature) {
const objectType = feature.get('type')
const typeRole = getTypeRoles(id)?.find((el) => el.id.toString() === String(objectType))
if (typeRole) {
figureStyle.setFill(new Fill({
color: `rgb(${typeRole.r},${typeRole.g},${typeRole.b})`
}))
}
figureStyle.getText()?.setText(feature.get('object_id'))
return figureStyle
}
})
const imageLayer = new ImageLayer<ImageStatic>({ properties: { id: uuidv4(), name: 'Изображение' } })
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',
style: (feature) => {
return new Style({
fill: new Fill({
color: "rgba(0, 0, 255, 0.3)", // Semi-transparent blue fill
}),
stroke: new Stroke({
color: "blue",
width: 2,
}),
image: undefined, // 🚀 This removes the default point!
});
},
geometryFunction: function (coords, geom) {
const printOrientation = getPrintOrientation()
return fixedAspectRatioBox(coords, geom, printOrientation)
},
})
printAreaDraw.on('drawend', (e) => {
const extent = e.feature.getGeometry()?.getExtent()
if (extent) {
setPrintArea(id, extent)
}
})
const map = new Map({
controls: [],
layers: [
baseLayer,
satLayer,
staticMapLayer,
regionsLayer,
districtBoundLayer,
citiesLayer,
linesLayer,
figuresLayer,
drawingLayer,
imageLayer,
overlayLayer,
nodeLayer,
measureLayer,
alignModeLayer,
printLayer
]
})
map.addInteraction(regionSelect)
map.addInteraction(lineSelect)
map.addInteraction(lineHover)
map.addInteraction(figureSelect)
map.addInteraction(figureHover)
// map.on('pointermove', function (e: MapBrowserEvent<UIEvent>) {
// setCurrentCoordinate(id, e.coordinate)
// const currentExtent = get('EPSG:3857')?.getExtent() as Extent
// const { tileX, tileY } = getGridCellPosition(e.coordinate[0], e.coordinate[1], currentExtent, Number(map?.getView().getZoom()?.toFixed(0)))
// setCurrentZ(id, Number(map?.getView().getZoom()?.toFixed(0)))
// setCurrentX(id, tileX)
// setCurrentY(id, tileY)
// const pixel = map?.getEventPixel(e.originalEvent)
// if (pixel) {
// map?.forEachFeatureAtPixel(pixel, function (feature, layer) {
// if (layer.get('type') === 'region') {
// if (feature.get('entity_id')) {
// setStatusText(id, feature.get('entity_id'))
// }
// }
// })
// }
// })
map.on('click', function (e: MapBrowserEvent<UIEvent>) {
if (getAlignMode(id)) {
const alignPoints = getAlignPoints(id)
const alignModeLayer = getAlignModeLayer(id)
if (alignPoints.length < 4) {
//setAlignPoints(id, [...alignPoints, e.coordinate])
alignPoints.push(e.coordinate)
alignModeLayer.getSource()?.addFeature(new Feature(new Point(e.coordinate)))
if (alignPoints.length === 4) {
const transformations = calculateTransformations(alignPoints);
// Use the first map point (P3) as the origin for scaling and rotation
const origin = alignPoints[2];
applyTransformations(getFiguresLayer(id), transformations, origin);
applyTransformations(getLinesLayer(id), transformations, origin);
setAlignPoints(id, [])
alignModeLayer.getSource()?.clear()
}
}
} else {
const pixel = map?.getEventPixel(e.originalEvent)
if (pixel) {
map?.forEachFeatureAtPixel(pixel, function (feature, layer) {
if (layer.get('type') === 'region') {
console.log("clicked on region")
zoomToFeature(id, feature as Feature)
if (feature.get('entity_id')) {
setSelectedRegion(id, feature.get('entity_id'))
}
}
})
}
}
})
map.on('moveend', function () {
const viewExtent = map?.getView().calculateExtent(map.getSize())
const features = regionsLayer.getSource()?.getFeatures()
let isViewCovered = false
features?.forEach((feature: Feature) => {
const featureExtent = feature?.getGeometry()?.getExtent()
if (viewExtent && featureExtent) {
if (containsExtent(featureExtent, viewExtent)) {
isViewCovered = true
}
}
})
regionsLayer.setVisible(!isViewCovered)
})
map.setView(
new View({
center: transform([129.7466541, 62.083504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
zoom: 5,
maxZoom: 21,
//extent: mapExtent,
})
)
return {
id: {
...state.id,
[id]: {
currentTool: null,
measureType: "LineString",
measureShowSegments: true,
measureClearPrevious: true,
tipPoint: null,
map: map,
currentZ: undefined,
currentX: undefined,
currentY: undefined,
currentCoordinate: null,
statusText: '',
satMapsProvider: 'google',
selectedObjectType: null,
alignMode: false,
mode: 'view',
typeRoles: null,
mapLabel: 'Карта',
file: null,
measureSource: measureSource,
measureDraw: null,
polygonExtent: undefined,
rectCoords: undefined,
alignPoints: [],
alignModeLayer: alignModeLayer,
drawingLayer: drawingLayer,
drawingLayerSource: drawingLayerSource,
draw: null,
snap: null,
translate: null,
overlayLayerSource: overlayLayerSource,
satLayer: satLayer,
nodeLayerSource: nodeLayerSource,
staticMapLayer: staticMapLayer,
figuresLayer: figuresLayer,
linesLayer: linesLayer,
regionsLayer: regionsLayer,
citiesLayer: citiesLayer,
districtBoundLayer: districtBoundLayer,
imageLayer: imageLayer,
selectedArea: null,
baseLayer: baseLayer,
measureLayer: measureLayer,
measureModify: measureModify,
nodeLayer: nodeLayer,
overlayLayer: overlayLayer,
regionSelect: regionSelect,
lineSelect: lineSelect,
previousView: null,
printArea: null,
printLayer: printLayer,
printSource: printSource,
printAreaDraw: printAreaDraw,
printPreviewSize: [640, 320],
printDim: 'a4',
printOrientation: 'horizontal',
printRes: 72,
printScale: '250',
printScaleLine: true
}
}
}
})
} }
const getTipPoint = () => { export const setPrintOrientation = (id: string, orientation: PrintOrientation) => useMapStore.setState((state) => {
return useMapStore.getState().tipPoint return {
} id: {
...state.id,
[id]: { ...state.id[id], printOrientation: orientation }
}
}
})
const getMap = () => { export const setPrintScaleLine = (id: string, bool: boolean) => useMapStore.setState((state) => {
return useMapStore.getState().map return {
} id: {
...state.id,
[id]: { ...state.id[id], printScaleLine: bool }
}
}
})
const setMeasureType = (tool: "LineString" | "Polygon") => { export const setPrintScale = (id: string, printScale: PrintScale) => useMapStore.setState((state) => {
useMapStore.setState(() => ({ measureType: tool })) return {
} id: {
...state.id,
[id]: { ...state.id[id], printScale: printScale }
}
}
})
const getMeasureType = () => { export const setPreviousView = (id: string, view: View | undefined | null) => useMapStore.setState((state) => {
return useMapStore.getState().measureType return {
} id: {
...state.id,
[id]: { ...state.id[id], previousView: view }
}
}
})
const setCurrentTool = (tool: ToolType) => { export const clearPrintArea = (id: string) => useMapStore.setState((state) => {
tool === useMapStore.getState().currentTool state.id[id].printSource.clear()
? useMapStore.setState(() => ({ currentTool: null }))
: useMapStore.setState(() => ({ currentTool: tool }))
}
const getCurrentTool = () => { return {
return useMapStore.getState().currentTool id: {
} ...state.id,
[id]: { ...state.id[id], printArea: null }
}
}
})
const getMeasureShowSegments = () => { export const setPrintArea = (id: string, extent: Extent | null) => useMapStore.setState((state) => {
return useMapStore.getState().measureShowSegments return {
} id: {
...state.id,
[id]: { ...state.id[id], printArea: extent }
}
}
})
const getMeasureClearPrevious = () => { export const getFiguresLayer = (id: string) => useMapStore.getState().id[id].figuresLayer
return useMapStore.getState().measureClearPrevious export const getLinesLayer = (id: string) => useMapStore.getState().id[id].linesLayer
} export const getMeasureModify = (id: string) => useMapStore.getState().id[id].measureModify
const setMeasureShowSegments = (bool: boolean) => { export const getOverlayLayerSource = (id: string) => useMapStore.getState().id[id].overlayLayerSource
useMapStore.setState(() => ({ measureShowSegments: bool })) export const setOverlayLayerSource = (id: string, source: VectorSource) => useMapStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], overlayLayerSource: source }
}
}
})
const setMeasureClearPrevious = (bool: boolean) => { export const getImageLayer = (id: string) => useMapStore.getState().id[id].imageLayer
useMapStore.setState(() => ({ measureClearPrevious: bool })) export const setImageLayer = (id: string, layer: ImageLayer<ImageStatic>) => useMapStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], imageLayer: layer }
}
}
})
export { export const getTranslate = (id: string) => useMapStore.getState().id[id].translate
setCurrentTool, export const setTranslate = (id: string, translate: Translate | null) => useMapStore.setState((state) => {
getCurrentTool, return {
setMeasureShowSegments, id: {
setMeasureClearPrevious, ...state.id,
getMeasureShowSegments, [id]: { ...state.id[id], translate: translate }
getMeasureClearPrevious, }
setMeasureType, }
getMeasureType, })
getTipPoint,
setTipPoint, export const getSnap = (id: string) => useMapStore.getState().id[id].snap
setCurrentZ, export const setSnap = (id: string, snap: Snap | null) => useMapStore.setState((state) => {
setCurrentX, return {
setCurrentY, id: {
setCurrentCoordinate, ...state.id,
setStatusText, [id]: { ...state.id[id], snap: snap }
setSatMapsProvider, }
setSelectedObjectType, }
setMap, })
getMap
} export const getDraw = (id: string) => useMapStore.getState().id[id].draw
export const setDraw = (id: string, draw: Draw | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], draw: draw }
}
}
})
export const getDrawingLayerSource = (id: string) => useMapStore.getState().id[id].drawingLayerSource
export const getMeasureSource = (id: string) => useMapStore.getState().id[id].measureSource
export const getMeasureDraw = (id: string) => useMapStore.getState().id[id].measureDraw
export const setMeasureDraw = (id: string, draw: Draw) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], measureDraw: draw }
}
}
})
export const setAlignPoints = (id: string, c: Coordinate[]) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], alignPoints: c }
}
}
})
export const setRectCoords = (id: string, c: IRectCoords | undefined) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], rectCoords: c }
}
}
})
export const setPolygonExtent = (id: string, extent: Extent | undefined) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], polygonExtent: extent }
}
}
})
export const setFile = (id: string, file: File | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], file: file }
}
}
})
export const setMapLabel = (id: string, label: string) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], mapLabel: label }
}
}
})
export const setCurrentZ = (id: string, z: number | undefined) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentZ: z }
}
}
})
export const setCurrentX = (id: string, x: number | undefined) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentX: x }
}
}
})
export const setCurrentY = (id: string, y: number | undefined) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentY: y }
}
}
})
export const setCurrentCoordinate = (id: string, c: Coordinate | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentCoordinate: c }
}
}
})
export const setStatusText = (id: string, t: string) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], statusText: t }
}
}
})
export const setSatMapsProvider = (id: string, p: SatelliteMapsProvider) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], satMapsProvider: p }
}
}
})
export const setSelectedObjectType = (id: string, t: number | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], selectedObjectType: t }
}
}
})
export const setMap = (id: string, m: Map | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], map: m }
}
}
})
export const setAlignMode = (id: string, m: boolean) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], alignMode: m }
}
}
})
export const setTipPoint = (id: string, tipPoint: Point | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], tipPoint: tipPoint }
}
}
})
export const getTipPoint = (id: string) => useMapStore.getState().id[id].tipPoint
export const getAlignMode = (id: string) => useMapStore.getState().id[id].alignMode
export const getAlignModeLayer = (id: string) => useMapStore.getState().id[id].alignModeLayer
export const getAlignPoints = (id: string) => useMapStore.getState().id[id].alignPoints
export const getMap = (id: string) => useMapStore.getState().id[id].map
export const setMeasureType = (id: string, tool: "LineString" | "Polygon") => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], measureType: tool }
}
}
})
export const getMeasureType = (id: string) => useMapStore.getState().id[id].measureType
export const setCurrentTool = (id: string, tool: ToolType) =>
tool === useMapStore.getState().id[id].currentTool
? useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentTool: null }
}
}
})
: useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], currentTool: tool }
}
}
})
export const getCurrentTool = (id: string) => useMapStore.getState().id[id].currentTool
export const getMeasureShowSegments = (id: string) => useMapStore.getState().id[id].measureShowSegments
export const getMeasureClearPrevious = (id: string) => useMapStore.getState().id[id].measureClearPrevious
export const setMeasureShowSegments = (id: string, bool: boolean) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], measureShowSegments: bool }
}
}
})
export const setMeasureClearPrevious = (id: string, bool: boolean) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], measureClearPrevious: bool }
}
}
})
export const getMode = (id: string) => useMapStore.getState().id[id].mode
export const setMode = (id: string, mode: Mode) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], mode: mode }
}
}
})
export const getTypeRoles = (id: string) => useMapStore.getState().id[id].typeRoles
export const setTypeRoles = (id: string, typeRoles: TypeRole[] | null) => useMapStore.setState((state) => {
return {
id: {
...state.id,
[id]: { ...state.id[id], typeRoles: typeRoles }
}
}
})

View File

@ -1,60 +1,87 @@
import { create } from 'zustand'; import { create } from 'zustand';
interface ObjectsState { interface ObjectsState {
selectedRegion: number | null; id: Record<string, {
selectedDistrict: number | null; selectedRegion: number | null;
selectedCity: number | null; selectedDistrict: number | null;
selectedYear: number | null; selectedCity: number | null;
currentObjectId: string | null; selectedYear: number | null;
currentObjectId: string | null;
}>;
} }
export const useObjectsStore = create<ObjectsState>(() => ({ export const useObjectsStore = create<ObjectsState>(() => ({
selectedRegion: null, id: {}
selectedDistrict: null,
selectedCity: null,
selectedYear: null,
currentObjectId: null
})); }));
const getSelectedCity = () => { export const initializeObjectsState = (
return useObjectsStore.getState().selectedCity id: string,
} selectedRegion: number | null,
selectedDistrict: number | null,
selectedCity: number | null,
selectedYear: number | null,
) => useObjectsStore.setState((state) => {
return {
id: {
...state.id,
[id]: {
selectedRegion: selectedRegion,
selectedDistrict: selectedDistrict,
selectedCity: selectedCity,
selectedYear: selectedYear,
currentObjectId: null,
}
}
}
})
const setSelectedRegion = (region: number | null) => { export const getSelectedCity = (id: string) => useObjectsStore.getState().id[id].selectedCity
useObjectsStore.setState(() => ({ selectedRegion: region })) export const setSelectedCity = (id: string, city: number | null) => useObjectsStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], selectedCity: city }
}
}
})
const setSelectedDistrict = (district: number | null) => { export const getSelectedRegion = (id: string) => useObjectsStore.getState().id[id].selectedRegion
useObjectsStore.setState(() => ({ selectedDistrict: district })) export const setSelectedRegion = (id: string, region: number | null) => useObjectsStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], selectedRegion: region }
}
}
})
const setSelectedCity = (city: number | null) => { export const getSelectedDistrict = (id: string) => useObjectsStore.getState().id[id].selectedDistrict
useObjectsStore.setState(() => ({ selectedCity: city })) export const setSelectedDistrict = (id: string, district: number | null) => useObjectsStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], selectedDistrict: district }
}
}
})
const getSelectedYear = () => {
return useObjectsStore.getState().selectedYear
}
const setSelectedYear = (year: number | null) => { export const getSelectedYear = (id: string) => useObjectsStore.getState().id[id].selectedYear
useObjectsStore.setState(() => ({ selectedYear: year })) export const setSelectedYear = (id: string, year: number | null) => useObjectsStore.setState((state) => {
} return {
id: {
...state.id,
[id]: { ...state.id[id], selectedYear: year }
}
}
})
const getCurrentObjectId = () => { export const getCurrentObjectId = (id: string) => useObjectsStore.getState().id[id].currentObjectId
return useObjectsStore.getState().currentObjectId export const setCurrentObjectId = (id: string, objectId: string | null) => useObjectsStore.setState((state) => {
} return {
id: {
const setCurrentObjectId = (objectId: string | null) => { ...state.id,
useObjectsStore.setState(() => ({ currentObjectId: objectId })) [id]: { ...state.id[id], currentObjectId: objectId }
} }
}
export { })
getSelectedCity,
setSelectedCity,
getSelectedYear,
setSelectedYear,
getCurrentObjectId,
setCurrentObjectId,
setSelectedRegion,
setSelectedDistrict
}

23
client/src/store/print.ts Normal file
View File

@ -0,0 +1,23 @@
import { create } from 'zustand';
export type PrintOrientation = 'horizontal' | 'vertical'
export type PrintFormat = 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5'
export const printResolutions = ['72', '150', '200', '300']
export interface PrintState {
printFormat: PrintFormat;
printOrientation: PrintOrientation;
printResolution: number;
}
export const usePrintStore = create<PrintState>(() => ({
printFormat: 'a4',
printOrientation: 'horizontal',
printResolution: 72
}))
export const getPrintOrientation = () => usePrintStore.getState().printOrientation
export const setPrintFormat = (format: PrintFormat) => usePrintStore.setState(() => ({ printFormat: format }))
export const setPrintOrientation = (orientation: PrintOrientation) => usePrintStore.setState(() => ({ printOrientation: orientation }))
export const setPrintResolution = (resolution: number) => usePrintStore.setState(() => ({ printResolution: resolution }))

View File

@ -1,11 +1,13 @@
import { defineConfig } from 'vite' import { defineConfig, type PluginOption } from 'vite'
import react from '@vitejs/plugin-react-swc' import react from '@vitejs/plugin-react-swc'
import { nodePolyfills } from 'vite-plugin-node-polyfills' import { nodePolyfills } from 'vite-plugin-node-polyfills'
import { visualizer } from 'rollup-plugin-visualizer'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
nodePolyfills(), nodePolyfills(),
react(), react(),
visualizer() as PluginOption
], ],
}) })

View File

@ -936,10 +936,10 @@
resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.6", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.24.7" version "7.26.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz"
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
@ -982,6 +982,29 @@
resolved "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz" resolved "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.0.tgz"
integrity sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ== integrity sha512-+imAQkHf7U/Rwvu0wk1XWgsP3WnpCWmK7B48f0XqSNzgk64+grljTKC7pnO/xBiEMUziF7vKRfbBnOQhg126qQ==
"@dnd-kit/accessibility@^3.1.1":
version "3.1.1"
resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz"
integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@^6.3.1":
version "6.3.1"
resolved "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz"
integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==
dependencies:
"@dnd-kit/accessibility" "^3.1.1"
"@dnd-kit/utilities" "^3.2.2"
tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.2":
version "3.2.2"
resolved "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz"
integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==
dependencies:
tslib "^2.0.0"
"@esbuild/win32-x64@0.21.5": "@esbuild/win32-x64@0.21.5":
version "0.21.5" version "0.21.5"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz"
@ -1065,6 +1088,19 @@
resolved "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.28.tgz" resolved "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-5.0.28.tgz"
integrity sha512-hBvJHY76pJT/JynGUB5EXWhnzjYfLdcMn655J5p1v9lTT9HdQSy+keq2KPVXO2Htlg998BBa3p6u/jlrZ6w0kg== integrity sha512-hBvJHY76pJT/JynGUB5EXWhnzjYfLdcMn655J5p1v9lTT9HdQSy+keq2KPVXO2Htlg998BBa3p6u/jlrZ6w0kg==
"@hello-pangea/dnd@^17.0.0":
version "17.0.0"
resolved "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz"
integrity sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==
dependencies:
"@babel/runtime" "^7.25.6"
css-box-model "^1.2.1"
memoize-one "^6.0.0"
raf-schd "^4.0.3"
react-redux "^9.1.2"
redux "^5.0.1"
use-memo-one "^1.1.3"
"@humanwhocodes/config-array@^0.11.14": "@humanwhocodes/config-array@^0.11.14":
version "0.11.14" version "0.11.14"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz"
@ -1430,6 +1466,11 @@
resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz" resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz"
integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg== integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==
"@techstark/opencv-js@^4.10.0-release.1":
version "4.10.0-release.1"
resolved "https://registry.npmjs.org/@techstark/opencv-js/-/opencv-js-4.10.0-release.1.tgz"
integrity sha512-S4XELidRiQeA0q1s9VQLo540wCxUo24r1O4C+LqZ6llX+sPCXvZCPv3Ice8dEIr0uavyZ8YZeKXSBdDgMXSXjw==
"@tiptap/core@^2.7.0", "@tiptap/core@^2.7.3": "@tiptap/core@^2.7.0", "@tiptap/core@^2.7.3":
version "2.7.3" version "2.7.3"
resolved "https://registry.npmjs.org/@tiptap/core/-/core-2.7.3.tgz" resolved "https://registry.npmjs.org/@tiptap/core/-/core-2.7.3.tgz"
@ -1707,7 +1748,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.66", "@types/react@>=16.8": "@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.25 || ^19", "@types/react@^18.2.66", "@types/react@>=16.8":
version "18.3.3" version "18.3.3"
resolved "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz"
integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
@ -1828,6 +1869,11 @@
dependencies: dependencies:
"@swc/core" "^1.5.7" "@swc/core" "^1.5.7"
"@xmldom/xmldom@0.8.10":
version "0.8.10"
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz"
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
"@zeit/schemas@2.36.0": "@zeit/schemas@2.36.0":
version "2.36.0" version "2.36.0"
resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz" resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz"
@ -2396,6 +2442,15 @@ clipboardy@3.0.0:
execa "^5.1.1" execa "^5.1.1"
is-wsl "^2.2.0" is-wsl "^2.2.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clsx@^2.0.0, clsx@^2.1.1: clsx@^2.0.0, clsx@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
@ -2611,6 +2666,13 @@ crypto-random-string@^2.0.0:
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
css-box-model@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz"
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
dependencies:
tiny-invariant "^1.0.6"
css-line-break@^2.1.0: css-line-break@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz" resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz"
@ -2774,6 +2836,11 @@ define-data-property@^1.0.1, define-data-property@^1.1.4:
es-errors "^1.3.0" es-errors "^1.3.0"
gopd "^1.0.1" gopd "^1.0.1"
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz"
@ -2834,6 +2901,14 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
docx-templates@^4.13.0:
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"
dom-helpers@^5.0.1: dom-helpers@^5.0.1:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz"
@ -2847,7 +2922,7 @@ domain-browser@^4.22.0:
resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz" resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz"
integrity sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA== integrity sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==
dompurify@^2.2.0: dompurify@^2.5.4:
version "2.5.6" version "2.5.6"
resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz" resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz"
integrity sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ== integrity sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==
@ -2862,6 +2937,16 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
easy-template-x@^5.1.0:
version "5.1.0"
resolved "https://registry.npmjs.org/easy-template-x/-/easy-template-x-5.1.0.tgz"
integrity sha512-vypMbIMLWLXoooA9rsL3SVN2oQtZwmmx1m4H8gi6JfbEXQQ5VLHGOUHYi9APbvN9R8Gx93r1fphdSFRHxozeYw==
dependencies:
"@xmldom/xmldom" "0.8.10"
json5 "2.2.3"
jszip "3.10.1"
lodash.get "4.4.2"
ejs@^3.1.6: ejs@^3.1.6:
version "3.1.10" version "3.1.10"
resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz"
@ -3038,7 +3123,7 @@ esbuild@^0.21.3:
"@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-ia32" "0.21.5"
"@esbuild/win32-x64" "0.21.5" "@esbuild/win32-x64" "0.21.5"
escalade@^3.1.2: escalade@^3.1.1, escalade@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz"
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
@ -3241,10 +3326,10 @@ fastq@^1.6.0:
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
fflate@^0.4.8: fflate@^0.8.1:
version "0.4.8" version "0.8.2"
resolved "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz" resolved "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz"
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
file-entry-cache@^6.0.1: file-entry-cache@^6.0.1:
version "6.0.1" version "6.0.1"
@ -3386,6 +3471,11 @@ geotiff@^2.0.7:
xml-utils "^1.0.2" xml-utils "^1.0.2"
zstddec "^0.1.0" zstddec "^0.1.0"
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
version "1.2.4" version "1.2.4"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz"
@ -3590,7 +3680,7 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1" minimalistic-crypto-utils "^1.0.1"
html2canvas@^1.0.0-rc.5: html2canvas@^1.0.0-rc.5, html2canvas@^1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz" resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz"
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
@ -3623,6 +3713,11 @@ ignore@^5.2.0, ignore@^5.3.1:
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immutable@^4.0.0: immutable@^4.0.0:
version "4.3.7" version "4.3.7"
resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz" resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz"
@ -3751,7 +3846,7 @@ is-date-object@^1.0.1:
dependencies: dependencies:
has-tostringtag "^1.0.0" has-tostringtag "^1.0.0"
is-docker@^2.0.0: is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
@ -3976,7 +4071,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json5@^2.2.0, json5@^2.2.3: json5@^2.2.0, json5@^2.2.3, json5@2.2.3:
version "2.2.3" version "2.2.3"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@ -3995,21 +4090,31 @@ jsonpointer@^5.0.0:
resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz" resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz"
integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
jspdf@^2.5.1: jspdf@^2.5.1, jspdf@^2.5.2:
version "2.5.1" version "2.5.2"
resolved "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz" resolved "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz"
integrity sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA== integrity sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==
dependencies: dependencies:
"@babel/runtime" "^7.14.0" "@babel/runtime" "^7.23.2"
atob "^2.1.2" atob "^2.1.2"
btoa "^1.2.1" btoa "^1.2.1"
fflate "^0.4.8" fflate "^0.8.1"
optionalDependencies: optionalDependencies:
canvg "^3.0.6" canvg "^3.0.6"
core-js "^3.6.0" core-js "^3.6.0"
dompurify "^2.2.0" dompurify "^2.5.4"
html2canvas "^1.0.0-rc.5" html2canvas "^1.0.0-rc.5"
jszip@^3.10.1, jszip@3.10.1:
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"
keyv@^4.5.3: keyv@^4.5.3:
version "4.5.4" version "4.5.4"
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
@ -4040,6 +4145,13 @@ levn@^0.4.1:
prelude-ls "^1.2.1" prelude-ls "^1.2.1"
type-check "~0.4.0" type-check "~0.4.0"
lie@~3.3.0:
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"
lilconfig@^2.1.0: lilconfig@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"
@ -4079,6 +4191,11 @@ lodash.debounce@^4.0.8:
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.get@4.4.2:
version "4.4.2"
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.merge@^4.6.2: lodash.merge@^4.6.2:
version "4.6.2" version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
@ -4153,6 +4270,11 @@ mdurl@^2.0.0:
resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz" resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz"
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz"
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
merge-stream@^2.0.0: merge-stream@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
@ -4438,6 +4560,15 @@ onetime@^5.1.2:
dependencies: dependencies:
mimic-fn "^2.1.0" mimic-fn "^2.1.0"
open@^8.4.0:
version "8.4.2"
resolved "https://registry.npmjs.org/open/-/open-8.4.2.tgz"
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
dependencies:
define-lazy-prop "^2.0.0"
is-docker "^2.1.1"
is-wsl "^2.2.0"
optionator@^0.9.3: optionator@^0.9.3:
version "0.9.4" version "0.9.4"
resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
@ -4479,7 +4610,7 @@ pako@^2.0.4:
resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz" resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
pako@~1.0.5: pako@~1.0.2, pako@~1.0.5:
version "1.0.11" version "1.0.11"
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
@ -4956,6 +5087,11 @@ quickselect@^2.0.0:
resolved "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz" resolved "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz"
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
raf-schd@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz"
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
raf@^3.4.1: raf@^3.4.1:
version "3.4.1" version "3.4.1"
resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz"
@ -5000,7 +5136,7 @@ rc@^1.0.1, rc@^1.1.6:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=18.0.0: "react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0, react-dom@>=18.0.0:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@ -5035,6 +5171,14 @@ react-number-format@^5.3.1:
resolved "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.2.tgz" resolved "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.2.tgz"
integrity sha512-cg//jVdS49PYDgmcYoBnMMHl4XNTMuV723ZnHD2aXYtWWWqbVF3hjQ8iB+UZEuXapLbeA8P8H+1o6ZB1lcw3vg== integrity sha512-cg//jVdS49PYDgmcYoBnMMHl4XNTMuV723ZnHD2aXYtWWWqbVF3hjQ8iB+UZEuXapLbeA8P8H+1o6ZB1lcw3vg==
react-redux@^9.1.2:
version "9.2.0"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz"
integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==
dependencies:
"@types/use-sync-external-store" "^0.0.6"
use-sync-external-store "^1.4.0"
react-remove-scroll-bar@^2.3.6: react-remove-scroll-bar@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz" resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz"
@ -5106,7 +5250,7 @@ react-transition-group@^4.4.5, react-transition-group@4.4.5:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.1 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1, "react@>= 16", "react@>= 16.8 || 18.0.0", react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=18.0.0: "react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.1 || ^18.0.0", "react@^17.0.0 || ^18.0.0", "react@^18.0 || ^19", react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16", "react@>= 16.8 || 18.0.0", react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=18.0.0:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@ -5142,6 +5286,19 @@ readable-stream@^3.5.0, readable-stream@^3.6.0:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.1" util-deprecate "^1.0.1"
readable-stream@~2.3.6:
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"
readable-web-to-node-stream@^3.0.2: readable-web-to-node-stream@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz" resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
@ -5177,6 +5334,11 @@ recharts@^2.10.3, recharts@^2.12.7:
tiny-invariant "^1.3.1" tiny-invariant "^1.3.1"
victory-vendor "^36.6.8" victory-vendor "^36.6.8"
redux@^5.0.0, redux@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
regenerate-unicode-properties@^10.1.0: regenerate-unicode-properties@^10.1.0:
version "10.1.1" version "10.1.1"
resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz"
@ -5250,6 +5412,11 @@ regjsparser@^0.9.1:
dependencies: dependencies:
jsesc "~0.5.0" jsesc "~0.5.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-from-string@^2.0.2: require-from-string@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
@ -5301,6 +5468,16 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0" hash-base "^3.0.0"
inherits "^2.0.1" inherits "^2.0.1"
rollup-plugin-visualizer@^5.12.0:
version "5.12.0"
resolved "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz"
integrity sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==
dependencies:
open "^8.4.0"
picomatch "^2.3.1"
source-map "^0.7.4"
yargs "^17.5.1"
"rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.43.1: "rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.43.1:
version "2.79.1" version "2.79.1"
resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz" resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
@ -5308,7 +5485,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^2.0.0||^3.0.0||^4.0.0, rollup@^2.78.0||^3.0.0||^4.0.0, rollup@^4.13.0: rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^2.0.0||^3.0.0||^4.0.0, rollup@^2.78.0||^3.0.0||^4.0.0, rollup@^4.13.0, "rollup@2.x || 3.x || 4.x":
version "4.18.0" version "4.18.0"
resolved "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz" resolved "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz"
integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==
@ -5367,12 +5544,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2,
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0: safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@~5.1.1:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@ -5430,6 +5602,11 @@ sass-embedded@*, sass-embedded@^1.79.5:
sass-embedded-win32-ia32 "1.79.5" sass-embedded-win32-ia32 "1.79.5"
sass-embedded-win32-x64 "1.79.5" sass-embedded-win32-x64 "1.79.5"
sax@1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz"
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
scheduler@^0.23.2: scheduler@^0.23.2:
version "0.23.2" version "0.23.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
@ -5507,7 +5684,7 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
functions-have-names "^1.2.3" functions-have-names "^1.2.3"
has-property-descriptors "^1.0.2" has-property-descriptors "^1.0.2"
setimmediate@^1.0.4: setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
@ -5580,6 +5757,11 @@ source-map@^0.6.0:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.4:
version "0.7.4"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
source-map@^0.8.0-beta.0: source-map@^0.8.0-beta.0:
version "0.8.0-beta.0" version "0.8.0-beta.0"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz" resolved "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz"
@ -5638,7 +5820,16 @@ string_decoder@~1.1.1:
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
string-width@^4.1.0: string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -5908,7 +6099,7 @@ timers-browserify@^2.0.4:
dependencies: dependencies:
setimmediate "^1.0.4" setimmediate "^1.0.4"
tiny-invariant@^1.3.1: tiny-invariant@^1.0.6, tiny-invariant@^1.3.1:
version "1.3.3" version "1.3.3"
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz"
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
@ -6158,6 +6349,11 @@ use-latest@^1.2.1:
dependencies: dependencies:
use-isomorphic-layout-effect "^1.1.1" use-isomorphic-layout-effect "^1.1.1"
use-memo-one@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz"
integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
use-sidecar@^1.1.2: use-sidecar@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz" resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz"
@ -6176,6 +6372,11 @@ use-sync-external-store@^1.2.2:
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz"
integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
use-sync-external-store@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@ -6506,6 +6707,15 @@ workbox-window@^7.1.0, workbox-window@7.1.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
@ -6530,6 +6740,11 @@ xtend@^4.0.2:
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^3.0.2: yallist@^3.0.2:
version "3.1.1" version "3.1.1"
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
@ -6540,6 +6755,24 @@ yaml@^2.3.4:
resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz"
integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^17.5.1:
version "17.7.2"
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.1.1"
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"

View File

@ -1,26 +1,26 @@
services: services:
client_app: web_client:
container_name: client_app container_name: web_client
build: build:
context: ./client context: ./client
dockerfile: Dockerfile dockerfile: Dockerfile
ports: ports:
- 5173:5173 - ${CLIENT_PORT}:5173
restart: always restart: always
redis_db: # redis_db:
image: "redis:alpine" # image: "redis:alpine"
container_name: redis_db # container_name: redis_db
ports: # ports:
- ${REDIS_PORT}:${REDIS_PORT} # - ${REDIS_PORT}:${REDIS_PORT}
environment: # environment:
- REDIS_PASSWORD=${REDIS_PASSWORD} # - REDIS_PASSWORD=${REDIS_PASSWORD}
command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD}" ] # command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD}" ]
volumes: # volumes:
- ./redis_data:/data # - ./redis_data:/data
expose: # expose:
- ${REDIS_PORT}:${REDIS_PORT} # - ${REDIS_PORT}:${REDIS_PORT}
restart: unless-stopped # restart: unless-stopped
ems: ems:
container_name: ems container_name: ems
@ -29,12 +29,12 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
volumes: volumes:
- ./ems/public:/app/public - ./ems/public:/app/public
links: # links:
- redis_db:redis_db # - redis_db:redis_db
- psql_db:psql_db # - psql_db:psql_db
depends_on: # depends_on:
- redis_db # - redis_db
- psql_db # - psql_db
environment: environment:
- REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_HOST=${REDIS_HOST} - REDIS_HOST=${REDIS_HOST}
@ -45,24 +45,33 @@ services:
- ${EMS_PORT}:${EMS_PORT} - ${EMS_PORT}:${EMS_PORT}
restart: always restart: always
monitor: postgis_db:
container_name: monitor container_name: postgis_db
build: image: postgis/postgis:17-3.4-alpine
context: ./monitor
dockerfile: Dockerfile
environment:
- MONITOR_PORT=${MONITOR_PORT}
ports:
- ${MONITOR_PORT}:${MONITOR_PORT}
volumes: volumes:
- ./monitor/data:/app/data - ./postgis_db:/var/lib/postgresql/data
restart: always environment:
- POSTGRES_DB=${POSTGRES_DB}
psql_db: - POSTGRES_USER=${POSTGRES_USER}
container_name: psql_db - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
image: postgres:16.4-alpine ports:
volumes: - ${POSTGRES_PORT}:${POSTGRES_PORT}
- ./psql_data:/var/lib/postgresql/data expose:
- ${POSTGRES_PORT}
healthcheck:
test:
['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: always
ems_db:
container_name: ems_db
image: postgres:16.4-alpine
volumes:
- ./ems_db:/var/lib/postgresql/data
environment: environment:
- POSTGRES_DB=${POSTGRES_DB} - POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER} - POSTGRES_USER=${POSTGRES_USER}

98
ems/package-lock.json generated
View File

@ -19,7 +19,7 @@
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"pg": "^8.13.0", "pg": "^8.13.1",
"pump": "^3.0.0", "pump": "^3.0.0",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"tedious": "^18.6.1" "tedious": "^18.6.1"
@ -31,6 +31,7 @@
"@types/md5": "^2.3.5", "@types/md5": "^2.3.5",
"@types/multer": "^1.4.12", "@types/multer": "^1.4.12",
"@types/node": "^22.4.1", "@types/node": "^22.4.1",
"@types/pg": "^8.11.10",
"@types/pump": "^1.1.3", "@types/pump": "^1.1.3",
"@types/redis": "^4.0.11", "@types/redis": "^4.0.11",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
@ -896,6 +897,74 @@
"undici-types": "~6.19.2" "undici-types": "~6.19.2"
} }
}, },
"node_modules/@types/pg": {
"version": "8.11.10",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz",
"integrity": "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
"pg-types": "^4.0.1"
}
},
"node_modules/@types/pg/node_modules/pg-types": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
"dev": true,
"dependencies": {
"pg-int8": "1.0.1",
"pg-numeric": "1.0.2",
"postgres-array": "~3.0.1",
"postgres-bytea": "~3.0.0",
"postgres-date": "~2.1.0",
"postgres-interval": "^3.0.0",
"postgres-range": "^1.1.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@types/pg/node_modules/postgres-array": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@types/pg/node_modules/postgres-bytea": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
"dev": true,
"dependencies": {
"obuf": "~1.1.2"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@types/pg/node_modules/postgres-date": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@types/pg/node_modules/postgres-interval": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@types/pump": { "node_modules/@types/pump": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/pump/-/pump-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/pump/-/pump-1.1.3.tgz",
@ -2454,6 +2523,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
"dev": true
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -2503,9 +2578,9 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.13.0", "version": "8.13.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
"integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==", "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.7.0", "pg-connection-string": "^2.7.0",
"pg-pool": "^3.7.0", "pg-pool": "^3.7.0",
@ -2547,6 +2622,15 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/pg-numeric": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
"integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/pg-pool": { "node_modules/pg-pool": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
@ -2630,6 +2714,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/postgres-range": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
"dev": true
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.19.1", "version": "5.19.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.19.1.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.19.1.tgz",

View File

@ -23,7 +23,7 @@
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"pg": "^8.13.0", "pg": "^8.13.1",
"pump": "^3.0.0", "pump": "^3.0.0",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"tedious": "^18.6.1" "tedious": "^18.6.1"
@ -35,6 +35,7 @@
"@types/md5": "^2.3.5", "@types/md5": "^2.3.5",
"@types/multer": "^1.4.12", "@types/multer": "^1.4.12",
"@types/node": "^22.4.1", "@types/node": "^22.4.1",
"@types/pg": "^8.11.10",
"@types/pump": "^1.1.3", "@types/pump": "^1.1.3",
"@types/redis": "^4.0.11", "@types/redis": "^4.0.11",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",

143
ems/src/api/db/index.ts Normal file
View File

@ -0,0 +1,143 @@
import express, { Request, Response } from 'express';
import { pgQuery } from '../../utils/postgres';
const router = express.Router()
router.get('/rows/:table_name', async (req: Request, res: Response) => {
try {
const { table_name } = req.params
const { offset, limit } = req.query
const result = await pgQuery(
`
SELECT * FROM ${table_name}
OFFSET ${offset || 0} LIMIT ${limit || 10}
`
)
if (Array.isArray(result)) {
if (result.length > 0) {
res.status(200).json(result)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/columns/:table_name', async (req: Request, res: Response) => {
try {
const { table_name } = req.params
const result = await pgQuery(
`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = '${table_name}'
`
)
if (Array.isArray(result)) {
if (result.length > 0) {
res.status(200).json(result)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/tables', async (req: Request, res: Response) => {
try {
const result = await pgQuery(
`
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
`
)
if (Array.isArray(result)) {
if (result.length > 0) {
res.status(200).json(result)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/figures/import', async (req: Request, res: Response) => {
try {
const { entity_type } = req.params
const result = await pgQuery(
`
SELECT * FROM bounds
WHERE entity_type = $1
`,
[entity_type]
)
if (Array.isArray(result)) {
if (result.length > 0) {
const geometries = result.map((bound: { id: number, entity_id: number, entity_type: string, geometry: JSON, published_at: string, deleted_at: string | null }) => {
return {
...bound.geometry,
properties: {
id: bound.id,
entity_id: bound.entity_id,
entity_type: bound.entity_type
}
}
})
res.status(200).json(geometries)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/bounds/:entity_type/:entity_id', async (req: Request, res: Response) => {
try {
const { entity_type, entity_id } = req.params
const result = await pgQuery(
`
SELECT * FROM bounds
WHERE entity_type = $1
AND entity_id = $2
`,
[entity_type, entity_id]
)
if (Array.isArray(result)) {
if (result.length > 0) {
res.status(200).json(result[0].geometry)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
export default router

View File

@ -171,27 +171,34 @@ router.get('/objects/list', async (req: Request, res: Response) => {
const result = await tediousQuery( const result = await tediousQuery(
` `
SELECT SELECT
tTypes.id AS id, ${GeneralDB}..tTypes.id AS id,
tTypes.name AS name, ${GeneralDB}..tTypes.name AS name,
COUNT(vObjects.type) AS count COUNT(vo.type) AS count,
tr.r,
tr.g,
tr.b
FROM FROM
vObjects ${GeneralDB}..vObjects vo
JOIN JOIN
tTypes ON vObjects.type = tTypes.id ${GeneralDB}..tTypes ON vo.type = ${GeneralDB}..tTypes.id
LEFT JOIN ${GisDB}..TypeRoles tr ON tr.id = ${GeneralDB}..tTypes.id
WHERE WHERE
vObjects.id_city = ${city_id} AND vObjects.year = ${year} vo.id_city = ${city_id} AND vo.year = ${year}
AND AND
( (
CASE CASE
WHEN TRY_CAST(vObjects.planning AS BIT) IS NOT NULL THEN TRY_CAST(vObjects.planning AS BIT) WHEN TRY_CAST(vo.planning AS BIT) IS NOT NULL THEN TRY_CAST(vo.planning AS BIT)
WHEN vObjects.planning = 'TRUE' THEN 1 WHEN vo.planning = 'TRUE' THEN 1
WHEN vObjects.planning = 'FALSE' THEN 0 WHEN vo.planning = 'FALSE' THEN 0
ELSE NULL ELSE NULL
END END
) = ${planning} ) = ${planning}
GROUP BY GROUP BY
tTypes.id, ${GeneralDB}..tTypes.id,
tTypes.name; ${GeneralDB}..tTypes.name,
tr.r,
tr.g,
tr.b;
` `
) )
res.status(200).json(result) res.status(200).json(result)

View File

@ -1,8 +1,86 @@
import express, { Request, Response } from 'express'; import express, { Request, Response } from 'express';
import { tediousQuery } from '../../utils/tedious'; import { tediousQuery } from '../../utils/tedious';
import { GeneralDB, GisDB } from '../../constants/db'; import { GeneralDB, GisDB } from '../../constants/db';
import { pgQuery } from '../../utils/postgres';
const router = express.Router() const router = express.Router()
router.get('/type-roles', async (req: Request, res: Response) => {
try {
const result = await tediousQuery(
`
SELECT * FROM ${GisDB}..TypeRoles;
`
)
res.status(200).json(result)
} catch (err) {
res.status(500)
}
})
router.get('/bounds/:entity_type', async (req: Request, res: Response) => {
try {
const { entity_type } = req.params
const result = await pgQuery(
`
SELECT * FROM bounds
WHERE entity_type = $1
`,
[entity_type]
)
if (Array.isArray(result)) {
if (result.length > 0) {
const geometries = result.map((bound: { id: number, entity_id: number, entity_type: string, geometry: JSON, published_at: string, deleted_at: string | null }) => {
return {
...bound.geometry,
properties: {
id: bound.id,
entity_id: bound.entity_id,
entity_type: bound.entity_type
}
}
})
res.status(200).json(geometries)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/bounds/:entity_type/:entity_id', async (req: Request, res: Response) => {
try {
const { entity_type, entity_id } = req.params
const result = await pgQuery(
`
SELECT * FROM bounds
WHERE entity_type = $1
AND entity_id = $2
`,
[entity_type, entity_id]
)
if (Array.isArray(result)) {
if (result.length > 0) {
res.status(200).json(result[0].geometry)
} else {
res.status(404).json('not found')
}
} else {
res.status(404).json('not found')
}
} catch (err) {
res.status(500)
}
})
router.get('/images/all', async (req: Request, res: Response) => { router.get('/images/all', async (req: Request, res: Response) => {
try { try {
const { offset, limit, city_id } = req.query const { offset, limit, city_id } = req.query

View File

@ -3,70 +3,70 @@ import { query, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
const router = express.Router() const router = express.Router()
const prisma = new PrismaClient() //const prisma = new PrismaClient()
router.get('/all', async (req: Request, res: Response) => { // router.get('/all', async (req: Request, res: Response) => {
try { // try {
const nodes = await prisma.nodes.findMany() // const nodes = await prisma.nodes.findMany()
res.json(nodes) // res.json(nodes)
} catch (error) { // } catch (error) {
console.error('Error getting node:', error); // console.error('Error getting node:', error);
res.status(500).json({ error: 'Failed to get node' }); // res.status(500).json({ error: 'Failed to get node' });
} // }
}) // })
router.get('/', query('id').isString().isUUID(), async (req: Request, res: Response) => { // router.get('/', query('id').isString().isUUID(), async (req: Request, res: Response) => {
try { // try {
const result = validationResult(req) // const result = validationResult(req)
if (!result.isEmpty()) { // if (!result.isEmpty()) {
return res.send({ errors: result.array() }) // return res.send({ errors: result.array() })
} // }
const { id } = req.params // const { id } = req.params
const node = await prisma.nodes.findFirst({ // const node = await prisma.nodes.findFirst({
where: { // where: {
id: id // id: id
} // }
}) // })
res.json(node) // res.json(node)
} catch (error) { // } catch (error) {
console.error('Error getting node:', error); // console.error('Error getting node:', error);
res.status(500).json({ error: 'Failed to get node' }); // res.status(500).json({ error: 'Failed to get node' });
} // }
}) // })
router.post('/', async (req: Request, res: Response) => { // router.post('/', async (req: Request, res: Response) => {
try { // try {
const { coordinates, object_id, type } = req.body; // const { coordinates, object_id, type } = req.body;
// Convert the incoming array of coordinates into the shape structure // // Convert the incoming array of coordinates into the shape structure
const shape = coordinates.map((point: number[]) => ({ // const shape = coordinates.map((point: number[]) => ({
object_id: object_id || null, // object_id: object_id || null,
x: point[0], // x: point[0],
y: point[1] // y: point[1]
})); // }));
console.log(shape) // console.log(shape)
// Create a new node in the database // // Create a new node in the database
const node = await prisma.nodes.create({ // const node = await prisma.nodes.create({
data: { // data: {
object_id: object_id || null, // Nullable if object_id is not provided // object_id: object_id || null, // Nullable if object_id is not provided
shape_type: type, // You can adjust this dynamically // shape_type: type, // You can adjust this dynamically
shape: shape, // Store the shape array as Json[] // shape: shape, // Store the shape array as Json[]
label: 'Default' // label: 'Default'
} // }
}); // });
res.status(201).json(node); // res.status(201).json(node);
} catch (error) { // } catch (error) {
console.error('Error creating node:', error); // console.error('Error creating node:', error);
res.status(500).json({ error: 'Failed to create node' }); // res.status(500).json({ error: 'Failed to create node' });
} // }
}) // })
export default router export default router

View File

@ -10,7 +10,7 @@ const router = express.Router()
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: function (req, file, cb) { destination: function (req, file, cb) {
cb(null, path.join(__dirname, '..', 'public', 'temp')) cb(null, path.join(__dirname, '..', '..', '..', 'public', 'temp'))
}, },
filename: function (req, file, cb) { filename: function (req, file, cb) {
cb(null, Date.now() + path.extname(file.originalname)) cb(null, Date.now() + path.extname(file.originalname))

View File

@ -1,6 +1,7 @@
import express from 'express' import express from 'express'
import bodyParser from 'body-parser' import bodyParser from 'body-parser'
import cors from 'cors' import cors from 'cors'
import dbRouter from './api/db'
import generalRouter from './api/general' import generalRouter from './api/general'
import gisRouter from './api/gis' import gisRouter from './api/gis'
import nodesRouter from './api/nodes' import nodesRouter from './api/nodes'
@ -17,6 +18,7 @@ app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true })) app.use(bodyParser.urlencoded({ extended: true }))
app.use('/db', dbRouter)
app.use('/general', generalRouter) app.use('/general', generalRouter)
app.use('/gis', gisRouter) app.use('/gis', gisRouter)
app.use('/nodes', nodesRouter) app.use('/nodes', nodesRouter)

37
ems/src/utils/postgres.ts Normal file
View File

@ -0,0 +1,37 @@
import { Pool } from 'pg';
import 'dotenv/config';
// Environment variables for database configuration
const PG_HOST = process.env.PG_HOST || 'localhost';
const PG_PORT = Number(process.env.PG_PORT) || 5432;
const PG_USER = process.env.PG_USER || 'postgres';
const PG_PASSWORD = process.env.PG_PASSWORD || '';
const PG_DB = process.env.PG_DB || 'postgres';
// Create a connection pool
const pool = new Pool({
host: PG_HOST,
port: PG_PORT,
user: PG_USER,
password: PG_PASSWORD,
database: PG_DB,
});
export async function pgQuery(query: string, params: any[] = []) {
try {
// Get a client from the pool
const client = await pool.connect();
try {
// Execute the query with parameters
const result = await client.query(query, params);
return result.rows; // Return only the rows
} finally {
// Release the client back to the pool
client.release();
}
} catch (err) {
// Log error and rethrow it
console.error(`Error executing query: ${query}`, err);
throw err;
}
}

View File

@ -0,0 +1,5 @@
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=

5200
tools/import_bounds/city.sql Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,75 @@
import os
import json
import psycopg2
from datetime import datetime
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Database connection parameters
DB_CONFIG = {
"dbname": os.getenv("DB_NAME"),
"user": os.getenv("DB_USER"),
"password": os.getenv("DB_PASSWORD"),
"host": os.getenv("DB_HOST"),
"port": int(os.getenv("DB_PORT", 5432)) # Default port to 5432 if not set
}
# Root directory where the folders are located
ROOT_DIR = "./"
def insert_bounds(entity_id, entity_type, geometry, conn):
"""Insert data into the bounds table."""
try:
with conn.cursor() as cur:
# Check if the GeoJSON is a GeometryCollection
if geometry.get('type') == 'GeometryCollection':
# Extract polygons from the GeometryCollection and convert them to MultiPolygon
geometry = {
"type": "MultiPolygon",
"coordinates": [
geom["coordinates"] for geom in geometry["geometries"] if geom["type"] == "Polygon"
]
}
# Insert into the bounds table
cur.execute(
"""
INSERT INTO bounds (entity_id, entity_type, geometry)
VALUES (%s, %s,
ST_Transform(
ST_Multi(
ST_GeomFromGeoJSON(%s::JSON)
),
3857)
);
""",
(entity_id, entity_type, json.dumps(geometry))
)
conn.commit()
except Exception as e:
print(f"Error inserting entity_id {entity_id} of type {entity_type}: {e}")
conn.rollback()
def process_geojson_files():
"""Process all GeoJSON files in the directory structure."""
try:
conn = psycopg2.connect(**DB_CONFIG)
for folder in os.listdir(ROOT_DIR):
folder_path = os.path.join(ROOT_DIR, folder)
if os.path.isdir(folder_path):
entity_type = folder # Folder name is the entity_type
for file in os.listdir(folder_path):
if file.endswith(".geojson"):
entity_id = int(os.path.splitext(file)[0]) # Extract ID from filename
file_path = os.path.join(folder_path, file)
with open(file_path, "r") as f:
geometry = json.load(f)
insert_bounds(entity_id, entity_type, geometry, conn)
conn.close()
except Exception as e:
print(f"Error processing files: {e}")
if __name__ == "__main__":
process_geojson_files()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,55 @@
import os
import json
# Root directory where the folders are located
ROOT_DIR = "./"
OUTPUT_FILES = {}
def get_insert_query(entity_id, entity_type, geometry):
"""Generate SQL insert query for bounds."""
# If it's a GeometryCollection, extract and convert to MultiPolygon
if geometry.get('type') == 'GeometryCollection':
geometry = {
"type": "MultiPolygon",
"coordinates": [
geom["coordinates"] for geom in geometry["geometries"] if geom["type"] == "Polygon"
]
}
geojson_str = json.dumps(geometry).replace("'", "''") # Escape single quotes for SQL
query = f"""
INSERT INTO bounds (entity_id, entity_type, geometry)
VALUES ({entity_id}, '{entity_type}',
ST_Transform(
ST_Multi(
ST_GeomFromGeoJSON('{geojson_str}'::JSON)
),
3857)
);
"""
return query
def process_geojson_files():
"""Process all GeoJSON files and write SQL inserts to appropriate files."""
for folder in os.listdir(ROOT_DIR):
folder_path = os.path.join(ROOT_DIR, folder)
if os.path.isdir(folder_path):
entity_type = folder
sql_filename = f"{entity_type}.sql"
with open(sql_filename, "w", encoding="utf-8") as sql_file:
for file in os.listdir(folder_path):
if file.endswith(".geojson"):
try:
entity_id = int(os.path.splitext(file)[0])
file_path = os.path.join(folder_path, file)
with open(file_path, "r", encoding="utf-8") as f:
geometry = json.load(f)
query = get_insert_query(entity_id, entity_type, geometry)
sql_file.write(query + "\n")
except Exception as e:
print(f"Error processing {file}: {e}")
if __name__ == "__main__":
process_geojson_files()