481 lines
16 KiB
TypeScript
481 lines
16 KiB
TypeScript
import { Gate as IGate } from "models/Gate";
|
|
//import { MatchParams } from "navigation/Routes";
|
|
//import { Redirect, useHistory, useRouteMatch } from "react-router";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
import PageContainer from "./PageContainer";
|
|
import { useGlobalState, useGateAPI, useUserAPI } from "providers";
|
|
import {
|
|
Box,
|
|
ButtonBase,
|
|
Card,
|
|
CircularProgress,
|
|
Drawer,
|
|
Grid2 as Grid,
|
|
IconButton,
|
|
MenuItem,
|
|
Tab,
|
|
Tabs,
|
|
Theme,
|
|
Typography
|
|
} from "@mui/material";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import DeviceLinkDrawer from "common/DeviceLinkDrawer";
|
|
import { Component, Device, Scope } from "models";
|
|
import GateActions from "gate/GateActions";
|
|
import GateDevice from "gate/GateDevice";
|
|
import ObjectControls from "common/ObjectControls";
|
|
import { Link } from "@mui/icons-material";
|
|
import Chat from "chat/Chat";
|
|
import PlaneIcon from "products/AviationIcons/PlaneIcon";
|
|
import NotesIcon from "@mui/icons-material/Notes";
|
|
import { useMobile, useSnackbar, useThemeType } from "hooks";
|
|
import { clone } from "lodash";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import { makeStyles } from "@mui/styles";
|
|
|
|
|
|
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>
|
|
);
|
|
}
|
|
|
|
interface Props {
|
|
gateKey?: string;
|
|
useMobile?: boolean;
|
|
}
|
|
|
|
const useStyles = makeStyles((theme: Theme) => ({
|
|
//const themeType = theme.palette.type;
|
|
inactiveButton: {
|
|
color: useThemeType() === "light" ? theme.palette.common.black : theme.palette.common.white,
|
|
backgroundColor: "transparent",
|
|
width: theme.spacing(5),
|
|
height: theme.spacing(5),
|
|
border: "1px solid",
|
|
borderColor: theme.palette.divider
|
|
},
|
|
activeButton: {
|
|
color: useThemeType() === "light" ? theme.palette.common.black : theme.palette.common.white,
|
|
backgroundColor: theme.palette.primary.main,
|
|
width: theme.spacing(5),
|
|
height: theme.spacing(5),
|
|
border: "1px solid",
|
|
borderColor: theme.palette.divider
|
|
},
|
|
drawerPaper: {
|
|
height: "100%",
|
|
width: "30%"
|
|
}
|
|
}));
|
|
|
|
export default function Gate(props: Props) {
|
|
//const match = useRouteMatch<MatchParams>();
|
|
//const gateID = props.gateID ?? match.params.gateID;
|
|
const { gateKey } = props
|
|
const gateID = gateKey ?? useParams<{ gateKey: string }>()?.gateKey ?? "";
|
|
const classes = useStyles();
|
|
const gateAPI = useGateAPI();
|
|
const userAPI = useUserAPI();
|
|
const [gate, setGate] = useState<IGate>(IGate.create());
|
|
const [devices, setDevices] = useState<Map<string, pond.ComprehensiveDevice>>(
|
|
new Map<string, pond.ComprehensiveDevice>()
|
|
);
|
|
const [components, setComponents] = useState<Component[]>([]);
|
|
const [openDeviceDrawer, setOpenDeviceDrawer] = useState(false);
|
|
const [loadingGate, setLoadingGate] = useState(false);
|
|
const [{ user, as }] = useGlobalState();
|
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
|
const [tabVal, setTabVal] = useState(0);
|
|
//const history = useHistory();
|
|
const navigate = useNavigate();
|
|
const [displayTab, setDisplayTab] = useState(0);
|
|
const [openNoteDrawer, setOpenNoteDrawer] = useState(false);
|
|
const isMobile = useMobile();
|
|
const [devPrefs, setDevPrefs] = useState<Map<number, number>>(new Map<number, number>());
|
|
const { openSnack } = useSnackbar();
|
|
const [invalid, setInvalid] = useState(false);
|
|
|
|
const goToMap = () => {
|
|
navigate("/aviationMap", { state: { long: gate.longitude(), lat:gate.latitude() }})
|
|
};
|
|
|
|
useEffect(() => {
|
|
console.log(gateID)
|
|
let key = gateID;
|
|
let kind = "gate";
|
|
if (as) {
|
|
key = as;
|
|
kind = "team";
|
|
}
|
|
console.log(key)
|
|
console.log(kind)
|
|
userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
|
|
console.log(resp)
|
|
setPermissions(resp.permissions);
|
|
});
|
|
}, [as, gateID, userAPI, user]);
|
|
|
|
const loadGate = useCallback(() => {
|
|
let id = gateID;
|
|
setTabVal(0);
|
|
if (loadingGate || id === undefined || id === "") return;
|
|
setLoadingGate(true);
|
|
gateAPI
|
|
.getGatePageData(id, as)
|
|
.then(resp => {
|
|
//console.log(resp.data);
|
|
let p = new Map<number, pond.GateDeviceType>();
|
|
Object.keys(resp.data.preferences).forEach(k => {
|
|
let prefKey = parseInt(k);
|
|
p.set(
|
|
prefKey,
|
|
pond.GatePreferences.fromObject(resp.data.preferences[k]).gateDevice ??
|
|
pond.GateDeviceType.GATE_DEVICE_TYPE_UNKNOWN
|
|
);
|
|
});
|
|
setDevPrefs(p);
|
|
if (resp.data.gate) {
|
|
setGate(IGate.any(resp.data.gate));
|
|
}
|
|
if (resp.data.linkedDevices) {
|
|
let devMap = new Map<string, pond.ComprehensiveDevice>();
|
|
resp.data.linkedDevices.forEach(dev => {
|
|
if (dev.device?.settings?.deviceId) {
|
|
devMap.set(dev.device.settings.deviceId.toString(), dev);
|
|
}
|
|
});
|
|
setDevices(devMap);
|
|
}
|
|
if (resp.data.linkedComponents) {
|
|
setComponents(resp.data.linkedComponents.map(c => Component.any(c)));
|
|
}
|
|
})
|
|
.catch(err => {
|
|
setInvalid(true);
|
|
})
|
|
.finally(() => {
|
|
setLoadingGate(false);
|
|
});
|
|
//eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [gateAPI, gateID, as]);
|
|
|
|
useEffect(() => {
|
|
loadGate();
|
|
}, [loadGate]);
|
|
|
|
const updateGateDevicePrefs = (deviceID: number, newPref: pond.GateDeviceType) => {
|
|
gateAPI
|
|
.updatePrefs(gate.key, "device", deviceID.toString(), newPref, [gate.key], ["gate"], as)
|
|
.then(resp => {
|
|
openSnack("Updated gate device type");
|
|
// have to do the clone method to force the select box to update
|
|
let newPrefMap = clone(devPrefs);
|
|
newPrefMap.set(deviceID, newPref);
|
|
setDevPrefs(newPrefMap);
|
|
})
|
|
.catch(err => {
|
|
openSnack("Failed to update device type");
|
|
});
|
|
};
|
|
|
|
const deviceDrawer = () => {
|
|
return (
|
|
<DeviceLinkDrawer
|
|
deviceTags={["omniair"]}
|
|
devicePrefMap={devPrefs}
|
|
prefOptions={[
|
|
<MenuItem
|
|
key={pond.GateDeviceType.GATE_DEVICE_TYPE_UNKNOWN}
|
|
value={pond.GateDeviceType.GATE_DEVICE_TYPE_UNKNOWN}>
|
|
None
|
|
</MenuItem>,
|
|
<MenuItem
|
|
key={pond.GateDeviceType.GATE_DEVICE_TYPE_PCA}
|
|
value={pond.GateDeviceType.GATE_DEVICE_TYPE_PCA}>
|
|
PCA Unit
|
|
</MenuItem>
|
|
]}
|
|
devicePrefChanged={(device, pref) => {
|
|
updateGateDevicePrefs(device.id(), pref);
|
|
}}
|
|
open={openDeviceDrawer}
|
|
close={() => {
|
|
setOpenDeviceDrawer(false);
|
|
}}
|
|
linkedDevices={devices}
|
|
linkedComponents={components}
|
|
updateLinkedDevices={(device, linked) => {
|
|
let devMap = devices;
|
|
let id = device.device?.settings?.deviceId;
|
|
if (id) {
|
|
if (linked) {
|
|
gateAPI
|
|
.updateLink(gateID, "gate", id.toString(), "device", [
|
|
"read",
|
|
"write",
|
|
"grant",
|
|
"revoke"
|
|
], as)
|
|
.then(resp => {
|
|
if (id) {
|
|
devMap.set(id.toString(), device);
|
|
setTabVal(id);
|
|
}
|
|
setDevices(devMap);
|
|
})
|
|
.catch(err => {
|
|
console.log("error linking device");
|
|
});
|
|
} else {
|
|
gateAPI.updateLink(gateID, "gate", id.toString(), "device", [], as).then(resp => {
|
|
if (id) {
|
|
devMap.delete(id.toString());
|
|
if (tabVal === id) {
|
|
let firstEntry = Array.from(devMap.values())[0];
|
|
if (firstEntry) {
|
|
setTabVal(firstEntry.device?.settings?.deviceId ?? -1);
|
|
} else {
|
|
setTabVal(0);
|
|
}
|
|
}
|
|
}
|
|
setDevices(devMap);
|
|
});
|
|
}
|
|
}
|
|
}}
|
|
updateLinkedComponents={(deviceID, component, linked) => {
|
|
let c = components;
|
|
if (linked) {
|
|
gateAPI
|
|
.updateLink(gateID, "gate", deviceID + ":" + component.key(), "component", [
|
|
"read",
|
|
"write",
|
|
"grant",
|
|
"revoke"
|
|
],as)
|
|
.then(resp => {
|
|
c.push(component);
|
|
setComponents([...c]);
|
|
});
|
|
} else {
|
|
gateAPI
|
|
.updateLink(gateID, "gate", deviceID + ":" + component.key(), "component", [], as)
|
|
.then(resp => {
|
|
c.forEach((comp, i) => {
|
|
if (component.key() === comp.key()) {
|
|
c.splice(i, 1);
|
|
setComponents([...c]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const gateDisplay = () => {
|
|
return (
|
|
<React.Fragment>
|
|
{loadingGate ? (
|
|
<Card>
|
|
<Box display="flex" justifyContent="center" alignItems="center" style={{ height: 300 }}>
|
|
<CircularProgress size={200} thickness={1.5} />
|
|
</Box>
|
|
</Card>
|
|
) : (
|
|
<React.Fragment>
|
|
<Card
|
|
style={{
|
|
//padding: isMobile ? 2 : 15,
|
|
marginLeft: isMobile ? 0 : 15,
|
|
marginRight: isMobile ? 0 : 15
|
|
}}>
|
|
<Tabs
|
|
style={{
|
|
marginBottom: 5,
|
|
display: Array.from(devices.keys()).length < 2 ? "none" : "block"
|
|
}}
|
|
value={tabVal}
|
|
indicatorColor="secondary"
|
|
//textColor="secondary"
|
|
onChange={(_, newVal) => {
|
|
setTabVal(newVal);
|
|
}}
|
|
aria-label="device tabs">
|
|
<Tab label={"Device Not Found"} value={-1} style={{ display: "none" }} />
|
|
<Tab label={"No Connected Devices"} value={0} style={{ display: "none" }} />
|
|
{Array.from(devices.values()).map(dev => {
|
|
let name = "Device Not Found";
|
|
let devKey = -1;
|
|
if (dev.device && dev.device.settings) {
|
|
name = dev.device.settings.name;
|
|
devKey = dev.device.settings.deviceId;
|
|
}
|
|
return <Tab key={devKey} label={name ?? devKey} value={devKey} />;
|
|
})}
|
|
</Tabs>
|
|
{/* panel to show if there are no devices */}
|
|
<TabPanelMine value={tabVal} index={0}>
|
|
<ButtonBase
|
|
onClick={() => {
|
|
setOpenDeviceDrawer(true);
|
|
}}
|
|
style={{ height: 150, width: "100%", margin: -15 }}>
|
|
<Grid
|
|
width="100%"
|
|
container
|
|
direction="row"
|
|
spacing={2}
|
|
alignItems="center"
|
|
alignContent="center">
|
|
<Grid>
|
|
<Box display="flex" justifyContent="center" alignItems="center">
|
|
<Link style={{ height: 50, width: 50 }} />
|
|
</Box>
|
|
</Grid>
|
|
<Grid>
|
|
<Typography style={{ fontSize: 25, fontWeight: 650 }}>
|
|
Connect Device
|
|
</Typography>
|
|
</Grid>
|
|
<Grid size={12}>
|
|
Click here to add a device to the gate. To add an additional device to a gate
|
|
use the "Link Device" icon on the top right side of this page
|
|
</Grid>
|
|
</Grid>
|
|
</ButtonBase>
|
|
</TabPanelMine>
|
|
{/* panel to show if there was an issue in the tab */}
|
|
<TabPanelMine value={tabVal} index={-1}>
|
|
Device Not Found
|
|
</TabPanelMine>
|
|
{Array.from(devices.values()).map(dev => {
|
|
let devKey = 0;
|
|
if (dev.device && dev.device.settings) {
|
|
devKey = dev.device.settings.deviceId;
|
|
if (tabVal === 0) {
|
|
setTabVal(devKey);
|
|
}
|
|
}
|
|
return (
|
|
<TabPanelMine key={devKey} value={tabVal} index={devKey}>
|
|
<GateDevice
|
|
key={devKey}
|
|
comprehensiveDevice={dev}
|
|
gate={gate}
|
|
linkedCompList={components}
|
|
drawerView={props.useMobile}
|
|
/>
|
|
</TabPanelMine>
|
|
);
|
|
})}
|
|
</Card>
|
|
</React.Fragment>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const noteDrawer = () => {
|
|
return (
|
|
<Drawer
|
|
open={openNoteDrawer}
|
|
onClose={() => {
|
|
setOpenNoteDrawer(false);
|
|
}}
|
|
anchor="right"
|
|
classes={{ paper: classes.drawerPaper }}>
|
|
<Box padding={2}>
|
|
<Typography>Notes</Typography>
|
|
<Card style={{ padding: 10 }}>
|
|
<Chat objectKey={gate.key} type={pond.NoteType.NOTE_TYPE_GATE} />
|
|
</Card>
|
|
</Box>
|
|
</Drawer>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<PageContainer>
|
|
<ObjectControls
|
|
objectButton={
|
|
<IconButton
|
|
size="small"
|
|
style={{
|
|
marginTop: "0.3625rem",
|
|
marginBottom: "0.3625rem",
|
|
marginRight: "0.5rem"
|
|
}}
|
|
onClick={() => {
|
|
setDisplayTab(0);
|
|
}}
|
|
className={displayTab === 0 ? classes.activeButton : classes.inactiveButton}
|
|
component="span">
|
|
<PlaneIcon />
|
|
</IconButton>
|
|
}
|
|
linkDeviceFunction={() => {
|
|
setOpenDeviceDrawer(true);
|
|
}}
|
|
notesButton={
|
|
<IconButton
|
|
onClick={() => {
|
|
if (props.useMobile || isMobile) {
|
|
setDisplayTab(1);
|
|
} else {
|
|
setOpenNoteDrawer(true);
|
|
}
|
|
}}
|
|
size="small"
|
|
style={{
|
|
marginTop: "0.3625rem",
|
|
marginBottom: "0.3625rem",
|
|
marginRight: "0.5rem"
|
|
}}
|
|
className={displayTab === 1 ? classes.activeButton : classes.inactiveButton}
|
|
component="span">
|
|
<NotesIcon />
|
|
</IconButton>
|
|
}
|
|
mapFunction={() => {
|
|
goToMap();
|
|
}}
|
|
actions={<GateActions gate={gate} refreshCallback={loadGate} permissions={permissions} />}
|
|
permissions={permissions}
|
|
devices={Array.from(devices.values()).map(compDev => Device.any(compDev.device))}
|
|
/>
|
|
<Box marginLeft={2} marginTop={isMobile ? -1.5 : -1} marginBottom={isMobile ? 0 : 1}>
|
|
<Typography style={{ fontSize: 25, fontWeight: 650 }}>{gate.name}</Typography>
|
|
</Box>
|
|
<TabPanelMine value={displayTab} index={0}>
|
|
{gateDisplay()}
|
|
</TabPanelMine>
|
|
{/* tab for notes on mobile and the map drawer */}
|
|
<TabPanelMine value={displayTab} index={1}>
|
|
<Card style={{ padding: 10 }}>
|
|
<Chat objectKey={gate.key} type={pond.NoteType.NOTE_TYPE_GATE} />
|
|
</Card>
|
|
</TabPanelMine>
|
|
{/* drawer is for displaying notes on desktop */}
|
|
{noteDrawer()}
|
|
{deviceDrawer()}
|
|
</PageContainer>
|
|
);
|
|
}
|