Updated meteor; Modified the assignments byPerson page considerably to improve the workflow; Added an external id to sites; Added an import for students; Improved the students page.

This commit is contained in:
2023-06-16 11:52:48 -07:00
parent 9444cac85d
commit 3c76d5e6a0
15 changed files with 664 additions and 180 deletions

View File

@@ -6,7 +6,7 @@ import _ from 'lodash';
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import MenuItem from '@mui/material/MenuItem';
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
import {InputLabel, List, ListItem, ListItemButton, ListItemText, Switch} from "@mui/material";
import Box from "@mui/material/Box";
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
@@ -19,7 +19,9 @@ import {Assets, conditions} from "/imports/api/assets";
import {AssetTypes} from "/imports/api/asset-types";
import {Students} from "/imports/api/students";
import {Staff} from "/imports/api/staff";
import {Link} from "react-router-dom";
import {Link, useLocation, useNavigate, useNavigationType} from "react-router-dom";
import { Action as NavigationType } from "@remix-run/router";
import FormControlLabel from "@mui/material/FormControlLabel";
const cssTwoColumnContainer = {
display: 'grid',
@@ -32,10 +34,15 @@ const cssEditorField = {
}
const AssignmentsByPerson = () => {
const theme = useTheme();
const [searchType, setSearchType] = useState("Email")
const [search, setSearch] = useState("")
const [selectedPerson, setSelectedPerson] = useState("")
const navigate = useNavigate()
const navigateType = useNavigationType()
const location = useLocation()
const state = location.state
const theme = useTheme()
// const [searchType, setSearchType] = useState("Email")
const [search, setSearch] = useState(state && state.search ? state.search : "")
const [includeInactive, setIncludeInactive] = useState(false)
const [selectedPerson, setSelectedPerson] = useState(state && state.person ? state.person : "")
const [assetId, setAssetId] = useState("")
const [openAssignDialog, setOpenAssignDialog] = useState(false)
const [assignCondition, setAssignCondition] = useState(conditions[2])
@@ -43,6 +50,7 @@ const AssignmentsByPerson = () => {
//Dialog stuff.
const [openUnassignDialog, setOpenUnassignDialog] = useState(false)
const [unassignDialogEditConditionOnly, setUnassignDialogEditConditionOnly] = useState(false)
const [unassignCondition, setUnassignCondition] = useState(conditions[2])
const [unassignComment, setUnassignComment] = useState("")
const [unassignConditionDetails, setUnassignConditionDetails] = useState("")
@@ -56,13 +64,17 @@ const AssignmentsByPerson = () => {
if(search && search.length > 1) {
let query;
if(searchType === "Email") {
query = {email: {$regex: search, $options: 'i'}};
} else if(searchType === 'First Name') {
query = {firstName: {$regex: search, $options: 'i'}}
} else {
query = {lastName: {$regex: search, $options: 'i'}}
}
query = {$or: [{email: {$regex: search, $options: 'i'}}, {firstName: {$regex: search, $options: 'i'}}, {firstNameAlias: {$regex: search, $options: 'i'}}, {lastName: {$regex: search, $options: 'i'}}]}
// if(searchType === "Email") {
// query = {email: {$regex: search, $options: 'i'}};
// } else if(searchType === 'First Name') {
// query = {firstName: {$regex: search, $options: 'i'}}
// } else {
// query = {lastName: {$regex: search, $options: 'i'}}
// }
// Look for students/staff that are active or whose active flag is not set.
if(!includeInactive) query = {$and: [query, {$or: [{active: true}, {active: {$exists: false}}]}]}
const students = Students.find(query).fetch();
const staff = Staff.find(query).fetch();
@@ -143,8 +155,9 @@ const AssignmentsByPerson = () => {
// if(assetIdInput) assetIdInput.focus()
// })
const unassign = (asset) => {
const unassign = (asset, editConditionOnly) => {
// Open the dialog to get condition and comment.
setUnassignDialogEditConditionOnly(editConditionOnly)
setUnassignAsset(asset);
setUnassignComment("")
setUnassignCondition(asset.condition ? asset.condition : conditions[2])
@@ -155,11 +168,19 @@ const AssignmentsByPerson = () => {
setOpenUnassignDialog(false)
if(unassign === true) {
// Call assets.unassign(assetId, comment, condition, conditionDetails, date)
Meteor.call('assets.unassign', unassignAsset.assetId, unassignComment, unassignCondition, unassignConditionDetails, (err, result) => {
if(err) console.error(err)
else if(assetIdInput) assetIdInput.focus()
})
if(unassignDialogEditConditionOnly) {
Meteor.call('assets.updateCondition', unassignAsset._id, unassignCondition, unassignConditionDetails, (err, result) => {
if(err) console.error(err)
else if(assetIdInput) assetIdInput.focus()
})
}
else {
// Call assets.unassign(assetId, comment, condition, conditionDetails, date)
Meteor.call('assets.unassign', unassignAsset.assetId, unassignComment, unassignCondition, unassignConditionDetails, (err, result) => {
if(err) console.error(err)
else if(assetIdInput) assetIdInput.focus()
})
}
}
}
@@ -171,6 +192,20 @@ const AssignmentsByPerson = () => {
userSelect: 'none',
// '&:nthChild(even)': {backgroundColor: '#935e5e'}
}
const changeSelectedPerson = (person) => {
setSelectedPerson(person)
navigate("/assignments", {replace: false, state: {person, search}});
}
useEffect(() => {
if(!state) navigate("/assignments/byPerson", {replace: true, state: {search: "", person: null}})
else {
if(navigateType === NavigationType.Pop) {
setSearch(state.search)
setSelectedPerson(state.person)
}
}
})
return (
<>
@@ -193,7 +228,7 @@ const AssignmentsByPerson = () => {
</Dialog>
<Dialog open={openUnassignDialog} onClose={unassignDialogClosed}>
<DialogTitle>Unassign Asset</DialogTitle>
<DialogTitle>{unassignDialogEditConditionOnly ? "Edit Condition" : "Unassign Asset"}</DialogTitle>
<DialogContent style={{display: 'flex', flexDirection: 'column'}}>
<div>
<TextField style={cssEditorField} select variant="standard" label="Condition" value={unassignCondition} onChange={(e)=>{setUnassignCondition(e.target.value)}}>
@@ -202,47 +237,51 @@ const AssignmentsByPerson = () => {
})}
</TextField>
</div>
<TextField style={{marginTop: '1rem',minWidth: '30rem'}} variant="standard" label="Comment" value={unassignComment} onChange={(e) => {setUnassignComment(e.target.value)}}/>
{!unassignDialogEditConditionOnly && <TextField style={{marginTop: '1rem',minWidth: '30rem'}} variant="standard" label="Comment" value={unassignComment} onChange={(e) => {setUnassignComment(e.target.value)}}/>}
<TextField style={{marginTop: '1rem',minWidth: '30rem'}} multiline rows={4} variant="outlined" label="Condition Details" value={unassignConditionDetails} onChange={(e) => {setUnassignConditionDetails(e.target.value)}}/>
</DialogContent>
<DialogActions>
<Button onClick={() => unassignDialogClosed(true)}>Unassign</Button>
<Button onClick={() => unassignDialogClosed(true)}>{unassignDialogEditConditionOnly ? "Save" : "Unassign"}</Button>
<Button onClick={() => unassignDialogClosed(false)}>Cancel</Button>
</DialogActions>
</Dialog>
<Box style={{marginTop: '1rem',...cssTwoColumnContainer}}>
<ToggleButtonGroup color="primary" value={searchType} exclusive onChange={(e, type)=>setSearchType(type)} aria-label="Search Type">
<ToggleButton value="Email">Email</ToggleButton>
<ToggleButton value="First Name">First Name</ToggleButton>
<ToggleButton value="Last Name">Last Name</ToggleButton>
</ToggleButtonGroup>
<Box style={{margin: '1rem 0 0 0', padding: '0 1rem',...cssTwoColumnContainer, width: "24rem"}}>
{/*<ToggleButtonGroup color="primary" value={searchType} exclusive onChange={(e, type)=>setSearchType(type)} aria-label="Search Type">*/}
{/* <ToggleButton value="Email">Email</ToggleButton>*/}
{/* <ToggleButton value="First Name">First Name</ToggleButton>*/}
{/* <ToggleButton value="Last Name">Last Name</ToggleButton>*/}
{/*</ToggleButtonGroup>*/}
<FormControlLabel sx={{marginTop: '0.7rem'}} control={<Switch variant="standard" checked={includeInactive} onChange={(e) => {setIncludeInactive(e.target.checked)}}/>} label="Inactive"/>
<TextField style={cssEditorField} variant="standard" label="Search" value={search} onChange={(e) => {setSearch(e.target.value)}}/>
</Box>
<Box style={cssTwoColumnContainer}>
<div style={{maxHeight: '26rem', overflowY:'auto', minWidth: '10rem', minHeight: '10rem'}}>
<Box style={{...cssTwoColumnContainer, gridTemplateColumns: "24rem 1fr"}}>
<div style={{maxHeight: '26rem', overflowY:'auto', minWidth: '10rem', minHeight: '10rem', maxWidth: '40rem'}}>
<List>
{people.map((next, i) => {
return (
<ListItemButton key={next._id} style={getListItemStyle(next)} selected={selectedPerson === next} onClick={(e) => {setSelectedPerson(next)}}>
<ListItemText primary={next.firstName + " " + next.lastName} secondary={next.email}/>
<ListItemButton key={next._id} style={getListItemStyle(next)} selected={selectedPerson === next} onClick={(e) => {changeSelectedPerson(next)}}>
<ListItemText primary={next.firstName + " " + (next.firstNameAlias ? "'" + next.firstNameAlias + "' " : "") + next.lastName + (next.grade ? " (" + next.grade + ")" : "")} secondary={next.email}/>
</ListItemButton>
)
})}
</List>
</div>
<div style={{display: 'flex', flexDirection: 'column', margin: '1rem 0 0 .5rem'}}>
<div style={{display: 'flex', flexDirection: 'column', margin: '0 0 0 .5rem'}}>
{selectedPerson && (
<div style={cssAssetTile}>
<div style={{marginBottom: '1rem'}}><TextField id='assetIdInput' inputRef={input=>setAssetIdInput(input)} style={cssEditorField} variant="standard" label="Asset ID" value={assetId} onChange={(e) => {setAssetId(e.target.value.toUpperCase())}}/></div>
<div>{foundAsset && foundAsset.assetType.name}</div>
<div>{foundAsset && <Link to={"/search?assetId=" + encodeURIComponent(foundAsset.assetId)}>{foundAsset.assetId}</Link>}</div>
<div>{foundAsset && <Link to={"/search?serial=" + encodeURIComponent(foundAsset.serial)}>{foundAsset.serial}</Link>}</div>
{foundAsset && foundAsset.assignee && (
<div>Assigned To: {foundAsset.assignee.firstName} {foundAsset.assignee.lastName}</div>
)}
<Button variant="contained" color='primary' className="button" disabled={!foundAsset || foundAsset.assignee !== undefined} onClick={()=>assign()}>Assign</Button>
</div>
<>
<h3 style={{margin: "0 0 0.5rem 0"}}>{selectedPerson.firstName + " " + (selectedPerson.firstNameAlias ? "'" + selectedPerson.firstNameAlias + "' " : "") + selectedPerson.lastName + (selectedPerson.grade ? " (" + selectedPerson.grade + ")" : "")}</h3>
<div style={{...cssAssetTile, paddingTop: "0"}}>
<div style={{marginBottom: '1rem'}}><TextField id='assetIdInput' inputRef={input=>setAssetIdInput(input)} style={cssEditorField} variant="standard" label="Asset ID" value={assetId} onChange={(e) => {setAssetId(e.target.value.toUpperCase())}}/></div>
<div>{foundAsset && foundAsset.assetType.name}</div>
<div>{foundAsset && <Link to={"/search?assetId=" + encodeURIComponent(foundAsset.assetId)}>{foundAsset.assetId}</Link>}</div>
<div>{foundAsset && <Link to={"/search?serial=" + encodeURIComponent(foundAsset.serial)}>{foundAsset.serial}</Link>}</div>
{foundAsset && foundAsset.assignee && (
<div>Assigned To: {foundAsset.assignee.firstName} {foundAsset.assignee.lastName}</div>
)}
<Button variant="contained" color='primary' className="button" disabled={!foundAsset || foundAsset.assignee !== undefined} onClick={()=>assign()}>Assign</Button>
</div>
</>
)}
{assets.map((next, i) => {
return (
@@ -250,7 +289,9 @@ const AssignmentsByPerson = () => {
<div>{next.assetType.name}</div>
<div><Link to={"/search?assetId=" + encodeURIComponent(next.assetId)}>{next.assetId}</Link></div>
<div><Link to={"/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link></div>
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(next)}>Unassign</Button>
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(next, false)}>Unassign</Button>
{" "}
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(next, true)}>Edit</Button>
</div>
)
})}