diff --git a/WebApplication/src/components/SideBar.vue b/WebApplication/src/components/SideBar.vue index 00d6c5b..65bcb3c 100644 --- a/WebApplication/src/components/SideBar.vue +++ b/WebApplication/src/components/SideBar.vue @@ -29,7 +29,7 @@ export default { queryableDicomWebServers: state => state.configuration.queryableDicomWebServers, studiesIds: state => state.studies.studiesIds, statistics: state => state.studies.statistics, - labelsFilter: state => state.studies.labelsFilter, + labelFilters: state => state.studies.labelFilters, jobs: state => state.jobs.jobsIds, allLabels: state => state.labels.allLabels, hasCustomLogo: state => state.configuration.hasCustomLogo, @@ -97,7 +97,7 @@ export default { this.messageBus.emit('filter-label-changed', label); }, isSelectedLabel(label) { - return this.labelsFilter.includes(label); + return this.labelFilters.includes(label); }, logout(event) { event.preventDefault(); diff --git a/WebApplication/src/components/StudyList.vue b/WebApplication/src/components/StudyList.vue index 9dfba3f..9d349b3 100644 --- a/WebApplication/src/components/StudyList.vue +++ b/WebApplication/src/components/StudyList.vue @@ -16,41 +16,52 @@ document._allowedFilters = ["StudyDate", "StudyTime", "AccessionNumber", "Patien document._studyColumns = { "StudyDate": { - "width": "7%" + "width": "7%", + "isOrderable": true }, "AccessionNumber": { "width": "11%", - "placeholder": "1234" + "placeholder": "1234", + "isOrderable": true }, "PatientID": { "width": "11%", - "placeholder": "1234" + "placeholder": "1234", + "isOrderable": true }, "PatientName": { "width": "15%", - "placeholder": "John^Doe" + "placeholder": "John^Doe", + "isOrderable": true }, "PatientBirthDate": { - "width": "7%" + "width": "7%", + "isOrderable": true }, "StudyDescription": { "width": "25%", - "placeholder": "Chest" + "placeholder": "Chest", + "isOrderable": true }, "modalities": { - "width": "6%" + "width": "6%", + "isOrderable": false }, "seriesCount": { - "width": "4%" + "width": "4%", + "isOrderable": false }, "instancesCount": { - "width": "4%" + "width": "4%", + "isOrderable": false }, "seriesAndInstancesCount": { - "width": "7%" + "width": "7%", + "isOrderable": false }, "undefined": { - "width": "10%" + "width": "10%", + "isOrderable": false } }; @@ -67,6 +78,9 @@ export default { filterGenericTags : {}, oldFilterGenericTags : {}, filterLabels: [], + currentOrderByTag: null, + currentOrderDirection: 'ASC', + filterOrderBy: [{'Type': 'Metadata', 'Key': 'LastUpdate', 'Direction': 'DESC'}], allModalities: true, noneModalities: false, updatingFilterUi: false, @@ -310,6 +324,40 @@ export default { return this.columns["undefined"].width; } }, + isOrderable(tagName) { + if (this.sourceType != SourceType.LOCAL_ORTHANC || !this['configuration/hasExtendedFind']) { + return false; + } + + if (tagName in document._studyColumns) { + return document._studyColumns[tagName].isOrderable; + } else { + return false; + } + }, + isOrderTagUp(tagName) { + return tagName == this.currentOrderByTag && this.currentOrderDirection == 'DESC'; + }, + isOrderTagDown(tagName) { + return tagName == this.currentOrderByTag && this.currentOrderDirection == 'ASC'; + }, + toggleOrder(ev, tagName) { + ev.preventDefault(); + ev.stopPropagation(); + + if (this.currentOrderByTag == tagName) { // this tag is already used for ordering -> change order direction + this.currentOrderDirection = (this.currentOrderDirection == 'ASC' ? 'DESC' : 'ASC'); + } else { // this tag is not used for ordering + this.currentOrderByTag = tagName; + this.currentOrderDirection = 'ASC'; + } + let o = {'Type': 'DicomTag', 'Key': this.currentOrderByTag, 'Direction': this.currentOrderDirection}; + + // remove all DICOM Tag orders and insert this one as the first one + this.filterOrderBy = [o].concat(this.filterOrderBy.filter(i => i['Type'] != 'DicomTag')); + + this._updateOrderBy(); + }, clearModalityFilter() { // console.log("StudyList: clearModalityFilter", this.updatingFilterUi); for (const modality of this.uiOptions.ModalitiesFilter) { @@ -440,7 +488,7 @@ export default { } }, _updateLabelsFilter(labels) { - this.$store.dispatch('studies/updateLabelsFilterNoReload', { labels: labels }); + this.$store.dispatch('studies/updateLabelFilterNoReload', { labels: labels }); this.updateUrlNoReload(); this.reloadStudyList(); }, @@ -450,6 +498,11 @@ export default { this.updateUrlNoReload(); this.reloadStudyList(); }, + _updateOrderBy() { + this.$store.dispatch('studies/updateOrderByNoReload', { orderBy: this.filterOrderBy }); + this.updateUrlNoReload(); + this.reloadStudyList(); + }, async updateFilterFromRoute(filters) { //console.log("StudyList: updateFilterFromRoute", this.updatingFilterUi, filters); @@ -475,14 +528,34 @@ export default { if (filterKey == "labels") { const labels = filterValue.split(","); keyValueFilters[filterKey] = labels; - await this.$store.dispatch('studies/updateLabelsFilterNoReload', { labels: labels }); + await this.$store.dispatch('studies/updateLabelFilterNoReload', { labels: labels }); + } else if (filterKey == 'order-by') { + if (this.sourceType == SourceType.LOCAL_ORTHANC) { // ignore order-by for remote sources + this.filterOrderBy = []; + this.currentOrderByTag = null; + this.currentOrderDirection = 'ASC'; + + let orders = filterValue.split(';'); + for (let order of orders) { + let o = order.split(','); + + this.filterOrderBy.push({'Type': o[0], 'Key': o[1], 'Direction': o[2]}); + + if (o[0] == 'DicomTag' && this.currentOrderByTag == null) { + this.currentOrderByTag = o[1]; + this.currentOrderDirection = o[2]; + } + } + this._updateOrderBy(); + } } else if (filterKey[0] === filterKey[0].toUpperCase()) { // DicomTags starts with a capital letter keyValueFilters[filterKey] = filterValue; await this.$store.dispatch('studies/updateFilterNoReload', { dicomTagName: filterKey, value: filterValue }); - } + } } await this.updateFilterForm(keyValueFilters); + await this.reloadStudyList(); this.updatingFilterUi = false; @@ -494,6 +567,12 @@ export default { for (const [key, value] of Object.entries(filters)) { if (key == "labels") { this.filterLabels = value; + } else if (key == "order-by") { + let orders = value.split(";") + for (let order of orders) { + let s = order.split(","); + + } } else if (key == "StudyDate") { this.filterStudyDate = value; this.filterStudyDateForDatePicker = dateHelpers.parseDateForDatePicker(value); @@ -543,7 +622,7 @@ export default { } } } - return this.filterStudyDate == '' && this.filterPatientBirthDate == '' && !hasGenericTagFilter && this.filterLabels.length > 0; + return this.filterStudyDate == '' && this.filterPatientBirthDate == '' && !hasGenericTagFilter && this.filterLabels.length > 0 && this.filterOrderBy.length == 0; }, async search() { if (this.isSearching) { @@ -558,7 +637,7 @@ export default { } } await this.$store.dispatch('studies/updateFilterNoReload', { dicomTagName: "ModalitiesInStudy", value: this.getModalityFilter() }); - await this.$store.dispatch('studies/updateLabelsFilterNoReload', { labels: this.filterLabels }); + await this.$store.dispatch('studies/updateLabelFilterNoReload', { labels: this.filterLabels }); } await this.updateUrlNoReload(); await this.reloadStudyList(); @@ -641,52 +720,67 @@ export default { activeFilters.push('labels=' + this.filterLabels.join(",")) } + let orderBy = ""; + if (this.filterOrderBy.length > 0) { + let orders = [] + for (let order of this.filterOrderBy) { + orders.push([order['Type'], order['Key'], order['Direction']].join(',')) + } + + orderBy = 'ordery-by=' + orders.join(';') + } + let newUrl = ""; - if (activeFilters.length > 0) { - newUrl = "/filtered-studies?" + activeFilters.join('&'); + if (activeFilters.length > 0 || orderBy.length > 0) { + newUrl = "/filtered-studies?" + [activeFilters.join('&'), orderBy].join('&'); } await this.$router.replace(newUrl); }, async reloadStudyList() { - // if we are displaying most recent studies and there is only a label filter -> continue to show the list of most recent studies (filtered by label) - const shouldShowMostRecentsWithLabel = this.uiOptions.StudyListContentIfNoSearch == "most-recents" && this.isFilteringOnlyOnLabels(); - - if (this.sourceType == SourceType.LOCAL_ORTHANC && (this['studies/isFilterEmpty'] || shouldShowMostRecentsWithLabel)) { + if (this.sourceType == SourceType.LOCAL_ORTHANC && this['configuration/hasExtendedFind']) { await this.$store.dispatch('studies/clearStudies'); - if (this.uiOptions.StudyListContentIfNoSearch == "empty") { - return; - } else if (this.uiOptions.StudyListContentIfNoSearch == "most-recents" && this['configuration/hasExtendedFind']) { - const studies = await api.getMostRecentStudies((this.filterLabels.length > 0 ? this.filterLabels[0] : null)); - for (const study of studies) { - this.$store.dispatch('studies/addStudy', { studyId: study["ID"], study: study, reloadStats: false }); - } - } else if (this.uiOptions.StudyListContentIfNoSearch == "most-recents") { - // legacy code - - if (this.isLoadingLatestStudies) { - // if currently loading, stop it - this.shouldStopLoadingLatestStudies = true; - this.isLoadingLatestStudies = false; - this.isDisplayingLatestStudies = true; - } - // restart loading - const lastChangeId = await api.getLastChangeId(); - + await this.$store.dispatch('studies/reloadFilteredStudies'); + } else { + // if we are displaying most recent studies and there is only a label filter -> continue to show the list of most recent studies (filtered by label) + const shouldShowMostRecentsWithLabel = this.uiOptions.StudyListContentIfNoSearch == "most-recents" && this.isFilteringOnlyOnLabels(); + + if (this.sourceType == SourceType.LOCAL_ORTHANC && (this['studies/isFilterEmpty'] || shouldShowMostRecentsWithLabel)) { await this.$store.dispatch('studies/clearStudies'); - this.latestStudiesIds = new Set(); - this.shouldStopLoadingLatestStudies = false; - this.isLoadingLatestStudies = true; - this.isDisplayingLatestStudies = false; + if (this.uiOptions.StudyListContentIfNoSearch == "empty") { + return; + } else if (this.uiOptions.StudyListContentIfNoSearch == "most-recents" && this['configuration/hasExtendedFind']) { + const studies = await api.getMostRecentStudies((this.filterLabels.length > 0 ? this.filterLabels[0] : null)); + for (const study of studies) { + this.$store.dispatch('studies/addStudy', { studyId: study["ID"], study: study, reloadStats: false }); + } + } else if (this.uiOptions.StudyListContentIfNoSearch == "most-recents") { + // legacy code - this.loadStudiesFromChange(lastChangeId, 1000); + if (this.isLoadingLatestStudies) { + // if currently loading, stop it + this.shouldStopLoadingLatestStudies = true; + this.isLoadingLatestStudies = false; + this.isDisplayingLatestStudies = true; + } + // restart loading + const lastChangeId = await api.getLastChangeId(); + + await this.$store.dispatch('studies/clearStudies'); + this.latestStudiesIds = new Set(); + this.shouldStopLoadingLatestStudies = false; + this.isLoadingLatestStudies = true; + this.isDisplayingLatestStudies = false; + + this.loadStudiesFromChange(lastChangeId, 1000); + } + } else { + this.shouldStopLoadingLatestStudies = true; + this.isLoadingLatestStudies = false; + this.isDisplayingLatestStudies = false; + // await this.$store.dispatch('studies/updateLabelFilterNoReload', { labels: this.filterLabels }); + await this.$store.dispatch('studies/reloadFilteredStudies'); } - } else { - this.shouldStopLoadingLatestStudies = true; - this.isLoadingLatestStudies = false; - this.isDisplayingLatestStudies = false; - // await this.$store.dispatch('studies/updateLabelsFilterNoReload', { labels: this.filterLabels }); - await this.$store.dispatch('studies/reloadFilteredStudies'); } }, async loadStudiesFromChange(toChangeId, limit) { @@ -755,7 +849,7 @@ export default {