| | |
| | | import React, { useState, useRef, useEffect, useMemo, useCallback } from "react"; |
| | | import React, { useEffect, useState } from "react"; |
| | | import { useTranslate } from "react-admin"; |
| | | import { Drawer, Box, Typography, Grid, IconButton, Stack, useTheme } from '@mui/material'; |
| | | import { |
| | | Drawer, |
| | | Box, |
| | | Typography, |
| | | IconButton, |
| | | Stack, |
| | | useTheme, |
| | | List, |
| | | ListItemButton, |
| | | ListItemText, |
| | | ListItemAvatar, |
| | | Avatar, |
| | | CircularProgress, |
| | | } from '@mui/material'; |
| | | import CloseIcon from '@mui/icons-material/Close'; |
| | | import * as Common from '@/utils/common'; |
| | | import { PAGE_DRAWER_WIDTH } from '@/config/setting'; |
| | | import * as Tool from './tool'; |
| | | import { fetchAreaList } from './http'; |
| | | |
| | | import shelf from '/map/shelf.svg'; |
| | | import charge from '/map/charge.svg'; |
| | | import station from '/map/station.svg'; |
| | | import direction from '/map/direction.svg'; |
| | | |
| | | const items = [ |
| | | { src: shelf, label: 'page.map.devices.shelf', type: 'SHELF', scale: 1 }, |
| | | { src: charge, label: 'page.map.devices.charge', type: 'CHARGE', scale: .7 }, |
| | | { src: station, label: 'page.map.devices.station', type: 'STATION', scale: 1 }, |
| | | { src: direction, label: 'page.map.devices.direction', type: 'DIRECTION', scale: 1 }, |
| | | ]; |
| | | |
| | | const AreaList = (props) => { |
| | | const { |
| | | title, |
| | | const AreaList = ({ |
| | | open, |
| | | onCancel, |
| | | closeCallback, |
| | | onClose, |
| | | zoneId, |
| | | width = PAGE_DRAWER_WIDTH, |
| | | children |
| | | } = props; |
| | | onSelect, |
| | | }) => { |
| | | const translate = useTranslate(); |
| | | const theme = useTheme(); |
| | | const themeMode = theme.palette.mode; |
| | | const translate = useTranslate(); |
| | | |
| | | const [dragging, setDragging] = useState(false); |
| | | const [dragSprite, setDragSprite] = useState(null); |
| | | const [dragSpriteType, setDragSpriteType] = useState(null); |
| | | |
| | | const handleClose = () => { |
| | | onCancel(); |
| | | if (closeCallback) { |
| | | closeCallback(); |
| | | } |
| | | } |
| | | |
| | | const onDragStart = (e, type) => { |
| | | setDragging(true); |
| | | const sprite = Tool.generateSprite(type); |
| | | setDragSprite(sprite); |
| | | setDragSpriteType(type); |
| | | }; |
| | | const [areas, setAreas] = useState([]); |
| | | const [loading, setLoading] = useState(false); |
| | | |
| | | useEffect(() => { |
| | | const handleMouseMove = (e) => { |
| | | if (dragging) { |
| | | props.onDrop(dragSprite, dragSpriteType, e.clientX, e.clientY); |
| | | setDragging(false); |
| | | setDragSpriteType(null); |
| | | if (!open) { |
| | | setAreas([]); |
| | | return; |
| | | } |
| | | }; |
| | | window.addEventListener('mousemove', handleMouseMove); |
| | | return () => window.removeEventListener('mousemove', handleMouseMove); |
| | | }, [dragging, props.onDrop, props.onCancel]); |
| | | setLoading(true); |
| | | fetchAreaList(zoneId) |
| | | .then((list) => { |
| | | setAreas(Array.isArray(list) ? list : []); |
| | | }) |
| | | .finally(() => setLoading(false)); |
| | | }, [open, zoneId]); |
| | | |
| | | const handleItemClick = (area) => { |
| | | onSelect?.(area); |
| | | }; |
| | | |
| | | return ( |
| | | <Drawer |
| | | variant="persistent" |
| | | open={open} |
| | | anchor="right" |
| | | onClose={handleClose} |
| | | sx={{ zIndex: 100, opacity: .8 }} |
| | | onClose={onClose} |
| | | sx={{ zIndex: 100, opacity: 0.95 }} |
| | | > |
| | | {open && ( |
| | | <Box pt={12} width={{ xs: '100vW', sm: width }} height={'calc(100vh - 200px);'} mt={{ xs: 2, sm: 1 }} sx={{ |
| | | }}> |
| | | <Stack direction="row" p={2}> |
| | | <Typography variant="h6" flex="1"> |
| | | {/* {title || translate('page.map.area.title')} */} |
| | | <Stack direction="row" alignItems="center" px={3} py={2}> |
| | | <Typography variant="h6" flex={1}> |
| | | {translate('page.map.action.areaList')} |
| | | </Typography> |
| | | <IconButton onClick={handleClose} size="small"> |
| | | <IconButton onClick={onClose} size="small"> |
| | | <CloseIcon /> |
| | | </IconButton> |
| | | </Stack> |
| | | <Box p={3}> |
| | | |
| | | <Box px={3} pb={3}> |
| | | {loading ? ( |
| | | <Box display="flex" justifyContent="center" mt={6}> |
| | | <CircularProgress /> |
| | | </Box> |
| | | ) : ( |
| | | <List dense sx={{ maxHeight: '70vh', overflowY: 'auto' }}> |
| | | {areas.length === 0 && ( |
| | | <Typography variant="body2" color="text.secondary" textAlign="center" py={4}> |
| | | {translate('page.map.area.form.codesEmpty')} |
| | | </Typography> |
| | | )} |
| | | {areas.map((area) => ( |
| | | <ListItemButton |
| | | key={area.id} |
| | | onClick={() => handleItemClick(area)} |
| | | sx={{ |
| | | borderRadius: 2, |
| | | mb: 1, |
| | | boxShadow: |
| | | themeMode === 'light' |
| | | ? '0 2px 8px rgba(0,0,0,0.07)' |
| | | : '0 2px 8px rgba(255,255,255,0.12)', |
| | | }} |
| | | > |
| | | <ListItemAvatar> |
| | | <Avatar |
| | | variant="rounded" |
| | | sx={{ |
| | | bgcolor: area.color ? Number(area.color) : theme.palette.grey[400], |
| | | color: theme.palette.getContrastText( |
| | | area.color ? Number(area.color) : theme.palette.grey[400] |
| | | ), |
| | | }} |
| | | > |
| | | {area.name?.slice(0, 1)?.toUpperCase() || 'A'} |
| | | </Avatar> |
| | | </ListItemAvatar> |
| | | <ListItemText |
| | | primary={area.name || translate('page.map.area.form.name')} |
| | | secondary={area.code ? `${translate('page.map.area.form.code')}: ${area.code}` : ''} |
| | | /> |
| | | </ListItemButton> |
| | | ))} |
| | | </List> |
| | | )} |
| | | </Box> |
| | | </Box> |
| | | )} |
| | | </Drawer> |
| | | ) |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | export default AreaList; |