Continued work on the historical pages.
This commit is contained in:
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import {Mongo} from "meteor/mongo";
|
import {Mongo} from "meteor/mongo";
|
||||||
|
import {Meteor} from "meteor/meteor";
|
||||||
|
import {check} from "meteor/check";
|
||||||
|
import {Assets} from "/imports/api/assets";
|
||||||
|
import {Students} from "/imports/api/students";
|
||||||
|
import {Staff} from "/imports/api/staff";
|
||||||
|
import {AssetTypes} from "/imports/api/asset-types";
|
||||||
|
|
||||||
export const AssetAssignmentHistory = new Mongo.Collection('assetAssignmentHistory');
|
export const AssetAssignmentHistory = new Mongo.Collection('assetAssignmentHistory');
|
||||||
|
|
||||||
@@ -18,3 +24,61 @@ endCondition: One of the condition options: [New, Like New, Good, Okay, Damaged]
|
|||||||
startConditionDetails: An optional text block for details on the condition.
|
startConditionDetails: An optional text block for details on the condition.
|
||||||
endConditionDetails: An optional text block for details on the condition.
|
endConditionDetails: An optional text block for details on the condition.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
Meteor.methods({
|
||||||
|
/**
|
||||||
|
* Collects historical data on asset assignments.
|
||||||
|
* @param params An object with a single attribute. The attribute must be one of: assetId, serial, staffId, or studentId. It will find all Assignment data for the given attribute value.
|
||||||
|
* @returns {any} Array of Asset Assignment History objects.
|
||||||
|
*/
|
||||||
|
'AssetAssignmentHistory.get'(params) {
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||||
|
let query = {};
|
||||||
|
|
||||||
|
if(params.studentId) check(params.studentId, String)
|
||||||
|
if(params.staffId) check(params.staffId, String)
|
||||||
|
if(params.assetId) check(params.assetId, String)
|
||||||
|
if(params.serial) check(params.serial, String)
|
||||||
|
|
||||||
|
if(params.serial) query.serial = params.serial;
|
||||||
|
else if(params.assetId) query.assetId = params.assetId;
|
||||||
|
else if(params.studentId) {
|
||||||
|
query.assigneeId = params.studentId
|
||||||
|
query.assigneeType = "Student"
|
||||||
|
}
|
||||||
|
else if(params.staffId) {
|
||||||
|
query.assigneeId = params.staffId
|
||||||
|
query.assigneeType = "Staff"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
query = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
//Add some additional data to the records.
|
||||||
|
for(let next of result) {
|
||||||
|
if(next.serial) {
|
||||||
|
next.asset = Assets.findOne({serial: next.serial});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(next.asset) {
|
||||||
|
next.assetType = AssetTypes.findOne({_id: next.asset.assetType})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(next.assigneeId) {
|
||||||
|
next.assignee = next.asset.assigneeType === "Student" ? Students.findOne({_id: next.assigneeId}) : Staff.findOne({_id: next.assigneeId})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -46,6 +46,9 @@ if (Meteor.isServer) {
|
|||||||
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||||
let query = {};
|
let query = {};
|
||||||
|
|
||||||
|
if(params.studentId) check(params.studentId, String)
|
||||||
|
if(params.staffId) check(params.staffId, String)
|
||||||
|
|
||||||
// For asset ID's, we need to get the serial from the asset collection first.
|
// For asset ID's, we need to get the serial from the asset collection first.
|
||||||
if(params.assetId) {
|
if(params.assetId) {
|
||||||
let asset = Assets.findOne({assetId : params.assetId});
|
let asset = Assets.findOne({assetId : params.assetId});
|
||||||
@@ -55,6 +58,8 @@ if (Meteor.isServer) {
|
|||||||
params.regex = false;
|
params.regex = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// console.log('chromebook data')
|
||||||
|
// console.log(params)
|
||||||
|
|
||||||
if (params.deviceId) query.deviceId = params.regex ? {
|
if (params.deviceId) query.deviceId = params.regex ? {
|
||||||
$regex: params.deviceId,
|
$regex: params.deviceId,
|
||||||
@@ -64,14 +69,19 @@ if (Meteor.isServer) {
|
|||||||
$regex: params.serial,
|
$regex: params.serial,
|
||||||
$options: "i"
|
$options: "i"
|
||||||
} : params.serial;
|
} : params.serial;
|
||||||
// else if (params.assetId) {
|
else if(params.studentId) {
|
||||||
// let asset = Assets.findOne({assetId: params.assetId});
|
const student = Students.findOne({_id: params.studentId})
|
||||||
//
|
|
||||||
// if(asset.serial) {
|
console.log(student)
|
||||||
// // An exact search.
|
if(student) query.email = student.email;
|
||||||
// query.serial = asset.serial;
|
else query = undefined
|
||||||
// }
|
}
|
||||||
// }
|
else if(params.staffId) {
|
||||||
|
const staff = Staff.findOne({_id: params.staffId})
|
||||||
|
|
||||||
|
if(staff) query.email = staff.email;
|
||||||
|
else query = undefined
|
||||||
|
}
|
||||||
else if (params.email) query.email = params.regex ? {
|
else if (params.email) query.email = params.regex ? {
|
||||||
$regex: params.email,
|
$regex: params.email,
|
||||||
$options: "i"
|
$options: "i"
|
||||||
@@ -83,6 +93,9 @@ if (Meteor.isServer) {
|
|||||||
query = undefined;
|
query = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log("query")
|
||||||
|
// console.log(query)
|
||||||
|
|
||||||
if(query) {
|
if(query) {
|
||||||
// console.log("Collecting Chromebook Data: ");
|
// console.log("Collecting Chromebook Data: ");
|
||||||
// console.log(query);
|
// console.log(query);
|
||||||
@@ -113,6 +126,8 @@ if (Meteor.isServer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('returning')
|
||||||
|
// console.log(result)
|
||||||
return result;
|
return result;
|
||||||
} else return null;
|
} else return null;
|
||||||
}
|
}
|
||||||
|
|||||||
72
imports/api/users.js
Normal file
72
imports/api/users.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
|
||||||
|
// console.log("Setting Up Users...")
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
Meteor.publish(null, function() {
|
||||||
|
if(this.userId) {
|
||||||
|
return Meteor.roleAssignment.find({'user._id': this.userId});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.ready();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish(null, function() {
|
||||||
|
return Meteor.roles.find({});
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish("allUsers", function() {
|
||||||
|
// console.log(Meteor.isServer);
|
||||||
|
// console.log("AllUsers");
|
||||||
|
// console.log("Meteor.userId(): " + Meteor.userId());
|
||||||
|
// // console.log(Roles.userIsInRole(Meteor.userId(), "laptop-management"));
|
||||||
|
// console.log(Meteor.roleAssignment.find({ 'user._id': Meteor.userId() }).fetch());
|
||||||
|
// console.log(Roles.userIsInRole(Meteor.user(), "admin", {anyScope:true}));
|
||||||
|
|
||||||
|
// Note: For some reason the {anyScope: true} is necessary on the server for the function to actually check roles.
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
//console.log(Meteor.users.find({}).fetch());
|
||||||
|
return Meteor.users.find({});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Meteor.publish("allRoleAssignments", function() {
|
||||||
|
// Note: For some reason the {anyScope: true} is necessary on the server for the function to actually check roles.
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
return Meteor.roleAssignment.find({});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
'users.setUserRoles'(userId, roles) {
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
check(userId, String);
|
||||||
|
check(roles, Array);
|
||||||
|
Roles.setUserRoles(userId, roles, {anyScope: true});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 'tasks.setPrivate'(taskId, setToPrivate) {
|
||||||
|
// check(taskId, String);
|
||||||
|
// check(setToPrivate, Boolean);
|
||||||
|
//
|
||||||
|
// const task = Tasks.findOne(taskId);
|
||||||
|
//
|
||||||
|
// // Make sure only the task owner can make a task private
|
||||||
|
// if (task.owner !== this.userId) {
|
||||||
|
// throw new Meteor.Error('not-authorized');
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Tasks.update(taskId, { $set: { private: setToPrivate } });
|
||||||
|
// },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("Users setup.")
|
||||||
@@ -3,16 +3,11 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useTracker } from 'meteor/react-meteor-data';
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import SimpleTable from "/imports/ui/util/SimpleTable";
|
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Select from '@mui/material/Select';
|
|
||||||
import Chip from '@mui/material/Chip';
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
|
||||||
import ToggleButton from '@mui/material/ToggleButton';
|
import ToggleButton from '@mui/material/ToggleButton';
|
||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from '@mui/material/Dialog';
|
||||||
@@ -222,7 +217,7 @@ const AssignmentsByPerson = () => {
|
|||||||
{people.map((next, i) => {
|
{people.map((next, i) => {
|
||||||
return (
|
return (
|
||||||
<ListItemButton key={next._id} style={getListItemStyle(next)} selected={selectedPerson === next} onClick={(e) => {setSelectedPerson(next)}}>
|
<ListItemButton key={next._id} style={getListItemStyle(next)} selected={selectedPerson === next} onClick={(e) => {setSelectedPerson(next)}}>
|
||||||
<ListItemText primary={next.firstName + " " + next.lastName} secondary={next.email} tertiary={"Hello World"}/>
|
<ListItemText primary={next.firstName + " " + next.lastName} secondary={next.email}/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import React, {lazy, Suspense, useState} from 'react';
|
|||||||
import { useTracker } from 'meteor/react-meteor-data';
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import TabNav from '../util/TabNav';
|
import TabNav from '../util/TabNav';
|
||||||
|
import {Route, Routes} from "react-router-dom";
|
||||||
|
import Search from './History/Search'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
let tabs = [
|
let tabs = [
|
||||||
@@ -16,15 +18,23 @@ export default () => {
|
|||||||
href: 'chromebookUsage'
|
href: 'chromebookUsage'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Asset Assignments",
|
title: "Asset History",
|
||||||
getElement: () => {
|
getElement: () => {
|
||||||
const AssetAssignments = lazy(()=>import('./History/AssetAssignments'))
|
const AssetHistory = lazy(()=>import('./History/AssetHistory'))
|
||||||
return <AssetAssignments/>
|
return <AssetHistory/>
|
||||||
},
|
},
|
||||||
path: '/assetAssignments',
|
path: '/assetHistory',
|
||||||
href: 'assetAssignments'
|
href: 'assetHistory'
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return <TabNav tabs={tabs}/>
|
return (
|
||||||
|
<>
|
||||||
|
<TabNav tabs={tabs}/>
|
||||||
|
|
||||||
|
<Routes>
|
||||||
|
<Route path="search" element={<Search/>}/>
|
||||||
|
</Routes>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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 SimpleTable from "/imports/ui/util/SimpleTable";
|
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import Select from '@mui/material/Select';
|
|
||||||
import Chip from '@mui/material/Chip';
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
|
||||||
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
|
||||||
import ToggleButton from '@mui/material/ToggleButton';
|
|
||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
|
||||||
125
imports/ui/pages/History/AssetHistory.jsx
Normal file
125
imports/ui/pages/History/AssetHistory.jsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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 SimpleTable from "/imports/ui/util/SimpleTable";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
import ToggleButton from '@mui/material/ToggleButton';
|
||||||
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
import {Students} from "/imports/api/students";
|
||||||
|
import {Staff} from "/imports/api/staff";
|
||||||
|
import {Assets} from "/imports/api/assets";
|
||||||
|
import Dialog from "@mui/material/Dialog";
|
||||||
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import InputBase from "@mui/material/InputBase";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [resultType, setResultType] = useState("usage")
|
||||||
|
const [searchType, setSearchType] = useState("email")
|
||||||
|
const [value, setValue] = useState("")
|
||||||
|
const search = () => {
|
||||||
|
if(searchType === 'email' || searchType === 'firstName' || searchType === 'lastName') {
|
||||||
|
if (value && value.length > 1) {
|
||||||
|
let query = searchType === 'email' ? {email: {$regex: value, $options: 'i'}} : searchType === 'firstName' ? {firstName: {$regex: value, $options: 'i'}} : {lastName: {$regex: value, $options: 'i'}}
|
||||||
|
let students = Students.find(query).fetch()
|
||||||
|
let staff = Staff.find(query).fetch()
|
||||||
|
let all = [...staff, ...students]
|
||||||
|
|
||||||
|
if (all.length > 1) {
|
||||||
|
setPeopleToPickFrom(all)
|
||||||
|
setOpenPickPersonDialog(true)
|
||||||
|
} else if (all.length === 1) {
|
||||||
|
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&" + (students.length ? "studentId" : 'staffId') + "=" + encodeURIComponent(all[0]._id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(searchType === 'assetID' || searchType === 'serial') {
|
||||||
|
let asset = Assets.findOne(searchType === 'assetID' ? {assetId: value} : {serial : value});
|
||||||
|
|
||||||
|
if(asset) {
|
||||||
|
if(searchType === 'assetID')
|
||||||
|
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&assetId=" + encodeURIComponent(asset.assetId))
|
||||||
|
else
|
||||||
|
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&serial=" + encodeURIComponent(asset.serial))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meteor.subscribe('students');
|
||||||
|
Meteor.subscribe('staff');
|
||||||
|
Meteor.subscribe('assets');
|
||||||
|
|
||||||
|
const [openPickPersonDialog, setOpenPickPersonDialog] = useState(false)
|
||||||
|
const [peopleToPickFrom, setPeopleToPickFrom] = useState([])
|
||||||
|
const pickPersonClosed = (cause, person) => {
|
||||||
|
if(openPickPersonDialog) setOpenPickPersonDialog(false)
|
||||||
|
|
||||||
|
if(person && person._id) {
|
||||||
|
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&" + (person.grade ? "studentId" : 'staffId') + "=" + encodeURIComponent(person._id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={openPickPersonDialog}>
|
||||||
|
<DialogTitle>Pick One</DialogTitle>
|
||||||
|
<DialogContent style={{display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<List>
|
||||||
|
{peopleToPickFrom.map((next, i) => {
|
||||||
|
return (
|
||||||
|
<ListItemButton key={next._id} onClick={(e) => {pickPersonClosed("selection", next)}}>
|
||||||
|
<ListItemText primary={next.firstName + " " + next.lastName} secondary={next.email}/>
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => pickPersonClosed("button")}>Cancel</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div style={{display: "flex", flexDirection: "column"}} sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}>
|
||||||
|
<Paper componet='form'>
|
||||||
|
<div style={{marginBottom: "1rem", marginTop: "2rem"}}>
|
||||||
|
<ToggleButtonGroup color="primary" value={resultType} exclusive onChange={(e, type)=>setResultType(type)} aria-label="Result Type">
|
||||||
|
<ToggleButton value="usage">Usage History</ToggleButton>
|
||||||
|
<ToggleButton value="assignment">Assignment History</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div style={{marginBottom: "1rem"}}>
|
||||||
|
<ToggleButtonGroup color="primary" value={searchType} exclusive onChange={(e, type)=>setSearchType(type)} aria-label="Search Type">
|
||||||
|
<ToggleButton value="email">Email</ToggleButton>
|
||||||
|
<ToggleButton value="firstName">First Name</ToggleButton>
|
||||||
|
<ToggleButton value="lastName">Last Name</ToggleButton>
|
||||||
|
<ToggleButton value="assetId">Asset ID</ToggleButton>
|
||||||
|
<ToggleButton value="serial">Serial</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InputBase value={value} onChange={(e) => {setValue(e.target.value)}} sx={{ ml: 1, flex: 1 }} placeholder="Value" inputProps={{ 'aria-label': 'Search Value' }}/>
|
||||||
|
<IconButton type="button" sx={{ p: '10px' }} aria-label="search" onClick={search}>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import Button from "@mui/material/Button";
|
|||||||
import Select from '@mui/material/Select';
|
import Select from '@mui/material/Select';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import InputBase from '@mui/material/InputBase';
|
import InputBase from '@mui/material/InputBase';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
@@ -19,20 +18,76 @@ import OutlinedInput from '@mui/material/OutlinedInput';
|
|||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import ToggleButton from '@mui/material/ToggleButton';
|
import ToggleButton from '@mui/material/ToggleButton';
|
||||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import {Students} from "/imports/api/students";
|
||||||
|
import {Staff} from "/imports/api/staff";
|
||||||
|
import {conditions} from "/imports/api/assets";
|
||||||
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
|
import Dialog from "@mui/material/Dialog";
|
||||||
|
import {InputLabel, List, ListItem, ListItemButton, ListItemText} from "@mui/material";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [value, setValue] = useState("")
|
||||||
|
const search = () => {
|
||||||
|
if(value && value.length > 1) {
|
||||||
|
let students = Students.find({email: {$regex: value, $options: 'i'}}).fetch()
|
||||||
|
let staff = Staff.find({email: {$regex: value, $options: 'i'}}).fetch()
|
||||||
|
let all = [...staff, ...students]
|
||||||
|
|
||||||
|
if(all.length > 1) {
|
||||||
|
setPeopleToPickFrom(all)
|
||||||
|
setOpenPickPersonDialog(true)
|
||||||
|
}
|
||||||
|
else if(all.length === 1) {
|
||||||
|
navigate("/history/search?" + (students.length ? "studentId" : 'staffId') + "=" + encodeURIComponent(all[0]._id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meteor.subscribe('students');
|
||||||
|
Meteor.subscribe('staff');
|
||||||
|
|
||||||
|
const [openPickPersonDialog, setOpenPickPersonDialog] = useState(false)
|
||||||
|
const [peopleToPickFrom, setPeopleToPickFrom] = useState([])
|
||||||
|
const pickPersonClosed = (cause, person) => {
|
||||||
|
if(openPickPersonDialog) setOpenPickPersonDialog(false)
|
||||||
|
|
||||||
|
if(person && person._id) {
|
||||||
|
navigate("/history/search?" + (person.grade ? "studentId" : 'staffId') + "=" + encodeURIComponent(person._id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={openPickPersonDialog}>
|
||||||
|
<DialogTitle>Pick One</DialogTitle>
|
||||||
|
<DialogContent style={{display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<List>
|
||||||
|
{peopleToPickFrom.map((next, i) => {
|
||||||
|
return (
|
||||||
|
<ListItemButton key={next._id} onClick={(e) => {pickPersonClosed("selection", next)}}>
|
||||||
|
<ListItemText primary={next.firstName + " " + next.lastName} secondary={next.email}/>
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => pickPersonClosed("button")}>Cancel</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<div style={{display: "flex", flexDirection: "column"}} sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}>
|
<div style={{display: "flex", flexDirection: "column"}} sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}>
|
||||||
<Paper componet='form'>
|
<Paper componet='form'>
|
||||||
<InputBase
|
<InputBase value={value} onChange={(e) => {setValue(e.target.value)}} sx={{ ml: 1, flex: 1 }} placeholder="Email" inputProps={{ 'aria-label': 'Search Email' }}/>
|
||||||
sx={{ ml: 1, flex: 1 }}
|
<IconButton type="button" sx={{ p: '10px' }} aria-label="search" onClick={search}>
|
||||||
placeholder="Email"
|
|
||||||
inputProps={{ 'aria-label': 'Search Email' }}
|
|
||||||
/>
|
|
||||||
<IconButton type="button" sx={{ p: '10px' }} aria-label="search">
|
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
135
imports/ui/pages/History/Search.jsx
Normal file
135
imports/ui/pages/History/Search.jsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
// import queryString from 'query-string'
|
||||||
|
import {Link, useSearchParams} from "react-router-dom";
|
||||||
|
|
||||||
|
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={"/history/search?email=" + encodeURIComponent(next.email)}>{next.email}</Link>)<br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
Device ID: <Link to={"/history/search?deviceId=" + encodeURIComponent(next.deviceId)}>{next.deviceId}</Link><br/>
|
||||||
|
Serial: <Link to={"/history/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link><br/>
|
||||||
|
{next.asset && (
|
||||||
|
<>
|
||||||
|
Asset ID: <Link to={"/history/search?assetId=" + encodeURIComponent(next.asset.assetId)}>{next.asset.assetId}</Link><br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{new Date(next.startTime).toLocaleDateString("en-US") + "-" + new Date(next.endTime).toLocaleDateString("en-US")}
|
||||||
|
{next.assetType && (
|
||||||
|
<>
|
||||||
|
<br/>Asset Type: {next.assetType.name}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{next.assignedTo && (
|
||||||
|
<>
|
||||||
|
<br/>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.assignee && (
|
||||||
|
<>
|
||||||
|
User: {next.assignee.firstName} {next.assignee.lastName} {next.assignee.grade ? "~ " + next.assignee.grade : ""} (<Link to={"/history/search?email=" + encodeURIComponent(next.assignee.email)}>{next.assignee.email}</Link>)<br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
Serial: <Link to={"/history/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link><br/>
|
||||||
|
{next.asset && (
|
||||||
|
<>
|
||||||
|
Asset ID: <Link to={"/history/search?assetId=" + encodeURIComponent(next.asset.assetId)}>{next.asset.assetId}</Link><br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{next.assetType && (
|
||||||
|
<>
|
||||||
|
Asset Type: {next.assetType.name}<br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{new Date(next.startDate).toLocaleDateString("en-US") + "-" + new Date(next.endDate).toLocaleDateString("en-US")}<br/>
|
||||||
|
Comment: {next.comment}<br/>
|
||||||
|
Start Condition: {next.startCondition}<br/>
|
||||||
|
Details: {next.startConditionDetails}<br/>
|
||||||
|
End Condition: {next.endCondition}<br/>
|
||||||
|
Details: {next.endConditionDetails}<br/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
// const query = queryString.parse(search)
|
||||||
|
const [data, setData] = useState([])
|
||||||
|
const [search, setSearch] = useSearchParams()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let args;
|
||||||
|
|
||||||
|
if(search.get('resultType') === 'usage') {
|
||||||
|
if(search.get('studentId')) {
|
||||||
|
args = {studentId: search.get('studentId')}
|
||||||
|
}
|
||||||
|
else if(search.get('staffId')) {
|
||||||
|
args = {staffId: search.get('staffId')}
|
||||||
|
}
|
||||||
|
else if(search.get('email')) {
|
||||||
|
args = {email: search.get('email')}
|
||||||
|
}
|
||||||
|
else if(search.get('deviceId')) {
|
||||||
|
args = {deviceId: search.get('deviceId')}
|
||||||
|
}
|
||||||
|
else if(search.get('serial')) {
|
||||||
|
args = {serial: search.get('serial')}
|
||||||
|
}
|
||||||
|
else if(search.get('assetId')) {
|
||||||
|
args = {assetId: search.get('assetId')}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meteor.call('DataCollection.chromebookData', args, (err, result) => {
|
||||||
|
if (err) console.error(err)
|
||||||
|
else setData(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(search.get('studentId')) {
|
||||||
|
args = {studentId: search.get('studentId')}
|
||||||
|
}
|
||||||
|
else if(search.get('staffId')) {
|
||||||
|
args = {staffId: search.get('staffId')}
|
||||||
|
}
|
||||||
|
else if(search.get('serial')) {
|
||||||
|
args = {serial: search.get('serial')}
|
||||||
|
}
|
||||||
|
else if(search.get('assetId')) {
|
||||||
|
args = {assetId: search.get('assetId')}
|
||||||
|
}
|
||||||
|
|
||||||
|
Meteor.call('AssetAssignmentHistory.get', args, (err, result) => {
|
||||||
|
if (err) console.error(err)
|
||||||
|
else setData(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [search])
|
||||||
|
|
||||||
|
return (search.get('resultType') === 'usage' ? <RenderUsage data={data}/> : <RenderAssignments data={data}/>)
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"meteor-node-stubs": "^1.0.0",
|
"meteor-node-stubs": "^1.0.0",
|
||||||
"moment": "^2.29.2",
|
"moment": "^2.29.2",
|
||||||
"mongodb": "^4.4.1",
|
"mongodb": "^4.4.1",
|
||||||
|
"query-string": "^7.1.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user