NestJS backend rewrite; migrate client to FluentUI V9
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { CSSProperties, useEffect, useRef, useState } from 'react'
|
||||
import 'ol/ol.css'
|
||||
import { Modify } from 'ol/interaction'
|
||||
import { ImageStatic, Vector as VectorSource } from 'ol/source'
|
||||
@ -14,8 +14,8 @@ import { addInteractions, handleImageDrop, loadFeatures, processFigure, processL
|
||||
import useSWR, { SWRConfiguration } from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { ActionIcon, Autocomplete, CloseButton, Flex, Select as MantineSelect, MantineStyleProp, rem, useMantineColorScheme, Portal, Menu, Button, Group, Divider, LoadingOverlay, Stack, Container, Transition, } from '@mantine/core'
|
||||
import { IconBoxMultiple, IconBoxPadding, IconChevronDown, IconChevronLeft, IconPlus, IconSearch, IconUpload, } from '@tabler/icons-react'
|
||||
import { useMantineColorScheme } from '@mantine/core'
|
||||
import { IconBoxMultiple, IconBoxPadding, IconChevronLeft, IconPlus, IconUpload, } from '@tabler/icons-react'
|
||||
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
|
||||
import axios from 'axios'
|
||||
import MapToolbar from './MapToolbar/MapToolbar'
|
||||
@ -34,6 +34,8 @@ import GisService from '../../services/GisService'
|
||||
import MapMode from './MapMode'
|
||||
import { satMapsProviders, schemas } from '../../constants/map'
|
||||
import MapPrint from './MapPrint/MapPrint'
|
||||
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal } from '@fluentui/react-components'
|
||||
import { IRegion } from '../../interfaces/fuel'
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateOnFocus: false
|
||||
@ -177,7 +179,7 @@ const MapComponent = ({
|
||||
})
|
||||
}
|
||||
|
||||
const mapControlsStyle: MantineStyleProp = {
|
||||
const mapControlsStyle: CSSProperties = {
|
||||
borderRadius: '4px',
|
||||
zIndex: '1',
|
||||
backgroundColor: colorScheme === 'light' ? '#F0F0F0CC' : '#000000CC',
|
||||
@ -381,7 +383,7 @@ const MapComponent = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedRegion) {
|
||||
setSelectedRegion(id, null)
|
||||
setSelectedRegion(id, undefined)
|
||||
setSelectedYear(id, null)
|
||||
}
|
||||
}, [selectedRegion, selectedDistrict, id])
|
||||
@ -468,73 +470,154 @@ const MapComponent = ({
|
||||
<MapPrint id={id} mapElement={mapElement} />
|
||||
|
||||
{active &&
|
||||
<Portal target='#header-portal'>
|
||||
<Flex gap={'sm'} direction={'row'}>
|
||||
<Autocomplete
|
||||
form='search_object'
|
||||
<Portal mountNode={document.querySelector('#header-portal')}>
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
<Combobox
|
||||
placeholder="Поиск"
|
||||
flex={'1'}
|
||||
data={searchData ? searchData.map((item: { value: string, id_object: string }) => ({ label: item.value, value: item.id_object.toString() })) : []}
|
||||
//onSelect={(e) => console.log(e.currentTarget.value)}
|
||||
onChange={(value) => setSearchObject(value)}
|
||||
onOptionSubmit={(value) => setCurrentObjectId(id, value)}
|
||||
rightSection={
|
||||
searchObject !== '' && (
|
||||
<CloseButton
|
||||
size="sm"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
setSearchObject('')
|
||||
}}
|
||||
aria-label="Clear value"
|
||||
/>
|
||||
)
|
||||
}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
value={searchObject}
|
||||
/>
|
||||
|
||||
<MantineSelect
|
||||
placeholder="Регион"
|
||||
flex={'1'}
|
||||
data={regionsData ? regionsData.map((item: { name: string, id: number }) => ({ label: item.name, value: item.id.toString() })) : []}
|
||||
onChange={(value) => setSelectedRegion(id, Number(value))}
|
||||
clearable
|
||||
onClear={() => setSelectedRegion(id, null)}
|
||||
searchable
|
||||
value={selectedRegion ? selectedRegion.toString() : null}
|
||||
/>
|
||||
|
||||
<MantineSelect
|
||||
placeholder="Населённый пункт"
|
||||
flex={'1'}
|
||||
data={districtsData ? districtsData.map((item: { name: string, id: number, district_name: string }) => ({ label: [item.name, item.district_name].join(' - '), value: item.id.toString() })) : []}
|
||||
onChange={(value) => setSelectedDistrict(id, Number(value))}
|
||||
clearable
|
||||
onClear={() => { setSelectedDistrict(id, null) }}
|
||||
searchable
|
||||
value={selectedDistrict ? selectedDistrict.toString() : null}
|
||||
/>
|
||||
|
||||
<MantineSelect placeholder='Схема' w='92px'
|
||||
data={schemas.map(el => ({ label: el, value: el }))}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
setSelectedYear(id, Number(e))
|
||||
} else {
|
||||
setSelectedYear(id, null)
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setCurrentObjectId(id, data.optionValue);
|
||||
setSearchObject(
|
||||
searchData?.find((item: any) => item.id_object.toString() === data.optionValue)?.value ?? ""
|
||||
);
|
||||
}
|
||||
}}
|
||||
onClear={() => setSelectedYear(id, null)}
|
||||
value={selectedYear ? selectedYear?.toString() : null}
|
||||
onChange={(e) => {
|
||||
setSearchObject(e.currentTarget.value); // free typing like Mantine's onChange
|
||||
}}
|
||||
clearable
|
||||
/>
|
||||
style={{ minWidth: 'auto' }}
|
||||
>
|
||||
{searchData
|
||||
? searchData.map((item: { value: string; id_object: string }) => (
|
||||
<Option key={item.id_object} value={item.id_object.toString()}>
|
||||
{item.value}
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Combobox>
|
||||
|
||||
<Button variant={alignMode ? 'filled' : 'transparent'} onClick={() => setAlignMode(id, !alignMode)}>
|
||||
<IconBoxPadding style={{ width: rem(20), height: rem(20) }} />
|
||||
</Button>
|
||||
<Combobox
|
||||
placeholder="Регион"
|
||||
clearable
|
||||
// 👇 show label instead of id
|
||||
value={
|
||||
selectedRegion
|
||||
? regionsData?.find((item: IRegion) => item.id === selectedRegion)?.name ?? ""
|
||||
: ""
|
||||
}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSelectedRegion(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedRegion(id, undefined);
|
||||
}
|
||||
}}
|
||||
style={{ minWidth: 'auto' }}
|
||||
>
|
||||
{regionsData
|
||||
? regionsData.map((item: { name: string; id: number }) => (
|
||||
<Option key={item.id} value={item.id.toString()}>
|
||||
{item.name}
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Combobox>
|
||||
|
||||
<Menu position="bottom-end" transitionProps={{ transition: 'pop-top-right' }}>
|
||||
<Combobox
|
||||
placeholder="Населённый пункт"
|
||||
clearable
|
||||
value={
|
||||
selectedDistrict
|
||||
? districtsData?.find((item: { id: number }) => item.id === selectedDistrict)?.name +
|
||||
" - " +
|
||||
districtsData?.find((item: { id: number }) => item.id === selectedDistrict)?.district_name
|
||||
: ""
|
||||
}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSelectedDistrict(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedDistrict(id, null);
|
||||
}
|
||||
}}
|
||||
style={{ minWidth: 'auto' }}
|
||||
>
|
||||
{districtsData
|
||||
? districtsData.map(
|
||||
(item: { name: string; id: number; district_name: string }) => (
|
||||
<Option text={`${item.name} - ${item.district_name}`} key={item.id} value={item.id.toString()}>
|
||||
{item.name} - {item.district_name}
|
||||
</Option>
|
||||
)
|
||||
)
|
||||
: null}
|
||||
</Combobox>
|
||||
|
||||
|
||||
<Combobox
|
||||
placeholder="Схема"
|
||||
clearable
|
||||
style={{ width: "92px", minWidth: 'auto' }}
|
||||
value={selectedYear ? selectedYear.toString() : ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSelectedYear(id, Number(data.optionValue));
|
||||
} else {
|
||||
setSelectedYear(id, null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{schemas.map((el) => (
|
||||
<Option key={el} value={el}>
|
||||
{el}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
|
||||
|
||||
<Button icon={<IconBoxPadding />} appearance={alignMode ? 'primary' : 'transparent'} onClick={() => setAlignMode(id, !alignMode)} />
|
||||
|
||||
<Menu persistOnItemClick positioning={{ autoSize: true }}>
|
||||
<MenuTrigger disableButtonEnhancement>
|
||||
<MenuButton appearance='subtle' icon={<IconBoxMultiple />}>Слои</MenuButton>
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
<Field>Настройка видимости слоёв</Field>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<Combobox
|
||||
defaultValue={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label ?? ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSatMapsProvider(id, data.optionValue as SatelliteMapsProvider);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{satMapsProviders.map((provider) => (
|
||||
<Option text={provider.label} key={provider.value} value={provider.value}>
|
||||
{provider.label}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
}}>
|
||||
<Button icon={<IconUpload />} appearance='transparent' onClick={() => submitOverlay(file, polygonExtent, rectCoords)} />
|
||||
|
||||
<Button icon={<IconPlus />} appearance='transparent' title='Добавить подложку' />
|
||||
</div>
|
||||
<MapLayers map={map} />
|
||||
</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
|
||||
{/* <Menu position="bottom-end" transitionProps={{ transition: 'pop-top-right' }}>
|
||||
<Menu.Target>
|
||||
<Button variant='transparent'>
|
||||
<Group gap={7} wrap='nowrap' style={{ flexShrink: 0 }} title='Слои'>
|
||||
@ -562,68 +645,99 @@ const MapComponent = ({
|
||||
<MapLayers map={map} />
|
||||
</Flex>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Flex>
|
||||
</Portal>
|
||||
</Menu> */}
|
||||
</div>
|
||||
</Portal >
|
||||
}
|
||||
|
||||
<Container pos='absolute' w='100%' h='100%' p='0' fluid>
|
||||
<Flex direction='column' w='100%' h='100%'>
|
||||
<Flex w='100%' h='94%' p='xs' style={{ flexGrow: 1 }}>
|
||||
<Stack w='100%' maw='380px'>
|
||||
<Flex w='100%' h='100%' gap='xs'>
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', width: '100%', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: '380px' }}>
|
||||
<div style={{ display: 'flex', width: '100%', height: '100%', gap: '0.5rem' }}>
|
||||
{selectedRegion && selectedDistrict && selectedYear &&
|
||||
<Flex direction='column' h={'100%'} w={leftPaneHidden ? '0px' : '100%'} style={{ ...mapControlsStyle, transition: 'width .3s ease' }}>
|
||||
<div
|
||||
style={{
|
||||
...mapControlsStyle,
|
||||
transition: 'width .3s ease',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: leftPaneHidden ? '0px' : '100%',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<TabsPane defaultTab='objects' tabs={objectsPane} />
|
||||
<Divider />
|
||||
<TabsPane defaultTab='parameters' tabs={paramsPane} />
|
||||
</Flex>
|
||||
</div>
|
||||
}
|
||||
|
||||
{!!selectedRegion && !!selectedDistrict && !!selectedYear &&
|
||||
<Button p='0' variant='subtle' w='32' style={{ zIndex: '1' }} onClick={() => setLeftPaneHidden(!leftPaneHidden)}>
|
||||
<IconChevronLeft size={16} style={{ transform: `${leftPaneHidden ? 'rotate(180deg)' : ''}` }} />
|
||||
</Button>
|
||||
<Button
|
||||
icon={<IconChevronLeft size={16}
|
||||
style={{
|
||||
transform: `${leftPaneHidden ? 'rotate(180deg)' : ''}`,
|
||||
}} />}
|
||||
|
||||
style={{
|
||||
zIndex: '1',
|
||||
display: 'flex',
|
||||
height: 'min-content'
|
||||
}}
|
||||
appearance='subtle'
|
||||
onClick={() => setLeftPaneHidden(!leftPaneHidden)}
|
||||
/>
|
||||
}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack w='100%' align='center'>
|
||||
<Stack style={mapControlsStyle} w='fit-content'>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center' }} >
|
||||
<div style={{ ...mapControlsStyle, display: 'flex', flexDirection: 'column', width: 'fit-content' }}>
|
||||
<MapMode map_id={id} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack w='100%' maw='340px' align='flex-end' justify='space-between'>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: '340px', alignItems: 'flex-end', justifyContent: 'space-between' }}>
|
||||
{selectedRegion && selectedDistrict && selectedYear && mode === 'edit' &&
|
||||
<MapToolbar map_id={id} />
|
||||
}
|
||||
|
||||
<Transition
|
||||
mounted={!!selectedRegion && !!selectedDistrict && !!selectedYear}
|
||||
transition="slide-left"
|
||||
duration={200}
|
||||
timingFunction="ease"
|
||||
>
|
||||
{(styles) => <MapLegend style={styles} selectedDistrict={selectedDistrict} selectedYear={selectedYear} />}
|
||||
</Transition>
|
||||
</Stack>
|
||||
</Flex>
|
||||
{!!selectedRegion && !!selectedDistrict && !!selectedYear &&
|
||||
<MapLegend selectedDistrict={selectedDistrict} selectedYear={selectedYear} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Flex w='100%'>
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
<MapStatusbar
|
||||
map_id={id}
|
||||
mapControlsStyle={mapControlsStyle}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Container pos='absolute' fluid p={0} w='100%' h='100%' mah='100%' ref={mapElement} onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleImageDrop(e, id)}>
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%', maxHeight: '100%' }} ref={mapElement} onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleImageDrop(e, id)}>
|
||||
<div ref={tooltipRef}></div>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
{(linesValidating || figuresValidating) && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.6)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 9999,
|
||||
}}
|
||||
>
|
||||
<Spinner size="large" label="Загрузка..." />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LoadingOverlay visible={linesValidating || figuresValidating} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user