Improvements to study list selection, study/timepoint association UI (LT-116 and LT-60)

This commit is contained in:
Erik Ziegler 2016-01-13 11:50:07 +01:00
parent 41b75de814
commit cb8672576e
8 changed files with 179 additions and 24 deletions

View File

@ -4,6 +4,8 @@
<div class="col-md-12">
<h4>Instructions</h4>
<p>Associate the selected studies with timepoints in the clinical trial.</p>
<p>We have automatically retrieved all studies within 14 days (<strong>{{formatDA earliestDate}}</strong> to <strong>{{formatDA latestDate}}</strong>) of your selected studies, in case you forgot to
select a study.</p>
</div>
</div>
@ -12,6 +14,7 @@
<tr>
<th class="center">Include Study?</th>
<th class="center">Study Date</th>
<th class="center">Patient Name</th>
<th class="center">Study Description</th>
<th class="center">Timepoint Type</th>
</tr>
@ -21,25 +24,34 @@
{{ #each relevantStudies }}
<tr>
<td class="center">
<input type="checkbox" class="includeStudy" checked/>
<input type="checkbox" class="includeStudy" {{inlineIf autoselected false 'checked'}}/>
</td>
<td class="center">
<td class="center studyDataCell {{ #if autoselected }}disabled{{ /if }}">
{{ #if autoselected}}
<p class="studyDate autoselected"
title="This study was automatically added to your list due to its
similarity with your other selected studies">
data-toggle="tooltip"
title="This study was autoselected.">
{{formatDA studyDate}}
</p>
{{ else }}
<p class="studyDate">{{formatDA studyDate}}</p>
{{ /if }}
</td>
<td>
<td class="center studyDataCell {{ #if autoselected }}disabled{{ /if }}">
<p>{{formatPN patientName}}</p>
</td>
<td class="studyDataCell {{ #if autoselected }}disabled{{ /if }}">
<p>{{studyDescription}}</p>
</td>
<td class="timepointOptions center">
<td class="timepointOptions center studyDataCell {{ #if autoselected }}disabled{{ /if }}">
{{ #each timepointOptions }}
<label><input type="radio" name="{{_id}}" value={{type}}> {{name}}</label>
<label>
<input type="radio"
name="{{../_id}}"
value={{type}}
{{ inlineIf autoselected true 'disabled'}}>
{{name}}
</label>
{{ /each }}
</td>
</tr>

View File

@ -1,5 +1,72 @@
function autoSelectStudies() {
return [];
/**
* Finds related studies within defined time window of =/- 14 days of selected studies
* @param selectedStudies
* @param range Object
*/
function getDateRange(selectedStudies, range) {
if (range === undefined) {
range = {
days: 14
};
}
if (!selectedStudies.length) {
return;
}
var earliestStudy = selectedStudies[0];
var latestStudy = selectedStudies[selectedStudies.length - 1];
var earliestDate = moment(earliestStudy.studyDate, 'YYYYMMDD');
earliestDate.subtract(range);
var latestDate = moment(latestStudy.studyDate, 'YYYYMMDD');
latestDate.add(range);
return {
earliestDate: earliestDate,
latestDate: latestDate
};
}
/**
*
* @returns {Array}
*/
function autoSelectStudies(selectedStudies) {
if (!selectedStudies.length) {
return;
}
var range = getDateRange(selectedStudies);
var autoselected = WorklistStudies.find({
studyDate: {
$gte: range.earliestDate.format('YYYYMMDD'),
$lte: range.latestDate.format('YYYYMMDD')
}
}, {
sort: {
studyDate: 1
}
}).fetch();
// Make an array of studyInstanceUids in selectedStudies
var studyInstanceUids = selectedStudies.map(function(selectedStudy) {
return selectedStudy.studyInstanceUid;
});
autoselected.forEach(function(study) {
var exists = studyInstanceUids.indexOf(study.studyInstanceUid);
if (exists > -1) {
study.autoselected = false;
return;
}
study.autoselected = true;
});
return autoselected;
}
Template.studyAssociationTable.helpers({
@ -10,9 +77,13 @@ Template.studyAssociationTable.helpers({
* @returns {Array.<T>}
*/
relevantStudies: function() {
var userSelectedStudies = WorklistSelectedStudies.find().fetch() || [];
var autoselected = autoSelectStudies(userSelectedStudies);
return userSelectedStudies.concat(autoselected);
var selectedStudies = WorklistSelectedStudies.find({}, {
sort: {
studyDate: 1
}
}).fetch() || [];
var autoselected = autoSelectStudies(selectedStudies);
return autoselected;
},
/**
* This helper returns the list of Timepoint types the user can set for this study
@ -30,7 +101,54 @@ Template.studyAssociationTable.helpers({
name: 'Follow-up'
}
];
},
earliestDate: function() {
var selectedStudies = WorklistSelectedStudies.find({}, {
sort: {
studyDate: 1
}
}).fetch();
var range = getDateRange(selectedStudies);
if (!range) {
return;
}
return range.earliestDate;
},
latestDate: function() {
var selectedStudies = WorklistSelectedStudies.find({}, {
sort: {
studyDate: 1
}
}).fetch();
var range = getDateRange(selectedStudies);
if (!range) {
return;
}
return range.latestDate;
}
});
//trial criteria!
Template.studyAssociationTable.events({
'change input.includeStudy': function(e) {
var checkbox = e.currentTarget;
var studyDataCells = $(checkbox).parents('tr').find('td.studyDataCell');
if (checkbox.checked === true) {
studyDataCells.removeClass('disabled');
studyDataCells.find('input').attr('disabled', false);
} else {
studyDataCells.addClass('disabled');
studyDataCells.find('input').attr('disabled', true);
}
}
});
//trial criteria!
/*There shall be Associate option in right-click dialog
If associated, double-click shall go to image view.
If not associated, user shall be directed to Associate Time Points Dialog
Use shall also be allowed to select multiple studies from study list to associate
Associate Time Point dialog shall present selected studies and studies within defined time window of =/- 14 days of selected studies
User should only be able to associate one time point at a time (user should not be able to select both BL and F/U for different studies in associate dialog)
*/

View File

@ -13,4 +13,7 @@
margin: 0 3px
label
padding: 0 5px
padding: 0 5px
td.disabled
opacity: 0.2

View File

@ -0,0 +1,10 @@
/**
* Helper for setting checkboxes as checked or unchecked inside templates,
* based on another variable's value
*/
UI.registerHelper("inlineIf", function (value, match, attributeName) {
if(value === match) {
return attributeName;
}
return '';
});

View File

@ -155,6 +155,7 @@ Package.onUse(function (api) {
api.addFiles('lib/helpers/formatNumberPrecision.js', 'client');
api.addFiles('lib/helpers/formatPN.js', 'client');
api.addFiles('lib/helpers/formatTM.js', 'client');
api.addFiles('lib/helpers/inlineIf.js', 'client');
// Server-side functions
api.addFiles('server/seed.js', 'server');

View File

@ -20,8 +20,10 @@
</span> Anonymize
</a>
<a><i class="fa fa-trash fa-lg"></i> Delete</a>
<a><i class="fa fa-share fa-lg"></i> Share</a>
<a><i class="fa fa-send-o fa-lg"></i> Send</a>
<a><i class="fa fa-exchange fa-lg"></i> Export</a>
<a><i class="fa fa-download fa-lg"></i> Download</a>
<a><i class="fa fa-photo fa-lg"></i> View Series Details</a>
</li>
</ul>
</div>

View File

@ -36,11 +36,11 @@ openStudyContextMenu = function(e, template) {
dialogProperty.top = e.pageY;// - dialog.outerHeight() - 40;
dialogProperty.left = e.pageX;// - dialog.outerWidth() / 2;
var pageHeight = $(window).height();
var pageHeight = $(document.body).height();
dialogProperty.top = Math.max(dialogProperty.top, 0);
dialogProperty.top = Math.min(dialogProperty.top, pageHeight - dialog.outerHeight());
var pageWidth = $(window).width();
var pageWidth = $(document.body).width();
dialogProperty.left = Math.max(dialogProperty.left, 0);
dialogProperty.left = Math.min(dialogProperty.left, pageWidth - dialog.outerWidth());
}

View File

@ -33,21 +33,28 @@ function handleShiftClick(studyRow, data) {
}
// Loop through the rows in between current and previous selected studies
rowsInBetween.each(function(index, row) {
if ($(row).is(studyRow)) {
// When we reach the currently clicked-on row, stop
return false;
} else if ($(row).hasClass('active')) {
rowsInBetween.each(function() {
var row = $(this);
if (row.hasClass('active')) {
// If we find one that is already selected, do nothing
return;
}
// Get the relevant studyInstanceUid
var studyInstanceUid = $(row).attr('studyInstanceUid');
var studyInstanceUid = row.attr('studyInstanceUid');
// Retrieve the data context through Blaze
var data = Blaze.getData(this);
// Set the current study as selected
WorklistSelectedStudies.insert(data);
studyRow.addClass('active');
row.addClass('active');
// When we reach the currently clicked-on row, stop the loop
if (row.is(studyRow)) {
return false;
}
return true;
});
@ -77,6 +84,7 @@ function handleCtrlClick(studyRow, data) {
// Set this as the previously selected row, so the user can
// use Shift to select from this point onwards
Worklist.previouslySelected = studyRow;
log.info('Worklist PreviouslySelected set: ' + studyRow.index());
}
}
@ -102,6 +110,7 @@ Template.worklistStudy.events({
// Set the previous study to the currently clicked-on study
Worklist.previouslySelected = studyRow;
log.info('Worklist PreviouslySelected set: ' + studyRow.index());
// Set the current study as selected
WorklistSelectedStudies.insert(data);