From 4f5015dea0dd1a6936996f2bbdc3392a71bcfe13 Mon Sep 17 00:00:00 2001 From: FLorial Jean Baptiste Date: Tue, 3 Oct 2023 16:55:04 +0200 Subject: [PATCH] Update sample list view Fix filter by cell and puck logic , specify for Single Cell Table View move function from child to Parent Add logic to control all Samples Menu --- .../containers/SampleGridTableContainer.jsx | 236 +++++------------- ui/src/containers/SampleListViewContainer.jsx | 175 ++++++++++--- 2 files changed, 214 insertions(+), 197 deletions(-) diff --git a/ui/src/containers/SampleGridTableContainer.jsx b/ui/src/containers/SampleGridTableContainer.jsx index 3a74374a8..625cd7b9e 100644 --- a/ui/src/containers/SampleGridTableContainer.jsx +++ b/ui/src/containers/SampleGridTableContainer.jsx @@ -40,7 +40,6 @@ import { QUEUE_STOPPED, QUEUE_RUNNING, isCollected, - hasLimsData, } from '../constants'; import { @@ -65,24 +64,26 @@ import { SampleGridTableItem } from '../components/SampleGrid/SampleGridTableIte import { TaskItem } from '../components/SampleGrid/TaskItem'; +const SETTINGS = { + dots: false, + infinite: false, + speed: 100, + slidesToShow: 6, + slidesToScroll: 6, +}; + class SampleGridTableContainer extends React.Component { constructor(props) { super(props); - this.state = { - loadGridTable: false, - }; this.shouldComponentUpdate = this.shouldComponentUpdate.bind(this); this.onMouseDown = this.onMouseDown.bind(this); this.onMouseUp = this.onMouseUp.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onKeyDown = this.onKeyDown.bind(this); - this.filter = this.filter.bind(this); - this.mutualExclusiveFilterOption = - this.mutualExclusiveFilterOption.bind(this); + this.sampleGridItemsSelectedHandler = this.sampleGridItemsSelectedHandler.bind(this); - this.inQueueSampleID = this.inQueueSampleID.bind(this); this.pickAllCellPuckItemsOnClick = this.pickAllCellPuckItemsOnClick.bind(this); @@ -119,12 +120,6 @@ class SampleGridTableContainer extends React.Component { componentDidMount() { document.addEventListener('keydown', this.onKeyDown, false); document.addEventListener('click', this.onClick, false); - - setTimeout(() => { - this.setState({ - loadGridTable: true, - }); - }, 50); } shouldComponentUpdate(nextProps) { @@ -311,84 +306,6 @@ class SampleGridTableContainer extends React.Component { } } - /** - * Performs filtering on a sample with two options that are mutually exclusive - * Includes sample according to provided options o1 and o2, always includes the - * sample if both options are either true or false simultaneously (ignoring the - * options o1 and o2) - * - * @property {Object} filterOptions - * @param {Object} sample - * @param {string} o1 - option name 1 - * @param {string} o2 - option name 2 - * @param {function} fun - function that tests for inclusion - * - * return {boolean} true if item is to be included otherwise false - */ - mutualExclusiveFilterOption(sample, o1, o2, testFun) { - let includeItem = false; - - // First case is included for clarity since the two options - // cancel each other out. Dont do anything same as both false. Otherwise - // apply filter. - - if (this.props.filterOptions[o1] && this.props.filterOptions[o2]) { - includeItem = true; - } else if (!this.props.filterOptions[o1] && !this.props.filterOptions[o2]) { - includeItem = true; - } else if (this.props.filterOptions[o1]) { - includeItem = testFun(sample); - } else if (this.props.filterOptions[o2]) { - includeItem = !testFun(sample); - } - - return includeItem; - } - - /** - * Filter function for SampleItems - * - * @property {Object} sampleList - * @property {Object} filterOptions - * - * @param {string} key - sampleID - * - * return {boolean} true if item is to be excluded otherwise false - */ - filter(key) { - const sample = this.props.sampleList[key]; - let fi = false; - if (sample) { - const sampleFilter = - `${sample.sampleName} ${sample.proteinAcronym}`.toLowerCase(); - - fi = sampleFilter.includes(this.props.filterOptions.text.toLowerCase()); - - fi &= this.mutualExclusiveFilterOption( - sample, - 'inQueue', - 'notInQueue', - this.inQueueSampleID, - ); - fi &= this.mutualExclusiveFilterOption( - sample, - 'collected', - 'notCollected', - isCollected, - ); - fi &= this.mutualExclusiveFilterOption( - sample, - 'limsSamples', - '', - hasLimsData, - ); - if (this.props.filterOptions.puckFilter != '') { - fi &= sample.puck_no == this.props.filterOptions.puckFilter; - } - } - - return fi; - } currentSample(sampleID) { let current = false; @@ -402,16 +319,6 @@ class SampleGridTableContainer extends React.Component { return current; } - /** - * Helper function for filter that takes a sample object instead of sampleID - * - * @param {object} sample - * return {boolean} true if sample is in queue otherwise false - */ - inQueueSampleID(sample) { - return this.props.inQueue(sample.sampleID); - } - /** * Handles click on sample item pick 'checkbox', adds sample to queue if its * not in the queue or removes it from the queue if it was already in. @@ -503,7 +410,7 @@ class SampleGridTableContainer extends React.Component { getSampleListFilteredByCellPuck(cell, puck) { const allCellSample = []; const allCellSampleCheck = []; - let puck_code; + let puck_code = ''; const allPuckSample = []; const allPuckSampleID = []; @@ -513,7 +420,7 @@ class SampleGridTableContainer extends React.Component { Object.values(this.props.sampleList) .filter((sample) => sample.cell_no === cell) .forEach((sample) => { - if (this.filter(sample.sampleID)) { + if (this.props.filterSampleByKey(sample.sampleID)) { allCellSample.push(sample.sampleID); if (this.props.inQueue(sample.sampleID) && sample.checked) { allCellSampleCheck.push(sample.sampleID); @@ -528,7 +435,7 @@ class SampleGridTableContainer extends React.Component { sample.cell_no === Number(cell) && sample.puck_no === Number(puck), ) .forEach((sample) => { - if (this.filter(sample.sampleID)) { + if (this.props.filterSampleByKey(sample.sampleID)) { allPuckSampleID.push(sample.sampleID); allPuckSample.push(sample); if (this.props.inQueue(sample.sampleID) && sample.checked) { @@ -538,7 +445,7 @@ class SampleGridTableContainer extends React.Component { }); puck_code = allPuckSample.length > 0 ? allPuckSample[0].containerCode : ''; - if (puck_code !== '') { + if (puck_code !== '' && puck_code !== undefined) { puck_code = `| Code : ${puck_code}`; } return [allPuckSampleID, allPuckSampleCheck, puck_code]; @@ -617,7 +524,6 @@ class SampleGridTableContainer extends React.Component { } getSampleItemCollapsibleHeaderActions(cell) { - const type = this.props.type; const cellMenuID = 'samples-grid-table-context-menu-cell'; return (
@@ -661,7 +567,7 @@ class SampleGridTableContainer extends React.Component { this.props.order.forEach((key) => { const sample = this.props.sampleList[key]; - if (this.filter(key)) { + if (this.props.filterSampleByKey(key)) { sampleItemList.push(
  • {sample.sampleID}
  • ); } }); @@ -695,18 +601,11 @@ class SampleGridTableContainer extends React.Component { this.props.selected[sample.sampleID], 'samples-grid-table-item-to-be-collected': picked, 'samples-grid-table-item-collected': isCollected(sample), + 'samples-grid-table-li-manual': Number(cell) === 0, }); - const settings = { - dots: false, - infinite: false, - speed: 100, - slidesToShow: 6, - slidesToScroll: 6, - }; - if ( - this.filter(key) && + this.props.filterSampleByKey(key) && sample.cell_no === Number(cell) && sample.puck_no === Number(puck) ) { @@ -746,7 +645,7 @@ class SampleGridTableContainer extends React.Component { current={this.currentSample(sample.sampleID)} picked={picked} > - + {sample.tasks.map((taskData, i) => ( sample.location === puck + ':' + sampleNumber, ); - if (typeof sample === 'undefined') { + if (sample === undefined) { return null; } - // const key = sample.sampleID; - const key = puck + ':' + sampleNumber; + const key = sample.sampleID; const picked = this.props.inQueue(sample.sampleID) && sample.checked; const classes = classNames('samples-grid-table-li', { @@ -786,14 +685,6 @@ class SampleGridTableContainer extends React.Component { 'samples-grid-table-item-collected': isCollected(sample), }); - const settings = { - dots: false, - infinite: false, - speed: 100, - slidesToShow: 6, - slidesToScroll: 6, - }; - let contextMenuID = 'samples-grid-table-context-menu'; if (this.currentSample(sample.sampleID)) { contextMenuID = 'samples-grid-table-context-menu-mounted'; @@ -828,7 +719,7 @@ class SampleGridTableContainer extends React.Component { current={this.currentSample(sample.sampleID)} picked={picked} > - + {sample.tasks.map((taskData, i) => (
    ); - - return null; } getManualSamples() { @@ -873,7 +762,7 @@ class SampleGridTableContainer extends React.Component { } return ( -
    + Manual Samples {rows.map((r) => { return ( @@ -885,7 +774,7 @@ class SampleGridTableContainer extends React.Component {
    ); })} - + ); } return null; @@ -893,13 +782,12 @@ class SampleGridTableContainer extends React.Component { getSampleTable(colsm) { const scContent = this.props.sampleChanger.contents; - const tableCell = []; - if (typeof scContent.children === 'undefined') { + if (scContent.children === undefined) { return null; } - scContent.children.map((cell) => { + return scContent.children.map((cell) => { if ( this.props.filterOptions.cellFilter.toLowerCase() === cell.name || this.props.filterOptions.cellFilter.toLowerCase() === '' @@ -911,7 +799,7 @@ class SampleGridTableContainer extends React.Component { (Number(this.props.filterOptions.puckFilter) === idxtd + 1 || this.props.filterOptions.puckFilter.toLowerCase() === '') ) { - nbpuck.push(puck); + nbpuck.push('puck'); } }); @@ -919,12 +807,12 @@ class SampleGridTableContainer extends React.Component { let colsmP; if (nbpuck.length === 1) { colsmP = 3; - } else if (nbpuck.length >= 4 && colsm === 'auto') { + } else if (nbpuck.length >= 4) { colsmP = 12; } else { colsmP = colsm; } - tableCell.push( + return ( - , + ); } } return null; }); - return tableCell; } renderPuck(puck) { @@ -1088,8 +975,8 @@ class SampleGridTableContainer extends React.Component { this.displayPuckCellContextMenu( e, puckMenuID, + 1, Number(puck.name), - 1, //idxth + 1, ); }} > @@ -1103,10 +990,10 @@ class SampleGridTableContainer extends React.Component { getSampleTableSingleCell(colsm) { const scContent = this.props.sampleChanger.contents; - const tableCell = []; + const nbpuck = []; // Do not try to render is SC is empty - if (typeof scContent.children === 'undefined') { + if (scContent.children === undefined) { return null; } @@ -1116,31 +1003,50 @@ class SampleGridTableContainer extends React.Component { return null; } - scContent.children.map((puck) => { + // for a better display of the Table puck we need to know + // how many puck we have to display + scContent.children.forEach((puck) => { + const currentPuckFilter = this.props.filterOptions.puckFilter; + const filterList = this.getSampleListFilteredByCellPuck( + 1, + Number(puck.name), + ); + + if ( + (currentPuckFilter === '' || currentPuckFilter === puck.name) && + filterList[0].length > 0 + ) { + + nbpuck.push('puck'); + } + + }); + + return scContent.children.map((puck) => { const currentPuckFilter = this.props.filterOptions.puckFilter; const filterList = this.getSampleListFilteredByCellPuck( 1, Number(puck.name), ); + if ( - (currentPuckFilter == '' || currentPuckFilter == puck.name) && + (currentPuckFilter === '' || currentPuckFilter === puck.name) && filterList[0].length > 0 ) { - const nbpuck = []; - nbpuck.push(puck); + if (nbpuck.length > 0) { let colsmP; - if (nbpuck.length === 1) { + if (nbpuck.length <= 4) { colsmP = 3; - } else if (nbpuck.length >= 4 && colsm === 'auto') { - colsmP = 12; + } else if (nbpuck.length >= 5) { + colsmP = 2; } else { colsmP = colsm; } - tableCell.push( - + return ( +
    - , + ); } } return null; }); - return tableCell; } /** @@ -1379,9 +1284,9 @@ class SampleGridTableContainer extends React.Component { } renderSampleChangerDrawing() { - if (this.props.type === 'CATS') { + if (this.props.type.includes('CATS')) { return ; - } else if (this.props.type === 'FLEX_HCD') { + } else if (this.props.type.includes('FLEX') || this.props.type.includes('Moc')) { return ; } else { return null; @@ -1389,7 +1294,7 @@ class SampleGridTableContainer extends React.Component { } render() { - return this.state.loadGridTable ? ( + return (
    {this.props.contextMenu.show ? (
    + {this.getManualSamples()} {this.renderSampleChangerDrawing()} {this.getManualSamples()} {this.isSingleCell() ? this.getSampleTableSingleCell(10) - : this.getSampleTable('auto')} - {/* {this.getSampleTable(this.isSingleCell() ? 12 : 'auto')} */} + : this.getSampleTable(9)} ) : ( {this.getManualSamples()} {this.isSingleCell() - ? this.getSampleTableSingleCell(12) + ? this.getSampleTableSingleCell(2) : this.getSampleTable(6)} )}
    - ) : ( -
    loading...
    - ); + ) } } @@ -1460,9 +1363,6 @@ function mapStateToProps(state) { viewMode: state.sampleGrid.viewMode, contextMenu: state.contextMenu.genericContextMenu, sampleChanger: state.sampleChanger, - type: state.sampleChanger.contents - ? state.sampleChanger.contents.name - : '"Mockup"', }; } diff --git a/ui/src/containers/SampleListViewContainer.jsx b/ui/src/containers/SampleListViewContainer.jsx index 156586801..ed50c7d65 100644 --- a/ui/src/containers/SampleListViewContainer.jsx +++ b/ui/src/containers/SampleListViewContainer.jsx @@ -22,7 +22,13 @@ import { import { MdGridView } from 'react-icons/md'; -import { QUEUE_RUNNING } from '../constants'; +import { LuSettings2 } from "react-icons/lu"; + +import { + QUEUE_RUNNING, + isCollected, + hasLimsData +} from '../constants'; import { sendGetSampleList, @@ -61,6 +67,10 @@ class SampleListViewContainer extends React.Component { super(props); this.onMouseDown = this.onMouseDown.bind(this); this.syncSamples = this.syncSamples.bind(this); + this.mutualExclusiveFilterOption = + this.mutualExclusiveFilterOption.bind(this); + this.inQueueSampleID = this.inQueueSampleID.bind(this); + this.filter = this.filter.bind(this); this.sampleGridFilter = this.sampleGridFilter.bind(this); this.getFilterOptionValue = this.getFilterOptionValue.bind(this); this.sampleGridClearFilter = this.sampleGridClearFilter.bind(this); @@ -114,7 +124,12 @@ class SampleListViewContainer extends React.Component { } setViewMode(mode) { - this.props.filter({ cellFilter: '' }); + if (this.props.type.includes('FLEX') && mode.includes("Graphical")) { + this.props.filter({ cellFilter: '1' }); + } else { + this.props.filter({ cellFilter: '' }); + } + localStorage.setItem('view-mode', mode); this.props.setViewMode(mode); } @@ -284,6 +299,99 @@ class SampleListViewContainer extends React.Component { } } + /** + * Helper function for filter that takes a sample object instead of sampleID + * + * @param {object} sample + * return {boolean} true if sample is in queue otherwise false + */ + inQueueSampleID(sample) { + return this.inQueue(sample.sampleID); + } + + + /** + * Performs filtering on a sample with two options that are mutually exclusive + * Includes sample according to provided options o1 and o2, always includes the + * sample if both options are either true or false simultaneously (ignoring the + * options o1 and o2) + * + * @property {Object} filterOptions + * @param {Object} sample + * @param {string} o1 - option name 1 + * @param {string} o2 - option name 2 + * @param {function} fun - function that tests for inclusion + * + * return {boolean} true if item is to be included otherwise false + */ + mutualExclusiveFilterOption(sample, o1, o2, testFun) { + let includeItem = false; + + // First case is included for clarity since the two options + // cancel each other out. Dont do anything same as both false. Otherwise + // apply filter. + + if (this.props.filterOptions[o1] && this.props.filterOptions[o2]) { + includeItem = true; + } else if (!this.props.filterOptions[o1] && !this.props.filterOptions[o2]) { + includeItem = true; + } else if (this.props.filterOptions[o1]) { + includeItem = testFun(sample); + } else if (this.props.filterOptions[o2]) { + includeItem = !testFun(sample); + } + + return includeItem; + } + + /** + * Filter function for SampleItems + * + * @property {Object} sampleList + * @property {Object} filterOptions + * + * @param {string} key - sampleID + * + * return {boolean} true if item is to be excluded otherwise false + */ + filter(key) { + const sample = this.props.sampleList[key]; + let fi = false; + if (sample) { + const sampleFilter = + `${sample.sampleName} ${sample.proteinAcronym}`.toLowerCase(); + + fi = sampleFilter.includes(this.props.filterOptions.text.toLowerCase()); + + fi &= this.mutualExclusiveFilterOption( + sample, + 'inQueue', + 'notInQueue', + this.inQueueSampleID, + ); + fi &= this.mutualExclusiveFilterOption( + sample, + 'collected', + 'notCollected', + isCollected, + ); + fi &= this.mutualExclusiveFilterOption( + sample, + 'limsSamples', + '', + hasLimsData, + ); + if (this.props.filterOptions.cellFilter !== '') { + fi &= sample.cell_no === Number(this.props.filterOptions.cellFilter); + } + if (this.props.filterOptions.puckFilter !== '') { + fi &= sample.puck_no === Number(this.props.filterOptions.puckFilter); + } + } + + return fi; + } + /** * @return {boolean} true if any filter option is used */ @@ -294,7 +402,9 @@ class SampleListViewContainer extends React.Component { this.props.filterOptions.collected || this.props.filterOptions.notCollected || this.props.filterOptions.limsSamples || - this.props.filterOptions.text.length > 0 + this.props.filterOptions.text.length > 0 || + this.props.filterOptions.cellFilter !== '' || + this.props.filterOptions.puckFilter !== '' ); } @@ -464,6 +574,21 @@ class SampleListViewContainer extends React.Component { this.props.selectSamples(Object.keys(this.props.sampleList), false); } + + displayContextMenu(e, contextMenuID) { + if (this.props.queue.queueStatus !== QUEUE_RUNNING) { + this.props.showGenericContextMenu(true, contextMenuID, e.pageX, e.pageY); + } + + const samplesListKeys= Object.keys(this.props.sampleList).filter((key) => + this.filter(key)); + + debugger; + + this.props.selectSamples(samplesListKeys) + e.stopPropagation(); + } + /** * Adds samples in sampleIDList to queue * @@ -675,7 +800,7 @@ class SampleListViewContainer extends React.Component { className="samples-grid-table-card-header" > - + - +
    - - + - + @@ -834,6 +946,8 @@ class SampleListViewContainer extends React.Component { removeSelectedSamples={this.removeSelectedSamples} removeSelectedTasks={this.removeSelectedTasks} setViewMode={this.setViewMode} + filterSampleByKey={this.filter} + type={this.props.type} /> @@ -865,6 +979,9 @@ function mapStateToProps(state) { sampleChanger: state.sampleChanger, contextMenu: state.contextMenu.genericContextMenu, general: state.general, + type: state.sampleChanger.contents + ? state.sampleChanger.contents.name + : 'Mockup', }; }