/
denis.dorogov
/
test-task
Обзор
Документация
Войти
/
denis.dorogov
/
test-task
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/framework/components/map/Map/Map.tsx
201 строка
7 KB
denis-dorogov
Добавлены возможности редактирования точек на карте
08 ноя 2025, 16:42
08 ноя 2025, 16:42
5324610
Код
Авторство
О чём код?
import { useEffect, useRef, useState } from 'react'; import { Circle, Group, Image, Layer, Stage, Text } from 'react-konva'; import { Paper } from '@mui/material'; import { useLocalStorage } from '@uidotdev/usehooks'; import { KonvaEventObject, Node, NodeConfig } from 'konva/lib/Node'; import useImage from 'use-image'; import mapSvg from 'assets/maps/tutzing.svg'; import { LocalStorageNamesAuth } from 'core/auth/enums'; import { MAP_HEIGHT, MAP_WIDTH, TRANSFORM_SKALE } from 'core/map/constants'; import mapPoints from 'core/map/mocks/model.json'; import { MapPointFullModel, MapPointModel } from 'core/map/types'; import ModalAddPoint from '../ModalAddPoint'; import ModalChangePoint from '../ModalChangePoint'; import styles from './Map.module.css'; const Map = () => { const stageRef = useRef<any>(null); const [img] = useImage(mapSvg); const [activePoint, setActivePoint] = useState<number | null>(null); const [lastId, setLastId] = useState<number>(0); const [pointsLocalStorage, savePointsLocalStorage] = useLocalStorage<MapPointFullModel[]>( LocalStorageNamesAuth.mapPoints, ); const [points, setPoints] = useState<MapPointFullModel[]>([]); const [addPointCoordinates, setAddPointCoordinates] = useState<{ x: number; y: number } | null>(null); const [changingPoint, setChangingPoint] = useState<MapPointFullModel | null>(null); const handleStageDBClick = (e: KonvaEventObject<MouseEvent, Node<NodeConfig>>) => { const stage = e.target.getStage(); const pos = stage?.getPointerPosition(); if (pos) { setAddPointCoordinates({ x: pos.x, y: pos.y }); } }; const handleGroupDBClick = (e: KonvaEventObject<MouseEvent, Node<NodeConfig>>, index: number) => { e.cancelBubble = true; setChangingPoint(points[index]); }; const onMouseEnter = (index: number) => { setActivePoint(index); }; const handleMouseLeave = () => { setActivePoint(null); }; const handleDragEnd = (e: KonvaEventObject<DragEvent, Node<NodeConfig>>, index: number) => { const newData: MapPointFullModel[] = points; const stage = e.target.getStage(); const pos = stage?.getPointerPosition(); if (pos) { newData[index] = { ...newData[index], x: pos.x, y: pos.y }; } savePointsLocalStorage(newData); setPoints(newData); }; const addPoint = (point: MapPointModel): void => { const newData: MapPointFullModel[] = points; newData.push({ id: lastId + 1, ...point }); setLastId(lastId + 1); savePointsLocalStorage(newData); setPoints(newData); setAddPointCoordinates(null); }; const clearAddPointCoordinates = () => { setAddPointCoordinates(null); }; const changePoint = (newItem: MapPointFullModel): void => { const newData: MapPointFullModel[] = points.map(item => (item.id === newItem.id ? newItem : item)); savePointsLocalStorage(newData); setPoints(newData); setChangingPoint(null); }; const clearChangingPoint = () => { setChangingPoint(null); }; const removePoint = (id: number) => { const index = points.findIndex(item => item.id === id); const newData = [...points]; newData.splice(index, 1); savePointsLocalStorage(newData); setPoints(newData); setChangingPoint(null); }; const transformInitialPointsData = (data: MapPointModel[]): MapPointFullModel[] => { setLastId(data.length - 1); return data.map((point, index) => { return { id: index, ...point, x: point.x * TRANSFORM_SKALE, y: point.y * TRANSFORM_SKALE }; }); }; const getMaxId = () => { const maxId = points.reduce((max, item) => (item.id > max ? item.id : max), 0); return maxId; }; useEffect(() => { if (pointsLocalStorage) { setPoints(pointsLocalStorage); } else { // Первичная загрузка точек из модели const points = transformInitialPointsData(mapPoints); savePointsLocalStorage(points); setPoints(points); } }, [pointsLocalStorage]); useEffect(() => { if (points) setLastId(getMaxId()); }, [points]); return ( <div className={styles.container}> <Paper elevation={3} sx={{ bgcolor: 'white', width: '100%', height: 632, padding: 2 }}> <Stage ref={stageRef} width={MAP_WIDTH} height={MAP_HEIGHT} onDblClick={e => handleStageDBClick(e)} scale={{ x: 1, y: 1 }} > <Layer scale={{ x: 1, y: 1 }}> <Image image={img} width={MAP_WIDTH} height={MAP_HEIGHT} /> {points.map((point, index) => ( <Group key={index} x={point.x} y={point.y} scale={{ x: 1, y: 1 }} draggable={true} onMouseEnter={() => onMouseEnter(index)} onMouseLeave={handleMouseLeave} onDragEnd={e => handleDragEnd(e, index)} onDblClick={e => handleGroupDBClick(e, index)} onTap={() => onMouseEnter(index)} > <Circle radius={activePoint === index ? 16 : 14} fill="#ff0000" stroke="#ffffff" strokeWidth={1} ></Circle> {/* Текст с количеством */} <Text text={point.amount.toString()} fontSize={10} fill="#ffffff" fontStyle="bold" align="center" verticalAlign="middle" width={18} height={6} offsetX={9} offsetY={2} listening={false} /> {/* Название точки (при наведении) */} <Text text={point.name} fontSize={14} fill="#333333" visible={activePoint === index} listening={false} ref={ref => { if (ref) { // Позиционируем текст над точкой ref.offsetX(ref.width() / 2); ref.y(-30); } }} /> </Group> ))} </Layer> </Stage> </Paper> <ModalAddPoint addPoint={addPoint} coordinates={addPointCoordinates} clear={clearAddPointCoordinates} /> <ModalChangePoint point={changingPoint} changePoint={changePoint} clear={clearChangingPoint} removePoint={removePoint} /> </div> ); }; export default Map;