Skip to content

Commit cbb001b

Browse files
committed
MORE-Platform#264: Implemented the new openapi spec with the observation reminder and added a reminder toggle
1 parent b68b710 commit cbb001b

7 files changed

Lines changed: 308 additions & 93 deletions

File tree

openapi/StudyManagerAPI.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,9 @@ components:
19031903
noSchedule:
19041904
type: boolean
19051905
default: false
1906+
reminder:
1907+
type: boolean
1908+
default: false
19061909
observationGroupIds:
19071910
type: array
19081911
description: The set of observation group Ids the observation is part of

src/components/ObservationList.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ Licensed under the Elastic License 2.0. */
288288
hidden: observation.hidden,
289289
noSchedule: observation.noSchedule,
290290
hasRepetition: getScheduleHasRepetition(observation.schedule),
291+
reminder: observation.reminder
291292
};
292293
});
293294
})

src/components/dialog/ObservationDialog.vue

Lines changed: 93 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Licensed under the Elastic License 2.0. */
3333
import { useToastService } from '../../composable/toastService';
3434
import MultiSelect from 'primevue/multiselect';
3535
import { useObservationGroupStore } from '../../stores/observationGroupStore';
36+
import VisibilityToggle from '../subComponents/VisibilityToggle.vue';
37+
import ReminderToggle from '../subComponents/ReminderToggle.vue';
3638
3739
const { handleToastErrors, showErrorToast } = useToastService();
3840
const dialog = useDialog();
@@ -45,13 +47,14 @@ Licensed under the Elastic License 2.0. */
4547
const dialogRef: any = inject('dialogRef');
4648
const observation = dialogRef.value.data.observation as Observation;
4749
const groupStates = dialogRef.value.data.groupStates || [];
48-
const observationGroupStates: MoreTableChoice[] = observationGroupStore.observationGroups.map(
49-
(observationGroup) =>
50-
({
51-
label: observationGroup.title,
52-
value: observationGroup.observationGroupId?.toString(),
53-
}) as MoreTableChoice,
54-
);
50+
const observationGroupStates: MoreTableChoice[] =
51+
observationGroupStore.observationGroups.map(
52+
(observationGroup) =>
53+
({
54+
label: observationGroup.title,
55+
value: observationGroup.observationGroupId?.toString(),
56+
}) as MoreTableChoice,
57+
);
5558
const factory = dialogRef.value.data.factory;
5659
const editable =
5760
studyStore.study.status === StudyStatus.Draft ||
@@ -67,15 +70,19 @@ Licensed under the Elastic License 2.0. */
6770
.map((p: Property<any>) => p.setValue(observation.properties?.[p.id])),
6871
);
6972
const selectedObservationGroups = ref(
70-
observation.observationGroupIds?.map((id: number) => id.toString()) ?? []
71-
)
73+
observation.observationGroupIds?.map((id: number) => id.toString()) ?? [],
74+
);
7275
7376
const hidden: Ref<boolean> = ref(
7477
observation.hidden !== undefined
7578
? observation.hidden
7679
: factory.visibility.hiddenByDefault,
7780
);
7881
82+
const reminder: Ref<boolean> = ref(
83+
observation.reminder !== undefined ? observation.reminder : false,
84+
);
85+
7986
const scheduler: Ref<ObservationSchedule> = ref(
8087
observation.schedule ? observation.schedule : {},
8188
);
@@ -186,12 +193,15 @@ Licensed under the Elastic License 2.0. */
186193
title: title.value,
187194
purpose: purpose.value,
188195
participantInfo: participantInfo.value,
189-
observationGroupIds: selectedObservationGroups.value?.length ? selectedObservationGroups.value.map((id: string) => parseInt(id)) : [],
196+
observationGroupIds: selectedObservationGroups.value?.length
197+
? selectedObservationGroups.value.map((id: string) => parseInt(id))
198+
: [],
190199
type: observation.type,
191200
properties: props,
192201
schedule: scheduler.value,
193202
studyGroupId: studyGroupId.value,
194203
hidden: hidden.value,
204+
reminder: reminder.value,
195205
} as Observation;
196206
197207
if (!isObjectEmpty(scheduler.value)) {
@@ -236,18 +246,21 @@ Licensed under the Elastic License 2.0. */
236246
}
237247
function extractCurrentLimeDomain(): string {
238248
const hostnameParts = window.location.hostname.split('.');
239-
return hostnameParts.slice(1).join('.') ? `https://lime.${hostnameParts.slice(1).join('.')}/admin/` : 'https://lime.platform-test.more.redlink.io/';
249+
return hostnameParts.slice(1).join('.')
250+
? `https://lime.${hostnameParts.slice(1).join('.')}/admin/`
251+
: 'https://lime.platform-test.more.redlink.io/';
240252
}
241-
242-
243253
</script>
244254

245255
<template>
246256
<div class="dialog" :class="{ 'dialog-disabled': !editable }">
247257
<div class="mb-4" :class="{ 'pb-4': !editable }">
248258
<h5 class="mb-1">{{ $t(factory.title) }}</h5>
249259
<!-- eslint-disable vue/no-v-html -->
250-
<h6 v-if="factory.description" v-html="$t(factory.description, {link: extractCurrentLimeDomain()})"></h6>
260+
<h6
261+
v-if="factory.description"
262+
v-html="$t(factory.description, { link: extractCurrentLimeDomain() })"
263+
></h6>
251264
</div>
252265

253266
<form
@@ -328,14 +341,22 @@ Licensed under the Elastic License 2.0. */
328341
<div class="col-start-0 col-span-8 flex items-center justify-between">
329342
<div>
330343
<h5 v-if="editable" class="pb-2 font-bold">
331-
{{ editable ? $t('study.dialog.label.chooseGroups') : $t('study.props.groups') }}
344+
{{
345+
editable
346+
? $t('study.dialog.label.chooseGroups')
347+
: $t('study.props.groups')
348+
}}
332349
</h5>
333350
<div v-if="editable" class="mb-2">
334-
{{ $t('study.dialog.description.howToCreateGroups', {for: $t('studyNavigation.tabs.observations')}) }}
351+
{{
352+
$t('study.dialog.description.howToCreateGroups', {
353+
for: $t('studyNavigation.tabs.observations'),
354+
})
355+
}}
335356
</div>
336357
<div class="flex gap-5">
337358
<div>
338-
<div class="mb-1">{{ $t('studyGroup.plural')}}</div>
359+
<div class="mb-1">{{ $t('studyGroup.plural') }}</div>
339360
<Dropdown
340361
v-model="studyGroupId"
341362
:options="groupStates"
@@ -351,87 +372,68 @@ Licensed under the Elastic License 2.0. */
351372
</div>
352373
<div>
353374
<div class="mb-1">{{ $t('observationGroup.plural') }}</div>
354-
<MultiSelect
355-
v-model="selectedObservationGroups"
356-
:options="observationGroupStates"
357-
:disabled="!editable"
358-
option-label="label"
359-
option-value="value"
360-
:placeholder="$t('global.placeholder.chooseDropdownOptionDefault')"
361-
:show-toggle-all="false"
362-
class="z-top custom-multiselect-root"
363-
:panel-class="'custom-multiselect-panel'"
364-
>
365-
<template #value="{ value }">
366-
<span v-if="value?.length > 0">{{ value.map((item: string) => observationGroupStates.find(
367-
(group: MoreTableChoice) => group.value === item
368-
)?.label).join(', ') }}</span>
369-
<span v-else class="text-gray-400">
370-
{{ $t('global.placeholder.chooseDropdownOptionDefault') }}
371-
</span>
372-
</template>
373-
374-
</MultiSelect>
375-
</div>
375+
<MultiSelect
376+
v-model="selectedObservationGroups"
377+
:options="observationGroupStates"
378+
:disabled="!editable"
379+
option-label="label"
380+
option-value="value"
381+
:placeholder="
382+
$t('global.placeholder.chooseDropdownOptionDefault')
383+
"
384+
:show-toggle-all="false"
385+
class="z-top custom-multiselect-root"
386+
:panel-class="'custom-multiselect-panel'"
387+
>
388+
<template #value="{ value }">
389+
<span v-if="value?.length > 0">{{
390+
value
391+
.map(
392+
(item: string) =>
393+
observationGroupStates.find(
394+
(group: MoreTableChoice) => group.value === item,
395+
)?.label,
396+
)
397+
.join(', ')
398+
}}</span>
399+
<span v-else class="text-gray-400">
400+
{{ $t('global.placeholder.chooseDropdownOptionDefault') }}
401+
</span>
402+
</template>
403+
</MultiSelect>
404+
</div>
376405
</div>
377406
</div>
378407
</div>
379408

380-
<div
381-
class="col-start-0 buttons col-span-8 mt-1 grid grid-cols-2"
382-
>
383-
<div
384-
class="info-box relative flex cursor-pointer flex-row items-center"
385-
>
386-
<div
387-
v-if="editable && factory.visibility.changeable"
388-
class="flex items-center"
389-
>
390-
<div class="icon-box eye">
391-
<span
392-
class="pi cursor-pointer"
393-
:class="hidden ? 'pi-eye-slash' : 'pi-eye'"
394-
@click="hidden = !hidden"
395-
/>
396-
</div>
397-
</div>
398-
<div v-else class="icon-box eye preview">
399-
<span
400-
class="pi"
401-
:class="
402-
observation.hidden
403-
? 'pi-eye color-approved'
404-
: 'pi-eye-slash color-important'
405-
"
406-
/>
407-
</div>
408-
<div class="inline">
409-
<div
410-
class="info-box-hidden pointer-events-none absolute bottom-full right-0 bg-white p-5 text-center opacity-0"
411-
>
412-
{{ $t('observation.dialog.msg.hiddenInfo') }}
413-
</div>
414-
</div>
415-
<span class="ml-2 inline">
416-
{{ $t(`observation.props.hidden.${hidden}`) }}
417-
</span>
418-
<i
419-
class="pi pi-info-circle color-primary mx-1"
420-
:class="{ 'me-2': editable && factory.visibility.changeable }"
409+
<div class="col-start-0 buttons col-span-8 mt-1 grid grid-cols-2">
410+
<div class="flex flex-col flex-wrap gap-3">
411+
<VisibilityToggle
412+
v-model="hidden"
413+
:editable="editable"
414+
:changeable="factory.visibility.changeable"
415+
:info-text="$t('observation.dialog.msg.hiddenInfo')"
416+
:label="$t(`observation.props.hidden.${hidden}`)"
417+
/>
418+
<ReminderToggle
419+
v-model="reminder"
420+
:editable="editable"
421+
:info-text="$t('observation.dialog.msg.reminderInfo')"
422+
:label="$t(`observation.props.reminder.${reminder}`)"
421423
/>
422424
</div>
423425
<div class="flex flex-row items-center justify-end text-right">
424-
<Button class="btn-gray" @click="cancel()">
425-
<span v-if="editable">{{ $t('global.labels.cancel') }}</span>
426-
<span v-else>{{ $t('global.labels.close') }}</span>
427-
</Button>
428-
<Button
429-
v-if="editable"
430-
:type="editable ? 'submit' : 'button'"
431-
:label="$t('global.labels.save')"
432-
:disabled="!editable"
433-
@click="checkRequiredFields()"
434-
/>
426+
<Button class="btn-gray" @click="cancel()">
427+
<span v-if="editable">{{ $t('global.labels.cancel') }}</span>
428+
<span v-else>{{ $t('global.labels.close') }}</span>
429+
</Button>
430+
<Button
431+
v-if="editable"
432+
:type="editable ? 'submit' : 'button'"
433+
:label="$t('global.labels.save')"
434+
:disabled="!editable"
435+
@click="checkRequiredFields()"
436+
/>
435437
</div>
436438
</div>
437439
</form>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<script setup lang="ts">
2+
interface Props {
3+
modelValue: boolean;
4+
editable?: boolean;
5+
infoText?: string;
6+
label?: string;
7+
}
8+
9+
const props = withDefaults(defineProps<Props>(), {
10+
editable: true,
11+
infoText: '',
12+
label: undefined,
13+
});
14+
15+
const emit = defineEmits<{
16+
(e: 'update:modelValue', value: boolean): void;
17+
}>();
18+
19+
function toggleReminder(): void {
20+
if (props.editable) {
21+
emit('update:modelValue', !props.modelValue);
22+
}
23+
}
24+
</script>
25+
26+
<template>
27+
<div
28+
class="info-box relative flex cursor-pointer flex-row items-center"
29+
:class="{ 'cursor-not-allowed': !editable }"
30+
>
31+
<div v-if="editable" class="flex items-center" @click="toggleReminder">
32+
<div class="icon-box reminder">
33+
<span
34+
class="pi cursor-pointer"
35+
:class="modelValue ? 'pi-bell' : 'pi-bell-slash'"
36+
/>
37+
</div>
38+
</div>
39+
<div v-else class="icon-box reminder preview">
40+
<span
41+
class="pi"
42+
:class="
43+
modelValue
44+
? 'pi-bell color-approved'
45+
: 'pi-bell-slash color-important'
46+
"
47+
/>
48+
</div>
49+
<div v-if="infoText" class="inline">
50+
<div
51+
class="info-box-hidden pointer-events-none absolute bottom-full right-0 bg-white p-5 text-center opacity-0"
52+
>
53+
{{ infoText }}
54+
</div>
55+
</div>
56+
<span v-if="label" class="ml-2 inline">
57+
<slot name="label">
58+
{{ label }}
59+
</slot>
60+
</span>
61+
<i
62+
class="pi pi-info-circle color-primary mx-1"
63+
:class="{ 'me-2': editable }"
64+
/>
65+
</div>
66+
</template>
67+
68+
<style scoped lang="postcss">
69+
.info-box {
70+
&-hidden {
71+
width: 20vw;
72+
border: 1px solid var(--bluegray-200);
73+
transition: ease-in-out opacity 0.25s;
74+
box-shadow: 1px 1px 5px var(--bluegray-200);
75+
}
76+
77+
&:hover {
78+
.info-box-hidden {
79+
opacity: 1;
80+
}
81+
}
82+
}
83+
84+
.icon-box.reminder {
85+
display: inline-flex;
86+
align-items: center;
87+
justify-content: center;
88+
width: 2rem;
89+
height: 2rem;
90+
border-radius: 0.375rem;
91+
transition: background-color 0.2s;
92+
93+
&:hover {
94+
background-color: var(--surface-100);
95+
}
96+
97+
.pi {
98+
font-size: 1.25rem;
99+
}
100+
}
101+
102+
.icon-box.reminder.preview {
103+
cursor: default;
104+
105+
&:hover {
106+
background-color: transparent;
107+
}
108+
}
109+
</style>

0 commit comments

Comments
 (0)