Update
This commit is contained in:
86
client/package-lock.json
generated
86
client/package-lock.json
generated
@ -41,6 +41,7 @@
|
||||
"axios": "^1.7.2",
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-templates": "^4.13.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"file-type": "^19.0.0",
|
||||
"jspdf": "^2.5.2",
|
||||
@ -5446,8 +5447,7 @@
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/create-ecdh": {
|
||||
"version": "4.0.4",
|
||||
@ -5913,6 +5913,18 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/docx-templates": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/docx-templates/-/docx-templates-4.13.0.tgz",
|
||||
"integrity": "sha512-tTmR3WhROYctuyVReQ+PfCU3zprmC45/VuSVzn8EjovzpRkXYUdXiDatB9M8pasj0V+wuuOyY8bcSHvlQ2GNag==",
|
||||
"dependencies": {
|
||||
"jszip": "^3.10.1",
|
||||
"sax": "1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
@ -7196,6 +7208,11 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||
@ -7880,6 +7897,49 @@
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/jszip/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/jszip/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@ -7924,6 +7984,14 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
@ -8537,8 +8605,7 @@
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
@ -8982,8 +9049,7 @@
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/proj4": {
|
||||
"version": "2.12.0",
|
||||
@ -10425,6 +10491,11 @@
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
@ -10607,8 +10678,7 @@
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||
},
|
||||
"node_modules/sha.js": {
|
||||
"version": "2.4.11",
|
||||
|
@ -44,6 +44,7 @@
|
||||
"axios": "^1.7.2",
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-templates": "^4.13.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"file-type": "^19.0.0",
|
||||
"jspdf": "^2.5.2",
|
||||
|
@ -5,47 +5,48 @@
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
background: #27bbff;
|
||||
background: var(--mantine-color-anchor);
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
border-radius: 6px;
|
||||
transition: opacity .2s ease;
|
||||
}
|
||||
|
||||
.resize_handler:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tr {
|
||||
display: flex;
|
||||
//width: 100%;
|
||||
//max-width: 100%;
|
||||
width: fit-content;
|
||||
}
|
||||
// .tr {
|
||||
// display: flex;
|
||||
// //width: 100%;
|
||||
// //max-width: 100%;
|
||||
// width: fit-content;
|
||||
// }
|
||||
|
||||
.th {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.th,
|
||||
.td {
|
||||
display: flex;
|
||||
width: auto;
|
||||
}
|
||||
// .th,
|
||||
// .td {
|
||||
// display: flex;
|
||||
// width: auto;
|
||||
// }
|
||||
|
||||
.thead {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
// .thead {
|
||||
// display: flex;
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
// .table {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
.tbody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
// .tbody {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// width: 100%;
|
||||
// }
|
@ -1,111 +1,205 @@
|
||||
import { Input, Table } from '@mantine/core';
|
||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Badge, Button, Flex, Input, Modal, ScrollAreaAutosize, Select, Stack, Table, TextInput } from '@mantine/core';
|
||||
import { Cell, ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import styles from './CustomTable.module.scss'
|
||||
import { useRoles } from '../hooks/swrHooks';
|
||||
import { IRole } from '../interfaces/role';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
import { CreateField } from '../interfaces/create';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import FormFields from './FormFields';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
|
||||
// Sample data
|
||||
|
||||
type DataType = {
|
||||
id: number,
|
||||
name: string,
|
||||
age: number
|
||||
type CustomTableProps<T> = {
|
||||
data: T[];
|
||||
columns: ColumnDef<T>[];
|
||||
createFields?: CreateField[];
|
||||
submitHandler?: (data: T) => Promise<AxiosResponse>
|
||||
}
|
||||
|
||||
// Define columns
|
||||
const columns: ColumnDef<DataType>[] = [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Name',
|
||||
cell: (info) => info.getValue(),
|
||||
maxSize: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
{
|
||||
accessorKey: 'age',
|
||||
header: 'Age',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
];
|
||||
|
||||
const CustomTable = () => {
|
||||
const [data, setData] = useState<DataType[]>([
|
||||
{ id: 1, name: 'John Doe', age: 25 },
|
||||
{ id: 2, name: 'Jane Smith', age: 30 },
|
||||
{ id: 3, name: 'Sam Green', age: 22 },
|
||||
]);
|
||||
const CustomTable = <T extends object>({
|
||||
data: initialData,
|
||||
columns,
|
||||
createFields,
|
||||
submitHandler
|
||||
}: CustomTableProps<T>) => {
|
||||
const [data, setData] = useState<T[]>(initialData);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
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({
|
||||
data,
|
||||
data: filteredData,
|
||||
columns: tableColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
columnResizeMode: "onChange",
|
||||
});
|
||||
|
||||
// Function to handle cell edit
|
||||
const handleEditCell = (
|
||||
rowIndex: number,
|
||||
columnId: keyof DataType,
|
||||
value: DataType[keyof DataType]
|
||||
) => {
|
||||
const updatedData = [...data];
|
||||
(updatedData[rowIndex][columnId] as DataType[keyof DataType]) = value;
|
||||
setData(updatedData);
|
||||
//setEditingCell({ rowIndex: null, columnId: null });
|
||||
};
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
return (
|
||||
<Table striped withColumnBorders highlightOnHover className={styles.table}>
|
||||
<Table.Thead className={styles.thead}>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<Table.Tr key={headerGroup.id} className={styles.tr}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<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;
|
||||
<Stack h='100%'>
|
||||
{createFields && submitHandler &&
|
||||
<Modal opened={opened} onClose={close} title="Добавление объекта" centered>
|
||||
<FormFields
|
||||
fields={createFields}
|
||||
submitHandler={submitHandler}
|
||||
/>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
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 DataType]}
|
||||
onChange={(e) => handleEditCell(rowIndex, (cell.column.id as keyof DataType), e.target.value)}
|
||||
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
flexRender(cell.column.columnDef.cell, cell.getContext())
|
||||
)}
|
||||
</Table.Td>
|
||||
);
|
||||
})}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<Flex w='100%' gap='sm'>
|
||||
<TextInput
|
||||
placeholder="Поиск"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
w='100%'
|
||||
/>
|
||||
{createFields && submitHandler &&
|
||||
<Button
|
||||
leftSection={<IconPlus />}
|
||||
onClick={open}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
}
|
||||
</Flex>
|
||||
|
||||
<ScrollAreaAutosize offsetScrollbars style={{ borderRadius: '4px' }}>
|
||||
<Table stickyHeader striped withColumnBorders highlightOnHover className={styles.table}>
|
||||
<Table.Thead className={styles.thead}>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<Table.Tr key={headerGroup.id} className={styles.tr}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<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;
|
||||
|
@ -2,14 +2,17 @@ import { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { Accordion, NavLink, Text } from '@mantine/core';
|
||||
import { setCurrentObjectId, useObjectsStore } from '../../store/objects';
|
||||
import { NavLink, Stack, Text } from '@mantine/core';
|
||||
import { IconChevronDown } from '@tabler/icons-react';
|
||||
import { setSelectedObjectType } from '../../store/map';
|
||||
import { setCurrentObjectId, useObjectsStore } from '../../store/objects';
|
||||
|
||||
const ObjectTree = () => {
|
||||
const { selectedDistrict, selectedYear } = useObjectsStore()
|
||||
|
||||
const ObjectTree = ({
|
||||
map_id
|
||||
}: {
|
||||
map_id: string,
|
||||
}) => {
|
||||
const { selectedYear, selectedDistrict } = useObjectsStore().id[map_id]
|
||||
const [existingCount, setExistingCount] = useState(0)
|
||||
const [planningCount, setPlanningCount] = useState(0)
|
||||
|
||||
@ -49,10 +52,10 @@ const ObjectTree = () => {
|
||||
|
||||
if (selectedDistrict) {
|
||||
return (
|
||||
<Accordion multiple chevronPosition='left'>
|
||||
<TypeTree label='Существующие' value={'existing'} count={existingCount} objectList={existingObjectsList} planning={0} />
|
||||
<TypeTree label='Планируемые' value={'planning'} count={planningCount} objectList={planningObjectsList} planning={1} />
|
||||
</Accordion>
|
||||
<Stack gap={0}>
|
||||
<TypeTree map_id={map_id} label='Существующие' value={'existing'} count={existingCount} objectList={existingObjectsList} planning={0} />
|
||||
<TypeTree map_id={map_id} label='Планируемые' value={'planning'} count={planningCount} objectList={planningObjectsList} planning={1} />
|
||||
</Stack>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
@ -69,19 +72,21 @@ interface TypeTreeProps {
|
||||
count: number;
|
||||
objectList: unknown;
|
||||
planning: number;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const TypeTree = ({
|
||||
label,
|
||||
objectList,
|
||||
count,
|
||||
planning
|
||||
planning,
|
||||
map_id
|
||||
}: TypeTreeProps) => {
|
||||
|
||||
return (
|
||||
<NavLink p={0} label={`${label} ${count ? `(${count})` : ''}`}>
|
||||
{Array.isArray(objectList) && objectList.map(list => (
|
||||
<ObjectList key={list.id} label={list.name} id={list.id} planning={planning} count={list.count} />
|
||||
<ObjectList map_id={map_id} key={list.id} label={list.name} id={list.id} planning={planning} count={list.count} />
|
||||
))}
|
||||
</NavLink>
|
||||
)
|
||||
@ -92,15 +97,17 @@ interface IObjectList {
|
||||
id: number;
|
||||
planning: number;
|
||||
count: number;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const ObjectList = ({
|
||||
label,
|
||||
id,
|
||||
planning,
|
||||
count
|
||||
count,
|
||||
map_id
|
||||
}: IObjectList) => {
|
||||
const { selectedDistrict, selectedYear } = useObjectsStore()
|
||||
const { selectedDistrict, selectedYear } = useObjectsStore().id[map_id]
|
||||
|
||||
const { data } = useSWR(
|
||||
selectedDistrict && selectedYear ? `/general/objects/list?type=${id}&city_id=${selectedDistrict}&year=${selectedYear}&planning=${planning}` : null,
|
||||
@ -113,10 +120,10 @@ const ObjectList = ({
|
||||
|
||||
return (
|
||||
<NavLink onClick={() => {
|
||||
setSelectedObjectType(id)
|
||||
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(type.object_id)} />
|
||||
<NavLink key={type.object_id} label={type.caption ? type.caption : 'Без названия'} p={0} onClick={() => setCurrentObjectId(map_id, type.object_id)} />
|
||||
))}
|
||||
</NavLink>
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
import { Checkbox, Flex, NavLink, Slider, Stack } from '@mantine/core'
|
||||
import BaseLayer from 'ol/layer/Base'
|
||||
import Map from 'ol/Map'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface MapLayersProps {
|
||||
map: React.MutableRefObject<Map | null>
|
||||
map: Map | null
|
||||
}
|
||||
|
||||
const MapLayers = ({
|
||||
@ -12,7 +12,7 @@ const MapLayers = ({
|
||||
}: MapLayersProps) => {
|
||||
return (
|
||||
<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} />
|
||||
))}
|
||||
</Stack>
|
||||
|
90
client/src/components/map/MapLegend/MapLegend.tsx
Normal file
90
client/src/components/map/MapLegend/MapLegend.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { Accordion, ColorSwatch, Flex, ScrollAreaAutosize, Stack, Text, useMantineColorScheme } from '@mantine/core'
|
||||
import useSWR from 'swr';
|
||||
import { fetcher } from '../../../http/axiosInstance';
|
||||
import { BASE_URL } from '../../../constants';
|
||||
|
||||
const MapLegend = ({
|
||||
selectedDistrict,
|
||||
selectedYear
|
||||
}: {
|
||||
selectedDistrict: number | null,
|
||||
selectedYear: number | null
|
||||
}) => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
const { data: planningObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=1` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollAreaAutosize offsetScrollbars maw='300px' w='100%' fz='xs' mt='auto' 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
|
32
client/src/components/map/MapMode.tsx
Normal file
32
client/src/components/map/MapMode.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Center, SegmentedControl } from '@mantine/core'
|
||||
import { getMode, Mode, setMode } from '../../store/map'
|
||||
import { IconEdit, IconEye } from '@tabler/icons-react'
|
||||
|
||||
const MapMode = ({
|
||||
map_id
|
||||
}: { map_id: string }) => {
|
||||
return (
|
||||
<SegmentedControl value={getMode(map_id)} onChange={(value) => setMode(map_id, value as Mode)} color="blue" w='auto' data={[
|
||||
{
|
||||
value: 'view',
|
||||
label: (
|
||||
<Center style={{ gap: 10 }}>
|
||||
<IconEye size={16} />
|
||||
<span>Просмотр</span>
|
||||
</Center>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'edit',
|
||||
label: (
|
||||
<Center style={{ gap: 10 }}>
|
||||
<IconEdit size={16} />
|
||||
<span>Редактирование</span>
|
||||
</Center>
|
||||
),
|
||||
},
|
||||
]} />
|
||||
)
|
||||
}
|
||||
|
||||
export default MapMode
|
@ -26,7 +26,7 @@ const customMapSource = new XYZ({
|
||||
})
|
||||
|
||||
const regionsLayerSource = new VectorSource({
|
||||
url: 'sakha_republic.geojson',
|
||||
url: 'http://localhost:5000/gis/bounds/region',
|
||||
format: new GeoJSON(),
|
||||
})
|
||||
|
||||
|
@ -4,39 +4,41 @@ import { useMapStore } from '../../../store/map';
|
||||
|
||||
interface IMapStatusbarProps {
|
||||
mapControlsStyle: CSSProperties;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const MapStatusbar = ({
|
||||
mapControlsStyle,
|
||||
map_id
|
||||
}: IMapStatusbarProps) => {
|
||||
const mapState = useMapStore()
|
||||
const { currentCoordinate, currentX, currentY, currentZ, statusText } = useMapStore().id[map_id]
|
||||
|
||||
return (
|
||||
<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)}>
|
||||
x: {mapState.currentCoordinate?.[0]}
|
||||
x: {currentCoordinate?.[0]}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' w={rem(130)}>
|
||||
y: {mapState.currentCoordinate?.[1]}
|
||||
y: {currentCoordinate?.[1]}
|
||||
</Text>
|
||||
|
||||
<Divider orientation='vertical' />
|
||||
|
||||
<Text fz='xs'>
|
||||
Z={mapState.currentZ}
|
||||
Z={currentZ}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
X={mapState.currentX}
|
||||
X={currentX}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs'>
|
||||
Y={mapState.currentY}
|
||||
Y={currentY}
|
||||
</Text>
|
||||
|
||||
<Text fz='xs' ml='auto'>
|
||||
{mapState.statusText}
|
||||
{statusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
|
@ -95,7 +95,7 @@ const figureStyle = new Style({
|
||||
color: 'rgba(255,255,255,0.4)'
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: '#3399CC',
|
||||
color: 'black',
|
||||
width: 1.25
|
||||
}),
|
||||
text: new Text({
|
||||
@ -108,12 +108,9 @@ const figureStyle = new Style({
|
||||
})
|
||||
|
||||
const lineStyle = new Style({
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,255,255,0.4)'
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: '#3399CC',
|
||||
width: 1.25
|
||||
width: 1
|
||||
}),
|
||||
text: new Text({
|
||||
font: '12px Calibri,sans-serif',
|
||||
|
@ -1,93 +1,90 @@
|
||||
import { ActionIcon, useMantineColorScheme } from '@mantine/core'
|
||||
import { ActionIcon, Flex, useMantineColorScheme } from '@mantine/core'
|
||||
import { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react'
|
||||
import { setCurrentTool, useMapStore } from '../../../store/map';
|
||||
|
||||
interface IToolbarProps {
|
||||
onSave: () => void;
|
||||
onRemove: () => void;
|
||||
}
|
||||
import { getDraw, setCurrentTool, useMapStore } from '../../../store/map';
|
||||
import { saveFeatures } from '../mapUtils';
|
||||
|
||||
const MapToolbar = ({
|
||||
onSave,
|
||||
onRemove,
|
||||
}: IToolbarProps) => {
|
||||
const mapState = useMapStore()
|
||||
map_id
|
||||
}: { map_id: string }) => {
|
||||
const { currentTool } = useMapStore().id[map_id]
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={onSave}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
<Flex>
|
||||
<ActionIcon.Group orientation='vertical' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => saveFeatures(map_id)}>
|
||||
<IconExclamationCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon size='lg' variant='transparent' onClick={onRemove}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
<ActionIcon size='lg' variant='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()}>
|
||||
<IconArrowBackUp />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Edit' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Edit')
|
||||
}}>
|
||||
<IconTransformPoint />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Edit' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Edit')
|
||||
}}>
|
||||
<IconTransformPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Point' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Point')
|
||||
}}>
|
||||
<IconPoint />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'LineString' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'LineString')
|
||||
}}>
|
||||
<IconLine />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Polygon' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Polygon')
|
||||
}}>
|
||||
<IconPolygon />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Circle' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Circle')
|
||||
}}>
|
||||
<IconCircle />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Mover' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Mover')
|
||||
}}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Mover' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Mover')
|
||||
}}
|
||||
>
|
||||
<IconArrowsMove />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={mapState.currentTool === 'Measure' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool('Measure')
|
||||
}}>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
<ActionIcon
|
||||
size='lg'
|
||||
variant={currentTool === 'Measure' ? 'filled' : 'transparent'}
|
||||
onClick={() => {
|
||||
setCurrentTool(map_id, 'Measure')
|
||||
}}>
|
||||
<IconRuler />
|
||||
</ActionIcon>
|
||||
</ActionIcon.Group>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ const formatArea = function (polygon: Geometry) {
|
||||
};
|
||||
|
||||
export function measureStyleFunction(
|
||||
map_id: string,
|
||||
feature: FeatureLike,
|
||||
drawType?: Type,
|
||||
tip?: string,
|
||||
@ -149,7 +150,7 @@ export function measureStyleFunction(
|
||||
const type = geometry?.getType();
|
||||
const segmentStyles = [segmentStyle];
|
||||
|
||||
const segments = getMeasureShowSegments()
|
||||
const segments = getMeasureShowSegments(map_id)
|
||||
|
||||
if (!geometry) return
|
||||
|
||||
|
@ -6,12 +6,14 @@ import TCBParameter from './TCBParameter'
|
||||
import TableValue from './TableValue'
|
||||
|
||||
interface ObjectParameterProps {
|
||||
showLabel?: boolean,
|
||||
param: IObjectParam,
|
||||
showLabel?: boolean;
|
||||
param: IObjectParam;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const ObjectParameter = ({
|
||||
param
|
||||
param,
|
||||
map_id
|
||||
}: ObjectParameterProps) => {
|
||||
const { data: paramData } = useSWR(
|
||||
`/general/params/all?param_id=${param.id_param}`,
|
||||
@ -26,44 +28,44 @@ const ObjectParameter = ({
|
||||
switch (type) {
|
||||
case 'bit':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='boolean' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='boolean' />
|
||||
)
|
||||
case 'bigint':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
case 'tinyint':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
// TODO: Calculate from calc procedures
|
||||
case 'calculate':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='value' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='value' />
|
||||
)
|
||||
case 'GTCB':
|
||||
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':
|
||||
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:
|
||||
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:
|
||||
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':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='number' />
|
||||
<TableValue map_id={map_id} value={value} name={name} type='number' />
|
||||
)
|
||||
case 'uniqueidentifier':
|
||||
return (
|
||||
<TableValue value={value} name={name} type='value'/>
|
||||
<TableValue map_id={map_id} value={value} name={name} type='value'/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
|
@ -6,8 +6,13 @@ import { BASE_URL } from '../../../constants';
|
||||
import { fetcher } from '../../../http/axiosInstance';
|
||||
import { useObjectsStore } from '../../../store/objects';
|
||||
|
||||
const ObjectParameters = () => {
|
||||
const { currentObjectId } = useObjectsStore()
|
||||
const ObjectParameters = ({
|
||||
map_id
|
||||
}: {
|
||||
map_id: string
|
||||
}) => {
|
||||
|
||||
const { currentObjectId } = useObjectsStore().id[map_id]
|
||||
|
||||
const { data: valuesData, isValidating: valuesValidating } = useSWR(
|
||||
currentObjectId ? `/general/values/all?object_id=${currentObjectId}` : null,
|
||||
@ -42,13 +47,13 @@ const ObjectParameters = () => {
|
||||
sortedParams.map((param: IObjectParam) => {
|
||||
if (param.date_po == null) {
|
||||
return (
|
||||
<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]} />
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ interface ITCBParameterProps {
|
||||
vtable: string;
|
||||
inactive?: boolean;
|
||||
name: string;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const TCBParameter = ({
|
||||
value,
|
||||
vtable,
|
||||
name
|
||||
name,
|
||||
map_id
|
||||
}: ITCBParameterProps) => {
|
||||
|
||||
//Get value
|
||||
@ -80,7 +82,7 @@ const TCBParameter = ({
|
||||
const TCBValue = (vtable: string) => {
|
||||
if (tables.includes(vtable)) {
|
||||
return (
|
||||
<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 {
|
||||
return (
|
||||
|
@ -10,6 +10,7 @@ interface TableValueProps {
|
||||
type: 'value' | 'boolean' | 'number' | 'select' | 'string';
|
||||
unit?: string | null | undefined;
|
||||
vtable?: string;
|
||||
map_id: string;
|
||||
}
|
||||
|
||||
const TableValue = ({
|
||||
@ -17,10 +18,10 @@ const TableValue = ({
|
||||
value,
|
||||
type,
|
||||
unit,
|
||||
vtable
|
||||
vtable,
|
||||
map_id
|
||||
}: TableValueProps) => {
|
||||
const { selectedDistrict } = useObjectsStore()
|
||||
|
||||
const { selectedDistrict } = useObjectsStore().id[map_id]
|
||||
//Get available values
|
||||
const { data: tcbAll, isValidating } = useSWR(
|
||||
type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null,
|
||||
@ -56,10 +57,10 @@ const TableValue = ({
|
||||
/>
|
||||
:
|
||||
type === 'select' && !isValidating && tcbAll ?
|
||||
<Select size='xs' data={tcbAll} defaultValue={JSON.stringify(value)} />
|
||||
<Select size='xs' data={tcbAll} value={JSON.stringify(value)} />
|
||||
:
|
||||
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>
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ const TabsPane = ({
|
||||
</ScrollAreaAutosize>
|
||||
|
||||
|
||||
<ScrollAreaAutosize h='100%' offsetScrollbars p='xs'>
|
||||
<ScrollAreaAutosize h='100%' offsetScrollbars>
|
||||
{tabs.map(tab => (
|
||||
<Tabs.Panel key={tab.value} value={tab.value}>
|
||||
<Tabs.Panel p='xs' key={tab.value} value={tab.value}>
|
||||
{tab.view}
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
|
@ -1,28 +1,22 @@
|
||||
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 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 VectorImageLayer from "ol/layer/VectorImage";
|
||||
import Map from "ol/Map";
|
||||
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import proj4 from "proj4";
|
||||
import { selectStyle } from "./MapStyles";
|
||||
import { Type } from "ol/geom/Geometry";
|
||||
import { Draw, Modify, Snap, Translate } from "ol/interaction";
|
||||
import { noModifierKeys } from "ol/events/condition";
|
||||
import { IGeometryType, IRectCoords } from "../../interfaces/map";
|
||||
import { never, noModifierKeys, platformModifierKeyOnly, primaryAction } from "ol/events/condition";
|
||||
import { IGeometryType } from "../../interfaces/map";
|
||||
import { uploadCoordinates } from "../../actions/map";
|
||||
import { ImageStatic } from "ol/source";
|
||||
import ImageLayer from "ol/layer/Image";
|
||||
import { IFigure, ILine } from "../../interfaces/gis";
|
||||
import { fromCircle, fromExtent } from "ol/geom/Polygon";
|
||||
import { measureStyleFunction, modifyStyle } from "./Measure/MeasureStyles";
|
||||
import { getCurrentTool, getMeasureClearPrevious, getMeasureType, getTipPoint, setStatusText } from "../../store/map";
|
||||
import { MutableRefObject } from "react";
|
||||
import { getSelectedCity, getSelectedYear, setSelectedRegion } from "../../store/objects";
|
||||
import { getCurrentTool, getDraw, getDrawingLayerSource, getImageLayer, getMap, getMeasureClearPrevious, getMeasureDraw, getMeasureModify, getMeasureSource, getMeasureType, getOverlayLayerSource, getSnap, getTipPoint, getTranslate, setDraw, setFile, setMeasureDraw, setPolygonExtent, setRectCoords, setSnap, setTranslate } from "../../store/map";
|
||||
import Collection from "ol/Collection";
|
||||
|
||||
const calculateAngle = (coords: [number, number][]) => {
|
||||
const [start, end] = coords;
|
||||
@ -34,8 +28,7 @@ const calculateAngle = (coords: [number, number][]) => {
|
||||
export function processLine(
|
||||
line: ILine,
|
||||
scaling: number,
|
||||
mapCenter: Coordinate,
|
||||
linesLayer: MutableRefObject<VectorLayer<VectorSource>>
|
||||
mapCenter: Coordinate
|
||||
) {
|
||||
const x1 = line.x1 * scaling
|
||||
const y1 = line.y1 * scaling
|
||||
@ -49,22 +42,25 @@ export function processLine(
|
||||
[center[0] + x2, center[1] - y2],
|
||||
]
|
||||
|
||||
const feature = new Feature(new LineString(testCoords))
|
||||
const geometry = new LineString(testCoords)
|
||||
|
||||
feature.set('type', line.type)
|
||||
feature.set('geometry_type', 'line')
|
||||
feature.set('planning', line.planning)
|
||||
feature.set('object_id', line.object_id)
|
||||
feature.set('rotation', calculateAngle(testCoords))
|
||||
|
||||
linesLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(geometry),
|
||||
properties: {
|
||||
type: line.type,
|
||||
geometry_type: 'line',
|
||||
object_id: line.object_id,
|
||||
planning: line.planning,
|
||||
rotation: calculateAngle(testCoords)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function processFigure(
|
||||
figure: IFigure,
|
||||
scaling: number,
|
||||
mapCenter: Coordinate,
|
||||
figuresLayer: MutableRefObject<VectorLayer<VectorSource>>
|
||||
) {
|
||||
if (figure.figure_type_id == 1) {
|
||||
const width = figure.width * scaling
|
||||
@ -82,12 +78,15 @@ export function processFigure(
|
||||
const ellipseGeom = fromCircle(circleGeom, 64)
|
||||
ellipseGeom.scale(1, height / width)
|
||||
|
||||
const feature = new Feature(ellipseGeom)
|
||||
|
||||
feature.set('type', figure.type)
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(ellipseGeom),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
object_id: figure.object_id,
|
||||
planning: figure.planning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (figure.figure_type_id == 3) {
|
||||
@ -107,14 +106,15 @@ export function processFigure(
|
||||
if (coords) {
|
||||
const polygon = new Polygon([coords])
|
||||
|
||||
const feature = new Feature({
|
||||
geometry: polygon
|
||||
})
|
||||
|
||||
feature.set('object_id', figure.object_id)
|
||||
feature.set('planning', figure.planning)
|
||||
feature.set('type', figure.type)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(polygon),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
object_id: figure.object_id,
|
||||
planning: figure.planning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,22 +140,179 @@ export function processFigure(
|
||||
const geometry1 = new Polygon([testCoords])
|
||||
const anchor1 = center
|
||||
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
|
||||
const feature1 = new Feature(geometry1)
|
||||
feature1.set('object_id', figure.object_id)
|
||||
feature1.set('planning', figure.planning)
|
||||
feature1.set('type', figure.type)
|
||||
feature1.set('angle', figure.angle)
|
||||
figuresLayer.current?.getSource()?.addFeature(feature1)
|
||||
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: new GeoJSON().writeGeometryObject(geometry1),
|
||||
properties: {
|
||||
type: figure.type,
|
||||
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
|
||||
export const updateImageSource = (
|
||||
map_id: string,
|
||||
imageUrl: string,
|
||||
imageLayer: React.MutableRefObject<ImageLayer<ImageStatic>>,
|
||||
polygonFeature: Feature<Polygon>,
|
||||
setPolygonExtent: (value: React.SetStateAction<Extent | undefined>) => void,
|
||||
setRectCoords: React.Dispatch<React.SetStateAction<IRectCoords | undefined>>
|
||||
polygonFeature: Feature<Polygon>
|
||||
) => {
|
||||
const newExtent = polygonFeature.getGeometry()?.getExtent();
|
||||
|
||||
@ -164,14 +321,14 @@ export const updateImageSource = (
|
||||
const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2]
|
||||
const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3]
|
||||
|
||||
setRectCoords({
|
||||
setRectCoords(map_id, {
|
||||
bl: bottomLeft,
|
||||
tl: topLeft,
|
||||
tr: topRight,
|
||||
br: bottomRight
|
||||
})
|
||||
|
||||
setPolygonExtent(newExtent)
|
||||
setPolygonExtent(map_id, newExtent)
|
||||
|
||||
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
|
||||
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
|
||||
@ -179,55 +336,58 @@ export const updateImageSource = (
|
||||
url: imageUrl,
|
||||
imageExtent: originalExtent,
|
||||
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
|
||||
});
|
||||
imageLayer.current.setSource(newImageSource);
|
||||
})
|
||||
getImageLayer(map_id).setSource(newImageSource)
|
||||
}
|
||||
};
|
||||
|
||||
export const addInteractions = (
|
||||
drawingLayerSource: React.MutableRefObject<VectorSource<Feature<Geometry>>>,
|
||||
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>,
|
||||
map_id: string
|
||||
) => {
|
||||
const currentTool = getCurrentTool()
|
||||
const clearPrevious = getMeasureClearPrevious()
|
||||
const measureType = getMeasureType()
|
||||
const tipPoint = getTipPoint()
|
||||
const currentTool = getCurrentTool(map_id)
|
||||
const clearPrevious = getMeasureClearPrevious(map_id)
|
||||
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') {
|
||||
draw.current = new Draw({
|
||||
source: drawingLayerSource.current,
|
||||
setDraw(map_id, new Draw({
|
||||
source: getDrawingLayerSource(map_id),
|
||||
type: currentTool as Type,
|
||||
condition: noModifierKeys
|
||||
})
|
||||
}))
|
||||
|
||||
draw.current.on('drawend', function (s) {
|
||||
console.log(s.feature.getGeometry()?.getType())
|
||||
let type: IGeometryType = 'POLYGON'
|
||||
const draw = getDraw(map_id)
|
||||
|
||||
switch (s.feature.getGeometry()?.getType()) {
|
||||
case 'LineString':
|
||||
type = 'LINE'
|
||||
break
|
||||
case 'Polygon':
|
||||
type = 'POLYGON'
|
||||
break
|
||||
default:
|
||||
type = 'POLYGON'
|
||||
break
|
||||
if (draw) {
|
||||
draw.on('drawend', function (s) {
|
||||
console.log(s.feature.getGeometry()?.getType())
|
||||
let type: IGeometryType = 'POLYGON'
|
||||
|
||||
switch (s.feature.getGeometry()?.getType()) {
|
||||
case 'LineString':
|
||||
type = 'LINE'
|
||||
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') {
|
||||
@ -238,35 +398,45 @@ export const addInteractions = (
|
||||
const idleTip = 'Кликните, чтобы начать измерение';
|
||||
let tip = idleTip;
|
||||
|
||||
measureDraw.current = new Draw({
|
||||
source: measureSource.current,
|
||||
setMeasureDraw(map_id, new Draw({
|
||||
source: getMeasureSource(map_id),
|
||||
type: drawType,
|
||||
style: function (feature) {
|
||||
return measureStyleFunction(feature, drawType, tip);
|
||||
return measureStyleFunction(map_id, feature, drawType, tip);
|
||||
},
|
||||
});
|
||||
measureDraw.current.on('drawstart', function () {
|
||||
if (clearPrevious) {
|
||||
measureSource.current.clear();
|
||||
}
|
||||
measureModify.current.setActive(false);
|
||||
tip = activeTip;
|
||||
});
|
||||
measureDraw.current.on('drawend', function () {
|
||||
modifyStyle.setGeometry(tipPoint as Geometry);
|
||||
measureModify.current.setActive(true);
|
||||
map.current?.once('pointermove', function () {
|
||||
modifyStyle.setGeometry('');
|
||||
}))
|
||||
|
||||
const measureDraw = getMeasureDraw(map_id)
|
||||
|
||||
if (measureDraw) {
|
||||
measureDraw.on('drawstart', function () {
|
||||
if (clearPrevious) {
|
||||
getMeasureSource(map_id).clear();
|
||||
}
|
||||
measureModify.setActive(false);
|
||||
tip = activeTip;
|
||||
});
|
||||
tip = idleTip;
|
||||
});
|
||||
measureModify.current.setActive(true);
|
||||
map.current?.addInteraction(measureDraw.current);
|
||||
measureDraw.on('drawend', function () {
|
||||
modifyStyle.setGeometry(tipPoint as Geometry);
|
||||
measureModify.setActive(true);
|
||||
map?.once('pointermove', function () {
|
||||
modifyStyle.setGeometry('');
|
||||
});
|
||||
tip = idleTip;
|
||||
});
|
||||
measureModify.setActive(true)
|
||||
|
||||
map?.addInteraction(measureDraw)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTool == 'Mover') {
|
||||
translate.current = new Translate()
|
||||
map?.current?.addInteraction(translate.current)
|
||||
setTranslate(map_id, new Translate())
|
||||
|
||||
const translate = getTranslate(map_id)
|
||||
if (translate) {
|
||||
map?.addInteraction(translate)
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTool == 'Edit') {
|
||||
@ -275,90 +445,12 @@ export const addInteractions = (
|
||||
}
|
||||
}
|
||||
|
||||
export function regionsInit(
|
||||
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() || getSelectedYear()) return
|
||||
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) => {
|
||||
export const zoomToFeature = (map_id: string, feature: Feature) => {
|
||||
const geometry = feature.getGeometry()
|
||||
const extent = geometry?.getExtent()
|
||||
|
||||
if (map.current && extent) {
|
||||
map.current.getView().fit(extent, {
|
||||
if (getMap(map_id) && extent) {
|
||||
getMap(map_id)?.getView().fit(extent, {
|
||||
duration: 300,
|
||||
maxZoom: 19,
|
||||
})
|
||||
@ -366,8 +458,8 @@ const zoomToFeature = (map: React.MutableRefObject<Map | null>, feature: Feature
|
||||
}
|
||||
|
||||
// Function to save features to localStorage
|
||||
export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource> | null>) => {
|
||||
const features = layerRef.current?.getSource()?.getFeatures()
|
||||
export const saveFeatures = (map_id: string) => {
|
||||
const features = getDrawingLayerSource(map_id).getFeatures()
|
||||
if (features && features.length > 0) {
|
||||
const geoJSON = new GeoJSON()
|
||||
const featuresJSON = geoJSON.writeFeatures(features)
|
||||
@ -376,14 +468,14 @@ export const saveFeatures = (layerRef: MutableRefObject<VectorLayer<VectorSource
|
||||
}
|
||||
|
||||
// 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')
|
||||
if (savedFeatures) {
|
||||
const geoJSON = new GeoJSON()
|
||||
const features = geoJSON.readFeatures(savedFeatures, {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -534,6 +626,25 @@ function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number)
|
||||
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) {
|
||||
let center, coordinates, minRadius;
|
||||
const type = geometry.getType();
|
||||
@ -577,8 +688,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 {
|
||||
rotateProjection,
|
||||
calculateTransformations,
|
||||
calculateRotationAngle,
|
||||
calculateExtent,
|
||||
calculateCentroid,
|
||||
@ -586,5 +730,6 @@ export {
|
||||
normalize,
|
||||
getTileIndex,
|
||||
getGridCellPosition,
|
||||
calculateCenter
|
||||
calculateCenter,
|
||||
applyTransformations
|
||||
}
|
@ -21,7 +21,7 @@ interface Props {
|
||||
}
|
||||
|
||||
interface ViewerProps {
|
||||
url: string
|
||||
url: string | ArrayBuffer | Blob
|
||||
}
|
||||
|
||||
function PdfViewer({
|
||||
@ -82,7 +82,7 @@ function DocxViewer({
|
||||
)
|
||||
}
|
||||
|
||||
function ExcelViewer({
|
||||
export function ExcelViewer({
|
||||
url
|
||||
}: ViewerProps) {
|
||||
const previewContainerRef = useRef(null)
|
||||
@ -120,7 +120,7 @@ function ImageViewer({
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}>
|
||||
<img alt='image-preview' src={url} style={{
|
||||
<img alt='image-preview' src={url as string} style={{
|
||||
display: 'flex',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%'
|
||||
|
@ -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 SignUp from "../pages/auth/SignUp";
|
||||
import PasswordReset from "../pages/auth/PasswordReset";
|
||||
import TableTest from "../pages/TableTest";
|
||||
import ComponentTest from "../pages/ComponentTest";
|
||||
import MonitorPage from "../pages/MonitorPage";
|
||||
import Settings from "../pages/Settings";
|
||||
@ -14,6 +13,8 @@ import Reports from "../pages/Reports";
|
||||
import Servers from "../pages/Servers";
|
||||
import Boilers from "../pages/Boilers";
|
||||
import MapTest from "../pages/MapTest";
|
||||
import PrintReport from "../pages/PrintReport";
|
||||
import DBManager from "../pages/DBManager";
|
||||
|
||||
// Определение страниц с путями и компонентом для рендера
|
||||
|
||||
@ -135,15 +136,6 @@ const pages = [
|
||||
dashboard: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: "Table test",
|
||||
path: "/table-test",
|
||||
icon: <IconTable />,
|
||||
component: <TableTest />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: "Component test",
|
||||
path: "/component-test",
|
||||
@ -153,6 +145,24 @@ const pages = [
|
||||
dashboard: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: "Print report test",
|
||||
path: "/print-report-test",
|
||||
icon: <IconComponents />,
|
||||
component: <PrintReport />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
label: "DB Manager",
|
||||
path: "/db-manager",
|
||||
icon: <IconComponents />,
|
||||
component: <DBManager />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
},
|
||||
]
|
||||
|
||||
export {
|
||||
|
@ -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 {
|
||||
object_id: string,
|
||||
figure_type_id: number,
|
||||
@ -34,3 +41,57 @@ export interface ILine {
|
||||
type: number,
|
||||
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;
|
||||
}
|
@ -50,7 +50,7 @@ function DashboardLayout() {
|
||||
</Group>
|
||||
|
||||
<Group w='auto'>
|
||||
{getPageTitle()}
|
||||
<Text fw='600'>{getPageTitle()}</Text>
|
||||
</Group>
|
||||
|
||||
<Group id='header-portal' w='auto' ml='auto'>
|
||||
@ -123,14 +123,20 @@ function DashboardLayout() {
|
||||
leftSection={item.icon}
|
||||
active={location.pathname === item.path}
|
||||
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.Main>
|
||||
<Flex w={{
|
||||
sm: desktopOpened ? 'calc(100% - 200px)' : 'calc(100% - 50px)',
|
||||
base: '100%'
|
||||
}} h={'calc(100% - 60px)'} style={{ transition: "width 0.2s ease" }} pos={'fixed'}>
|
||||
<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'}>
|
||||
<Outlet />
|
||||
</Flex>
|
||||
</AppShell.Main>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useBoilers } from '../hooks/swrHooks'
|
||||
import { Badge, ScrollAreaAutosize, Table, Text } from '@mantine/core'
|
||||
import { IBoiler } from '../interfaces/fuel'
|
||||
import { Stack, Text } from '@mantine/core'
|
||||
import CustomTable from '../components/CustomTable'
|
||||
|
||||
function Boilers() {
|
||||
const [boilersPage, setBoilersPage] = useState(1)
|
||||
@ -24,21 +24,43 @@ function Boilers() {
|
||||
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 (
|
||||
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'>
|
||||
<Stack w={'100%'} h={'100%'} p='sm'>
|
||||
<Text size="xl" fw={600}>
|
||||
Котельные
|
||||
</Text>
|
||||
|
||||
{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.Thead>
|
||||
<Table.Tr>
|
||||
@ -77,8 +99,8 @@ function Boilers() {
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
}
|
||||
</ScrollAreaAutosize>
|
||||
} */}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
78
client/src/pages/DBManager.tsx
Normal file
78
client/src/pages/DBManager.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { Button, Card, Flex, Grid, Group, ScrollAreaAutosize, Stack, Tabs, Text } from '@mantine/core'
|
||||
import useSWR from 'swr'
|
||||
import { BASE_URL } from '../constants'
|
||||
import { fetcher } from '../http/axiosInstance'
|
||||
import { useEffect, 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, setOffset] = useState(0)
|
||||
const [limit, setLimit] = 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
|
@ -1,8 +1,81 @@
|
||||
import { Container, Stack, Tabs } from '@mantine/core'
|
||||
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() {
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
967
client/src/pages/PrintReport.tsx
Normal file
967
client/src/pages/PrintReport.tsx
Normal file
@ -0,0 +1,967 @@
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
|
||||
const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Шаблон >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<xsl:template match="/">
|
||||
<xsl:processing-instruction name="mso-application">
|
||||
<xsl:text>progid="Excel.Sheet"</xsl:text>
|
||||
</xsl:processing-instruction>
|
||||
<Workbook>
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Стили шаблона >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Styles>
|
||||
<Style ss:ID="Bordered1">
|
||||
<Borders>
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Bottom" ss:Weight="1" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Left" ss:Weight="1" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Right" ss:Weight="1" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Top" ss:Weight="1" />
|
||||
</Borders>
|
||||
</Style>
|
||||
<Style ss:ID="Bordered2">
|
||||
<Borders>
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Bottom" ss:Weight="2" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Left" ss:Weight="2" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Right" ss:Weight="2" />
|
||||
<Border ss:LineStyle="Continuous" ss:Position="Top" ss:Weight="2" />
|
||||
</Borders>
|
||||
</Style>
|
||||
<Style ss:ID="Column" ss:Parent="Bordered2">
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center" ss:WrapText="1" />
|
||||
<Font ss:Color="#000000" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
</Style>
|
||||
<Style ss:ID="Cell1" ss:Parent="Bordered1">
|
||||
<Font ss:Bold="1" ss:Color="#000000" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
<Interior ss:Color="#47FF19" ss:Pattern="Solid" />
|
||||
</Style>
|
||||
<Style ss:ID="Cell2" ss:Parent="Bordered1">
|
||||
<Font ss:Bold="1" ss:Color="#003366" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
<Interior ss:Color="#F2FF02" ss:Pattern="Solid" />
|
||||
</Style>
|
||||
<Style ss:ID="Cell3" ss:Parent="Bordered1">
|
||||
<Font ss:Bold="1" ss:Color="#333333" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
<Interior ss:Color="#C0C0C0" ss:Pattern="Solid" />
|
||||
</Style>
|
||||
<Style ss:ID="Cell4" ss:Parent="Bordered1">
|
||||
<Font ss:Color="#000000" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
</Style>
|
||||
|
||||
<Style ss:ID="CellReg" ss:Parent="Bordered1">
|
||||
<Font ss:Color="#003366" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
|
||||
</Style>
|
||||
<Style ss:ID="CellCity" ss:Parent="Bordered1">
|
||||
<Font ss:Color="#333333" ss:FontName="Arial Cyr" x:CharSet="204" />
|
||||
|
||||
</Style>
|
||||
</Styles>
|
||||
<Worksheet ss:Name="Жилищний фонд">
|
||||
<Table>
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="150" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="70" />
|
||||
<Column ss:Width="80" />
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Столбцы таблицы >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Регион</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Нас. пункт</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Котельная</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Адрес</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Кол-во домов</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Этаж.</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Общая пл.</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Кол-во прожив.</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Отопление</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">ГВС</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">ХВС</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Канализация</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сальдо исход.</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row>
|
||||
<Cell ss:Index="9" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Данные таблицы >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<xsl:apply-templates select="//Kvp" />
|
||||
</Table>
|
||||
<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">
|
||||
<FreezePanes />
|
||||
<FrozenNoSplit />
|
||||
<SplitHorizontal>2</SplitHorizontal>
|
||||
<TopRowBottomPane>2</TopRowBottomPane>
|
||||
<ActivePane>2</ActivePane>
|
||||
</WorksheetOptions>
|
||||
</Worksheet>
|
||||
<Worksheet ss:Name="Договорные подключения">
|
||||
<Table>
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="100" />
|
||||
<Column ss:Width="150" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="50" />
|
||||
<Column ss:Width="70" />
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Столбцы таблицы >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Регион</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Нас. пункт</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Котельная</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Наименование</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Кол-во объектов</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем здания</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Отопление</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Подогрев</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">ГВС</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">ХВС</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Канализация</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeDown="1" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Итого</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<Row>
|
||||
<Cell ss:Index="7" ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Объем</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Column">
|
||||
<Data ss:Type="String">Сумма</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Данные таблицы >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<xsl:apply-templates select="//Jur" />
|
||||
</Table>
|
||||
</Worksheet>
|
||||
</Workbook>
|
||||
</xsl:template>
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Шаблон данных >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<xsl:template match="Kvp">
|
||||
<xsl:choose>
|
||||
<xsl:when test="style_id = 1">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с улусом >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:MergeAcross="3" ss:StyleID="Cell1">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="region" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="house_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="String" />
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="square" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="people_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="saldo_out" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 2">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с населенным пунктом >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="region" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="Cell2">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="house_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="String" />
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="square" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="people_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="saldo_out" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 3">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с котельной >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="region" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="3" ss:MergeAcross="1" ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="boiler" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="house_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="String" />
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="square" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="people_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="saldo_out" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 4">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с объектами >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="CellReg">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="region" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="2" ss:StyleID="CellCity">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="3" ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="boiler" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:Index="4" ss:StyleID="Cell4">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="address" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="floors" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="square" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="people_count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_heat" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_hwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_cwater" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="volume_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_sewers" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="saldo_out" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
<xsl:template match="Jur">
|
||||
<xsl:choose>
|
||||
<xsl:when test="style_id = 1">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с улусом >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:MergeAcross="3" ss:StyleID="Cell1">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="reg" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="building_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_vol" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell1">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="all_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 2">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с населенным пунктом >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="reg" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="2" ss:MergeAcross="2" ss:StyleID="Cell2">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="building_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_vol" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell2">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="all_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 3">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с котельной >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="reg" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="3" ss:MergeAcross="1" ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="boiler" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="count" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="building_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_vol" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell3">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="all_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
<xsl:when test="style_id = 4">
|
||||
<!-- <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Строка с объектами >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
|
||||
<Row>
|
||||
<Cell ss:StyleID="CellReg">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="reg" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="2" ss:StyleID="CellCity">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="city" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:Index="3" ss:StyleID="Cell3">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="boiler" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:MergeAcross="1" ss:Index="4" ss:StyleID="Cell4">
|
||||
<Data ss:Type="String">
|
||||
<xsl:value-of select="name" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="building_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_volume" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="heat_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="gcal_ost_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_vol" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="hw_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_xvs" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="vol_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="sum_kan" />
|
||||
</Data>
|
||||
</Cell>
|
||||
<Cell ss:StyleID="Cell4">
|
||||
<Data ss:Type="Number">
|
||||
<xsl:value-of select="all_summa" />
|
||||
</Data>
|
||||
</Cell>
|
||||
</Row>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>`
|
||||
|
||||
const PrintReport = () => {
|
||||
const handleGenerateExcel = () => {
|
||||
// Define the example XML data
|
||||
const xmlData = `
|
||||
<root>
|
||||
<Kvp>
|
||||
<style_id>1</style_id>
|
||||
<region>Region 1</region>
|
||||
<city>City 1</city>
|
||||
<house_count>10</house_count>
|
||||
<square>1000</square>
|
||||
<people_count>500</people_count>
|
||||
<volume_heat>200</volume_heat>
|
||||
<sum_heat>1000</sum_heat>
|
||||
<volume_hwater>300</volume_hwater>
|
||||
<sum_hwater>1500</sum_hwater>
|
||||
<volume_cwater>400</volume_cwater>
|
||||
<sum_cwater>2000</sum_cwater>
|
||||
<volume_sewers>500</volume_sewers>
|
||||
<sum_sewers>2500</sum_sewers>
|
||||
<saldo_out>300</saldo_out>
|
||||
</Kvp>
|
||||
</root>
|
||||
`;
|
||||
|
||||
// Parse the XSL template and XML data
|
||||
const parser = new DOMParser();
|
||||
const xslDoc = parser.parseFromString(xslTemplate, "application/xml");
|
||||
const xmlDoc = parser.parseFromString(xmlData, "application/xml");
|
||||
|
||||
// Apply the transformation
|
||||
const xsltProcessor = new XSLTProcessor();
|
||||
xsltProcessor.importStylesheet(xslDoc);
|
||||
const resultDocument = xsltProcessor.transformToDocument(xmlDoc);
|
||||
|
||||
// Serialize the result to a string
|
||||
const serializer = new XMLSerializer();
|
||||
const resultXml = serializer.serializeToString(resultDocument);
|
||||
|
||||
// Add missing Excel-specific headers if needed
|
||||
const correctedXml = `<?xml version="1.0" encoding="utf-8"?>\n` + resultXml
|
||||
|
||||
// Convert to Blob and trigger download
|
||||
const blob = new Blob([correctedXml], { type: "application/vnd.ms-excel" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "template.xls";
|
||||
link.click();
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex p='sm'>
|
||||
<Button onClick={handleGenerateExcel}>Сохранить в Excel</Button>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintReport
|
@ -93,11 +93,11 @@ export default function Reports() {
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{[...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 => {
|
||||
row[key] = report[key][id];
|
||||
});
|
||||
return (<Table.Tr key={row.id}>
|
||||
return (<Table.Tr key={row.id as number}>
|
||||
{[
|
||||
{ field: 'id', headerName: '№', width: 70 },
|
||||
...Object.keys(report).map(key => ({
|
||||
@ -125,7 +125,7 @@ export default function Reports() {
|
||||
)
|
||||
}
|
||||
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>)
|
||||
|
@ -1,63 +1,42 @@
|
||||
import { useRoles } from '../hooks/swrHooks'
|
||||
import { CreateField } from '../interfaces/create'
|
||||
import RoleService from '../services/RoleService'
|
||||
import FormFields from '../components/FormFields'
|
||||
import { Button, Loader, Modal, ScrollAreaAutosize, Table } from '@mantine/core'
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
import { IRole } from '../interfaces/role'
|
||||
import { Loader, Stack } from '@mantine/core'
|
||||
import CustomTable from '../components/CustomTable'
|
||||
|
||||
export default function Roles() {
|
||||
const { roles, isError, isLoading } = useRoles()
|
||||
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
const createFields: CreateField[] = [
|
||||
{ key: 'name', headerName: 'Название', type: 'string', required: true, 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 (isLoading) return <Loader />
|
||||
|
||||
return (
|
||||
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'>
|
||||
<Button onClick={open}>
|
||||
Добавить роль
|
||||
</Button>
|
||||
|
||||
<Modal opened={opened} onClose={close} title="Создание роли" centered>
|
||||
<FormFields
|
||||
fields={createFields}
|
||||
submitHandler={RoleService.createRole}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Table highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{columns.map(column => (
|
||||
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{roles.map((role: IRole) => (
|
||||
<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>
|
||||
<Stack w={'100%'} h={'100%'} p='sm'>
|
||||
<CustomTable
|
||||
createFields={createFields}
|
||||
submitHandler={RoleService.createRole}
|
||||
data={roles} columns={[
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'id',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Название',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Описание',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
]} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -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
|
@ -3,10 +3,8 @@ import { IRole } from "../interfaces/role"
|
||||
import { useEffect, useState } from "react"
|
||||
import { CreateField } from "../interfaces/create"
|
||||
import UserService from "../services/UserService"
|
||||
import FormFields from "../components/FormFields"
|
||||
import { Badge, Button, Flex, Loader, Modal, Pagination, ScrollAreaAutosize, Select, Table } from "@mantine/core"
|
||||
import { useDisclosure } from "@mantine/hooks"
|
||||
import { IUser } from "../interfaces/user"
|
||||
import { Flex, Loader, Stack } from "@mantine/core"
|
||||
import CustomTable from "../components/CustomTable"
|
||||
|
||||
export default function Users() {
|
||||
const { users, isError, isLoading } = useUsers()
|
||||
@ -21,8 +19,6 @@ export default function Users() {
|
||||
}
|
||||
}, [roles])
|
||||
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
const createFields: CreateField[] = [
|
||||
{ key: 'email', headerName: 'E-mail', 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: '' },
|
||||
]
|
||||
|
||||
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 (
|
||||
<div>
|
||||
Произошла ошибка при получении данных.
|
||||
@ -65,97 +43,50 @@ export default function Users() {
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollAreaAutosize w={'100%'} h={'100%'} p='sm'>
|
||||
<Button onClick={open}>
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
|
||||
<Modal opened={opened} onClose={close} title="Регистрация пользователя" centered>
|
||||
<FormFields
|
||||
fields={createFields}
|
||||
submitHandler={UserService.createUser}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Stack w={'100%'} h={'100%'} p='xs'>
|
||||
{Array.isArray(roleOptions) &&
|
||||
<Table highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
{columns.map(column => (
|
||||
<Table.Th key={column.field}>{column.headerName}</Table.Th>
|
||||
))}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{users.map((user: IUser) => (
|
||||
<Table.Tr
|
||||
key={user.id}
|
||||
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
|
||||
>
|
||||
{columns.map(column => {
|
||||
if (column.field === 'is_active') {
|
||||
return (
|
||||
user.is_active ? (
|
||||
<Table.Td key={column.field}>
|
||||
<Badge fullWidth variant="light">
|
||||
Активен
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
) : (
|
||||
<Table.Td key={column.field}>
|
||||
<Badge color="gray" fullWidth variant="light">
|
||||
Отключен
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
)
|
||||
)
|
||||
}
|
||||
else if (column.field === 'role_id') {
|
||||
return (
|
||||
<Table.Td key={column.field}>
|
||||
<Select
|
||||
data={roleOptions}
|
||||
defaultValue={user.role_id.toString()}
|
||||
variant="unstyled"
|
||||
allowDeselect={false}
|
||||
/>
|
||||
</Table.Td>
|
||||
)
|
||||
}
|
||||
else return (
|
||||
<Table.Td key={column.field}>{user[column.field as keyof IUser]}</Table.Td>
|
||||
)
|
||||
})}
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
<CustomTable
|
||||
createFields={createFields}
|
||||
submitHandler={UserService.createUser}
|
||||
data={users}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'E-mail',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'login',
|
||||
header: 'Логин',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'phone',
|
||||
header: 'Телефон',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Имя',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'surname',
|
||||
header: 'Фамилия',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'is_active',
|
||||
header: 'Активен',
|
||||
cell: (info) => info.getValue(),
|
||||
},
|
||||
{
|
||||
accessorKey: 'role_id',
|
||||
header: 'Роль',
|
||||
cell: (info) => info.getValue(),
|
||||
}
|
||||
]} />
|
||||
}
|
||||
|
||||
<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>
|
||||
</Stack>
|
||||
)
|
||||
}
|
39
client/src/services/GisService.ts
Normal file
39
client/src/services/GisService.ts
Normal 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
43
client/src/store/app.ts
Normal 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
|
||||
}
|
@ -1,108 +1,658 @@
|
||||
import { create } from 'zustand';
|
||||
import { ToolType } from '../types/tools';
|
||||
import { Point } from 'ol/geom';
|
||||
import { Geometry, Point } from 'ol/geom';
|
||||
import Map from 'ol/Map';
|
||||
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, 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, zoomToFeature } from '../components/map/mapUtils';
|
||||
import { setCurrentObjectId, setSelectedRegion } from './objects';
|
||||
import View from 'ol/View';
|
||||
|
||||
export type Mode = 'edit' | 'view'
|
||||
|
||||
interface MapState {
|
||||
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;
|
||||
id: Record<string, {
|
||||
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;
|
||||
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;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const useMapStore = create<MapState>(() => ({
|
||||
currentTool: null,
|
||||
measureType: "LineString",
|
||||
measureShowSegments: true,
|
||||
measureClearPrevious: true,
|
||||
tipPoint: null,
|
||||
map: null,
|
||||
currentZ: undefined,
|
||||
currentX: undefined,
|
||||
currentY: undefined,
|
||||
currentCoordinate: null,
|
||||
statusText: '',
|
||||
satMapsProvider: 'google',
|
||||
selectedObjectType: null,
|
||||
alignMode: false
|
||||
}));
|
||||
id: {}
|
||||
}))
|
||||
|
||||
const setCurrentZ = (z: number | undefined) => useMapStore.setState(() => ({ currentZ: z }))
|
||||
const setCurrentX = (x: number | undefined) => useMapStore.setState(() => ({ currentX: x }))
|
||||
const setCurrentY = (y: number | undefined) => useMapStore.setState(() => ({ currentY: y }))
|
||||
const setCurrentCoordinate = (c: Coordinate | null) => useMapStore.setState(() => ({ currentCoordinate: c }))
|
||||
const setStatusText = (t: string) => useMapStore.setState(() => ({ statusText: t }))
|
||||
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 setAlignMode = (m: boolean) => useMapStore.setState(() => ({ alignMode: m }))
|
||||
export const initializeMapState = (
|
||||
id: string
|
||||
) => {
|
||||
useMapStore.setState((state) => {
|
||||
const baseLayer = new TileLayer({ source: new OSM(), properties: { id: uuidv4(), name: 'OpenStreetMap' } })
|
||||
|
||||
const setTipPoint = (tipPoint: Point | null) => {
|
||||
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, layers: (layer) => layer.get('type') === 'line', })
|
||||
lineSelect.on('select', (e) => {
|
||||
if (e.selected[0]) {
|
||||
setCurrentObjectId(id, e.selected[0].get('object_id'))
|
||||
}
|
||||
})
|
||||
|
||||
// Figure select
|
||||
const figureSelect = new Select({ condition: click, style: highlightStyleRed, layers: (layer) => layer.get('type') === 'figure' })
|
||||
figureSelect.on('select', (e) => {
|
||||
if (e.selected[0]) {
|
||||
setCurrentObjectId(id, e.selected[0].get('object_id'))
|
||||
}
|
||||
})
|
||||
|
||||
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 map = new Map({
|
||||
controls: [],
|
||||
layers: [
|
||||
baseLayer,
|
||||
satLayer,
|
||||
staticMapLayer,
|
||||
regionsLayer,
|
||||
districtBoundLayer,
|
||||
citiesLayer,
|
||||
linesLayer,
|
||||
figuresLayer,
|
||||
drawingLayer,
|
||||
imageLayer,
|
||||
overlayLayer,
|
||||
nodeLayer,
|
||||
measureLayer,
|
||||
alignModeLayer
|
||||
]
|
||||
})
|
||||
|
||||
map.addInteraction(regionSelect)
|
||||
map.addInteraction(lineSelect)
|
||||
map.addInteraction(figureSelect)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getTipPoint = () => useMapStore.getState().tipPoint
|
||||
export const getFiguresLayer = (id: string) => useMapStore.getState().id[id].figuresLayer
|
||||
export const getLinesLayer = (id: string) => useMapStore.getState().id[id].linesLayer
|
||||
export const getMeasureModify = (id: string) => useMapStore.getState().id[id].measureModify
|
||||
|
||||
const getAlignMode = () => useMapStore.getState().alignMode
|
||||
export const getOverlayLayerSource = (id: string) => useMapStore.getState().id[id].overlayLayerSource
|
||||
export const setOverlayLayerSource = (id: string, source: VectorSource) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], overlayLayerSource: source }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getMap = () => useMapStore.getState().map
|
||||
export const getImageLayer = (id: string) => useMapStore.getState().id[id].imageLayer
|
||||
export const setImageLayer = (id: string, layer: ImageLayer<ImageStatic>) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], imageLayer: layer }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const setMeasureType = (tool: "LineString" | "Polygon") => useMapStore.setState(() => ({ measureType: tool }))
|
||||
export const getTranslate = (id: string) => useMapStore.getState().id[id].translate
|
||||
export const setTranslate = (id: string, translate: Translate | null) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], translate: translate }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getMeasureType = () => useMapStore.getState().measureType
|
||||
export const getSnap = (id: string) => useMapStore.getState().id[id].snap
|
||||
export const setSnap = (id: string, snap: Snap | null) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], snap: snap }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const setCurrentTool = (tool: ToolType) => {
|
||||
tool === useMapStore.getState().currentTool
|
||||
? useMapStore.setState(() => ({ currentTool: null }))
|
||||
: useMapStore.setState(() => ({ currentTool: tool }))
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getCurrentTool = () => useMapStore.getState().currentTool
|
||||
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
|
||||
|
||||
const getMeasureShowSegments = () => useMapStore.getState().measureShowSegments
|
||||
export const setMeasureDraw = (id: string, draw: Draw) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], measureDraw: draw }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getMeasureClearPrevious = () => useMapStore.getState().measureClearPrevious
|
||||
export const setAlignPoints = (id: string, c: Coordinate[]) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], alignPoints: c }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const setMeasureShowSegments = (bool: boolean) => {
|
||||
useMapStore.setState(() => ({ measureShowSegments: bool }))
|
||||
}
|
||||
export const setRectCoords = (id: string, c: IRectCoords | undefined) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], rectCoords: c }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const setMeasureClearPrevious = (bool: boolean) => {
|
||||
useMapStore.setState(() => ({ measureClearPrevious: bool }))
|
||||
}
|
||||
export const setPolygonExtent = (id: string, extent: Extent | undefined) => useMapStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], polygonExtent: extent }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export {
|
||||
setCurrentTool,
|
||||
getCurrentTool,
|
||||
setMeasureShowSegments,
|
||||
setMeasureClearPrevious,
|
||||
getMeasureShowSegments,
|
||||
getMeasureClearPrevious,
|
||||
setMeasureType,
|
||||
getMeasureType,
|
||||
getTipPoint,
|
||||
setTipPoint,
|
||||
setCurrentZ,
|
||||
setCurrentX,
|
||||
setCurrentY,
|
||||
setCurrentCoordinate,
|
||||
setStatusText,
|
||||
setSatMapsProvider,
|
||||
setSelectedObjectType,
|
||||
setMap,
|
||||
getMap,
|
||||
setAlignMode,
|
||||
getAlignMode
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
})
|
@ -1,60 +1,87 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface ObjectsState {
|
||||
selectedRegion: number | null;
|
||||
selectedDistrict: number | null;
|
||||
selectedCity: number | null;
|
||||
selectedYear: number | null;
|
||||
currentObjectId: string | null;
|
||||
id: Record<string, {
|
||||
selectedRegion: number | null;
|
||||
selectedDistrict: number | null;
|
||||
selectedCity: number | null;
|
||||
selectedYear: number | null;
|
||||
currentObjectId: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const useObjectsStore = create<ObjectsState>(() => ({
|
||||
selectedRegion: null,
|
||||
selectedDistrict: null,
|
||||
selectedCity: null,
|
||||
selectedYear: null,
|
||||
currentObjectId: null
|
||||
id: {}
|
||||
}));
|
||||
|
||||
const getSelectedCity = () => {
|
||||
return useObjectsStore.getState().selectedCity
|
||||
}
|
||||
export const initializeObjectsState = (
|
||||
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) => {
|
||||
useObjectsStore.setState(() => ({ selectedRegion: region }))
|
||||
}
|
||||
export const getSelectedCity = (id: string) => useObjectsStore.getState().id[id].selectedCity
|
||||
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) => {
|
||||
useObjectsStore.setState(() => ({ selectedDistrict: district }))
|
||||
}
|
||||
export const getSelectedRegion = (id: string) => useObjectsStore.getState().id[id].selectedRegion
|
||||
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) => {
|
||||
useObjectsStore.setState(() => ({ selectedCity: city }))
|
||||
}
|
||||
export const getSelectedDistrict = (id: string) => useObjectsStore.getState().id[id].selectedDistrict
|
||||
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) => {
|
||||
useObjectsStore.setState(() => ({ selectedYear: year }))
|
||||
}
|
||||
export const getSelectedYear = (id: string) => useObjectsStore.getState().id[id].selectedYear
|
||||
export const setSelectedYear = (id: string, year: number | null) => useObjectsStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], selectedYear: year }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getCurrentObjectId = () => {
|
||||
return useObjectsStore.getState().currentObjectId
|
||||
}
|
||||
|
||||
const setCurrentObjectId = (objectId: string | null) => {
|
||||
useObjectsStore.setState(() => ({ currentObjectId: objectId }))
|
||||
}
|
||||
|
||||
export {
|
||||
getSelectedCity,
|
||||
setSelectedCity,
|
||||
getSelectedYear,
|
||||
setSelectedYear,
|
||||
getCurrentObjectId,
|
||||
setCurrentObjectId,
|
||||
setSelectedRegion,
|
||||
setSelectedDistrict
|
||||
}
|
||||
export const getCurrentObjectId = (id: string) => useObjectsStore.getState().id[id].currentObjectId
|
||||
export const setCurrentObjectId = (id: string, objectId: string | null) => useObjectsStore.setState((state) => {
|
||||
return {
|
||||
id: {
|
||||
...state.id,
|
||||
[id]: { ...state.id[id], currentObjectId: objectId }
|
||||
}
|
||||
}
|
||||
})
|
@ -2896,6 +2896,14 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz"
|
||||
@ -3690,6 +3698,11 @@ ignore@^5.2.0, ignore@^5.3.1:
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
|
||||
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:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz"
|
||||
@ -4077,6 +4090,16 @@ jspdf@^2.5.1, jspdf@^2.5.2:
|
||||
dompurify "^2.5.4"
|
||||
html2canvas "^1.0.0-rc.5"
|
||||
|
||||
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:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
|
||||
@ -4107,6 +4130,13 @@ levn@^0.4.1:
|
||||
prelude-ls "^1.2.1"
|
||||
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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"
|
||||
@ -4560,7 +4590,7 @@ pako@^2.0.4:
|
||||
resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz"
|
||||
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
|
||||
|
||||
pako@~1.0.5:
|
||||
pako@~1.0.2, pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
@ -5236,6 +5266,19 @@ readable-stream@^3.5.0, readable-stream@^3.6.0:
|
||||
string_decoder "^1.1.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:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz"
|
||||
@ -5481,12 +5524,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"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-buffer@~5.1.0:
|
||||
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:
|
||||
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==
|
||||
@ -5544,6 +5582,11 @@ sass-embedded@*, sass-embedded@^1.79.5:
|
||||
sass-embedded-win32-ia32 "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:
|
||||
version "0.23.2"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
|
||||
@ -5621,7 +5664,7 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
|
||||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
setimmediate@^1.0.4:
|
||||
setimmediate@^1.0.4, setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
|
||||
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||
|
98
ems/package-lock.json
generated
98
ems/package-lock.json
generated
@ -19,7 +19,7 @@
|
||||
"ioredis": "^5.4.1",
|
||||
"md5": "^2.3.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"pg": "^8.13.0",
|
||||
"pg": "^8.13.1",
|
||||
"pump": "^3.0.0",
|
||||
"sharp": "^0.33.5",
|
||||
"tedious": "^18.6.1"
|
||||
@ -31,6 +31,7 @@
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.4.1",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/pump": "^1.1.3",
|
||||
"@types/redis": "^4.0.11",
|
||||
"nodemon": "^3.1.4",
|
||||
@ -896,6 +897,74 @@
|
||||
"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": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/pump/-/pump-1.1.3.tgz",
|
||||
@ -2454,6 +2523,12 @@
|
||||
"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": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@ -2503,9 +2578,9 @@
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz",
|
||||
"integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==",
|
||||
"version": "8.13.1",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
||||
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.7.0",
|
||||
"pg-pool": "^3.7.0",
|
||||
@ -2547,6 +2622,15 @@
|
||||
"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": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
||||
@ -2630,6 +2714,12 @@
|
||||
"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": {
|
||||
"version": "5.19.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.19.1.tgz",
|
||||
|
143
ems/src/api/db/index.ts
Normal file
143
ems/src/api/db/index.ts
Normal 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
|
@ -171,27 +171,34 @@ router.get('/objects/list', async (req: Request, res: Response) => {
|
||||
const result = await tediousQuery(
|
||||
`
|
||||
SELECT
|
||||
tTypes.id AS id,
|
||||
tTypes.name AS name,
|
||||
COUNT(vObjects.type) AS count
|
||||
${GeneralDB}..tTypes.id AS id,
|
||||
${GeneralDB}..tTypes.name AS name,
|
||||
COUNT(vo.type) AS count,
|
||||
tr.r,
|
||||
tr.g,
|
||||
tr.b
|
||||
FROM
|
||||
vObjects
|
||||
${GeneralDB}..vObjects vo
|
||||
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
|
||||
vObjects.id_city = ${city_id} AND vObjects.year = ${year}
|
||||
vo.id_city = ${city_id} AND vo.year = ${year}
|
||||
AND
|
||||
(
|
||||
CASE
|
||||
WHEN TRY_CAST(vObjects.planning AS BIT) IS NOT NULL THEN TRY_CAST(vObjects.planning AS BIT)
|
||||
WHEN vObjects.planning = 'TRUE' THEN 1
|
||||
WHEN vObjects.planning = 'FALSE' THEN 0
|
||||
WHEN TRY_CAST(vo.planning AS BIT) IS NOT NULL THEN TRY_CAST(vo.planning AS BIT)
|
||||
WHEN vo.planning = 'TRUE' THEN 1
|
||||
WHEN vo.planning = 'FALSE' THEN 0
|
||||
ELSE NULL
|
||||
END
|
||||
) = ${planning}
|
||||
GROUP BY
|
||||
tTypes.id,
|
||||
tTypes.name;
|
||||
${GeneralDB}..tTypes.id,
|
||||
${GeneralDB}..tTypes.name,
|
||||
tr.r,
|
||||
tr.g,
|
||||
tr.b;
|
||||
`
|
||||
)
|
||||
res.status(200).json(result)
|
||||
|
@ -4,6 +4,19 @@ import { GeneralDB, GisDB } from '../../constants/db';
|
||||
import { pgQuery } from '../../utils/postgres';
|
||||
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
|
||||
|
@ -10,7 +10,7 @@ const router = express.Router()
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, path.join(__dirname, '..', 'public', 'temp'))
|
||||
cb(null, path.join(__dirname, '..', '..', '..', 'public', 'temp'))
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, Date.now() + path.extname(file.originalname))
|
||||
|
@ -1,6 +1,7 @@
|
||||
import express from 'express'
|
||||
import bodyParser from 'body-parser'
|
||||
import cors from 'cors'
|
||||
import dbRouter from './api/db'
|
||||
import generalRouter from './api/general'
|
||||
import gisRouter from './api/gis'
|
||||
import nodesRouter from './api/nodes'
|
||||
@ -17,6 +18,7 @@ app.use(bodyParser.json())
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
|
||||
app.use('/db', dbRouter)
|
||||
app.use('/general', generalRouter)
|
||||
app.use('/gis', gisRouter)
|
||||
app.use('/nodes', nodesRouter)
|
||||
|
Reference in New Issue
Block a user