328 lines
13 KiB
Vue
328 lines
13 KiB
Vue
<script>
|
|
import SeriesList from "./SeriesList.vue"
|
|
import StudyDetails from "./StudyDetails.vue";
|
|
import { mapState } from "vuex"
|
|
import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js"
|
|
import api from "../orthancApi";
|
|
import dateHelpers from "../helpers/date-helpers"
|
|
import SourceType from '../helpers/source-type';
|
|
import resourceHelpers from "../helpers/resource-helpers";
|
|
import TokenLinkButton from "./TokenLinkButton.vue";
|
|
|
|
export default {
|
|
props: ["studyId"],
|
|
emits: ["deletedStudy"],
|
|
data() {
|
|
return {
|
|
study: {},
|
|
loaded: false,
|
|
expanded: false,
|
|
collapseElement: null,
|
|
selected: false,
|
|
pdfReports: []
|
|
};
|
|
},
|
|
created() {
|
|
this.messageBus.on('selected-all', this.onSelectedStudy);
|
|
this.messageBus.on('unselected-all', this.onUnselectedStudy);
|
|
this.messageBus.on('added-series-to-study-' + this.studyId, () => {this.onStudyUpdated(this.studyId)});
|
|
this.messageBus.on('deleted-series-from-study-' + this.studyId, () => {this.onStudyUpdated(this.studyId)});
|
|
},
|
|
async mounted() {
|
|
this.study = this.studies.filter(s => s["ID"] == this.studyId)[0];
|
|
this.loaded = true;
|
|
this.seriesIds = this.study.Series;
|
|
this.selected = this.selectedStudiesIds.indexOf(this.studyId) != -1;
|
|
|
|
if (!this.$refs['study-collapsible-details']) {
|
|
console.log('no refs: ', studyResponse);
|
|
}
|
|
this.$refs['study-collapsible-details'].addEventListener('show.bs.collapse', (e) => {
|
|
if (e.target == e.currentTarget) {
|
|
this.expanded = true;
|
|
}
|
|
});
|
|
this.$refs['study-collapsible-details'].addEventListener('hide.bs.collapse', (e) => {
|
|
if (e.target == e.currentTarget) {
|
|
this.expanded = false;
|
|
}
|
|
});
|
|
|
|
var el = this.$refs['study-collapsible-details'];
|
|
this.collapseElement = new bootstrap.Collapse(el, { toggle: false });
|
|
|
|
for (const [k, v] of Object.entries(this.$route.query)) {
|
|
if (k === 'expand') {
|
|
if (v == null || v === 'study' || v === 'series' || v === 'instance') {
|
|
this.collapseElement.show();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasPdfReportIcon) {
|
|
let instances = await api.getStudyInstancesExpanded(this.study.ID, ["SOPClassUID", "SeriesDate", "SeriesDescription"]);
|
|
for (let instance of instances) {
|
|
if (instance.RequestedTags.SOPClassUID == "1.2.840.10008.5.1.4.1.1.104.1") {
|
|
let titles = [];
|
|
if (instance.RequestedTags.SeriesDate) {
|
|
titles.push(dateHelpers.formatDateForDisplay(instance.RequestedTags.SeriesDate, this.uiOptions.DateFormat));
|
|
}
|
|
if (instance.RequestedTags.SeriesDescription) {
|
|
titles.push(instance.RequestedTags.SeriesDescription);
|
|
}
|
|
|
|
this.pdfReports.push({
|
|
'url': api.getInstancePdfUrl(instance.ID),
|
|
'title': titles.join(' - '),
|
|
'id': instance.ID
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
},
|
|
methods: {
|
|
onDeletedStudy(studyId) {
|
|
this.$emit("deletedStudy", this.studyId);
|
|
},
|
|
async onStudyUpdated(studyId) {
|
|
await this.$store.dispatch('studies/reloadStudy', {
|
|
'studyId': studyId,
|
|
'study': await api.getStudy(studyId)
|
|
})
|
|
this.study = this.studies.filter(s => s["ID"] == this.studyId)[0];
|
|
},
|
|
onSelectedStudy() {
|
|
this.selected = true;
|
|
},
|
|
onUnselectedStudy() {
|
|
this.selected = false;
|
|
},
|
|
async clickedSelect() {
|
|
// console.log(this.studyId, this.selected);
|
|
await this.$store.dispatch('studies/selectStudy', { studyId: this.studyId, isSelected: !this.selected }); // this.selected is the value before the click
|
|
this.selected = !this.selected;
|
|
// console.log(this.studyId, this.selected);
|
|
}
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
uiOptions: state => state.configuration.uiOptions,
|
|
studies: state => state.studies.studies,
|
|
studiesSourceType: state => state.studies.sourceType,
|
|
selectedStudiesIds: state => state.studies.selectedStudiesIds,
|
|
allLabels: state => state.labels.allLabels
|
|
}),
|
|
modalitiesInStudyForDisplay() {
|
|
if (this.study.RequestedTags.ModalitiesInStudy) {
|
|
return this.study.RequestedTags.ModalitiesInStudy.split('\\').join(',');
|
|
} else {
|
|
return "";
|
|
}
|
|
},
|
|
showLabels() {
|
|
if (this.studiesSourceType == SourceType.LOCAL_ORTHANC) {
|
|
return !this.expanded && ((this.allLabels && this.allLabels.length > 0));
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
hasLabels() {
|
|
return this.study && this.study.Labels && this.study.Labels.length > 0;
|
|
},
|
|
formattedPatientName() {
|
|
return resourceHelpers.formatPatientName(this.study.PatientMainDicomTags.PatientName);
|
|
},
|
|
formattedPatientBirthDate() {
|
|
return dateHelpers.formatDateForDisplay(this.study.PatientMainDicomTags.PatientBirthDate, this.uiOptions.DateFormat);
|
|
},
|
|
formattedStudyDate() {
|
|
return dateHelpers.formatDateForDisplay(this.study.MainDicomTags.StudyDate, this.uiOptions.DateFormat);
|
|
},
|
|
seriesCount() {
|
|
if (this.study.sourceType == SourceType.REMOTE_DICOM || this.study.sourceType == SourceType.REMOTE_DICOM_WEB) {
|
|
return this.study.MainDicomTags.NumberOfStudyRelatedSeries;
|
|
} else if (this.study.Series) {
|
|
return this.study.Series.length;
|
|
}
|
|
},
|
|
instancesCount() {
|
|
if (this.study.RequestedTags) {
|
|
return this.study.RequestedTags.NumberOfStudyRelatedInstances;
|
|
}
|
|
},
|
|
seriesAndInstancesCount() {
|
|
const seriesCount = this.seriesCount;
|
|
const instancesCount = this.instancesCount;
|
|
if (instancesCount) {
|
|
return String(seriesCount) + "/" + String(instancesCount);
|
|
} else {
|
|
return seriesCount;
|
|
}
|
|
},
|
|
hasPdfReportIconPlaceholder() {
|
|
return this.studiesSourceType == SourceType.LOCAL_ORTHANC && this.uiOptions.EnableReportQuickButton && !this.hasPdfReportIcon;
|
|
},
|
|
hasPdfReportIcon() {
|
|
return this.study.RequestedTags.SOPClassesInStudy && this.study.RequestedTags.SOPClassesInStudy.indexOf("1.2.840.10008.5.1.4.1.1.104.1") != -1 && this.uiOptions.EnableReportQuickButton;
|
|
},
|
|
hasPrimaryViewerIconPlaceholder() {
|
|
return this.studiesSourceType == SourceType.LOCAL_ORTHANC && this.uiOptions.EnableViewerQuickButton && !this.hasPrimaryViewerIcon;
|
|
},
|
|
hasPrimaryViewerIcon() {
|
|
return this.studiesSourceType == SourceType.LOCAL_ORTHANC && this.primaryViewerUrl && this.uiOptions.EnableViewerQuickButton;
|
|
},
|
|
primaryViewerUrl() {
|
|
return resourceHelpers.getPrimaryViewerUrl("study", this.study.ID, this.study.MainDicomTags.StudyInstanceUID);
|
|
}
|
|
},
|
|
components: { SeriesList, StudyDetails, TokenLinkButton }
|
|
}
|
|
</script>
|
|
|
|
|
|
<template>
|
|
<tbody>
|
|
<tr v-if="loaded" :class="{ 'study-row-collapsed': !expanded, 'study-row-expanded': expanded, 'study-row-show-labels': showLabels }" @dblclick="doubleClicked">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" v-model="selected" @click="clickedSelect">
|
|
</div>
|
|
</td>
|
|
<td v-if="hasPrimaryViewerIcon" class="td-viewer-icon">
|
|
<TokenLinkButton v-if="primaryViewerUrl"
|
|
level="study" :linkUrl="primaryViewerUrl"
|
|
:resourcesOrthancId="[study.ID]" linkType="icon"
|
|
iconClass="bi bi-eye-fill"
|
|
:tokenType="'viewer-instant-link'" :opensInNewTab="true">
|
|
</TokenLinkButton>
|
|
</td>
|
|
<td v-if="hasPrimaryViewerIconPlaceholder"></td>
|
|
<td v-if="hasPdfReportIcon" class="td-pdf-icon">
|
|
<TokenLinkButton v-for="pdfReport in pdfReports" :key="pdfReport.id"
|
|
level="study" :linkUrl="pdfReport.url"
|
|
:resourcesOrthancId="[study.ID]" linkType="icon"
|
|
iconClass="bi bi-file-earmark-text"
|
|
:tokenType="'download-instant-link'" :opensInNewTab="true"
|
|
:title="pdfReport.title">
|
|
</TokenLinkButton>
|
|
</td>
|
|
<td v-if="hasPdfReportIconPlaceholder"></td>
|
|
|
|
<td v-for="columnTag in uiOptions.StudyListColumns" :key="columnTag" class="cut-text"
|
|
:class="{ 'text-center': columnTag in ['modalities', 'seriesCount', 'instancesCount', 'seriesAndInstancesCount'] }" data-bs-toggle="collapse"
|
|
v-bind:data-bs-target="'#study-details-' + this.studyId">
|
|
<span v-if="columnTag == 'StudyDate'" data-bs-toggle="tooltip"
|
|
v-bind:title="formattedStudyDate">{{ formattedStudyDate }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'AccessionNumber'" data-bs-toggle="tooltip"
|
|
v-bind:title="study.MainDicomTags.AccessionNumber">{{ study.MainDicomTags.AccessionNumber }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'PatientID'" data-bs-toggle="tooltip"
|
|
v-bind:title="study.PatientMainDicomTags.PatientID">{{ study.PatientMainDicomTags.PatientID }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'PatientName'" data-bs-toggle="tooltip"
|
|
v-bind:title="formattedPatientName">{{ formattedPatientName }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'PatientBirthDate'" data-bs-toggle="tooltip"
|
|
v-bind:title="formattedPatientBirthDate">{{ formattedPatientBirthDate }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'StudyDescription'" data-bs-toggle="tooltip"
|
|
v-bind:title="study.MainDicomTags.StudyDescription">{{ study.MainDicomTags.StudyDescription }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'modalities'" data-bs-toggle="tooltip"
|
|
v-bind:title="modalitiesInStudyForDisplay">{{
|
|
modalitiesInStudyForDisplay }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'seriesCount'" data-bs-toggle="tooltip"
|
|
v-bind:title="seriesCount">{{ seriesCount }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'instancesCount'" data-bs-toggle="tooltip"
|
|
v-bind:title="instancesCount">{{ instancesCount }}
|
|
</span>
|
|
<span v-else-if="columnTag == 'seriesAndInstancesCount'" data-bs-toggle="tooltip"
|
|
v-bind:title="seriesAndInstancesCount">{{ seriesAndInstancesCount }}
|
|
</span>
|
|
<span v-else>{{ study.MainDicomTags[columnTag] }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr v-show="showLabels">
|
|
<td></td>
|
|
<td colspan="100%" class="label-row">
|
|
<span v-for="label in study.Labels" :key="label" class="label badge">{{ label }}</span>
|
|
<span v-if="!hasLabels"> </span>
|
|
</td>
|
|
</tr>
|
|
<tr v-show="loaded" class="collapse"
|
|
:class="{ 'study-details-collapsed': !expanded, 'study-details-expanded': expanded }"
|
|
v-bind:id="'study-details-' + this.studyId" ref="study-collapsible-details">
|
|
<td v-if="loaded && expanded" colspan="100">
|
|
<StudyDetails :studyId="this.studyId" :studyMainDicomTags="this.study.MainDicomTags"
|
|
:patientMainDicomTags="this.study.PatientMainDicomTags" :labels="this.study.Labels" @deletedStudy="onDeletedStudy" @studyLabelsUpdated="onStudyUpdated"></StudyDetails>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.study-row-collapsed {
|
|
border-top-width: 1px;
|
|
border-color: #ddd;
|
|
}
|
|
|
|
.study-row-expanded {
|
|
background-color: var(--study-details-bg-color);
|
|
font-weight: 700;
|
|
|
|
border-top: 3px !important;
|
|
border-style: solid !important;
|
|
border-color: black !important;
|
|
}
|
|
|
|
.study-row-expanded>:first-child {
|
|
border-bottom: 5px !important;
|
|
}
|
|
|
|
.study-row-show-labels {
|
|
border-bottom: 0px !important;
|
|
}
|
|
|
|
|
|
.study-table>tbody>tr.study-row-expanded:hover {
|
|
background-color: var(--study-details-bg-color);
|
|
color: red;
|
|
}
|
|
|
|
.study-table>tbody>tr.study-details-expanded:hover {
|
|
background-color: var(--study-details-bg-color);
|
|
color: red;
|
|
}
|
|
|
|
.study-details-expanded {
|
|
background-color: var(--study-details-bg-color);
|
|
|
|
border-top: 0px !important;
|
|
border-bottom: 3px !important;
|
|
border-style: solid !important;
|
|
border-color: black !important;
|
|
}
|
|
|
|
.label {
|
|
margin-left: 2px;
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.label-row {
|
|
border-top: 0px !important;
|
|
border-bottom: 0px !important;
|
|
}
|
|
|
|
.td-viewer-icon {
|
|
padding: 0; /* to maximize click space for the icon */
|
|
}
|
|
|
|
.td-pdf-icon {
|
|
padding: 0; /* to maximize click space for the icon */
|
|
}
|
|
</style> |