import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import Draggable from 'react-draggable';
import {
Card,
CardContent,
Typography,
CardActions,
IconButton,
Table,
TableBody,
TableRow,
TableCell,
Menu,
MenuItem,
CardMedia,
Snackbar,
} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import CloseIcon from '@mui/icons-material/Close';
import ReplayIcon from '@mui/icons-material/Replay';
import PublishIcon from '@mui/icons-material/Publish';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import PendingIcon from '@mui/icons-material/Pending';
import { useTranslation } from './LocalizationProvider';
import RemoveDialog from './RemoveDialog';
import PositionValue from './PositionValue';
import { useDeviceReadonly } from '../util/permissions';
import usePositionAttributes from '../attributes/usePositionAttributes';
import { devicesActions } from '../../store';
import { useCatch, useCatchCallback } from '../../reactHelper';
import { useAttributePreference } from '../util/preferences';
import { snackBarDurationShortMs } from '../util/duration';
const useStyles = makeStyles((theme) => ({
card: {
pointerEvents: 'auto',
width: theme.dimensions.popupMaxWidth,
},
media: {
height: theme.dimensions.popupImageHeight,
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'flex-start',
},
mediaButton: {
color: theme.palette.primary.contrastText,
mixBlendMode: 'difference',
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: theme.spacing(1, 1, 0, 2),
},
content: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
maxHeight: theme.dimensions.cardContentMaxHeight,
overflow: 'auto',
},
delete: {
color: theme.palette.error.main,
},
icon: {
width: '25px',
height: '25px',
filter: 'brightness(0) invert(1)',
},
table: {
'& .MuiTableCell-sizeSmall': {
paddingLeft: 0,
paddingRight: 0,
},
},
cell: {
borderBottom: 'none',
},
actions: {
justifyContent: 'space-between',
},
root: ({ desktopPadding }) => ({
pointerEvents: 'none',
position: 'fixed',
zIndex: 5,
left: '50%',
[theme.breakpoints.up('md')]: {
left: `calc(50% + ${desktopPadding} / 2)`,
bottom: theme.spacing(3),
},
[theme.breakpoints.down('md')]: {
left: '50%',
bottom: `calc(${theme.spacing(3)} + ${theme.dimensions.bottomBarHeight}px)`,
},
transform: 'translateX(-50%)',
}),
}));
const StatusRow = ({ name, content }) => {
const classes = useStyles();
return (
{name}
{content}
);
};
const StatusCard = ({ deviceId, position, onClose, disableActions, desktopPadding = 0 }) => {
const classes = useStyles({ desktopPadding });
const navigate = useNavigate();
const dispatch = useDispatch();
const t = useTranslation();
const deviceReadonly = useDeviceReadonly();
const user = useSelector((state) => state.session.user);
const device = useSelector((state) => state.devices.items[deviceId]);
const deviceImage = device?.attributes?.deviceImage;
const positionAttributes = usePositionAttributes(t);
const positionItems = useAttributePreference('positionItems', 'speed,address,totalDistance,course');
const [anchorEl, setAnchorEl] = useState(null);
const [removing, setRemoving] = useState(false);
const [shared, setShared] = useState(false);
const handleRemove = useCatch(async (removed) => {
if (removed) {
const response = await fetch('/api/devices');
if (response.ok) {
dispatch(devicesActions.refresh(await response.json()));
} else {
throw Error(await response.text());
}
}
setRemoving(false);
});
const handleGeofence = useCatchCallback(async () => {
const newItem = {
name: t('sharedGeofence'),
area: `CIRCLE (${position.latitude} ${position.longitude}, 50)`,
};
const response = await fetch('/api/geofences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newItem),
});
if (response.ok) {
const item = await response.json();
const permissionResponse = await fetch('/api/permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceId: position.deviceId, geofenceId: item.id }),
});
if (!permissionResponse.ok) {
throw Error(await permissionResponse.text());
}
navigate(`/settings/geofence/${item.id}`);
} else {
throw Error(await response.text());
}
}, [navigate, position]);
const handleShare = useCatchCallback(async () => {
const expiration = dayjs().add(1, 'week').toISOString();
const response = await fetch('/api/devices/share', {
method: 'POST',
body: new URLSearchParams(`deviceId=${deviceId}&expiration=${expiration}`),
});
if (response.ok) {
const token = await response.text();
navigator.clipboard.writeText(`${window.location.origin}?token=${token}`);
setShared(true);
} else {
throw Error(await response.text());
}
}, [deviceId, setShared]);
return (
<>
{device && (
{deviceImage ? (
) : (
{device.name}
)}
{position && (
{positionItems.split(',').filter((key) => position.hasOwnProperty(key) || position.attributes.hasOwnProperty(key)).map((key) => (
)}
/>
))}
)}
setAnchorEl(e.currentTarget)}
disabled={!position}
>
navigate('/replay')}
disabled={disableActions || !position}
>
navigate(`/settings/device/${deviceId}/command`)}
disabled={disableActions}
>
navigate(`/settings/device/${deviceId}`)}
disabled={disableActions || deviceReadonly}
>
setRemoving(true)}
disabled={disableActions || deviceReadonly}
className={classes.delete}
>
)}
{position && (
)}
handleRemove(removed)}
/>
setShared(false)}
autoHideDuration={snackBarDurationShortMs}
message={t('sharedLinkCopied')}
/>
>
);
};
export default StatusCard;