diff --git a/imports/api/asset-assignment-history.js b/imports/api/asset-assignment-history.js index 07620d8..4ea20d3 100644 --- a/imports/api/asset-assignment-history.js +++ b/imports/api/asset-assignment-history.js @@ -30,7 +30,7 @@ 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. + * @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. */ 'AssetAssignmentHistory.get'(params) { @@ -41,9 +41,24 @@ if (Meteor.isServer) { if(params.staffId) check(params.staffId, String) if(params.assetId) check(params.assetId, 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; 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" @@ -59,11 +74,48 @@ if (Meteor.isServer) { 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 = []; + + // 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. for(let next of result) { - if(next.serial) { - next.asset = Assets.findOne({serial: next.serial}); + // 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(next.asset) { diff --git a/imports/ui/App.jsx b/imports/ui/App.jsx index 44e86d3..cb7bd32 100644 --- a/imports/ui/App.jsx +++ b/imports/ui/App.jsx @@ -9,6 +9,7 @@ import {Page} from './Page' import Assignments from './pages/Assignments' import Assets from './pages/Assets' import History from './pages/History' +import Search from './pages/Search' import Users from './pages/Users' import Admin from './pages/Admin' @@ -81,28 +82,45 @@ export const App = () => { - -
-
- TODO: Some statistics and such. + +
+
+ TODO: Some statistics and such. +
-
- }/> - - {canManageLaptops && } - }/> - - {isAdmin && } - }/> - - {isAdmin && } - }/> - + } + /> + + {canManageLaptops && } + } + /> + + {isAdmin && } + } + /> + + {isAdmin && } + } + /> + {canManageLaptops && } - }/> - - {isAdmin && } - }/> + } + /> + + {canManageLaptops && } + } + /> + + {isAdmin && } + } + /> diff --git a/imports/ui/pages/Assets/AssetList.jsx b/imports/ui/pages/Assets/AssetList.jsx index efd9887..559f1c4 100644 --- a/imports/ui/pages/Assets/AssetList.jsx +++ b/imports/ui/pages/Assets/AssetList.jsx @@ -9,9 +9,11 @@ import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import {Assets, conditions} from "/imports/api/assets"; import {AssetTypes} from "/imports/api/asset-types"; +import {Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Grid, Box} from "@mui/material"; const cssEditorField = { margin: '0.6rem 0', + minWidth: '200px' } const cssGridFieldContainer = { display: 'grid', @@ -84,8 +86,21 @@ export default () => { Meteor.subscribe('assetTypes'); 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 = 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 assetTypeNameMap = assetTypes.reduce((map, obj) => { map[obj._id] = obj; @@ -151,11 +166,47 @@ export default () => { key: (row) => row._id, editor: (row, close) => {return ()}, add: true, + remove: (row) => {setRemoveRow(row)}, maxHeight: '40rem' } + + const removeAsset = (asset) => { + Meteor.call("assets.remove", asset._id); + setRemoveRow(undefined) + } return ( <> + setRemoveRow(undefined)}> + Remove Asset? + + + Are you certain you want to remove the selected Asset? + + + + + + + + +

Filter

+ + + {setAssetId(e.target.value)}}/> + + + {setSerial(e.target.value)}}/> + + + {setAssetTypeId(e.target.value)}} label="Asset Type"> + {assetTypes.map((assetType, i) => { + return {assetType.name} + })} + + + +
) diff --git a/imports/ui/pages/History.jsx b/imports/ui/pages/History.jsx index 070a58a..02cc260 100644 --- a/imports/ui/pages/History.jsx +++ b/imports/ui/pages/History.jsx @@ -4,19 +4,18 @@ import { useTracker } from 'meteor/react-meteor-data'; import _ from 'lodash'; import TabNav from '../util/TabNav'; import {Route, Routes} from "react-router-dom"; -import Search from './History/Search' export default () => { let tabs = [ - { - title: "Chromebook Usage", - getElement: () => { - const ChromebookUsage = lazy(()=>import('./History/ChromebookUsage')) - return - }, - path: '/chromebookUsage', - href: 'chromebookUsage' - }, + // { + // title: "Chromebook Usage", + // getElement: () => { + // const ChromebookUsage = lazy(()=>import('./History/ChromebookUsage')) + // return + // }, + // path: '/chromebookUsage', + // href: 'chromebookUsage' + // }, { title: "Asset History", getElement: () => { @@ -31,10 +30,6 @@ export default () => { return ( <> - - - }/> - ) } \ No newline at end of file diff --git a/imports/ui/pages/History/AssetHistory.jsx b/imports/ui/pages/History/AssetHistory.jsx index 7833811..60db566 100644 --- a/imports/ui/pages/History/AssetHistory.jsx +++ b/imports/ui/pages/History/AssetHistory.jsx @@ -30,9 +30,9 @@ 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) { @@ -45,18 +45,18 @@ export default () => { setPeopleToPickFrom(all) setOpenPickPersonDialog(true) } 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') { - let asset = Assets.findOne(searchType === 'assetID' ? {assetId: value} : {serial : value}); - + else if(searchType === 'assetId' || searchType === 'serial') { + let asset = Assets.findOne(searchType === 'assetId' ? {assetId: value.toUpperCase()} : {serial : value}); + console.log(asset) if(asset) { - if(searchType === 'assetID') - navigate("/history/search?resultType=" + encodeURIComponent(resultType) + "&assetId=" + encodeURIComponent(asset.assetId)) + if(searchType === 'assetId') + navigate("/search?assetId=" + encodeURIComponent(asset.assetId.toUpperCase())) 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(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 () => { -
- -
- setResultType(type)} aria-label="Result Type"> - Usage History - Assignment History - -
+
+ {/**/} + {/*
*/} + {/* setResultType(type)} aria-label="Result Type">*/} + {/* Usage History*/} + {/* Assignment History*/} + {/* */} + {/*
*/}
setSearchType(type)} aria-label="Search Type"> Email @@ -113,12 +119,12 @@ export default () => {
- {setValue(e.target.value)}} sx={{ ml: 1, flex: 1 }} placeholder="Value" inputProps={{ 'aria-label': 'Search Value' }}/> + {setValue(e.target.value)}} sx={{ ml: 1, flex: 1 }} placeholder="Value" inputProps={{ 'aria-label': 'Search Value' }}/>
-
+ {/**/}
) diff --git a/imports/ui/pages/History/Search.jsx b/imports/ui/pages/History/Search.jsx deleted file mode 100644 index 8c2a04c..0000000 --- a/imports/ui/pages/History/Search.jsx +++ /dev/null @@ -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 ( - <> -
    - {data.map((next, i) => ( -
  • - {next.person && ( - <> - User: {next.person.firstName} {next.person.lastName} {next.person.grade ? "~ " + next.person.grade : ""} ({next.email})
    - - )} - Device ID: {next.deviceId}
    - Serial: {next.serial}
    - {next.asset && ( - <> - Asset ID: {next.asset.assetId}
    - - )} - {new Date(next.startTime).toLocaleDateString("en-US") + "-" + new Date(next.endTime).toLocaleDateString("en-US")} - {next.assetType && ( - <> -
    Asset Type: {next.assetType.name} - - )} - {next.assignedTo && ( - <> -
    Currently assigned to: {next.assignedTo.firstName} {next.assignedTo.lastName} {next.assignedTo.grade ? "~ " + next.assignedTo.grade : ""} ({next.assignedTo.email}) - - )} -
  • - ))} -
- - ) -} - -const RenderAssignments = ({data}) => { - return ( - <> -
    - {data.map((next, i) => ( -
  • - {next.assignee && ( - <> - User: {next.assignee.firstName} {next.assignee.lastName} {next.assignee.grade ? "~ " + next.assignee.grade : ""} ({next.assignee.email})
    - - )} - Serial: {next.serial}
    - {next.asset && ( - <> - Asset ID: {next.asset.assetId}
    - - )} - {next.assetType && ( - <> - Asset Type: {next.assetType.name}
    - - )} - {new Date(next.startDate).toLocaleDateString("en-US") + "-" + new Date(next.endDate).toLocaleDateString("en-US")}
    - Comment: {next.comment}
    - Start Condition: {next.startCondition}
    - Details: {next.startConditionDetails}
    - End Condition: {next.endCondition}
    - Details: {next.endConditionDetails}
    -
  • - ))} -
- - ) -} - -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' ? : ) -} \ No newline at end of file diff --git a/imports/ui/pages/Search.jsx b/imports/ui/pages/Search.jsx new file mode 100644 index 0000000..6d1c785 --- /dev/null +++ b/imports/ui/pages/Search.jsx @@ -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 ( + <> +
    + {data.map((next, i) => ( +
  • + {next.person && ( + <> + User: {next.person.firstName} {next.person.lastName} {next.person.grade ? "~ " + next.person.grade : ""} ({next.email})
    + + )} + Device ID: {next.deviceId}
    + Serial: {next.serial}
    + {next.asset && ( + <> + Asset ID: {next.asset.assetId}
    + + )} + {new Date(next.startTime).toLocaleDateString("en-US") + "-" + new Date(next.endTime).toLocaleDateString("en-US")} + {next.assetType && ( + <> +
    Asset Type: {next.assetType.name} + + )} + {next.assignedTo && ( + <> +
    Currently assigned to: {next.assignedTo.firstName} {next.assignedTo.lastName} {next.assignedTo.grade ? "~ " + next.assignedTo.grade : ""} ({next.assignedTo.email}) + + )} +
  • + ))} +
+ + ) +} + +const RenderAssignments = ({data}) => { + return ( + <> +
    + {data.map((next, i) => ( +
  • + {next.assignee && ( + <> + User: {next.assignee.firstName} {next.assignee.lastName} {next.assignee.grade ? "~ " + next.assignee.grade : ""} ({next.assignee.email})
    + + )} + Serial: {next.serial}
    + {next.asset && ( + <> + Asset ID: {next.asset.assetId}
    + + )} + {next.assetType && ( + <> + Asset Type: {next.assetType.name}
    + + )} + {new Date(next.startDate).toLocaleDateString("en-US") + (next.endDate ? "-" + new Date(next.endDate).toLocaleDateString("en-US") : " - Still Assigned")}
    + {next.endDate && ( + <>Comment: {next.comment}
    + )} + Start Condition: {next.startCondition}
    + Details: {next.startConditionDetails}
    + {next.endDate && ( + <> + End Condition: {next.endCondition}
    + Details: {next.endConditionDetails}
    + + )} +
  • + ))} +
+ + ) +} + +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' ? : ) + return ( + <> + + {setTabIndex(index)}} aria-label='nav tabs'> + + + + + + + + ) +} \ No newline at end of file diff --git a/imports/ui/util/GridTable.jsx b/imports/ui/util/GridTable.jsx new file mode 100644 index 0000000..af5b54f --- /dev/null +++ b/imports/ui/util/GridTable.jsx @@ -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) => {column.value(row)})} +const WholeRow = ({row, columns}) => { + return Test +} + +const ColumnHeader = ({column}) => { + // console.log("Rendering column header") + return + {column.isActions ? + "TODO: Add widgets" + : + column.title + } + +} + +const Row = ({columns, row, getRowKey, editedRowKey, editor, setEdited}) => { + return Test +} +/* + + {!editedRowKey || getRowKey(row) !== editedRowKey ? + columns.map((column, i) => ) + : + editor + } + + */ +// onDoubleClick={(e) => {(!editedRowKey || getRowKey(row) !== editedRowKey) && setEdited(row)}} +const Cell = ({row, column}) => { + return test +} +/* + + {column.isActions ? + "TODO" + : + column.value(row) + } + + */ + +/** + * Example: + * import {useState} from 'react'; + * const[edited, setEdited] = useState(undefined); + * + * + * + * + * 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 += + // // contents += + // // for(let column of columns) { + // // contents += + // // } + // // contents += + // } + + return
+ + + + {columns.map((column, i) => )} + + + + {/*{rows.map((row, i) => )}*/} + {rows.map((row)=>)} + +
+
+} \ No newline at end of file diff --git a/imports/ui/util/GridTable2.jsx b/imports/ui/util/GridTable2.jsx new file mode 100644 index 0000000..40ded7b --- /dev/null +++ b/imports/ui/util/GridTable2.jsx @@ -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 + + {/**/} + {/**/} + {/**/} + {/*{this.state.rows.map((row) => )}*/} +
TestTest2
+ } +} + +class GridTableRow extends React.Component { + constructor(props) { + super(props) + this.state = { + columns: props.columns, + row: props.row, + } + } + render() { + console.log("Rendering GridTableRow") + return + {this.state.columns.map((column)=>)} + + } +} + +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 {this.state.value} + } +} \ No newline at end of file diff --git a/imports/ui/util/SimpleTable.jsx b/imports/ui/util/SimpleTable.jsx index 3900b30..5e14222 100644 --- a/imports/ui/util/SimpleTable.jsx +++ b/imports/ui/util/SimpleTable.jsx @@ -32,11 +32,15 @@ import Box from "@mui/material/Box"; // let options = { // key: (row) => row._id, // editor: (row) => {return ()} +// add: true, +// maxHeight: "40rem", +// remove: (row) => { /* show dialog and/or perform remove */ } // } const cssTopControls = { display: 'flex', - flexDirection: 'row-reverse', + flexDirection: 'row', + justifyContent: 'flex-end', } /* @@ -79,6 +83,10 @@ export default ({columns, rows, options}) => { setEdited({}) e.stopPropagation() } + + if(!edited && e.key === 'Delete' && selected && options.delete && _.isFunction(options.delete)) { + options.delete(selected) + } } const sort = (e, column) => { @@ -119,7 +127,10 @@ export default ({columns, rows, options}) => { // console.log(rows) return (
- {options.add &&
} +
+ {options.add && } + {options.remove && _.isFunction(options.remove) && } +
diff --git a/package.json b/package.json index 147c54e..3305aac 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "simple-todos-react", + "name": "Tempest", "private": true, "scripts": { "start": "meteor run",