Modified Assets to update the assetId in the history records when it gets changed. Changing the assetId is handy when a sticker is removed, making it possible to just alter the ID instead of re-printing the sticker.
This commit is contained in:
@@ -34,8 +34,16 @@ if (Meteor.isServer) {
|
||||
* @returns {any} Array of Asset Assignment History objects.
|
||||
*/
|
||||
'AssetAssignmentHistory.get'(params) {
|
||||
let result
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||
let query = {};
|
||||
let query = {}
|
||||
let person
|
||||
let asset
|
||||
let assetType
|
||||
|
||||
// console.log("AssetAssignmentHistory: ")
|
||||
// console.log(params)
|
||||
|
||||
if(params.studentId) check(params.studentId, String)
|
||||
if(params.staffId) check(params.staffId, String)
|
||||
@@ -56,81 +64,52 @@ if (Meteor.isServer) {
|
||||
}
|
||||
}
|
||||
|
||||
if(params.serial) query.serial = params.serial;
|
||||
else if(params.assetId) query.assetId = params.assetId;
|
||||
else if(params.deviceId) query.deviceId = params.deviceId;
|
||||
else if(params.studentId) {
|
||||
query.assigneeId = params.studentId
|
||||
query.assigneeType = "Student"
|
||||
}
|
||||
else if(params.staffId) {
|
||||
query.assigneeId = params.staffId
|
||||
query.assigneeType = "Staff"
|
||||
if(params.serial || params.assetId || params.deviceId) {
|
||||
if(params.serial) query.serial = params.serial;
|
||||
else if(params.assetId) query.assetId = params.assetId;
|
||||
else if(params.deviceId) query.deviceId = params.deviceId;
|
||||
|
||||
asset = Assets.findOne(query)
|
||||
if(asset) assetType = AssetTypes.findOne({_id: asset.assetTypeId})
|
||||
}
|
||||
else {
|
||||
query = undefined;
|
||||
if(params.studentId) {
|
||||
query.assigneeId = params.studentId
|
||||
query.assigneeType = "Student"
|
||||
}
|
||||
else if(params.staffId) {
|
||||
query.assigneeId = params.staffId
|
||||
query.assigneeType = "Staff"
|
||||
}
|
||||
else {
|
||||
query = undefined;
|
||||
}
|
||||
|
||||
person = query.assigneeType === "Student" ? Students.findOne({id: query.assigneeId}) : Staff.findOne({id: query.assigneeId})
|
||||
}
|
||||
|
||||
if(query) {
|
||||
//Sort by the last time the record was updated from most to least recent.
|
||||
let result = AssetAssignmentHistory.find(query, {sort: {endDate: -1}}).fetch();
|
||||
let assets = [];
|
||||
result = AssetAssignmentHistory.find(query, {sort: {endDate: -1}}).fetch();
|
||||
|
||||
// Get the current assignment for the device or person.
|
||||
if(query.assetId || query.deviceId || query.serial) {
|
||||
let asset = Assets.findOne(query)
|
||||
|
||||
if(asset) assets = [asset]
|
||||
}
|
||||
else {
|
||||
// Find the assets assigned to the person.
|
||||
assets = Assets.find({assigneeId: params.studentId ? params.studentId : params.staffId}).fetch()
|
||||
}
|
||||
|
||||
// Prepend a partial assignment history record to the list. We want to show active assignments in the results.
|
||||
for(let asset of assets) {
|
||||
if(asset && asset.assigneeId) {
|
||||
let assetType = AssetTypes.findOne(asset.assetTypeId)
|
||||
let current = {
|
||||
_id: 0,
|
||||
assetKey: asset._id,
|
||||
assetId: asset.assetId,
|
||||
serial: asset.serial,
|
||||
assetTypeName: assetType.name,
|
||||
assigneeType: asset.assigneeType,
|
||||
assigneeId: asset.assigneeId,
|
||||
startDate: asset.assignmentDate,
|
||||
startCondition: asset.condition,
|
||||
startConditionDetails: asset.conditionDetails
|
||||
}
|
||||
|
||||
result = [current, ...result]
|
||||
}
|
||||
}
|
||||
|
||||
//Add some additional data to the records.
|
||||
//Expand the assignee, asset, and asset type data.
|
||||
for(let next of result) {
|
||||
// console.log(next)
|
||||
if(next.assetKey) {
|
||||
next.asset = Assets.findOne({_id: next.assetKey})
|
||||
}
|
||||
else if(next.assetId) {
|
||||
next.asset = Assets.findOne({assetId: next.assetId});
|
||||
}
|
||||
if(person) next.assignee = person
|
||||
else next.assignee = next.assigneeType === "Student" ? Students.findOne({_id: next.assigneeId}) : Staff.findOne({_id: next.assigneeId})
|
||||
|
||||
if(next.asset) {
|
||||
next.assetType = AssetTypes.findOne({_id: next.asset.assetTypeId})
|
||||
if(asset) {
|
||||
next.asset = asset
|
||||
next.assetType = assetType
|
||||
}
|
||||
|
||||
if(next.assigneeId) {
|
||||
next.assignee = next.asset.assigneeType === "Student" ? Students.findOne({_id: next.assigneeId}) : Staff.findOne({_id: next.assigneeId})
|
||||
else {
|
||||
next.asset = Assets.findOne({assetId: next.assetId})
|
||||
if(next.asset) next.assetType = AssetTypes.findOne({_id: next.asset.assetTypeId})
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else return null;
|
||||
}
|
||||
}
|
||||
else return null;
|
||||
|
||||
return result
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -123,6 +123,7 @@ Meteor.methods({
|
||||
if(serial) check(serial, String);
|
||||
check(condition, String);
|
||||
if(conditionDetails) check(conditionDetails, String);
|
||||
const existing = Assets.findOne({_id})
|
||||
|
||||
if(!conditions.includes(condition)) {
|
||||
//Should never happen.
|
||||
@@ -133,6 +134,12 @@ Meteor.methods({
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||
Assets.update({_id}, {$set: {assetTypeId, assetId, serial, condition, conditionDetails}});
|
||||
|
||||
if(assetId !== existing.assetId) {
|
||||
//When changing the asset id we also need to update the other locations in the data where that ID exists.
|
||||
// assetAssignmentHistory.assetId
|
||||
AssetAssignmentHistory.updateMany({assetId: existing.assetId}, {$set: {assetId}})
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
@@ -217,7 +224,7 @@ Meteor.methods({
|
||||
throw new Meteor.Error("Asset is already assigned.", "Cannot assign an asset that has already been assigned.");
|
||||
}
|
||||
else {
|
||||
Assets.update({assetId}, {$set: {assigneeType, assigneeId, assignmentDate: date, condition, conditionDetails}});
|
||||
Assets.update({assetId}, {$set: {assigneeType, assigneeId, assignmentDate: date, condition, conditionDetails, assignedBy: Meteor.userId()}});
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -257,11 +264,11 @@ Meteor.methods({
|
||||
let assetType = AssetTypes.findOne({_id: asset.assetTypeId});
|
||||
|
||||
try {
|
||||
AssetAssignmentHistory.insert({assetKey: asset._id, assetId, serial: asset.serial, assetTypeName: (assetType ? assetType.name : "UNK"), assigneeType: asset.assigneeType, assigneeId: asset.assigneeId, startDate: asset.assignmentDate, endDate: date, startCondition: asset.condition, endCondition: condition, startConditionDetails: asset.conditionDetails, endConditionDetails: conditionDetails, comment});
|
||||
AssetAssignmentHistory.insert({assetKey: asset._id, assetId, serial: asset.serial, assetTypeName: (assetType ? assetType.name : "UNK"), assigneeType: asset.assigneeType, assigneeId: asset.assigneeId, startDate: asset.assignmentDate, endDate: date, startCondition: asset.condition, endCondition: condition, startConditionDetails: asset.conditionDetails, endConditionDetails: conditionDetails, comment, unassignedBy: Meteor.userId(), assignedBy: asset.assignedBy});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
Assets.update({assetId}, {$unset: {assigneeType: "", assigneeId: "", assignmentDate: ""}, $set: {condition, conditionDetails}});
|
||||
Assets.update({assetId}, {$unset: {assigneeType: "", assigneeId: "", assignmentDate: "", assignedBy: ""}, $set: {condition, conditionDetails}});
|
||||
}
|
||||
else {
|
||||
console.error("Could not find the asset: " + assetId);
|
||||
|
||||
@@ -72,7 +72,7 @@ if (Meteor.isServer) {
|
||||
else if(params.studentId) {
|
||||
const student = Students.findOne({_id: params.studentId})
|
||||
|
||||
console.log(student)
|
||||
// console.log(student)
|
||||
if(student) query.email = student.email;
|
||||
else query = undefined
|
||||
}
|
||||
|
||||
@@ -24,7 +24,12 @@ 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/history";
|
||||
import Tab from '@mui/material/Tab';
|
||||
import TabContext from '@mui/lab/TabContext';
|
||||
import TabList from '@mui/lab/TabList';
|
||||
import TabPanel from '@mui/lab/TabPanel';
|
||||
|
||||
const cssTwoColumnContainer = {
|
||||
display: 'grid',
|
||||
@@ -37,6 +42,10 @@ const cssEditorField = {
|
||||
}
|
||||
|
||||
const AssignmentsByAsset = () => {
|
||||
const navigate = useNavigate()
|
||||
const navigateType = useNavigationType()
|
||||
const location = useLocation()
|
||||
const state = location.state
|
||||
const theme = useTheme();
|
||||
const [assetId, setAssetId] = useState("")
|
||||
|
||||
@@ -49,7 +58,6 @@ const AssignmentsByAsset = () => {
|
||||
|
||||
const [assetIdInput, setAssetIdInput] = useState(undefined)
|
||||
|
||||
|
||||
const {foundAsset} = useTracker(() => {
|
||||
let foundAsset = null;
|
||||
|
||||
@@ -65,12 +73,72 @@ const AssignmentsByAsset = () => {
|
||||
}
|
||||
|
||||
return {foundAsset}
|
||||
});
|
||||
}, [assetId]);
|
||||
|
||||
//This works too well. The field always gets focus anytime anything is typed anywhere.
|
||||
// useEffect(() => {
|
||||
// if(assetIdInput) assetIdInput.focus()
|
||||
// })
|
||||
// Set a timer function to create history for the browser if the user pauses on an asset long enough.
|
||||
useEffect(() => {
|
||||
let clearTimer;
|
||||
|
||||
// Only setup the timer to update navigation if we have found an asset for the current text input, and that asset is not the same as the one already current in the browser history.
|
||||
if(foundAsset && (!state || state.assetId !== foundAsset.assetId)) {
|
||||
const prevFoundAssetId = foundAsset.assetId
|
||||
|
||||
// If the asset id doesn't change in 3 seconds then add this asset to the browser history so the back functionality works.
|
||||
const timer = setTimeout(() => {
|
||||
if(foundAsset && foundAsset.assetId === prevFoundAssetId) navigate("/assignments/byAsset", {replace: false, state: {assetId: foundAsset.assetId}});
|
||||
}, 3000)
|
||||
|
||||
clearTimer = () => clearTimeout(timer)
|
||||
}
|
||||
|
||||
return clearTimer
|
||||
}, [foundAsset])
|
||||
|
||||
const [usageData, setUsageData] = useState([])
|
||||
const [assignmentData, setAssignmentData] = useState([])
|
||||
|
||||
// Collect the usage and assignment data when the selected person changes.
|
||||
useEffect(() => {
|
||||
try {
|
||||
if(foundAsset) {
|
||||
let query = {assetId: foundAsset.assetId}
|
||||
|
||||
console.log("Requesting asset historical data")
|
||||
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 ByAsset.jsx: " + e)}
|
||||
}, [foundAsset])
|
||||
|
||||
// Restore the state if the forward/back/refresh functionality of the browser was utilized.
|
||||
useEffect(() => {
|
||||
console.log("useEffect - navigation")
|
||||
if(!state) {
|
||||
console.log("no state")
|
||||
navigate("/assignments/byAsset", {replace: true, state: {asset: null}})
|
||||
}
|
||||
else {
|
||||
console.log(navigateType)
|
||||
console.log(state)
|
||||
if(navigateType === "POP" || navigateType === 'REPLACE' || navigateType === "PUSH") {
|
||||
setAssetId(state.assetId ? state.assetId : "")
|
||||
}
|
||||
}
|
||||
}, [state])
|
||||
|
||||
//Set focus on initial rendering.
|
||||
useEffect(() => {
|
||||
if(assetIdInput) assetIdInput.focus()
|
||||
}, [assetIdInput])
|
||||
|
||||
const unassign = (editConditionOnly) => {
|
||||
// Open the dialog to get condition and comment.
|
||||
@@ -100,6 +168,82 @@ const AssignmentsByAsset = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const [tab, setTab] = useState('assignments')
|
||||
|
||||
const RenderUsage = ({data}) => {
|
||||
return (
|
||||
<>
|
||||
<ul>
|
||||
{data.map((next, i) => (
|
||||
<li key={next._id}>
|
||||
{next.person && (
|
||||
<>
|
||||
User: <Link to={"/assignments/byPerson"} state={{search: next.person.lastName, person: next.person}}>{next.person.firstName} {next.person.lastName} {next.person.grade ? "~ " + next.person.grade : ""} ({next.email})</Link><br/>
|
||||
</>
|
||||
)}
|
||||
{!next.person && (
|
||||
<>
|
||||
User: N/A<br/>
|
||||
</>
|
||||
)}
|
||||
{/*Device ID: <Link to={"/search?deviceId=" + encodeURIComponent(next.deviceId)}>{next.deviceId}</Link><br/>*/}
|
||||
{/*{next.asset && (*/}
|
||||
{/* <>Asset ID: <Link to={"/search?assetId=" + encodeURIComponent(next.asset.assetId)}>{next.asset.assetId}</Link><br/></>*/}
|
||||
{/*)}*/}
|
||||
{/*<>Asset Type: {next.assetType ? next.assetType.name : "Unknown"}<br/></>*/}
|
||||
{/*Serial: <Link to={"/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link><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: <Link to={"/assignments/byPerson"} state={{search: next.assignee.lastName, person: next.assignee}}>{next.assignee.firstName} {next.assignee.lastName} {next.assignee.grade ? "~ " + next.assignee.grade : ""} ({next.assignee.email})</Link><br/>
|
||||
</>
|
||||
)}
|
||||
{/*{next.asset && (*/}
|
||||
{/* <>*/}
|
||||
{/* Asset ID: <Link to={"/search?assetId=" + encodeURIComponent(next.asset.assetId)}>{next.asset.assetId}</Link><br/>*/}
|
||||
{/* </>*/}
|
||||
{/*)}*/}
|
||||
{/*{next.assetType && (*/}
|
||||
{/* <>*/}
|
||||
{/* Asset Type: {next.assetType.name}<br/>*/}
|
||||
{/* </>*/}
|
||||
{/*)}*/}
|
||||
{/*Serial: <Link to={"/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link><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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={openUnassignDialog} onClose={unassignDialogClosed}>
|
||||
@@ -122,24 +266,44 @@ const AssignmentsByAsset = () => {
|
||||
</Dialog>
|
||||
|
||||
<Box style={{marginTop: '1rem',...cssTwoColumnContainer}}>
|
||||
<TextField style={cssEditorField} variant="standard" label="Asset ID" inputRef={input=>setAssetIdInput(input)} value={assetId} onChange={(e) => {setAssetId(e.target.value.toUpperCase())}}/>
|
||||
<TextField style={{...cssEditorField, maxWidth: "40rem", minWidth: "10rem"}} variant="standard" label="Asset ID" inputRef={input=>setAssetIdInput(input)} value={assetId} onChange={(e) => {setAssetId(e.target.value.toUpperCase())}}/>
|
||||
{foundAsset && (
|
||||
<div>
|
||||
<h3 style={{margin: "0 0 0.5rem 0"}}>Asset ID: {foundAsset.assetId}</h3>
|
||||
<h3 style={{margin: "0 0 0.5rem 0"}}>Serial: {foundAsset.serial}</h3>
|
||||
<h3 style={{margin: "0 0 0.5rem 0"}}>Current Condition: {foundAsset.condition}</h3>
|
||||
<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>
|
||||
<div>Condition Details: {foundAsset.conditionDetails}</div>
|
||||
{foundAsset.assignee && (
|
||||
<>
|
||||
<div>Assigned on: {new Date(foundAsset.assignmentDate).toLocaleDateString("en-US")} ({Math.ceil((new Date().getTime() - foundAsset.assignmentDate) / (1000*60*60*24))} days)</div>
|
||||
<div>Assigned to: <Link to={"/assignments/byPerson"} state={{search: foundAsset.assignee.lastName, person: foundAsset.assignee}}>{foundAsset.assignee.firstName} {foundAsset.assignee.lastName} {foundAsset.assignee.grade && foundAsset.assignee.grade} ({foundAsset.assignee.email})</Link></div>
|
||||
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(false)}>Unassign</Button>
|
||||
{" "}
|
||||
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(true)}>Edit</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel value="assignmentHistory">
|
||||
<RenderAssignments data={assignmentData}/>
|
||||
</TabPanel>
|
||||
<TabPanel value="usageHistory">
|
||||
<RenderUsage data={usageData}/>
|
||||
</TabPanel>
|
||||
</TabContext>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
{foundAsset && (
|
||||
<div>
|
||||
<div>Serial: {foundAsset.serial}</div>
|
||||
<div>Condition: {foundAsset.condition}</div>
|
||||
<div>Condition Details: {foundAsset.conditionDetails}</div>
|
||||
{foundAsset.assignee && (
|
||||
<>
|
||||
<div>Assigned on: {foundAsset.assignmentDate.toString()}</div>
|
||||
<div>Assigned to: {foundAsset.assignee.firstName} {foundAsset.assignee.lastName} {foundAsset.assignee.grade && foundAsset.assignee.grade} (<Link to={"/search?email=" + encodeURIComponent(foundAsset.assignee.email)}>{foundAsset.assignee.email}</Link>)</div>
|
||||
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(false)}>Unassign</Button>
|
||||
{" "}
|
||||
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(true)}>Edit</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import {Staff} from "/imports/api/staff";
|
||||
import {Link, useLocation, useNavigate, useNavigationType} from "react-router-dom";
|
||||
import { Action as NavigationType } from "@remix-run/router";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Tab from '@mui/material/Tab';
|
||||
import TabContext from '@mui/lab/TabContext';
|
||||
import TabList from '@mui/lab/TabList';
|
||||
import TabPanel from '@mui/lab/TabPanel';
|
||||
|
||||
const cssTwoColumnContainer = {
|
||||
display: 'grid',
|
||||
@@ -56,7 +60,7 @@ const AssignmentsByPerson = () => {
|
||||
const [unassignConditionDetails, setUnassignConditionDetails] = useState("")
|
||||
const [unassignAsset, setUnassignAsset] = useState(undefined)
|
||||
|
||||
const [assetIdInput, setAssetIdInput] = useState(undefined)
|
||||
const [searchInput, setSearchInput] = useState(undefined)
|
||||
|
||||
const {people} = useTracker(() => {
|
||||
let people = [];
|
||||
@@ -86,7 +90,7 @@ const AssignmentsByPerson = () => {
|
||||
}
|
||||
|
||||
return {people}
|
||||
});
|
||||
}, [search]);
|
||||
|
||||
const {assets} = useTracker(() => {
|
||||
let assets = [];
|
||||
@@ -100,7 +104,7 @@ const AssignmentsByPerson = () => {
|
||||
}
|
||||
|
||||
return {assets}
|
||||
});
|
||||
}, [selectedPerson]);
|
||||
|
||||
const {foundAsset} = useTracker(() => {
|
||||
let foundAsset = null;
|
||||
@@ -117,7 +121,32 @@ const AssignmentsByPerson = () => {
|
||||
}
|
||||
|
||||
return {foundAsset}
|
||||
});
|
||||
}, [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])
|
||||
|
||||
const getListItemStyle = (item) => {
|
||||
return {
|
||||
@@ -150,10 +179,10 @@ const AssignmentsByPerson = () => {
|
||||
}
|
||||
}
|
||||
|
||||
//This works too well. The field always gets focus anytime anything is typed anywhere.
|
||||
// useEffect(() => {
|
||||
// if(assetIdInput) assetIdInput.focus()
|
||||
// })
|
||||
//Force focus to the search input field when initially rendering.
|
||||
useEffect(() => {
|
||||
if(searchInput) searchInput.focus()
|
||||
}, [searchInput])
|
||||
|
||||
const unassign = (asset, editConditionOnly) => {
|
||||
// Open the dialog to get condition and comment.
|
||||
@@ -193,19 +222,93 @@ const AssignmentsByPerson = () => {
|
||||
// '&:nthChild(even)': {backgroundColor: '#935e5e'}
|
||||
}
|
||||
|
||||
// Changes the selected person and updates the browser history.
|
||||
const changeSelectedPerson = (person) => {
|
||||
setSelectedPerson(person)
|
||||
navigate("/assignments", {replace: false, state: {person, search}});
|
||||
navigate("/assignments/byPerson", {replace: false, state: {person, search}});
|
||||
}
|
||||
|
||||
// Restore the state if the forward/back/refresh functionality of the browser was utilized.
|
||||
useEffect(() => {
|
||||
if(!state) navigate("/assignments/byPerson", {replace: true, state: {search: "", person: null}})
|
||||
else {
|
||||
if(navigateType === NavigationType.Pop) {
|
||||
if(navigateType === "POP" || navigateType === 'REPLACE' || navigateType === "PUSH") {
|
||||
setSearch(state.search)
|
||||
setSelectedPerson(state.person)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -253,7 +356,7 @@ const AssignmentsByPerson = () => {
|
||||
{/* <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)}}/>
|
||||
<TextField style={cssEditorField} variant="standard" label="Search" inputRef={input=>setSearchInput(input)} value={search} onChange={(e) => {setSearch(e.target.value)}}/>
|
||||
</Box>
|
||||
<Box style={{...cssTwoColumnContainer, gridTemplateColumns: "24rem 1fr"}}>
|
||||
<div style={{maxHeight: '26rem', overflowY:'auto', minWidth: '10rem', minHeight: '10rem', maxWidth: '40rem'}}>
|
||||
@@ -271,44 +374,55 @@ const AssignmentsByPerson = () => {
|
||||
{selectedPerson && (
|
||||
<>
|
||||
<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>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
{assets.map((next, i) => {
|
||||
return (
|
||||
<div key={next._id} style={{...getAssetTileStyles(i), ...cssAssetTile}}>
|
||||
<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, false)}>Unassign</Button>
|
||||
{" "}
|
||||
<Button variant="contained" color='secondary' className="button" onClick={()=>unassign(next, true)}>Edit</Button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{/*<div style={{display: 'flex', flexDirection: 'row'}}>*/}
|
||||
{/* <TextField style={cssEditorField} variant="standard" label="Asset ID" value={assetId} onChange={(e) => {setAssetId(e.target.value)}}/>*/}
|
||||
{/* <TextField style={cssEditorField} variant="standard" label="Serial" value={serial} onChange={(e) => {setSerial(e.target.value)}}/>*/}
|
||||
{/*</div>*/}
|
||||
{/*<div style={{display: 'flex', flexDirection: 'row'}}>*/}
|
||||
{/* <TextField style={cssEditorField} select variant="standard" label="Condition" value={condition} onChange={(e)=>{setCondition(e.target.value)}}>*/}
|
||||
{/* {conditions.map((condition, i) => {*/}
|
||||
{/* return <MenuItem key={i} value={condition}>{condition}</MenuItem>*/}
|
||||
{/* })}*/}
|
||||
{/* </TextField>*/}
|
||||
{/*</div>*/}
|
||||
{/*<div style={{display: 'flex', flexDirection: 'row'}}>*/}
|
||||
{/* <TextField style={{width: '100%', margin: '1rem'}} multiline variant="outlined" rows={4} label="Condition Details" value={conditionDetails} onChange={(e) => {setConditionDetails(e.target.value)}}/>*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
"@lexical/link": "^0.5.0",
|
||||
"@lexical/react": "^0.5.0",
|
||||
"@mui/icons-material": "^5.11.16",
|
||||
"@mui/lab": "^5.0.0-alpha.134",
|
||||
"@mui/material": "^5.13.4",
|
||||
"@remix-run/router": "^1.6.3",
|
||||
"bcrypt": "^5.0.1",
|
||||
"classnames": "^2.2.6",
|
||||
"csv-parse": "^5.3.0",
|
||||
|
||||
Reference in New Issue
Block a user