Skip to content

Commit 94da85a

Browse files
committed
MORE-Platform#308: P2R improve observation dropdown
1 parent 764fdc4 commit 94da85a

6 files changed

Lines changed: 605 additions & 53 deletions

File tree

src/components/ObservationList.vue

Lines changed: 106 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ Prevention -- A research institute of the Ludwig Boltzmann Gesellschaft,
44
Oesterreichische Vereinigung zur Foerderung der wissenschaftlichen Forschung).
55
Licensed under the Elastic License 2.0. */
66
<script setup lang="ts">
7-
import { PropType, Ref, ref } from 'vue';
7+
import { computed, PropType, Ref, ref } from 'vue';
88
import { useComponentsApi, useObservationsApi } from '../composable/useApi';
99
import {
1010
ComponentFactory,
1111
Event,
12-
Observation, ObservationGroup,
12+
Observation,
13+
ObservationGroup,
1314
ObservationSchedule,
1415
RelativeEvent,
1516
StudyGroup,
1617
StudyRole,
17-
StudyStatus
18+
StudyStatus,
1819
} from '@gs';
1920
import {
2021
MoreObservationListTableRow,
@@ -24,7 +25,7 @@ Licensed under the Elastic License 2.0. */
2425
MoreTableFieldType,
2526
MoreTableRowActionResult,
2627
MoreTableSortOptions,
27-
RowSelectionMode
28+
RowSelectionMode,
2829
} from '../models/MoreTableModel';
2930
import ConfirmDialog from 'primevue/confirmdialog';
3031
import DynamicDialog from 'primevue/dynamicdialog';
@@ -38,8 +39,10 @@ Licensed under the Elastic License 2.0. */
3839
import DeleteMoreTableRowDialog from './dialog/DeleteMoreTableRowDialog.vue';
3940
import { ScheduleType } from '../models/Scheduler';
4041
import Button from 'primevue/button';
41-
import Menu from 'primevue/menu';
4242
import { timeToHourMinuteString } from '../utils/dateUtils';
43+
import OverlayPanel from 'primevue/overlaypanel';
44+
import InputText from 'primevue/inputtext';
45+
import { extractCurrentLimeDomain } from '../utils/limeSurveyUtils';
4346
4447
const loader = useLoader();
4548
const { observationsApi } = useObservationsApi();
@@ -54,7 +57,10 @@ Licensed under the Elastic License 2.0. */
5457
studyId: { type: Number, required: true },
5558
studyGroups: { type: Array as PropType<Array<StudyGroup>>, required: true },
5659
studyStatus: { type: String as PropType<StudyStatus>, required: true },
57-
observationGroups: { type: Array as PropType<Array<ObservationGroup>>, required: true}
60+
observationGroups: {
61+
type: Array as PropType<Array<ObservationGroup>>,
62+
required: true,
63+
},
5864
});
5965
6066
const sortOptions: MoreTableSortOptions = {
@@ -79,13 +85,14 @@ Licensed under the Elastic License 2.0. */
7985
value: null,
8086
} as MoreTableChoice);
8187
82-
const observationGroupStatuses: MoreTableChoice[] = props.observationGroups.map(
83-
(observationGroup) =>
84-
({
85-
label: observationGroup.title,
86-
value: observationGroup.observationGroupId?.toString(),
87-
}) as MoreTableChoice,
88-
);
88+
const observationGroupStatuses: MoreTableChoice[] =
89+
props.observationGroups.map(
90+
(observationGroup) =>
91+
({
92+
label: observationGroup.title,
93+
value: observationGroup.observationGroupId?.toString(),
94+
}) as MoreTableChoice,
95+
);
8996
9097
async function getFactories(): Promise<ComponentFactory[]> {
9198
return componentsApi
@@ -94,15 +101,20 @@ Licensed under the Elastic License 2.0. */
94101
}
95102
96103
const factories: ComponentFactory[] = await getFactories();
97-
const observationTypes: any[] = factories.map((cf: ComponentFactory) => ({
98-
label: cf.title ? t(cf.title) : '',
99-
value: cf.componentId,
100-
command: (): void => {
101-
openObservationDialog(t('observation.dialog.header.create'), {
102-
type: cf.componentId,
103-
});
104-
},
105-
}));
104+
const observationTypes: any[] = factories
105+
.map((cf: ComponentFactory) => ({
106+
label: cf.title ? t(cf.title) : '',
107+
value: cf.componentId,
108+
description: cf.description
109+
? t(cf.description, { link: extractCurrentLimeDomain() })
110+
: '',
111+
command: (): void => {
112+
openObservationDialog(t('observation.dialog.header.create'), {
113+
type: cf.componentId,
114+
});
115+
},
116+
}))
117+
.sort((a, b) => a.label.localeCompare(b.label));
106118
107119
const observationColumns: MoreTableColumn[] = [
108120
{
@@ -144,11 +156,11 @@ Licensed under the Elastic License 2.0. */
144156
arrayLabels: observationGroupStatuses,
145157
editable: {
146158
enabled: actionsVisible,
147-
values: observationGroupStatuses
159+
values: observationGroupStatuses,
148160
},
149161
sortable: true,
150162
placeholder: t('global.placeholder.noGroup'),
151-
columnWidth: '10vw'
163+
columnWidth: '10vw',
152164
},
153165
{
154166
field: 'hidden',
@@ -255,7 +267,9 @@ Licensed under the Elastic License 2.0. */
255267
}
256268
257269
function getObservationGroupItem(id: number): MoreTableChoice | undefined {
258-
return observationGroupStatuses?.find((groupStatus) => groupStatus.value === id.toString())
270+
return observationGroupStatuses?.find(
271+
(groupStatus) => groupStatus.value === id.toString(),
272+
);
259273
}
260274
261275
async function listObservations(): Promise<void> {
@@ -269,7 +283,9 @@ Licensed under the Elastic License 2.0. */
269283
studyGroupId: observation.studyGroupId,
270284
observationGroupIds: observation.observationGroupIds,
271285
observationGroupValues: observation.observationGroupIds?.length
272-
? observation.observationGroupIds.map((id: number) => getObservationGroupItem(id))
286+
? observation.observationGroupIds.map((id: number) =>
287+
getObservationGroupItem(id),
288+
)
273289
: [],
274290
title: observation.title,
275291
purpose: observation.purpose,
@@ -288,7 +304,7 @@ Licensed under the Elastic License 2.0. */
288304
hidden: observation.hidden,
289305
noSchedule: observation.noSchedule,
290306
hasRepetition: getScheduleHasRepetition(observation.schedule),
291-
reminder: observation.reminder
307+
reminder: observation.reminder,
292308
};
293309
});
294310
})
@@ -377,16 +393,23 @@ Licensed under the Elastic License 2.0. */
377393
}
378394
}
379395
380-
async function updateObservation(observation: MoreObservationListTableRow, fromDialog: boolean = false): Promise<void> {
381-
const {observationGroupValues, ...newObservation} = observation;
396+
async function updateObservation(
397+
observation: MoreObservationListTableRow,
398+
fromDialog: boolean = false,
399+
): Promise<void> {
400+
const { observationGroupValues, ...newObservation } = observation;
382401
383402
await observationsApi
384403
.updateObservation(
385404
props.studyId,
386405
newObservation.observationId as number,
387406
{
388407
...newObservation,
389-
observationGroupIds: fromDialog ? newObservation.observationGroupIds : observationGroupValues?.map((choice: MoreTableChoice) => parseInt(choice.value as string))
408+
observationGroupIds: fromDialog
409+
? newObservation.observationGroupIds
410+
: observationGroupValues?.map((choice: MoreTableChoice) =>
411+
parseInt(choice.value as string),
412+
),
390413
},
391414
)
392415
.then(listObservations)
@@ -488,9 +511,24 @@ Licensed under the Elastic License 2.0. */
488511
489512
listObservations();
490513
491-
const menu = ref();
492-
function toggleButtonMenu(event: MouseEvent): void {
493-
menu.value.toggle(event);
514+
const observationTypeOverlayPanel = ref<InstanceType<
515+
typeof OverlayPanel
516+
> | null>(null);
517+
518+
const observationTypeQuery = ref('');
519+
520+
const filteredObservationTypes = computed(() => {
521+
const q = observationTypeQuery.value.trim().toLowerCase();
522+
if (!q) return observationTypes;
523+
return observationTypes.filter((i) => i.label.toLowerCase().includes(q));
524+
});
525+
526+
function openObservatinTypeOverlay(event: MouseEvent): void {
527+
observationTypeOverlayPanel.value?.toggle(event);
528+
}
529+
function selectObservationType(item: any): any {
530+
item.command();
531+
observationTypeOverlayPanel.value?.hide();
494532
}
495533
</script>
496534

@@ -521,11 +559,44 @@ Licensed under the Elastic License 2.0. */
521559
<Button
522560
type="button"
523561
:disabled="isInEditMode ? true : !actionsVisible"
524-
@click="toggleButtonMenu($event)"
562+
@click="openObservatinTypeOverlay($event)"
525563
>{{ t('observation.observationList.action.add') }}
526564
<span class="pi pi-angle-down ml-3"></span
527565
></Button>
528-
<Menu ref="menu" :model="observationTypes" :popup="true" />
566+
567+
<OverlayPanel
568+
ref="observationTypeOverlayPanel"
569+
style="width: 40vw; min-width: 32rem"
570+
>
571+
<InputText
572+
v-model="observationTypeQuery"
573+
placeholder="Search…"
574+
class="mb-3 w-full"
575+
/>
576+
577+
<div class="scrollbar-stable max-h-[38vh] overflow-y-auto">
578+
<button
579+
v-for="observationType in filteredObservationTypes"
580+
:key="observationType.label"
581+
type="button"
582+
class="w-full px-3 py-2 text-left hover:bg-gray-50"
583+
@click="selectObservationType(observationType)"
584+
>
585+
<div class="font-medium">{{ observationType.label }}</div>
586+
<div class="text-sm opacity-70">
587+
<!-- eslint-disable vue/no-v-html -->
588+
<span v-html="observationType.description" />
589+
</div>
590+
</button>
591+
592+
<div
593+
v-if="filteredObservationTypes.length === 0"
594+
class="px-3 py-2 text-sm opacity-70"
595+
>
596+
{{ $t('studyCollaborator.placeholder.noResultsFound') }}
597+
</div>
598+
</div>
599+
</OverlayPanel>
529600
</div>
530601
</template>
531602
</MoreTable>

src/components/dialog/ObservationDialog.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Licensed under the Elastic License 2.0. */
3434
import MultiSelect from 'primevue/multiselect';
3535
import { useObservationGroupStore } from '../../stores/observationGroupStore';
3636
import ObservationToggle from '../subComponents/ObservationToggle.vue';
37+
import { extractCurrentLimeDomain } from '../../utils/limeSurveyUtils';
3738
3839
const { handleToastErrors, showErrorToast } = useToastService();
3940
const dialog = useDialog();
@@ -243,12 +244,6 @@ Licensed under the Elastic License 2.0. */
243244
function updateProperty(item: PropertyEmit): void {
244245
properties.value[item.index].value = item.value;
245246
}
246-
function extractCurrentLimeDomain(): string {
247-
const hostnameParts = window.location.hostname.split('.');
248-
return hostnameParts.slice(1).join('.')
249-
? `https://lime.${hostnameParts.slice(1).join('.')}/admin/`
250-
: 'https://lime.platform-test.more.redlink.io/';
251-
}
252247
</script>
253248

254249
<template>

0 commit comments

Comments
 (0)