326 lines
9.9 KiB
TypeScript
326 lines
9.9 KiB
TypeScript
import {
|
|
Accordion,
|
|
AccordionDetails,
|
|
AccordionSummary,
|
|
Box,
|
|
Checkbox,
|
|
CircularProgress,
|
|
Drawer,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
Tab,
|
|
Tabs,
|
|
TextField
|
|
} from "@mui/material";
|
|
import { ExpandMore } from "@mui/icons-material";
|
|
import SearchBar from "common/SearchBar";
|
|
import { useDeviceAPI, useMobile } from "hooks";
|
|
import { Component, Device } from "models";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import React, { useEffect, useState } from "react";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { useGlobalState } from "providers";
|
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
|
|
|
interface TabPanelProps {
|
|
children?: React.ReactNode;
|
|
index: any;
|
|
value: any;
|
|
}
|
|
|
|
function TabPanelMine(props: TabPanelProps) {
|
|
const { children, value, index, ...other } = props;
|
|
|
|
return (
|
|
<div
|
|
role="tabpanel"
|
|
hidden={value !== index}
|
|
aria-labelledby={`simple-tab-${index}`}
|
|
{...other}>
|
|
{value === index && <React.Fragment>{children}</React.Fragment>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const useStyles = makeStyles(() => ({
|
|
drawerPaperDesktop: {
|
|
height: "100%",
|
|
width: "40%"
|
|
},
|
|
drawerPaperMobile: {
|
|
height: "60%",
|
|
width: "100%"
|
|
}
|
|
}));
|
|
interface Props {
|
|
open: boolean;
|
|
deviceTags?: string[];
|
|
close: (reload?: boolean) => void;
|
|
linkedDevices: Map<string, pond.ComprehensiveDevice>;
|
|
updateLinkedDevices: (dev: pond.ComprehensiveDevice, linked: boolean) => void;
|
|
linkedComponents?: Component[];
|
|
updateLinkedComponents?: (
|
|
deviceID: string | number,
|
|
componen: Component,
|
|
linked: boolean
|
|
) => void;
|
|
//all of these need to be sent in for the device preference selector to work
|
|
devicePrefMap?: Map<number, number>; //map using the device id as the key and its pref enums value as the value
|
|
prefOptions?: JSX.Element[];
|
|
devicePrefChanged?: (device: Device, newPref: number) => void;
|
|
}
|
|
|
|
export default function DeviceLinkDrawer(props: Props) {
|
|
const {
|
|
open,
|
|
close,
|
|
linkedDevices,
|
|
linkedComponents,
|
|
updateLinkedDevices,
|
|
updateLinkedComponents,
|
|
deviceTags,
|
|
devicePrefMap,
|
|
prefOptions,
|
|
devicePrefChanged
|
|
} = props;
|
|
const [value, setValue] = useState(0);
|
|
const [{as}] = useGlobalState();
|
|
const deviceAPI = useDeviceAPI();
|
|
const classes = useStyles();
|
|
const isMobile = useMobile();
|
|
const [deviceList, setDeviceList] = useState<Map<string, pond.ComprehensiveDevice>>(
|
|
new Map<string, pond.ComprehensiveDevice>()
|
|
);
|
|
const [searchVal, setSearchVal] = useState("");
|
|
const [searchedDevices, setSearchedDevices] = useState<Map<string, pond.ComprehensiveDevice>>(
|
|
new Map<string, pond.ComprehensiveDevice>()
|
|
);
|
|
const [accordionController, setAccordionController] = useState<boolean[]>([]);
|
|
const [loadingDevices, setLoadingDevices] = useState(false);
|
|
|
|
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
|
setValue(newValue);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (loadingDevices) return;
|
|
setLoadingDevices(true);
|
|
deviceAPI
|
|
.list(
|
|
500,
|
|
0,
|
|
"asc",
|
|
"key",
|
|
deviceTags?.toString(),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
true
|
|
)
|
|
.then(resp => {
|
|
let devMap = new Map<string, pond.ComprehensiveDevice>();
|
|
resp.data.comprehensiveDevices.forEach(compDev => {
|
|
if (compDev.device?.settings?.deviceId) {
|
|
devMap.set(compDev.device?.settings?.deviceId.toString(), compDev);
|
|
}
|
|
});
|
|
setDeviceList(devMap);
|
|
setSearchedDevices(devMap);
|
|
})
|
|
.catch(err => {})
|
|
.finally(() => {
|
|
setLoadingDevices(false);
|
|
});
|
|
//eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [deviceAPI, as]);
|
|
|
|
useEffect(() => {
|
|
let searchedDevices: Map<string, pond.ComprehensiveDevice> = new Map();
|
|
if (searchVal !== "") {
|
|
deviceList.forEach(dev => {
|
|
if (dev.device?.settings?.name) {
|
|
if (
|
|
dev.device?.settings?.name.toLowerCase().includes(searchVal.toLowerCase()) ||
|
|
dev.device?.settings?.deviceId.toString().includes(searchVal)
|
|
) {
|
|
searchedDevices.set(dev.device.settings.deviceId.toString(), dev);
|
|
}
|
|
} else {
|
|
if (dev.device?.settings?.deviceId.toString().includes(searchVal)) {
|
|
searchedDevices.set(dev.device.settings.deviceId.toString(), dev);
|
|
}
|
|
}
|
|
});
|
|
setSearchedDevices(searchedDevices);
|
|
} else {
|
|
setSearchedDevices(deviceList);
|
|
}
|
|
}, [searchVal, deviceList]);
|
|
|
|
//sets the length of the array of booleans to control opening each of the accordions
|
|
useEffect(() => {
|
|
let accordionControls: boolean[] = [];
|
|
linkedDevices.forEach(() => {
|
|
accordionControls.push(false);
|
|
});
|
|
deviceList.forEach(dev => {
|
|
if (
|
|
dev.device?.settings?.deviceId &&
|
|
!linkedDevices.get(dev.device?.settings?.deviceId.toString())
|
|
) {
|
|
accordionControls.push(false);
|
|
}
|
|
});
|
|
setAccordionController(accordionControls);
|
|
}, [linkedDevices, deviceList]);
|
|
|
|
const closeDrawer = () => {
|
|
close();
|
|
};
|
|
|
|
const devicesTab = () => {
|
|
return (
|
|
<Box>
|
|
Select Devices to link
|
|
<List>
|
|
<SearchBar
|
|
value={searchVal}
|
|
onChange={val => {
|
|
setSearchVal(val);
|
|
}}
|
|
/>
|
|
{Array.from(searchedDevices.values()).map(dev => {
|
|
let defCheck = linkedDevices.has(dev.device?.settings?.deviceId.toString() ?? "");
|
|
return (
|
|
<ListItem key={dev.device?.settings?.deviceId}>
|
|
<ListItemText
|
|
primary={dev.device?.settings?.name ?? "Device " + dev.device?.settings?.deviceId}
|
|
/>
|
|
<Checkbox
|
|
onChange={(_, checked) => {
|
|
if (dev.device?.settings?.deviceId) {
|
|
updateLinkedDevices(dev, checked);
|
|
}
|
|
}}
|
|
edge="end"
|
|
defaultChecked={defCheck}
|
|
inputProps={{ "aria-labelledby": dev.device?.settings?.name }}
|
|
/>
|
|
</ListItem>
|
|
);
|
|
})}
|
|
</List>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const linkedDevicesTab = () => {
|
|
let linkOptions: pond.ComprehensiveDevice[] = [];
|
|
Array.from(linkedDevices.keys()).forEach(key => {
|
|
let dev = deviceList.get(key);
|
|
let linkedDev = linkedDevices.get(key);
|
|
if (dev) {
|
|
linkOptions.push(dev);
|
|
} else if (linkedDev) {
|
|
linkOptions.push(linkedDev);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<Box>
|
|
From the linked Devices select which components to use
|
|
{linkOptions.map((op, i) => {
|
|
let device = Device.any(op.device);
|
|
let pref = 0;
|
|
if (devicePrefMap) {
|
|
pref = devicePrefMap.get(device.id()) ?? 0;
|
|
}
|
|
return (
|
|
<Accordion
|
|
key={device.id()}
|
|
expanded={accordionController[i]}
|
|
onChange={(_, expanded) => {
|
|
let accordionControls = accordionController;
|
|
accordionControls[i] = expanded;
|
|
setAccordionController([...accordionControls]);
|
|
}}>
|
|
<AccordionSummary expandIcon={<ExpandMore />}>{device.name()}</AccordionSummary>
|
|
<AccordionDetails>
|
|
<List>
|
|
{op.components.map(comp => {
|
|
let component = Component.any(comp);
|
|
return (
|
|
<ListItem key={component.key()}>
|
|
<ListItemText primary={component.name()} />
|
|
<Checkbox
|
|
onChange={(_, checked) => {
|
|
if (component.key() && device.id() && updateLinkedComponents) {
|
|
updateLinkedComponents(device.id(), component, checked);
|
|
}
|
|
}}
|
|
edge="end"
|
|
checked={
|
|
linkedComponents &&
|
|
linkedComponents.some(el => el.key() === component.key())
|
|
}
|
|
inputProps={{ "aria-labelledby": component.name() }}
|
|
/>
|
|
</ListItem>
|
|
);
|
|
})}
|
|
</List>
|
|
{devicePrefMap && prefOptions && devicePrefChanged && (
|
|
<TextField
|
|
title="Gate Device Type"
|
|
select
|
|
value={pref}
|
|
variant="outlined"
|
|
onChange={e => {
|
|
pref = parseInt(e.target.value);
|
|
devicePrefChanged(device, pref);
|
|
}}
|
|
fullWidth>
|
|
{prefOptions}
|
|
</TextField>
|
|
)}
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Drawer
|
|
open={open}
|
|
onClose={closeDrawer}
|
|
classes={{ paper: isMobile ? classes.drawerPaperMobile : classes.drawerPaperDesktop }}
|
|
anchor={isMobile ? "bottom" : "right"}>
|
|
{open && (
|
|
<React.Fragment>
|
|
<Tabs value={value} indicatorColor="primary" textColor="primary" onChange={handleChange}>
|
|
<Tab label="Devices" />
|
|
{linkedComponents && <Tab label="Linked Devices" />}
|
|
</Tabs>
|
|
<TabPanelMine value={value} index={0}>
|
|
{loadingDevices ? (
|
|
<Box style={{ textAlign: "center", paddingTop: 20 }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
) : (
|
|
devicesTab()
|
|
)}
|
|
</TabPanelMine>
|
|
{linkedComponents && (
|
|
<TabPanelMine value={value} index={1}>
|
|
{linkedDevicesTab()}
|
|
</TabPanelMine>
|
|
)}
|
|
</React.Fragment>
|
|
)}
|
|
</Drawer>
|
|
);
|
|
}
|