PWV-1: Refactoring ohif:studylist package
This commit is contained in:
1 parent
984ef94403
commit
4d79bf84a8
79 files changed
+450
-551
No files matched your search
+107
@@ -0,0 +1,107 @@
|
||||
<template name="studylistResult">
|
||||
<div class="studyListToolbar">
|
||||
<div class="header">
|
||||
Study list
|
||||
</div>
|
||||
<div class="studyCount pull-right">
|
||||
{{numberOfStudies}}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{>studylistToolbar}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="theadBackground">
|
||||
</div>
|
||||
<div id="studyListContainer">
|
||||
<table id="tblStudyList" class="studylistResult table noselect">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="patientName">
|
||||
<div id="_patientName" class="sortingCell">
|
||||
<span>Patient Name</span>
|
||||
<i class="{{sortingColumnsIcons.patientName}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="patientName"
|
||||
placeholder="Patient Name">
|
||||
</th>
|
||||
<th class="patientId">
|
||||
<div id="_patientId" class="sortingCell">
|
||||
<span>MRN</span>
|
||||
<i class="{{sortingColumnsIcons.patientId}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="patientId"
|
||||
placeholder="MRN">
|
||||
</th>
|
||||
<th class="accessionNumber">
|
||||
<div id="_accessionNumber" class="sortingCell ">
|
||||
<span>Accession #</span>
|
||||
<i class="{{sortingColumnsIcons.accessionNumber}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="accessionNumber"
|
||||
placeholder="Accession #">
|
||||
</th>
|
||||
<th class="studyDate">
|
||||
<div id="_studyDate" class="sortingCell">
|
||||
<span>Study Date</span>
|
||||
<i class="{{sortingColumnsIcons.studyDate}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
name="daterange"
|
||||
id="studyDate"
|
||||
placeholder="Study Date">
|
||||
</th>
|
||||
<th class="modalities">
|
||||
<div id="_modalities" class="sortingCell">
|
||||
<span>Modality</span>
|
||||
<i class="{{sortingColumnsIcons.modalities}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="modality"
|
||||
placeholder="Modality">
|
||||
</th>
|
||||
<th class="studyDescription">
|
||||
<div id="_studyDescription" class="sortingCell">
|
||||
<span>Study Description</span>
|
||||
<i class="{{sortingColumnsIcons.studyDescription}}"> </i>
|
||||
</div>
|
||||
<input type="text"
|
||||
class="form-control studylist-search"
|
||||
id="studyDescription"
|
||||
placeholder="Study Description">
|
||||
</th>
|
||||
<th class="numberOfStudyRelatedInstances">
|
||||
<div id="_numberOfStudyRelatedInstances" class="sortingCell">
|
||||
<span># Images</span>
|
||||
<i class="{{sortingColumnsIcons.numberOfStudyRelatedInstances}}"> </i>
|
||||
</div>
|
||||
<input type="text" class="form-control studylist-search invisible"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="studyListData">
|
||||
{{#each study in studies}}
|
||||
{{>studylistStudy (clone study this)}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
{{> studylistPagination }}
|
||||
|
||||
{{#if session "showLoadingText"}}
|
||||
{{>loadingText}}
|
||||
{{ else }}
|
||||
{{#unless numberOfStudies}}
|
||||
<div class="notFound">No matching results</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
Session.setDefault('showLoadingText', true);
|
||||
|
||||
Template.studylistResult.helpers({
|
||||
/**
|
||||
* Returns a sorted instance of the StudyListStudies Collection
|
||||
* by Patient name and Study Date in Ascending order.
|
||||
*/
|
||||
studies() {
|
||||
const instance = Template.instance();
|
||||
let studies;
|
||||
let sortOption = {
|
||||
patientName: 1,
|
||||
studyDate: 1
|
||||
};
|
||||
|
||||
// Update sort option if session is defined
|
||||
if (Session.get('sortOption')) {
|
||||
sortOption = Session.get('sortOption');
|
||||
}
|
||||
|
||||
// Pagination parameters
|
||||
const rowsPerPage = instance.rowsPerPage.get();
|
||||
const currentPage = instance.currentPage.get();
|
||||
const offset = rowsPerPage * currentPage;
|
||||
const limit = offset + rowsPerPage;
|
||||
|
||||
studies = StudyListStudies.find({}, {
|
||||
sort: sortOption
|
||||
}).fetch();
|
||||
|
||||
if (!studies) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update record count
|
||||
instance.recordCount.set(studies.length);
|
||||
|
||||
// Limit studies
|
||||
return studies.slice(offset, limit);
|
||||
},
|
||||
|
||||
numberOfStudies() {
|
||||
return StudyListStudies.find().count();
|
||||
},
|
||||
|
||||
sortingColumnsIcons() {
|
||||
const instance = Template.instance();
|
||||
|
||||
let sortingColumnsIcons = {};
|
||||
Object.keys(instance.sortingColumns.keys).forEach(key => {
|
||||
const value = instance.sortingColumns.get(key);
|
||||
|
||||
if (value === 1) {
|
||||
sortingColumnsIcons[key] = 'fa fa-fw fa-sort-up';
|
||||
} else if (value === -1) {
|
||||
sortingColumnsIcons[key] = 'fa fa-fw fa-sort-down';
|
||||
} else {
|
||||
// fa-fw is blank
|
||||
sortingColumnsIcons[key] = 'fa fa-fw';
|
||||
}
|
||||
});
|
||||
return sortingColumnsIcons;
|
||||
}
|
||||
});
|
||||
|
||||
var studyDateFrom;
|
||||
var studyDateTo;
|
||||
var filter;
|
||||
|
||||
/**
|
||||
* Transforms an input string into a search filter for
|
||||
* the StudyList Search call
|
||||
*
|
||||
* @param filter The input string to be searched for
|
||||
* @returns {*}
|
||||
*/
|
||||
function getFilter(filter) {
|
||||
if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
|
||||
filter += '*';
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a value in a string
|
||||
*/
|
||||
function isIndexOf(mainVal, searchVal) {
|
||||
if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace object if undefined
|
||||
*/
|
||||
function replaceUndefinedColumnValue(text) {
|
||||
if (text === undefined || text === 'undefined') {
|
||||
return '';
|
||||
} else {
|
||||
return text.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
||||
/**
|
||||
* Convert string to study date
|
||||
*/
|
||||
function convertStringToStudyDate(dateStr) {
|
||||
const y = dateStr.substring(0, 4);
|
||||
const m = dateStr.substring(4, 6);
|
||||
const d = dateStr.substring(6, 8);
|
||||
const newDateStr = m + '/' + d + '/' + y;
|
||||
return new Date(newDateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a search for studies matching the studylist query parameters
|
||||
* Inserts the identified studies into the StudyListStudies Collection
|
||||
*/
|
||||
function search() {
|
||||
console.log('search()');
|
||||
|
||||
// Show loading message
|
||||
Session.set('showLoadingText', true);
|
||||
|
||||
// Create the filters to be used for the StudyList Search
|
||||
filter = {
|
||||
patientName: getFilter($('input#patientName').val()),
|
||||
patientId: getFilter($('input#patientId').val()),
|
||||
accessionNumber: getFilter($('input#accessionNumber').val()),
|
||||
studyDescription: getFilter($('input#studyDescription').val()),
|
||||
studyDateFrom: studyDateFrom,
|
||||
studyDateTo: studyDateTo,
|
||||
modalitiesInStudy: $('input#modality').val() ? $('input#modality').val() : ""
|
||||
};
|
||||
|
||||
// Make sure that modality has a reasonable value, since it is occasionally
|
||||
// returned as 'undefined'
|
||||
const modality = replaceUndefinedColumnValue($('input#modality').val());
|
||||
|
||||
// Clear all current studies
|
||||
StudyListStudies.remove({});
|
||||
|
||||
Meteor.call('StudyListSearch', filter, (error, studies) => {
|
||||
console.log('StudyListSearch');
|
||||
if (error) {
|
||||
OHIF.log.warn(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide loading text
|
||||
Session.set('showLoadingText', false);
|
||||
|
||||
if (!studies) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through all identified studies
|
||||
studies.forEach(study => {
|
||||
// Search the rest of the parameters that aren't done via the server call
|
||||
if (isIndexOf(study.modalities, modality) &&
|
||||
(new Date(studyDateFrom).setHours(0, 0, 0, 0) <= convertStringToStudyDate(study.studyDate) || !studyDateFrom || studyDateFrom === '') &&
|
||||
(convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0, 0, 0, 0) || !studyDateTo || studyDateTo === '')) {
|
||||
|
||||
// Convert numberOfStudyRelatedInstance string into integer
|
||||
study.numberOfStudyRelatedInstances = !isNaN(study.numberOfStudyRelatedInstances) ? parseInt(study.numberOfStudyRelatedInstances) : undefined;
|
||||
|
||||
// Insert any matching studies into the StudyListStudies Collection
|
||||
StudyListStudies.insert(study);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Template.studylistResult.onCreated(() => {
|
||||
let instance = Template.instance();
|
||||
instance.sortOption = new ReactiveVar();
|
||||
instance.sortingColumns = new ReactiveDict();
|
||||
|
||||
// Pagination parameters
|
||||
|
||||
// Rows per page is 25 as default
|
||||
instance.rowsPerPage = new ReactiveVar(25);
|
||||
|
||||
// Set currentPage indexed 0
|
||||
instance.currentPage = new ReactiveVar(0);
|
||||
instance.recordCount = new ReactiveVar();
|
||||
|
||||
// Set sortOption
|
||||
const sortOptionSession = Session.get('sortOption');
|
||||
if (sortOptionSession) {
|
||||
instance.sortingColumns.set(sortOptionSession);
|
||||
} else {
|
||||
instance.sortingColumns.set({
|
||||
patientName: 1,
|
||||
studyDate: 1,
|
||||
patientId: 0,
|
||||
accessionNumber: 0,
|
||||
studyDescription: 0,
|
||||
modality: 0,
|
||||
numberOfStudyRelatedInstances: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Template.studylistResult.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
// Initialize daterangepicker
|
||||
const today = moment();
|
||||
const lastWeek = moment().subtract(6, 'days');
|
||||
const lastMonth = moment().subtract(29, 'days');
|
||||
const datePicker = instance.$('#studyDate').daterangepicker({
|
||||
maxDate: today,
|
||||
autoUpdateInput: true,
|
||||
ranges: {
|
||||
Today: [today, today],
|
||||
'Last 7 Days': [lastWeek, today],
|
||||
'Last 30 Days': [lastMonth, today]
|
||||
}
|
||||
}).data('daterangepicker');
|
||||
|
||||
// Retrieve all studies
|
||||
search();
|
||||
});
|
||||
|
||||
function resetSortingColumns(instance, sortingColumn) {
|
||||
Object.keys(instance.sortingColumns.keys).forEach(key => {
|
||||
if (key !== sortingColumn) {
|
||||
instance.sortingColumns.set(key, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Template.studylistResult.events({
|
||||
'keydown input'(event) {
|
||||
if (event.which === 13) { // Enter
|
||||
search();
|
||||
}
|
||||
},
|
||||
|
||||
'onsearch input'() {
|
||||
search();
|
||||
},
|
||||
|
||||
'change #studyDate'(event) {
|
||||
let dateRange = $(event.currentTarget).val();
|
||||
|
||||
// Remove all space chars
|
||||
dateRange = dateRange.replace(/ /g, '');
|
||||
|
||||
// Split dateRange into subdates
|
||||
const dates = dateRange.split('-');
|
||||
studyDateFrom = dates[0];
|
||||
studyDateTo = dates[1];
|
||||
|
||||
if (dateRange !== '') {
|
||||
search();
|
||||
}
|
||||
},
|
||||
|
||||
'click div.sortingCell'(event, instance) {
|
||||
const elementId = event.currentTarget.id;
|
||||
|
||||
// Remove _ from id
|
||||
const columnName = elementId.replace('_', '');
|
||||
|
||||
let sortOption = {};
|
||||
resetSortingColumns(instance, columnName);
|
||||
|
||||
const columnObject = instance.sortingColumns.get(columnName);
|
||||
if (columnObject) {
|
||||
instance.sortingColumns.set(columnName, columnObject * -1);
|
||||
sortOption[columnName] = columnObject * -1;
|
||||
} else {
|
||||
instance.sortingColumns.set(columnName, 1);
|
||||
sortOption[columnName] = 1;
|
||||
}
|
||||
|
||||
instance.sortOption.set(sortOption);
|
||||
Session.set('sortOption', sortOption);
|
||||
}
|
||||
});
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
@import "{design}/app"
|
||||
|
||||
$tableHoverColor = #2c363f
|
||||
$tableTextPrimaryColor = white
|
||||
$tableTextSecondaryColor = #91b9cd
|
||||
|
||||
$inputBackgroundColor = #2c363f
|
||||
$inputPlaceholderColor = lightgray
|
||||
|
||||
$studyListToolbarHeight = 75px
|
||||
$tablePadding = 8%
|
||||
$bodyCellHeight = 40px
|
||||
|
||||
.studyListToolbar
|
||||
theme('background', '$uiGrayDarker')
|
||||
height: $studyListToolbarHeight
|
||||
border-bottom: 2px solid black
|
||||
padding: 0 30px
|
||||
|
||||
div
|
||||
display: inline-block
|
||||
|
||||
.header
|
||||
font-size: 22px
|
||||
font-weight: 300
|
||||
color: $tableTextSecondaryColor
|
||||
padding: 0 10px
|
||||
line-height: $studyListToolbarHeight
|
||||
|
||||
.studylistToolbar
|
||||
height: $studyListToolbarHeight
|
||||
line-height: $studyListToolbarHeight
|
||||
|
||||
.studyCount
|
||||
theme('color', '$uiSkyBlue')
|
||||
font-size: 40px
|
||||
font-weight: 100
|
||||
padding: 0 10px
|
||||
line-height: $studyListToolbarHeight
|
||||
|
||||
.theadBackground
|
||||
theme('background-color', '$uiGrayDarker')
|
||||
width: 100%
|
||||
height: 122px
|
||||
position: absolute
|
||||
z-index: 1
|
||||
|
||||
#studyListContainer
|
||||
background: black
|
||||
width: 100%
|
||||
padding: 0 $tablePadding
|
||||
position: absolute
|
||||
z-index: 2
|
||||
|
||||
.loadingTextDiv
|
||||
color: $tableTextSecondaryColor
|
||||
font-size: 30px
|
||||
|
||||
.notFound
|
||||
color: $tableTextSecondaryColor
|
||||
font-size: 30px
|
||||
font-weight: 200
|
||||
text-align: center
|
||||
|
||||
table#tblStudyList
|
||||
thead
|
||||
theme('background-color', '$uiGrayDarker')
|
||||
white-space: nowrap
|
||||
|
||||
tr
|
||||
th
|
||||
padding: 0
|
||||
border-bottom: solid 1px #6fbde2
|
||||
width: 100%
|
||||
|
||||
&:first-child
|
||||
padding-left: $tablePadding
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePadding
|
||||
|
||||
div.sortingCell
|
||||
display: inline-block
|
||||
cursor: pointer
|
||||
width: 100%
|
||||
min-width: 95px
|
||||
margin: 0 auto
|
||||
color: $tableTextPrimaryColor
|
||||
font-weight: 400
|
||||
padding: 20px
|
||||
|
||||
span
|
||||
font-size: 15px
|
||||
float: left
|
||||
|
||||
i
|
||||
margin: 0 5px
|
||||
|
||||
&:hover
|
||||
theme('color', '$activeColor')
|
||||
|
||||
&.active, &:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
input.studylist-search
|
||||
height: 34px
|
||||
margin: 0 5px 20px 5px
|
||||
padding: 20px
|
||||
cursor: pointer
|
||||
border: none
|
||||
background-color: $inputBackgroundColor
|
||||
color: $inputPlaceholderColor
|
||||
font-size: 10pt
|
||||
font-weight: normal
|
||||
width: calc(100% - 10px)
|
||||
border-radius: 4px
|
||||
box-sizing: border-box
|
||||
|
||||
transition(all 0.15s ease)
|
||||
placeholder-color($inputPlaceholderColor)
|
||||
|
||||
&.invisible
|
||||
visibility: hidden
|
||||
|
||||
&:active, &:hover
|
||||
background-color: $inputBackgroundColor
|
||||
|
||||
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
|
||||
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
|
||||
::-webkit-datetime-edit-day-field:not([aria-valuenow])
|
||||
color: transparent
|
||||
|
||||
tbody
|
||||
tr
|
||||
padding: 5px
|
||||
background-color: black
|
||||
|
||||
&:nth-child(even)
|
||||
theme('background-color', '$uiGrayDarker')
|
||||
|
||||
td
|
||||
height: $bodyCellHeight
|
||||
line-height: $bodyCellHeight
|
||||
color: $tableTextPrimaryColor
|
||||
font-weight: 300
|
||||
border-top: 1px solid #436270
|
||||
white-space: nowrap
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:first-child
|
||||
padding-left: $tablePadding
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePadding
|
||||
|
||||
&:hover, &:active, &.active
|
||||
background-color: $tableHoverColor
|
||||
color: white
|
||||
|
||||
td
|
||||
// This selector is necessary to override bootstrap's 'table' class
|
||||
border-top: 1px solid #436270
|
||||
border-bottom: 1px solid #436270
|
||||
background-color: $tableHoverColor
|
||||
|
||||
|
||||
@media only screen and (max-width: 1362px)
|
||||
$tablePadding = 5%
|
||||
|
||||
#studyListContainer
|
||||
padding: 0 $tablePadding
|
||||
|
||||
table#tblStudyList
|
||||
thead,
|
||||
tbody
|
||||
tr
|
||||
th,
|
||||
td
|
||||
&:first-child
|
||||
padding-left: $tablePadding
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePadding
|
||||
|
||||
@media only screen and (max-width: 1161px)
|
||||
$tablePadding = 3%
|
||||
|
||||
#studyListContainer
|
||||
padding: 0 $tablePadding
|
||||
|
||||
table#tblStudyList
|
||||
thead,
|
||||
tbody
|
||||
tr
|
||||
th,
|
||||
td
|
||||
&:first-child
|
||||
padding-left: $tablePadding
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePadding
|
||||
|
||||
@media only screen and (max-width: 1069px)
|
||||
$tablePaddingMediumScreen = 5px
|
||||
|
||||
.studyListToolbar
|
||||
padding: 0 $tablePaddingMediumScreen
|
||||
|
||||
#studyListContainer
|
||||
padding: 0
|
||||
|
||||
table#tblStudyList
|
||||
thead > tr > th
|
||||
|
||||
&:first-child
|
||||
padding-left: $tablePaddingMediumScreen
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePaddingMediumScreen
|
||||
|
||||
input.worklist-search
|
||||
padding: 10px
|
||||
|
||||
div.sortingCell
|
||||
padding: 10px 5px
|
||||
min-width: auto
|
||||
|
||||
i
|
||||
width: auto
|
||||
|
||||
tbody > tr > td
|
||||
&:first-child
|
||||
padding-left: $tablePaddingMediumScreen
|
||||
|
||||
&:last-child
|
||||
padding-right: $tablePaddingMediumScreen
|
||||
|
||||
.worklistPagination
|
||||
.row
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
Reference in new issue
Block a user