2022-09-07 08:58:00 -07:00
import { Meteor } from 'meteor/meteor' ;
import React , { useState , useEffect } from 'react' ;
import { useTracker } from 'meteor/react-meteor-data' ;
import { useTheme } from '@mui/material/styles' ;
import _ from 'lodash' ;
import TextField from "@mui/material/TextField" ;
import Button from "@mui/material/Button" ;
import MenuItem from '@mui/material/MenuItem' ;
2023-06-16 11:52:48 -07:00
import { InputLabel , List , ListItem , ListItemButton , ListItemText , Switch } from "@mui/material" ;
2022-09-07 08:58:00 -07:00
import Box from "@mui/material/Box" ;
import ToggleButton from '@mui/material/ToggleButton' ;
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup' ;
import Dialog from '@mui/material/Dialog' ;
import DialogActions from '@mui/material/DialogActions' ;
import DialogContent from '@mui/material/DialogContent' ;
import DialogContentText from '@mui/material/DialogContentText' ;
import DialogTitle from '@mui/material/DialogTitle' ;
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" ;
2023-06-16 11:52:48 -07:00
import { Link , useLocation , useNavigate , useNavigationType } from "react-router-dom" ;
import { Action as NavigationType } from "@remix-run/router" ;
import FormControlLabel from "@mui/material/FormControlLabel" ;
2023-07-30 14:11:12 -07:00
import Tab from '@mui/material/Tab' ;
import TabContext from '@mui/lab/TabContext' ;
import TabList from '@mui/lab/TabList' ;
import TabPanel from '@mui/lab/TabPanel' ;
2022-09-07 08:58:00 -07:00
const cssTwoColumnContainer = {
display : 'grid' ,
gridTemplateColumns : "1fr 1fr" ,
columnGap : '1rem' ,
rowGap : '0.4rem' ,
}
const cssEditorField = {
minWidth : '10rem'
}
const AssignmentsByPerson = ( ) => {
2023-06-16 11:52:48 -07:00
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 : "" )
2022-09-07 08:58:00 -07:00
const [ assetId , setAssetId ] = useState ( "" )
const [ openAssignDialog , setOpenAssignDialog ] = useState ( false )
const [ assignCondition , setAssignCondition ] = useState ( conditions [ 2 ] )
const [ assignConditionDetails , setAssignConditionDetails ] = useState ( "" )
//Dialog stuff.
const [ openUnassignDialog , setOpenUnassignDialog ] = useState ( false )
2023-06-16 11:52:48 -07:00
const [ unassignDialogEditConditionOnly , setUnassignDialogEditConditionOnly ] = useState ( false )
2022-09-07 08:58:00 -07:00
const [ unassignCondition , setUnassignCondition ] = useState ( conditions [ 2 ] )
const [ unassignComment , setUnassignComment ] = useState ( "" )
const [ unassignConditionDetails , setUnassignConditionDetails ] = useState ( "" )
const [ unassignAsset , setUnassignAsset ] = useState ( undefined )
2023-07-30 14:11:12 -07:00
const [ searchInput , setSearchInput ] = useState ( undefined )
2022-09-07 08:58:00 -07:00
const { people } = useTracker ( ( ) => {
let people = [ ] ;
if ( search && search . length > 1 ) {
let query ;
2023-06-16 11:52:48 -07:00
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 } } ] } ] }
2022-09-07 08:58:00 -07:00
const students = Students . find ( query ) . fetch ( ) ;
const staff = Staff . find ( query ) . fetch ( ) ;
for ( let next of students ) next . type = "Student"
for ( let next of staff ) next . type = "Staff"
people = [ ... staff , ... students ]
}
return { people }
2023-07-30 14:11:12 -07:00
} , [ search ] ) ;
2022-09-07 08:58:00 -07:00
const { assets } = useTracker ( ( ) => {
let assets = [ ] ;
if ( selectedPerson ) {
assets = Assets . find ( { assigneeId : selectedPerson . _id } ) . fetch ( ) ;
for ( let next of assets ) {
next . assetType = AssetTypes . findOne ( { _id : next . assetTypeId } )
}
}
return { assets }
2023-07-30 14:11:12 -07:00
} , [ selectedPerson ] ) ;
2022-09-07 08:58:00 -07:00
const { foundAsset } = useTracker ( ( ) => {
let foundAsset = null ;
if ( assetId ) {
foundAsset = Assets . findOne ( { assetId : assetId } ) ;
if ( foundAsset ) {
foundAsset . assetType = AssetTypes . findOne ( { _id : foundAsset . assetTypeId } )
if ( foundAsset . assigneeId )
foundAsset . assignee = foundAsset . assigneeType === "Student" ? Students . findOne ( { _id : foundAsset . assigneeId } ) : Staff . findOne ( { _id : foundAsset . assigneeId } )
}
}
return { foundAsset }
2023-07-30 14:11:12 -07:00
} , [ assetId ] ) ;
const [ usageData , setUsageData ] = useState ( [ ] )
const [ assignmentData , setAssignmentData ] = useState ( [ ] )
// Collect the usage and assignment data when the selected person changes.
useEffect ( ( ) => {
try {
if ( selectedPerson ) {
let query = selectedPerson . type === "Student" ? { studentId : selectedPerson . _id } : { staffId : selectedPerson . _id }
console . log ( "Collecting person history" )
console . log ( query )
Meteor . call ( 'DataCollection.chromebookData' , query , ( err , result ) => {
if ( err ) console . error ( err )
else setUsageData ( result )
} )
Meteor . call ( 'AssetAssignmentHistory.get' , query , ( err , result ) => {
if ( err ) console . error ( err )
else setAssignmentData ( result )
} )
}
else setUsageData ( { } )
} catch ( e ) { console . log ( "Found error in collecting chromebook history & usage in ByPerson.jsx: " + e ) }
} , [ selectedPerson ] )
2022-09-07 08:58:00 -07:00
const getListItemStyle = ( item ) => {
return {
backgroundColor : selectedPerson === item ? '#EECFA6' : 'white'
}
}
const assign = ( ) => {
if ( foundAsset ) {
//Open the dialog to get condition.
2023-06-01 20:33:03 -07:00
setAssignCondition ( foundAsset . condition ? foundAsset . condition : conditions [ 2 ] )
setAssignConditionDetails ( foundAsset . conditionDetails || "" )
2022-09-07 08:58:00 -07:00
setOpenAssignDialog ( true )
}
}
const assignDialogClosed = ( assign ) => {
setOpenAssignDialog ( false )
if ( assign === true ) {
// Call assets.assign
2022-09-15 09:13:30 -07:00
Meteor . call ( 'assets.assign' , foundAsset . assetId , selectedPerson . type , selectedPerson . _id , assignCondition , assignConditionDetails , ( err , result ) => {
if ( err ) console . error ( err )
else {
// Clear the asset id field and set focus to it.
setAssetId ( "" )
if ( assetIdInput ) assetIdInput . focus ( )
}
} )
//document.getElementById('assetIdInput').focus()
2022-09-07 08:58:00 -07:00
}
}
2023-07-30 14:11:12 -07:00
//Force focus to the search input field when initially rendering.
useEffect ( ( ) => {
if ( searchInput ) searchInput . focus ( )
} , [ searchInput ] )
2022-09-07 08:58:00 -07:00
2023-06-16 11:52:48 -07:00
const unassign = ( asset , editConditionOnly ) => {
2022-09-07 08:58:00 -07:00
// Open the dialog to get condition and comment.
2023-06-16 11:52:48 -07:00
setUnassignDialogEditConditionOnly ( editConditionOnly )
2022-09-07 08:58:00 -07:00
setUnassignAsset ( asset ) ;
setUnassignComment ( "" )
setUnassignCondition ( asset . condition ? asset . condition : conditions [ 2 ] )
setUnassignConditionDetails ( asset . conditionDetails || "" )
setOpenUnassignDialog ( true ) ;
}
const unassignDialogClosed = ( unassign ) => {
setOpenUnassignDialog ( false )
if ( unassign === true ) {
2023-06-16 11:52:48 -07:00
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 ( )
} )
}
2022-09-07 08:58:00 -07:00
}
}
const getAssetTileStyles = ( index ) => {
return index % 2 ? { backgroundColor : '#FFF' } : { backgroundColor : '#d2d2d2' }
}
const cssAssetTile = {
padding : '.8rem' ,
userSelect : 'none' ,
// '&:nthChild(even)': {backgroundColor: '#935e5e'}
}
2023-06-16 11:52:48 -07:00
2023-07-30 14:11:12 -07:00
// Changes the selected person and updates the browser history.
2023-06-16 11:52:48 -07:00
const changeSelectedPerson = ( person ) => {
setSelectedPerson ( person )
2023-07-30 14:11:12 -07:00
navigate ( "/assignments/byPerson" , { replace : false , state : { person , search } } ) ;
2023-06-16 11:52:48 -07:00
}
2023-07-30 14:11:12 -07:00
// Restore the state if the forward/back/refresh functionality of the browser was utilized.
2023-06-16 11:52:48 -07:00
useEffect ( ( ) => {
if ( ! state ) navigate ( "/assignments/byPerson" , { replace : true , state : { search : "" , person : null } } )
else {
2023-07-30 14:11:12 -07:00
if ( navigateType === "POP" || navigateType === 'REPLACE' || navigateType === "PUSH" ) {
2023-06-16 11:52:48 -07:00
setSearch ( state . search )
setSelectedPerson ( state . person )
}
}
2023-07-30 14:11:12 -07:00
} , [ state , navigateType ] )
const [ tab , setTab ] = useState ( 'assignments' )
const RenderUsage = ( { data } ) => {
return (
< >
< ul >
{ data . map ( ( next , i ) => (
< li key = { next . _id } >
{ /*{next.person && (*/ }
{ /* <>*/ }
{ /* User: {next.person.firstName} {next.person.lastName} {next.person.grade ? "~ " + next.person.grade : ""} (<Link to={"/search?email=" + encodeURIComponent(next.email)}>{next.email}</Link>)<br/>*/ }
{ /* </>*/ }
{ /*)}*/ }
{ /*Device ID: <Link to={"/search?deviceId=" + encodeURIComponent(next.deviceId)}>{next.deviceId}</Link><br/>*/ }
{ next . asset && (
< > Asset ID : < Link to = { "/assignments/byAsset" } state = { { assetId : next . asset . assetId } } > { next . asset . assetId } < / Link > < br / > < / >
) }
< > Asset Type : { next . assetType ? next . assetType . name : "Unknown" } < br / > < / >
Serial : { next . serial } < br / >
{ new Date ( next . startTime ) . toLocaleDateString ( "en-US" ) + "-" + new Date ( next . endTime ) . toLocaleDateString ( "en-US" ) + " @ " + new Date ( next . endTime ) . toLocaleTimeString ( "en-US" ) } ( { Math . ceil ( ( ( next . endTime ? next . endTime : new Date ( ) . getTime ( ) ) - next . startTime ) / ( 1000 * 60 * 60 * 24 ) ) } days ) < br / >
{ next . assignedTo && (
< > Currently assigned to : { next . assignedTo . firstName } { next . assignedTo . lastName } { next . assignedTo . grade ? "~ " + next . assignedTo . grade : "" } ( { next . assignedTo . email } ) < / >
) }
< / li >
) ) }
< / ul >
< / >
)
}
const RenderAssignments = ( { data } ) => {
return (
< >
< ul >
{ data . map ( ( next , i ) => (
< li key = { next . _id + "/" + next . assetId } >
{ /*{next.assignee && (*/ }
{ /* <>*/ }
{ /* User: {next.assignee.firstName} {next.assignee.lastName} {next.assignee.grade ? "~ " + next.assignee.grade : ""} (<Link to={"/search?email=" + encodeURIComponent(next.assignee.email)}>{next.assignee.email}</Link>)<br/>*/ }
{ /* </>*/ }
{ /*)}*/ }
{ next . asset && (
< >
Asset ID : < Link to = { "/assignments/byAsset" } state = { { assetId : next . asset . assetId } } > { next . asset . assetId } < / Link > < br / >
< / >
) }
{ next . assetType && (
< >
Asset Type : { next . assetType . name } < br / >
< / >
) }
Serial : { next . serial } < br / >
{ new Date ( next . startDate ) . toLocaleDateString ( "en-US" ) + ( next . endDate ? "-" + new Date ( next . endDate ) . toLocaleDateString ( "en-US" ) : " - Still Assigned" ) } ( { Math . ceil ( ( ( next . endDate ? next . endDate : new Date ( ) . getTime ( ) ) - next . startDate ) / ( 1000 * 60 * 60 * 24 ) ) } days ) < br / >
{ next . comment && (
< > Comment : { next . comment } < br / > < / >
) }
Start Condition : { next . startCondition } < br / >
{ next . startConditionDetails && < > Details : { next . startConditionDetails } < br / > < / > }
{ next . endDate && (
< >
End Condition : { next . endCondition } < br / >
{ next . endConditionDetails && < > Details : { next . endConditionDetails } < br / > < / > }
< / >
) }
< / li >
) ) }
< / ul >
< / >
)
}
2022-09-07 08:58:00 -07:00
return (
< >
< Dialog open = { openAssignDialog } onClose = { assignDialogClosed } >
< DialogTitle > Assign Asset < / DialogTitle >
< DialogContent style = { { display : 'flex' , flexDirection : 'column' } } >
< div >
< TextField style = { cssEditorField } select variant = "standard" label = "Condition" value = { assignCondition } onChange = { ( e ) => { setAssignCondition ( e . target . value ) } } >
{ conditions . map ( ( condition , i ) => {
return < MenuItem key = { i } value = { condition } > { condition } < / MenuItem >
} ) }
< / TextField >
< / div >
< TextField style = { { marginTop : '1rem' , minWidth : '30rem' } } multiline rows = { 4 } variant = "outlined" label = "Condition Details" value = { assignConditionDetails } onChange = { ( e ) => { setAssignConditionDetails ( e . target . value ) } } / >
< / DialogContent >
< DialogActions >
< Button onClick = { ( ) => assignDialogClosed ( true ) } > Assign < / Button >
< Button onClick = { ( ) => assignDialogClosed ( false ) } > Cancel < / Button >
< / DialogActions >
< / Dialog >
< Dialog open = { openUnassignDialog } onClose = { unassignDialogClosed } >
2023-06-16 11:52:48 -07:00
< DialogTitle > { unassignDialogEditConditionOnly ? "Edit Condition" : "Unassign Asset" } < / DialogTitle >
2022-09-07 08:58:00 -07:00
< DialogContent style = { { display : 'flex' , flexDirection : 'column' } } >
< div >
< TextField style = { cssEditorField } select variant = "standard" label = "Condition" value = { unassignCondition } onChange = { ( e ) => { setUnassignCondition ( e . target . value ) } } >
{ conditions . map ( ( condition , i ) => {
return < MenuItem key = { i } value = { condition } > { condition } < / MenuItem >
} ) }
< / TextField >
< / div >
2023-06-16 11:52:48 -07:00
{ ! unassignDialogEditConditionOnly && < TextField style = { { marginTop : '1rem' , minWidth : '30rem' } } variant = "standard" label = "Comment" value = { unassignComment } onChange = { ( e ) => { setUnassignComment ( e . target . value ) } } / > }
2022-09-07 08:58:00 -07:00
< TextField style = { { marginTop : '1rem' , minWidth : '30rem' } } multiline rows = { 4 } variant = "outlined" label = "Condition Details" value = { unassignConditionDetails } onChange = { ( e ) => { setUnassignConditionDetails ( e . target . value ) } } / >
< / DialogContent >
< DialogActions >
2023-06-16 11:52:48 -07:00
< Button onClick = { ( ) => unassignDialogClosed ( true ) } > { unassignDialogEditConditionOnly ? "Save" : "Unassign" } < / Button >
2022-09-07 08:58:00 -07:00
< Button onClick = { ( ) => unassignDialogClosed ( false ) } > Cancel < / Button >
< / DialogActions >
< / Dialog >
2023-06-16 11:52:48 -07:00
< 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" / >
2023-07-30 14:11:12 -07:00
< TextField style = { cssEditorField } variant = "standard" label = "Search" inputRef = { input => setSearchInput ( input ) } value = { search } onChange = { ( e ) => { setSearch ( e . target . value ) } } / >
2022-09-07 08:58:00 -07:00
< / Box >
2023-06-16 11:52:48 -07:00
< Box style = { { ... cssTwoColumnContainer , gridTemplateColumns : "24rem 1fr" } } >
< div style = { { maxHeight : '26rem' , overflowY : 'auto' , minWidth : '10rem' , minHeight : '10rem' , maxWidth : '40rem' } } >
2022-09-07 08:58:00 -07:00
< List >
{ people . map ( ( next , i ) => {
return (
2023-06-16 11:52:48 -07:00
< 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 } / >
2022-09-07 08:58:00 -07:00
< / ListItemButton >
)
} ) }
< / List >
< / div >
2023-06-16 11:52:48 -07:00
< div style = { { display : 'flex' , flexDirection : 'column' , margin : '0 0 0 .5rem' } } >
2022-09-07 08:58:00 -07:00
{ selectedPerson && (
2023-06-16 11:52:48 -07:00
< >
< h3 style = { { margin : "0 0 0.5rem 0" } } > { selectedPerson . firstName + " " + ( selectedPerson . firstNameAlias ? "'" + selectedPerson . firstNameAlias + "' " : "" ) + selectedPerson . lastName + ( selectedPerson . grade ? " (" + selectedPerson . grade + ")" : "" ) } < / h3 >
2023-07-30 14:11:12 -07:00
< TabContext value = { tab } >
< Box sx = { { borderBottom : 1 , borderColor : 'divider' } } >
< TabList onChange = { ( e , v ) => setTab ( v ) } >
< Tab label = "Assignments" value = "assignments" / >
< Tab label = "Assignment History" value = "assignmentHistory" / >
< Tab label = "Usage History" value = "usageHistory" / >
< / TabList >
< / Box >
< TabPanel value = "assignments" >
< div style = { { ... cssAssetTile , paddingTop : "0" } } >
< div style = { { marginBottom : '1rem' } } > < TextField id = 'assetIdInput' style = { cssEditorField } variant = "standard" label = "Asset ID" value = { assetId } onChange = { ( e ) => { setAssetId ( e . target . value . toUpperCase ( ) ) } } / > < / div >
{ foundAsset && (
< >
< div > { foundAsset && foundAsset . assetType . name } < / div >
< div > Asset ID : < Link to = { "/search?assetId=" + encodeURIComponent ( foundAsset . assetId ) } > { foundAsset . assetId } < / Link > < / div >
< div > Serial : < Link to = { "/search?serial=" + encodeURIComponent ( foundAsset . serial ) } > { foundAsset . serial } < / Link > < / div >
< / >
) }
{ foundAsset && foundAsset . assignee && (
< >
< div > Assigned To : { foundAsset . assignee . firstName } { foundAsset . assignee . lastName } < / div >
< div > Assigned : { new Date ( foundAsset . assignmentDate ) . toLocaleDateString ( "en-US" ) } ( { Math . ceil ( ( new Date ( ) . getTime ( ) - foundAsset . assignmentDate ) / ( 1000 * 60 * 60 * 24 ) ) } days ) < / div >
< / >
) }
< Button variant = "contained" color = 'primary' className = "button" disabled = { ! foundAsset || foundAsset . assignee !== undefined } onClick = { ( ) => assign ( ) } > Assign < / Button >
< / div >
{ assets . map ( ( next , i ) => {
return (
< div key = { next . _id } style = { { ... getAssetTileStyles ( i ) , ... cssAssetTile } } >
< div style = { { fontWeight : 800 } } > { next . assetType . name } < / div >
< div > Asset ID : < Link to = { "/assignments/byAsset" } state = { { assetId : next . assetId } } > { next . assetId } < / Link > < / div >
< div > Serial : { next . serial } < / div >
< div > Assigned : { new Date ( next . assignmentDate ) . toLocaleDateString ( "en-US" ) } ( { Math . ceil ( ( new Date ( ) . getTime ( ) - next . assignmentDate ) / ( 1000 * 60 * 60 * 24 ) ) } days ) < / div >
< 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 >
)
} ) }
< / TabPanel >
< TabPanel value = "assignmentHistory" >
< RenderAssignments data = { assignmentData } / >
< / TabPanel >
< TabPanel value = "usageHistory" >
< RenderUsage data = { usageData } / >
< / TabPanel >
< / TabContext >
2023-06-16 11:52:48 -07:00
< / >
2022-09-07 08:58:00 -07:00
) }
< / div >
< / Box >
< / >
)
}
export default ( ) => {
Meteor . subscribe ( 'students' ) ;
Meteor . subscribe ( 'staff' ) ;
Meteor . subscribe ( 'assetTypes' ) ;
Meteor . subscribe ( 'assets' ) ;
return (
< AssignmentsByPerson / >
)
}