Finished core functionality.
This commit is contained in:
@@ -30,7 +30,7 @@ if (Meteor.isServer) {
|
|||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
/**
|
/**
|
||||||
* Collects historical data on asset assignments.
|
* 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.
|
* @param params An object with a single attribute. The attribute must be one of: email, deviceId, assetId, serial, staffId, or studentId. It will find all Assignment data for the given attribute value.
|
||||||
* @returns {any} Array of Asset Assignment History objects.
|
* @returns {any} Array of Asset Assignment History objects.
|
||||||
*/
|
*/
|
||||||
'AssetAssignmentHistory.get'(params) {
|
'AssetAssignmentHistory.get'(params) {
|
||||||
@@ -41,9 +41,24 @@ if (Meteor.isServer) {
|
|||||||
if(params.staffId) check(params.staffId, String)
|
if(params.staffId) check(params.staffId, String)
|
||||||
if(params.assetId) check(params.assetId, String)
|
if(params.assetId) check(params.assetId, String)
|
||||||
if(params.serial) check(params.serial, String)
|
if(params.serial) check(params.serial, String)
|
||||||
|
if(params.deviceId) check(params.deviceId, String)
|
||||||
|
if(params.email) check(params.email, String)
|
||||||
|
|
||||||
|
if(params.email) {
|
||||||
|
let person = Students.findOne({email: params.email})
|
||||||
|
|
||||||
|
if(person) params.studentId = person._id;
|
||||||
|
else {
|
||||||
|
person = Staff.findOne({email: params.email})
|
||||||
|
|
||||||
|
if(person) params.staffId = person._id;
|
||||||
|
// else throw new Meteor.Error("Could not find a student or staff member with the given email.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(params.serial) query.serial = params.serial;
|
if(params.serial) query.serial = params.serial;
|
||||||
else if(params.assetId) query.assetId = params.assetId;
|
else if(params.assetId) query.assetId = params.assetId;
|
||||||
|
else if(params.deviceId) query.deviceId = params.deviceId;
|
||||||
else if(params.studentId) {
|
else if(params.studentId) {
|
||||||
query.assigneeId = params.studentId
|
query.assigneeId = params.studentId
|
||||||
query.assigneeType = "Student"
|
query.assigneeType = "Student"
|
||||||
@@ -59,11 +74,48 @@ if (Meteor.isServer) {
|
|||||||
if(query) {
|
if(query) {
|
||||||
//Sort by the last time the record was updated from most to least recent.
|
//Sort by the last time the record was updated from most to least recent.
|
||||||
let result = AssetAssignmentHistory.find(query, {sort: {endDate: -1}}).fetch();
|
let result = AssetAssignmentHistory.find(query, {sort: {endDate: -1}}).fetch();
|
||||||
|
let assets = [];
|
||||||
|
|
||||||
|
// 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.
|
//Add some additional data to the records.
|
||||||
for(let next of result) {
|
for(let next of result) {
|
||||||
if(next.serial) {
|
// console.log(next)
|
||||||
next.asset = Assets.findOne({serial: next.serial});
|
if(next.assetKey) {
|
||||||
|
next.asset = Assets.findOne({_id: next.assetKey})
|
||||||
|
}
|
||||||
|
else if(next.assetId) {
|
||||||
|
next.asset = Assets.findOne({assetId: next.assetId});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(next.asset) {
|
if(next.asset) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {Page} from './Page'
|
|||||||
import Assignments from './pages/Assignments'
|
import Assignments from './pages/Assignments'
|
||||||
import Assets from './pages/Assets'
|
import Assets from './pages/Assets'
|
||||||
import History from './pages/History'
|
import History from './pages/History'
|
||||||
|
import Search from './pages/Search'
|
||||||
import Users from './pages/Users'
|
import Users from './pages/Users'
|
||||||
import Admin from './pages/Admin'
|
import Admin from './pages/Admin'
|
||||||
|
|
||||||
@@ -81,28 +82,45 @@ export const App = () => {
|
|||||||
<ThemeProvider theme={appTheme}>
|
<ThemeProvider theme={appTheme}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Page>
|
<Route path="/" element={
|
||||||
<div className="container">
|
<Page>
|
||||||
<div className="row">
|
<div className="container">
|
||||||
TODO: Some statistics and such.
|
<div className="row">
|
||||||
|
TODO: Some statistics and such.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Page>}
|
||||||
</Page>}/>
|
/>
|
||||||
<Route path="/assignments/*" element={<Page>
|
<Route path="/assignments/*" element={
|
||||||
{canManageLaptops && <Assignments/>}
|
<Page>
|
||||||
</Page>}/>
|
{canManageLaptops && <Assignments/>}
|
||||||
<Route path="/assets/*" element={<Page>
|
</Page>}
|
||||||
{isAdmin && <Assets/>}
|
/>
|
||||||
</Page>}/>
|
<Route path="/assets/*" element={
|
||||||
<Route path="/admin/*" element={<Page>
|
<Page>
|
||||||
{isAdmin && <Admin/>}
|
{isAdmin && <Assets/>}
|
||||||
</Page>}/>
|
</Page>}
|
||||||
<Route path="/history/*" element={<Page>
|
/>
|
||||||
|
<Route path="/admin/*" element={
|
||||||
|
<Page>
|
||||||
|
{isAdmin && <Admin/>}
|
||||||
|
</Page>}
|
||||||
|
/>
|
||||||
|
<Route path="/history/*" element={
|
||||||
|
<Page>
|
||||||
{canManageLaptops && <History/>}
|
{canManageLaptops && <History/>}
|
||||||
</Page>}/>
|
</Page>}
|
||||||
<Route path="/users/*" element={<Page>
|
/>
|
||||||
{isAdmin && <Users/>}
|
<Route path="/search" element={
|
||||||
</Page>}/>
|
<Page>
|
||||||
|
{canManageLaptops && <Search/>}
|
||||||
|
</Page>}
|
||||||
|
/>
|
||||||
|
<Route path="/users/*" element={
|
||||||
|
<Page>
|
||||||
|
{isAdmin && <Users/>}
|
||||||
|
</Page>}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import Select from '@mui/material/Select';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import {Assets, conditions} from "/imports/api/assets";
|
import {Assets, conditions} from "/imports/api/assets";
|
||||||
import {AssetTypes} from "/imports/api/asset-types";
|
import {AssetTypes} from "/imports/api/asset-types";
|
||||||
|
import {Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Grid, Box} from "@mui/material";
|
||||||
|
|
||||||
const cssEditorField = {
|
const cssEditorField = {
|
||||||
margin: '0.6rem 0',
|
margin: '0.6rem 0',
|
||||||
|
minWidth: '200px'
|
||||||
}
|
}
|
||||||
const cssGridFieldContainer = {
|
const cssGridFieldContainer = {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@@ -84,8 +86,21 @@ export default () => {
|
|||||||
Meteor.subscribe('assetTypes');
|
Meteor.subscribe('assetTypes');
|
||||||
Meteor.subscribe('assets');
|
Meteor.subscribe('assets');
|
||||||
|
|
||||||
|
const [removeRow, setRemoveRow] = useState(undefined)
|
||||||
|
const [assetId, setAssetId] = useState("")
|
||||||
|
const [serial, setSerial] = useState("")
|
||||||
|
const [assetTypeId, setAssetTypeId] = useState("")
|
||||||
|
const assetTypes = [{name: "All", _id: 0}, ...AssetTypes.find({}, {sort: {year: -1}}).fetch()]
|
||||||
|
|
||||||
|
|
||||||
const {assets} = useTracker(() => {
|
const {assets} = useTracker(() => {
|
||||||
const assets = Assets.find({}).fetch();
|
let query = {}
|
||||||
|
|
||||||
|
if(assetId) query.assetId = {$regex: assetId, $options: 'i'}
|
||||||
|
if(serial) query.serial = {$regex: serial, $options: 'i'}
|
||||||
|
if(assetTypeId) query.assetTypeId = assetTypeId
|
||||||
|
|
||||||
|
const assets = Assets.find(query).fetch();
|
||||||
const assetTypes = AssetTypes.find({}, {sort: {year: -1}}).fetch();
|
const assetTypes = AssetTypes.find({}, {sort: {year: -1}}).fetch();
|
||||||
const assetTypeNameMap = assetTypes.reduce((map, obj) => {
|
const assetTypeNameMap = assetTypes.reduce((map, obj) => {
|
||||||
map[obj._id] = obj;
|
map[obj._id] = obj;
|
||||||
@@ -151,11 +166,47 @@ export default () => {
|
|||||||
key: (row) => row._id,
|
key: (row) => row._id,
|
||||||
editor: (row, close) => {return (<AssetEditor value={row} close={close}/>)},
|
editor: (row, close) => {return (<AssetEditor value={row} close={close}/>)},
|
||||||
add: true,
|
add: true,
|
||||||
|
remove: (row) => {setRemoveRow(row)},
|
||||||
maxHeight: '40rem'
|
maxHeight: '40rem'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeAsset = (asset) => {
|
||||||
|
Meteor.call("assets.remove", asset._id);
|
||||||
|
setRemoveRow(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Dialog open={removeRow ? true : false} onClose={() => setRemoveRow(undefined)}>
|
||||||
|
<DialogTitle>Remove Asset?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Are you certain you want to remove the selected Asset?
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => removeAsset(removeRow)}>Remove Asset</Button>
|
||||||
|
<Button onClick={() => setRemoveRow(undefined)}>Cancel</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Box component="div" sx={{m: 2, p: 2, border: '1px dashed grey'}}>
|
||||||
|
<h4 style={{margin: 0, padding: 0}}>Filter</h4>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField style={cssEditorField} variant="standard" label="Asset ID" value={assetId} onChange={(e) => {setAssetId(e.target.value)}}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField style={cssEditorField} variant="standard" label="Serial" value={serial} onChange={(e) => {setSerial(e.target.value)}}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField style={cssEditorField} select variant="standard" value={assetTypeId} onChange={(e)=>{setAssetTypeId(e.target.value)}} label="Asset Type">
|
||||||
|
{assetTypes.map((assetType, i) => {
|
||||||
|
return <MenuItem key={i} value={assetType._id}>{assetType.name}</MenuItem>
|
||||||
|
})}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
<SimpleTable rows={assets} columns={columns} options={options}/>
|
<SimpleTable rows={assets} columns={columns} options={options}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,19 +4,18 @@ 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 {Route, Routes} from "react-router-dom";
|
||||||
import Search from './History/Search'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
let tabs = [
|
let tabs = [
|
||||||
{
|
// {
|
||||||
title: "Chromebook Usage",
|
// title: "Chromebook Usage",
|
||||||
getElement: () => {
|
// getElement: () => {
|
||||||
const ChromebookUsage = lazy(()=>import('./History/ChromebookUsage'))
|
// const ChromebookUsage = lazy(()=>import('./History/ChromebookUsage'))
|
||||||
return <ChromebookUsage/>
|
// return <ChromebookUsage/>
|
||||||
},
|
// },
|
||||||
path: '/chromebookUsage',
|
// path: '/chromebookUsage',
|
||||||
href: 'chromebookUsage'
|
// href: 'chromebookUsage'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "Asset History",
|
title: "Asset History",
|
||||||
getElement: () => {
|
getElement: () => {
|
||||||
@@ -31,10 +30,6 @@ export default () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TabNav tabs={tabs}/>
|
<TabNav tabs={tabs}/>
|
||||||
|
|
||||||
<Routes>
|
|
||||||
<Route path="search" element={<Search/>}/>
|
|
||||||
</Routes>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -30,9 +30,9 @@ import SearchIcon from "@mui/icons-material/Search";
|
|||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [resultType, setResultType] = useState("usage")
|
|
||||||
const [searchType, setSearchType] = useState("email")
|
const [searchType, setSearchType] = useState("email")
|
||||||
const [value, setValue] = useState("")
|
const [value, setValue] = useState("")
|
||||||
|
|
||||||
const search = () => {
|
const search = () => {
|
||||||
if(searchType === 'email' || searchType === 'firstName' || searchType === 'lastName') {
|
if(searchType === 'email' || searchType === 'firstName' || searchType === 'lastName') {
|
||||||
if (value && value.length > 1) {
|
if (value && value.length > 1) {
|
||||||
@@ -45,18 +45,18 @@ export default () => {
|
|||||||
setPeopleToPickFrom(all)
|
setPeopleToPickFrom(all)
|
||||||
setOpenPickPersonDialog(true)
|
setOpenPickPersonDialog(true)
|
||||||
} else if (all.length === 1) {
|
} else if (all.length === 1) {
|
||||||
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&" + (students.length ? "studentId" : 'staffId') + "=" + encodeURIComponent(all[0]._id));
|
navigate("/search?" + (students.length ? "studentId" : 'staffId') + "=" + encodeURIComponent(all[0]._id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(searchType === 'assetID' || searchType === 'serial') {
|
else if(searchType === 'assetId' || searchType === 'serial') {
|
||||||
let asset = Assets.findOne(searchType === 'assetID' ? {assetId: value} : {serial : value});
|
let asset = Assets.findOne(searchType === 'assetId' ? {assetId: value.toUpperCase()} : {serial : value});
|
||||||
|
console.log(asset)
|
||||||
if(asset) {
|
if(asset) {
|
||||||
if(searchType === 'assetID')
|
if(searchType === 'assetId')
|
||||||
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&assetId=" + encodeURIComponent(asset.assetId))
|
navigate("/search?assetId=" + encodeURIComponent(asset.assetId.toUpperCase()))
|
||||||
else
|
else
|
||||||
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&serial=" + encodeURIComponent(asset.serial))
|
navigate("/search?serial=" + encodeURIComponent(asset.serial))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,13 @@ export default () => {
|
|||||||
if(openPickPersonDialog) setOpenPickPersonDialog(false)
|
if(openPickPersonDialog) setOpenPickPersonDialog(false)
|
||||||
|
|
||||||
if(person && person._id) {
|
if(person && person._id) {
|
||||||
navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&" + (person.grade ? "studentId" : 'staffId') + "=" + encodeURIComponent(person._id));
|
navigate("/search?" + (person.grade ? "studentId" : 'staffId') + "=" + encodeURIComponent(person._id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputKeyPress = (e) => {
|
||||||
|
if(e.key === 'Enter' && value.length > 0) {
|
||||||
|
search()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,14 +101,14 @@ export default () => {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<div style={{display: "flex", flexDirection: "column"}} sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}>
|
<div style={{display: "flex", flexDirection: "column", marginTop: "1rem"}} sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}>
|
||||||
<Paper componet='form'>
|
{/*<Paper componet='form'>*/}
|
||||||
<div style={{marginBottom: "1rem", marginTop: "2rem"}}>
|
{/* <div style={{marginBottom: "1rem", marginTop: "2rem"}}>*/}
|
||||||
<ToggleButtonGroup color="primary" value={resultType} exclusive onChange={(e, type)=>setResultType(type)} aria-label="Result Type">
|
{/* <ToggleButtonGroup color="primary" value={resultType} exclusive onChange={(e, type)=>setResultType(type)} aria-label="Result Type">*/}
|
||||||
<ToggleButton value="usage">Usage History</ToggleButton>
|
{/* <ToggleButton value="usage">Usage History</ToggleButton>*/}
|
||||||
<ToggleButton value="assignment">Assignment History</ToggleButton>
|
{/* <ToggleButton value="assignment">Assignment History</ToggleButton>*/}
|
||||||
</ToggleButtonGroup>
|
{/* </ToggleButtonGroup>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
<div style={{marginBottom: "1rem"}}>
|
<div style={{marginBottom: "1rem"}}>
|
||||||
<ToggleButtonGroup color="primary" value={searchType} exclusive onChange={(e, type)=>setSearchType(type)} aria-label="Search Type">
|
<ToggleButtonGroup color="primary" value={searchType} exclusive onChange={(e, type)=>setSearchType(type)} aria-label="Search Type">
|
||||||
<ToggleButton value="email">Email</ToggleButton>
|
<ToggleButton value="email">Email</ToggleButton>
|
||||||
@@ -113,12 +119,12 @@ export default () => {
|
|||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<InputBase value={value} onChange={(e) => {setValue(e.target.value)}} sx={{ ml: 1, flex: 1 }} placeholder="Value" inputProps={{ 'aria-label': 'Search Value' }}/>
|
<InputBase value={value} onKeyPress={inputKeyPress} 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}>
|
<IconButton type="button" sx={{ p: '10px' }} aria-label="search" onClick={search}>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
{/*</Paper>*/}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useTracker } from 'meteor/react-meteor-data';
|
|
||||||
import _ from 'lodash';
|
|
||||||
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}/>)
|
|
||||||
}
|
|
||||||
160
imports/ui/pages/Search.jsx
Normal file
160
imports/ui/pages/Search.jsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {Link, useSearchParams} from "react-router-dom";
|
||||||
|
import Tabs from '@mui/material/Tabs';
|
||||||
|
import Tab from '@mui/material/Tab';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
|
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/>
|
||||||
|
Serial: <Link to={"/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</Link><br/>
|
||||||
|
{next.asset && (
|
||||||
|
<>
|
||||||
|
Asset ID: <Link to={"/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.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/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
Serial: <Link to={"/search?serial=" + encodeURIComponent(next.serial)}>{next.serial}</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/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{new Date(next.startDate).toLocaleDateString("en-US") + (next.endDate ? "-" + new Date(next.endDate).toLocaleDateString("en-US") : " - Still Assigned")}<br/>
|
||||||
|
{next.endDate && (
|
||||||
|
<>Comment: {next.comment}<br/></>
|
||||||
|
)}
|
||||||
|
Start Condition: {next.startCondition}<br/>
|
||||||
|
Details: {next.startConditionDetails}<br/>
|
||||||
|
{next.endDate && (
|
||||||
|
<>
|
||||||
|
End Condition: {next.endCondition}<br/>
|
||||||
|
Details: {next.endConditionDetails}<br/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
// const query = queryString.parse(search)
|
||||||
|
const [usageData, setUsageData] = useState([])
|
||||||
|
const [assignmentData, setAssignmentData] = useState([])
|
||||||
|
const [search, setSearch] = useSearchParams()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let args;
|
||||||
|
|
||||||
|
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 setUsageData(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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 setAssignmentData(result)
|
||||||
|
})
|
||||||
|
}, [search])
|
||||||
|
|
||||||
|
const [tabIndex, setTabIndex] = useState(0)
|
||||||
|
|
||||||
|
console.log(assignmentData)
|
||||||
|
|
||||||
|
// return (search.get('resultType') === 'usage' ? <RenderUsage data={data}/> : <RenderAssignments data={data}/>)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{width: '100%'}}>
|
||||||
|
<Tabs value={tabIndex} onChange={(e, index) => {setTabIndex(index)}} aria-label='nav tabs'>
|
||||||
|
<Tab label="Usage"/>
|
||||||
|
<Tab label="Assignments"/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
<div role="tabpanel" hidden={tabIndex !== 0}>
|
||||||
|
<RenderUsage data={usageData}/>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" hidden={tabIndex !== 1}>
|
||||||
|
<RenderAssignments data={assignmentData}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
199
imports/ui/util/GridTable.jsx
Normal file
199
imports/ui/util/GridTable.jsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//{columns.map((column) => <td>{column.value(row)}</td>)}
|
||||||
|
const WholeRow = ({row, columns}) => {
|
||||||
|
return <tr><td>Test</td></tr>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColumnHeader = ({column}) => {
|
||||||
|
// console.log("Rendering column header")
|
||||||
|
return <td>
|
||||||
|
{column.isActions ?
|
||||||
|
"TODO: Add widgets"
|
||||||
|
:
|
||||||
|
column.title
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Row = ({columns, row, getRowKey, editedRowKey, editor, setEdited}) => {
|
||||||
|
return <tr><td>Test</td></tr>
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<tr>
|
||||||
|
{!editedRowKey || getRowKey(row) !== editedRowKey ?
|
||||||
|
columns.map((column, i) => <Cell column={column} row={row}/>)
|
||||||
|
:
|
||||||
|
editor
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
*/
|
||||||
|
// onDoubleClick={(e) => {(!editedRowKey || getRowKey(row) !== editedRowKey) && setEdited(row)}}
|
||||||
|
const Cell = ({row, column}) => {
|
||||||
|
return <td>test</td>
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<td>
|
||||||
|
{column.isActions ?
|
||||||
|
"TODO"
|
||||||
|
:
|
||||||
|
column.value(row)
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example:
|
||||||
|
* import {useState} from 'react';
|
||||||
|
* const[edited, setEdited] = useState(undefined);
|
||||||
|
* <GridTable setEdited={setEdited} edited={edited} rows={rows} columns={columnns} actions={actions}>
|
||||||
|
* <!-- Editor JSX here. -->
|
||||||
|
* </GridTable>
|
||||||
|
*
|
||||||
|
* export let rows;
|
||||||
|
export let columns;
|
||||||
|
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
|
||||||
|
export let edited;
|
||||||
|
export let actions;
|
||||||
|
export let selection;
|
||||||
|
* @param props
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export default ({columns, rows, actions, getRowKey, edited, setEdited, children}) => {
|
||||||
|
// Setup a width for each column.
|
||||||
|
columns.forEach(column => {
|
||||||
|
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
|
||||||
|
let weight = column.weight ? Math.max(1, column.weight) : 1;
|
||||||
|
column.width = 'minmax(' + min + 'px, ' + weight + 'fr)';
|
||||||
|
column.isActions = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the actions column to the end.
|
||||||
|
// TODO: Allow it to be positioned.
|
||||||
|
if(actions) {
|
||||||
|
// TODO: make this fixed width based on the possible widget widths.
|
||||||
|
actions.width = 'minmax(10px, 1fr)';
|
||||||
|
actions.isActions = true;
|
||||||
|
columns[columns.length] = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the column widths for the layout.
|
||||||
|
let gridTemplateColumns = columns.map(({width}) => width).join(' ');
|
||||||
|
|
||||||
|
// Resize column code.
|
||||||
|
let headerBeingResized = null;
|
||||||
|
let horizontalScrollOffset = 0;
|
||||||
|
const initResize = ({target}) => {
|
||||||
|
headerBeingResized = target.parentNode;
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
window.addEventListener('mouseup', completeResize);
|
||||||
|
headerBeingResized.classList.add('header--being-resized');
|
||||||
|
};
|
||||||
|
const completeResize = () => {
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
window.removeEventListener('mouseup', completeResize);
|
||||||
|
headerBeingResized.classList.remove('header--being-resized');
|
||||||
|
headerBeingResized = null;
|
||||||
|
};
|
||||||
|
const onMouseMove = e => {
|
||||||
|
try {
|
||||||
|
// Calculate the desired width.
|
||||||
|
horizontalScrollOffset = document.documentElement.scrollLeft;
|
||||||
|
let parentX = Math.round(headerBeingResized.getBoundingClientRect().x);
|
||||||
|
const width = horizontalScrollOffset + (e.clientX - parentX);
|
||||||
|
// Update the column object with the new size value.
|
||||||
|
const column = columns.find(({element}) => element === headerBeingResized);
|
||||||
|
column.width = Math.max(column.minWidth, width) + "px";
|
||||||
|
// Ensure all the column widths are converted to fixed sizes.
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
if((index < columns.length - 1) && (column.width.startsWith('minmax'))) {
|
||||||
|
column.width = parseInt(column.element.clientWidth, 10) + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Render the new column sizes.
|
||||||
|
gridTemplateColumns = columns.map(({width}) => width).join(' ');
|
||||||
|
} catch(e) {console.log(e);}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select row code.
|
||||||
|
let selectedRowElement = null;
|
||||||
|
const selectRow = (e, row) => {
|
||||||
|
let element = e.target;
|
||||||
|
|
||||||
|
while(element && element.nodeName !== "TR") element = element.parentNode;
|
||||||
|
|
||||||
|
if(selectedRowElement) {
|
||||||
|
selectedRowElement.classList.remove('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedRowElement = element;
|
||||||
|
element.classList.add('selected');
|
||||||
|
dispatch('selection', selectedRowElement.dataset.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit row code.
|
||||||
|
let editorContainer;
|
||||||
|
let editorTable;
|
||||||
|
// Listen for changes to the edited variable. Move the editor row and display it when there is a value.
|
||||||
|
// Relies on the table row being hidden when the edited value matches the row's value.
|
||||||
|
useTracker(() => {
|
||||||
|
if(editorContainer) {
|
||||||
|
if(edited) {
|
||||||
|
let id = rowKey($edited);
|
||||||
|
let hiddenRow = editorTable.querySelector('tbody tr[data-key="' + id + '"]');
|
||||||
|
|
||||||
|
if(!hiddenRow) {
|
||||||
|
let body = editorTable.querySelector('tbody');
|
||||||
|
body.firstChild ? body.insertBefore(editorContainer, body.firstChild) : body.appendChild(editorContainer);
|
||||||
|
editorContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//let editor = hiddenRow.querySelector('.editor');
|
||||||
|
let body = editorTable.querySelector('tbody');
|
||||||
|
let next = hiddenRow.nextSibling;
|
||||||
|
//editor.appendChild(editorContainer);
|
||||||
|
next ? body.insertBefore(editorContainer, next) : body.appendChild(editorContainer);
|
||||||
|
editorContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Edited cleared");
|
||||||
|
editorContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let editedRowKey = edited ? getRowKey(edited) : undefined;
|
||||||
|
console.log("Rendering grid table")
|
||||||
|
|
||||||
|
// let contents = "";
|
||||||
|
//
|
||||||
|
// for(let row of rows) {
|
||||||
|
// contents += <WholeRow row={row} columns={columns}/>
|
||||||
|
// // contents += <tr>
|
||||||
|
// // for(let column of columns) {
|
||||||
|
// // contents += <CellValue row={row} column={column}></CellValue>
|
||||||
|
// // }
|
||||||
|
// // contents += </tr>
|
||||||
|
// }
|
||||||
|
|
||||||
|
return <div className={'grid-table-container'}>
|
||||||
|
<table style={{gridTemplateColumns}}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{columns.map((column, i) => <ColumnHeader key={column.key} column={column}/>)}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{/*{rows.map((row, i) => <Row key={getRowKey(row)} row={row} columns={columns} getRowKey={getRowKey} editedRowKey={editedRowKey} editor={children} setEdited={setEdited}/>)}*/}
|
||||||
|
{rows.map((row)=><WholeRow row={row} columns={columns}/>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
56
imports/ui/util/GridTable2.jsx
Normal file
56
imports/ui/util/GridTable2.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTracker } from 'meteor/react-meteor-data';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export default class GridTable2 extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
rows: [{_id: 1234, name: "Fred"}, {_id: 1235, }],
|
||||||
|
columns: [{id: "first", value: row => {return row._id}}, {id: "second", value: row => {return row.name}}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
console.log("Rendering GridTable2")
|
||||||
|
return <table><tbody>
|
||||||
|
<tr><td>Test</td><td>Test2</td></tr>
|
||||||
|
{/*<tr>*/}
|
||||||
|
{/*<GridTableCell row={this.state.row} column={this.state.column}></GridTableCell>*/}
|
||||||
|
{/*</tr>*/}
|
||||||
|
{/*{this.state.rows.map((row) => <GridTableRow key={row._id} row={row} columns={this.state.columns}></GridTableRow>)}*/}
|
||||||
|
</tbody></table>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridTableRow extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
columns: props.columns,
|
||||||
|
row: props.row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
console.log("Rendering GridTableRow")
|
||||||
|
return <tr>
|
||||||
|
{this.state.columns.map((column)=><GridTableCell key={this.state.row._id + "-" + column.id} row={this.state.row} column={column}/>)}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridTableCell extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
console.log(props);
|
||||||
|
this.state = {
|
||||||
|
row: props.row,
|
||||||
|
column: props.column,
|
||||||
|
value: props.column.value(props.row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
console.log("Rendering GridTableCell")
|
||||||
|
return <td>{this.state.value}</td>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,11 +32,15 @@ import Box from "@mui/material/Box";
|
|||||||
// let options = {
|
// let options = {
|
||||||
// key: (row) => row._id,
|
// key: (row) => row._id,
|
||||||
// editor: (row) => {return (<MyRowEditor value={row}/>)}
|
// editor: (row) => {return (<MyRowEditor value={row}/>)}
|
||||||
|
// add: true,
|
||||||
|
// maxHeight: "40rem",
|
||||||
|
// remove: (row) => { /* show dialog and/or perform remove */ }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const cssTopControls = {
|
const cssTopControls = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row-reverse',
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -79,6 +83,10 @@ export default ({columns, rows, options}) => {
|
|||||||
setEdited({})
|
setEdited({})
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!edited && e.key === 'Delete' && selected && options.delete && _.isFunction(options.delete)) {
|
||||||
|
options.delete(selected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sort = (e, column) => {
|
const sort = (e, column) => {
|
||||||
@@ -119,7 +127,10 @@ export default ({columns, rows, options}) => {
|
|||||||
// console.log(rows)
|
// console.log(rows)
|
||||||
return (
|
return (
|
||||||
<div className='simpleTableContainer'>
|
<div className='simpleTableContainer'>
|
||||||
{options.add && <div style={cssTopControls}><Button variant="text" className="button" onClick={addRow}>Add</Button></div>}
|
<div style={cssTopControls}>
|
||||||
|
{options.add && <Button variant="text" className="button" onClick={addRow}>Add</Button>}
|
||||||
|
{options.remove && _.isFunction(options.remove) && <Button disabled={!selected} variant='text' className='button' onClick={() => {selected && options.remove(selected)}}>Remove</Button>}
|
||||||
|
</div>
|
||||||
<TableContainer className="simpleTable" component={Paper} style={containerStyle}>
|
<TableContainer className="simpleTable" component={Paper} style={containerStyle}>
|
||||||
<Table size="small" aria-label="Table" tabIndex="0" onKeyDown={keyHandler}>
|
<Table size="small" aria-label="Table" tabIndex="0" onKeyDown={keyHandler}>
|
||||||
<TableHead className="sticky">
|
<TableHead className="sticky">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "simple-todos-react",
|
"name": "Tempest",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "meteor run",
|
"start": "meteor run",
|
||||||
|
|||||||
Reference in New Issue
Block a user