Skip to content

Commit a7f1132

Browse files
authored
Merge pull request #619 from MORE-Platform/prehab2rehab/617-loading-indicator-for-downloading-study-data
617: loading indicator for downloading study data + confirmation dialog
2 parents 47dd299 + e1356b8 commit a7f1132

File tree

7 files changed

+198
-12
lines changed

7 files changed

+198
-12
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* Copyright LBI-DHP and/or licensed to LBI-DHP under one or more contributor
2+
license agreements (LBI-DHP: Ludwig Boltzmann Institute for Digital Health and
3+
Prevention -- A research institute of the Ludwig Boltzmann Gesellschaft,
4+
Oesterreichische Vereinigung zur Foerderung der wissenschaftlichen Forschung).
5+
Licensed under the Elastic License 2.0. */
6+
<script setup lang="ts">
7+
import { inject } from 'vue';
8+
import Button from 'primevue/button';
9+
import ExclamationIcon from '../shared/ExclamationIcon.vue';
10+
import { useI18n } from 'vue-i18n';
11+
12+
const { t } = useI18n();
13+
14+
const dialogRef: any = inject('dialogRef');
15+
16+
const message: string = dialogRef.value?.data?.message ?? t('global.labels.leavePage');
17+
const cancelBtn: string = dialogRef.value?.data?.cancelBtn ?? t('global.labels.cancel');
18+
const approveBtn: string = dialogRef.value?.data?.approveBtn ?? t('global.labels.confirm');
19+
20+
function closeDialog(navigate: boolean): void {
21+
dialogRef.value.close(navigate)
22+
}
23+
24+
</script>
25+
26+
<template>
27+
28+
<div class="text-base">
29+
<div class="mb-8 grid grid-cols-12 place-items-center gap-4">
30+
<div class="col-span-2">
31+
<ExclamationIcon id="exclamation" />
32+
</div>
33+
<div class="col-span-10">
34+
{{message}}
35+
</div>
36+
</div>
37+
<div class="flex flex-row items-center justify-between">
38+
<Button
39+
type="button"
40+
class="p-button btn-important"
41+
:label="approveBtn"
42+
@click="closeDialog(true)"
43+
/>
44+
<Button
45+
type="button"
46+
class="btn-primary"
47+
:label="cancelBtn"
48+
@click="closeDialog(false)"/>
49+
</div>
50+
</div>
51+
52+
53+
</template>

src/components/subComponents/AuditLogDownload.vue

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<script setup lang="ts">
22
import { useI18n } from 'vue-i18n';
3-
import { watch, computed } from 'vue';
3+
import { watch, computed, ref } from 'vue';
44
import { useStudyStore } from '../../stores/studyStore';
55
import Button from 'primevue/button';
6+
import ProgressSpinner from 'primevue/progressspinner';
67
78
const { t } = useI18n();
89
const studyStore = useStudyStore();
10+
const isLoading = ref<boolean>(false);
911
1012
const props = defineProps({
1113
studyId: {
@@ -28,11 +30,15 @@
2830
}, {immediate: true})
2931
3032
async function getAuditlogMetadata(): Promise<void> {
31-
await studyStore.getAuditLogMetadata(studyStore.studyId);
33+
34+
await studyStore.getAuditLogMetadata(studyStore.studyId)
35+
3236
}
3337
3438
function downloadCurrentAuditlog(): void {
39+
isLoading.value = true
3540
studyStore.exportAuditLog(studyStore.studyId)
41+
.finally(() => isLoading.value = false);
3642
}
3743
</script>
3844

@@ -51,11 +57,25 @@
5157
icon="pi pi-download"
5258
class="mt-8"
5359
:label="$t('data.auditLogDownload.btnLabel')"
60+
:disabled="isLoading"
5461
@click="downloadCurrentAuditlog()"
55-
/>
62+
>
63+
<span class="p-button-icon p-button-icon-left pi pi-download"></span>
64+
<span>{{t('data.auditLogDownload.btnLabel')}}</span>
65+
<ProgressSpinner
66+
v-if="isLoading"
67+
class="!text-white ml-2"
68+
style="width: 25px; height: 25px"
69+
stroke-width="6"
70+
fill="transparent"
71+
animation-duration=".5s"
72+
/>
73+
</Button>
5674
</div>
5775
</template>
5876

59-
<style scoped lang="scss">
60-
77+
<style scoped lang="postcss">
78+
:deep(.p-progress-spinner-circle) {
79+
stroke: currentColor!important;
80+
}
6181
</style>

src/components/subComponents/DataDownload.vue

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import Calendar from 'primevue/calendar';
33
import Button from 'primevue/button';
4-
import { computed, ComputedRef, Ref, ref } from 'vue';
4+
import { computed, ComputedRef, onBeforeMount, onBeforeUnmount, Ref, ref } from 'vue';
55
import { DropdownOption } from '../../models/Common';
66
import { ComponentFactory, Observation, Participant } from '@gs';
77
import { AxiosError, AxiosResponse } from 'axios';
@@ -17,6 +17,14 @@
1717
import { useGlobalStore } from '../../stores/globalStore';
1818
import MultiSelect from 'primevue/multiselect';
1919
import { DownloadDataFilter } from '../../models/DataDownloadModel';
20+
import ProgressSpinner from 'primevue/progressspinner';
21+
import { useDialog } from 'primevue/usedialog';
22+
import ConfirmationDialog from '../dialog/ConfirmationDialog.vue';
23+
import { onBeforeRouteLeave, useRouter } from 'vue-router';
24+
25+
const dialog = useDialog();
26+
const router = useRouter();
27+
const pendingRoute = ref<any>(null);
2028
2129
const { t } = useI18n();
2230
const { componentsApi } = useComponentsApi();
@@ -165,6 +173,60 @@
165173
.then((response: any) => response.data)
166174
.then((rs) => (factories = rs))
167175
.then(loadData);
176+
177+
function interceptPageNavigation(): void {
178+
dialog.open(ConfirmationDialog, {
179+
data: {
180+
message: t('monitoringData.dialog.msg.downloadStudyData'),
181+
cancelBtn: t('monitoringData.dialog.waitForDownload'),
182+
approveBtn: t('monitoringData.dialog.navigatePage')
183+
},
184+
props: {
185+
header: t('monitoringData.dialog.header.downloadStudyData'),
186+
style: {
187+
width: '50vw',
188+
},
189+
breakpoints: {
190+
'960px': '75vw',
191+
'640px': '90vw',
192+
},
193+
modal: true,
194+
draggable: false,
195+
},
196+
onClose: (options) =>{
197+
if (options?.data) {
198+
if (pendingRoute.value) {
199+
isDownloadDataLoading.value = false
200+
router.push(pendingRoute.value)
201+
}
202+
}
203+
pendingRoute.value = null
204+
}
205+
})
206+
}
207+
208+
onBeforeRouteLeave((to, from, next) => {
209+
if (isDownloadDataLoading.value) {
210+
pendingRoute.value = to
211+
interceptPageNavigation()
212+
// preventNavigation
213+
next(false)
214+
} else {
215+
// navigate
216+
next()
217+
}
218+
})
219+
220+
onBeforeMount(() => {
221+
window.addEventListener('beforeunload', (e) => {
222+
if (isDownloadDataLoading.value) e.preventDefault() }
223+
)
224+
})
225+
onBeforeUnmount(() => {
226+
window.removeEventListener('beforeunload', (e) => {
227+
if (isDownloadDataLoading.value) e.preventDefault() }
228+
)
229+
})
168230
</script>
169231

170232
<template>
@@ -259,9 +321,24 @@
259321
:label="$t('study.studyList.labels.exportStudyData')"
260322
:disabled="isDownloadDataLoading"
261323
@click="downloadStudyData()"
262-
/>
324+
>
325+
<span class="p-button-icon p-button-icon-left pi pi-download"></span>
326+
<span>{{t('study.studyList.labels.exportStudyData')}}</span>
327+
<ProgressSpinner
328+
v-if="isDownloadDataLoading"
329+
class="!text-white ml-2"
330+
style="width: 25px; height: 25px"
331+
stroke-width="6"
332+
fill="transparent"
333+
animation-duration=".5s"
334+
/>
335+
</Button>
263336
</div>
264337
</div>
265338
</template>
266339

267-
<style scoped lang="postcss"></style>
340+
<style scoped lang="postcss">
341+
:deep(.p-progress-spinner-circle) {
342+
stroke: currentColor!important;
343+
}
344+
</style>

src/i18n/de.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@
125125
"setPause": "Pausieren",
126126
"start": "Start",
127127
"time": "Zeit",
128-
"to": "zu"
128+
"to": "zu",
129+
"leavePage": "Möchten Sie die Seite wirklich verlassen?"
129130
},
130131
"placeholder": {
131132
"all": "Alle",
@@ -392,7 +393,17 @@
392393
"dataDownload": "Studiendaten herunterladen",
393394
"lastDataPoints": "Letzte Datenpunkte",
394395
"recordedObservation": "Erhobene Daten",
395-
"auditLog": "AuditLog herunterladen"
396+
"auditLog": "Auditlog herunterladen"
397+
},
398+
"dialog": {
399+
"waitForDownload": "Auf Download warten",
400+
"navigatePage": "Seite verlassen",
401+
"msg": {
402+
"downloadStudyData": "Der Daten-Download läuft noch. Sie können die Seite trotzdem verlassen – der Vorgang wird im Hintergrund gebuffert und der Download öffnet sich automatisch in einem neuen Tab, sobald er bereitsteht. Änderungen, die Sie ab jetzt vornehmen, sind darin nicht enthalten."
403+
},
404+
"header": {
405+
"downloadStudyData": "Seite navigieren"
406+
}
396407
}
397408
},
398409
"moreTable": {

src/i18n/en.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"title": "Download study data"
6464
},
6565
"auditLogDownload": {
66-
"title": "Download AuditLog",
66+
"title": "Download Audit log",
6767
"description": "The number of the current audit log entries is: {length}.",
6868
"btnLabel": "Download current audit log",
6969
"notStartedInfo": "Audit log data is only recorded when a study is active.",
@@ -125,7 +125,8 @@
125125
"setPause": "Pause",
126126
"start": "Start",
127127
"time": "Time",
128-
"to": "to"
128+
"to": "to",
129+
"leavePage": "Do you really want to leave the page?"
129130
},
130131
"placeholder": {
131132
"all": "All",
@@ -393,6 +394,16 @@
393394
"lastDataPoints": "Latest Data Points",
394395
"recordedObservation": "Recorded Observations",
395396
"auditLog": "Download AuditLog"
397+
},
398+
"dialog": {
399+
"navigatePage": "Leave page",
400+
"waitForDownload": "Wait for download",
401+
"msg": {
402+
"downloadStudyData": "The data download is still in progress. You may leave the page – the process will be buffered in the background and the download will automatically open in a new tab once it is ready. Any changes you make from now on will not be included in this download."
403+
},
404+
"header": {
405+
"downloadStudyData": "Navigate page"
406+
}
396407
}
397408
},
398409
"moreTable": {

src/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,23 @@
99
import { createApp } from 'vue';
1010
import App from './App.vue';
1111
import './index.pcss';
12+
13+
// Tailwind + deine Overrides -> set theme and ovverride with brand colors -> flexibility to switch themes later
14+
// animations and optimization run over the theme since the last update, so we need a basic theme before overriding it with our brand colors
15+
import 'primevue/resources/themes/lara-light-blue/theme.css';
16+
import "primevue/resources/primevue.min.css";
17+
import '../src/styles/more-light/theme.pcss';
1218
import '../src/style.pcss';
19+
1320
// PrimeVue
1421
import PrimeVue from 'primevue/config';
1522
import Tooltip from 'primevue/tooltip';
1623
import ConfirmationService from 'primevue/confirmationservice';
1724
import DialogService from 'primevue/dialogservice';
1825
import ToastService from 'primevue/toastservice';
26+
27+
28+
1929
// Router
2030
import { Router } from './router';
2131
import AuthService from './service/AuthService';

src/styles/more-light/theme.pcss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5867,3 +5867,7 @@
58675867
box-shadow: inset 0 -2px 0 0 var(--primary-color);
58685868
}
58695869

5870+
:deep(.p-progress-spinner-circle) {
5871+
stroke: currentColor!important; /* nimmt dann die text-red-600 aus Tailwind */
5872+
}
5873+

0 commit comments

Comments
 (0)