Skip to content

Commit 82664a8

Browse files
committed
feat(prompts): sleep schedule & meal sorting (V4-1504)
1 parent 3e6cd31 commit 82664a8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+552
-126
lines changed

apps/admin/src/components/prompts/custom/time-picker-prompt.vue

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,11 @@
1515
</template>
1616

1717
<script lang="ts" setup>
18-
import type { PropType } from 'vue';
19-
import type { Prompts } from '@intake24/common/prompts';
20-
import { TimePickerSettings, useBasePrompt } from '../partials';
18+
import { TimePickerSettings, timerPickerProps, useBasePrompt } from '../partials';
2119
2220
defineOptions({ name: 'TimePickerPrompt' });
2321
24-
const props = defineProps({
25-
allowedMinutes: {
26-
type: Number as PropType<Prompts['meal-time-prompt']['allowedMinutes']>,
27-
required: true,
28-
},
29-
amPmToggle: {
30-
type: Boolean as PropType<Prompts['meal-time-prompt']['amPmToggle']>,
31-
required: true,
32-
},
33-
format: {
34-
type: String as PropType<Prompts['meal-time-prompt']['format']>,
35-
required: true,
36-
},
37-
ui: {
38-
type: String as PropType<Prompts['meal-time-prompt']['ui']>,
39-
required: true,
40-
},
41-
});
22+
const props = defineProps(timerPickerProps);
4223
4324
const emit = defineEmits(['update:options']);
4425

apps/admin/src/components/prompts/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ export const promptSettings: PromptSettings = {
156156
tabs: [...tabs],
157157
sections: ['foods'],
158158
},
159+
'sleep-schedule-prompt': {
160+
tabs: [...tabs],
161+
sections: ['preMeals'],
162+
},
159163
'split-food-prompt': {
160164
tabs: [...tabs],
161165
sections: ['foods'],

apps/admin/src/components/prompts/partials/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { default as PromptJson } from './prompt-json.vue';
1616
export { default as PromptValidation } from './prompt-validation.vue';
1717
export { default as selectListPrompt } from './select-list-prompt';
1818
export { default as SliderSettings } from './slider-settings.vue';
19+
export * from './timer-picker-props';
1920
export { default as TimePickerSettings } from './timer-picker-settings.vue';
2021
export * from './use-base-prompt';
2122
export * from './use-multiple';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { PropType } from 'vue';
2+
import type { TimePicker } from '@intake24/common/prompts';
3+
4+
export const timerPickerProps = {
5+
allowedMinutes: {
6+
type: Number as PropType<TimePicker['allowedMinutes']>,
7+
required: true,
8+
},
9+
amPmToggle: {
10+
type: Boolean as PropType<TimePicker['amPmToggle']>,
11+
required: true,
12+
},
13+
format: {
14+
type: String as PropType<TimePicker['format']>,
15+
required: true,
16+
},
17+
ui: {
18+
type: String as PropType<TimePicker['ui']>,
19+
required: true,
20+
},
21+
} as const;

apps/admin/src/components/prompts/partials/timer-picker-settings.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<v-card border flat>
33
<v-toolbar color="grey-lighten-4">
4-
<v-icon end icon="fas fa-barcode" />
4+
<v-icon end icon="fas fa-clock" />
55
<v-toolbar-title>
66
{{ $t('survey-schemes.prompts.timePicker.title') }}
77
</v-toolbar-title>

apps/admin/src/components/prompts/standard/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import RecallDatePrompt from './recall-date-prompt.vue';
1414
import RedirectPrompt from './redirect-prompt.vue';
1515
import ReviewConfirmPrompt from './review-confirm-prompt.vue';
1616
import SameAsBeforePrompt from './same-as-before-prompt.vue';
17+
import SleepSchedulePrompt from './sleep-schedule-prompt.vue';
1718
import SplitFoodPrompt from './split-food-prompt.vue';
1819
import SubmitPrompt from './submit-prompt.vue';
1920

@@ -34,6 +35,7 @@ export default {
3435
RedirectPrompt,
3536
ReviewConfirmPrompt,
3637
SameAsBeforePrompt,
38+
SleepSchedulePrompt,
3739
SplitFoodPrompt,
3840
SubmitPrompt,
3941
};

apps/admin/src/components/prompts/standard/meal-time-prompt.vue

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,11 @@
1515
</template>
1616

1717
<script lang="ts" setup>
18-
import type { PropType } from 'vue';
19-
import type { Prompts } from '@intake24/common/prompts';
20-
import { TimePickerSettings, useBasePrompt } from '../partials';
18+
import { TimePickerSettings, timerPickerProps, useBasePrompt } from '../partials';
2119
2220
defineOptions({ name: 'MealTimePrompt' });
2321
24-
const props = defineProps({
25-
allowedMinutes: {
26-
type: Number as PropType<Prompts['meal-time-prompt']['allowedMinutes']>,
27-
required: true,
28-
},
29-
amPmToggle: {
30-
type: Boolean as PropType<Prompts['meal-time-prompt']['amPmToggle']>,
31-
required: true,
32-
},
33-
format: {
34-
type: String as PropType<Prompts['meal-time-prompt']['format']>,
35-
required: true,
36-
},
37-
ui: {
38-
type: String as PropType<Prompts['meal-time-prompt']['ui']>,
39-
required: true,
40-
},
41-
});
22+
const props = defineProps(timerPickerProps);
4223
4324
const emit = defineEmits(['update:options']);
4425
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<v-tabs-window-item key="options" value="options">
3+
<v-row>
4+
<v-col cols="12" md="7">
5+
<v-card border flat>
6+
<v-toolbar color="grey-lighten-4">
7+
<v-icon end icon="fas fa-bed" />
8+
<v-toolbar-title>
9+
{{ $t('survey-schemes.prompts.sleep-schedule-prompt.schedule') }}
10+
</v-toolbar-title>
11+
</v-toolbar>
12+
<v-card-text class="d-flex flex-column flex-sm-row pa-0">
13+
<v-time-picker
14+
:landscape="$vuetify.display.smAndUp"
15+
:model-value="wakeUpTime"
16+
:title="$t('survey-schemes.prompts.sleep-schedule-prompt.wakeUpTime')"
17+
@update:model-value="update('wakeUpTime', $event)"
18+
/>
19+
<v-time-picker
20+
:landscape="$vuetify.display.smAndUp"
21+
:model-value="sleepTime"
22+
:title="$t('survey-schemes.prompts.sleep-schedule-prompt.sleepTime')"
23+
@update:model-value="update('sleepTime', $event)"
24+
/>
25+
</v-card-text>
26+
</v-card>
27+
</v-col>
28+
<v-col class="px-4" cols="12" md="5">
29+
<time-picker-settings
30+
v-bind="{ allowedMinutes, amPmToggle, format, ui }"
31+
@update:allowed-minutes="update('allowedMinutes', $event)"
32+
@update:am-pm-toggle="update('amPmToggle', $event)"
33+
@update:format="update('format', $event)"
34+
@update:ui="update('ui', $event)"
35+
/>
36+
</v-col>
37+
</v-row>
38+
</v-tabs-window-item>
39+
</template>
40+
41+
<script lang="ts" setup>
42+
import type { PropType } from 'vue';
43+
import type { Prompts } from '@intake24/common/prompts';
44+
import { TimePickerSettings, timerPickerProps, useBasePrompt } from '../partials';
45+
46+
const props = defineProps({
47+
...timerPickerProps,
48+
wakeUpTime: {
49+
type: String as PropType<Prompts['sleep-schedule-prompt']['wakeUpTime']>,
50+
required: true,
51+
},
52+
sleepTime: {
53+
type: String as PropType<Prompts['sleep-schedule-prompt']['sleepTime']>,
54+
required: true,
55+
},
56+
});
57+
58+
const emit = defineEmits(['update:options']);
59+
60+
const { update } = useBasePrompt(props, { emit });
61+
</script>
62+
63+
<style lang="scss" scoped></style>

apps/api/__tests__/integration/surveys/clear-session.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export default () => {
2929
mode: 'auto',
3030
},
3131
meals: [],
32+
wakeUpTime: null,
33+
sleepTime: null,
3234
};
3335

3436
input = {

apps/api/__tests__/integration/surveys/get-session.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export default () => {
6868
meals: [],
6969
customPromptAnswers: {},
7070
selection: { element: null, mode: 'auto' },
71+
wakeUpTime: null,
72+
sleepTime: null,
7173
},
7274
};
7375
await UserSurveySession.create(input);

apps/api/__tests__/integration/surveys/save-session.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export default () => {
2727
mode: 'auto',
2828
},
2929
meals: [],
30+
wakeUpTime: null,
31+
sleepTime: null,
3032
};
3133

3234
input = { session };

apps/api/__tests__/integration/surveys/start-session.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export default () => {
2828
mode: 'auto',
2929
},
3030
meals: [],
31+
wakeUpTime: null,
32+
sleepTime: null,
3133
};
3234

3335
input = { session };

apps/api/src/services/admin/data-export/data-export-fields.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { differenceInMinutes } from 'date-fns';
33
import { orderBy } from 'lodash';
44
import stringify from 'safe-stable-stringify';
55
import { UAParser } from 'ua-parser-js';
6-
76
import { externalSources as externalSourceProviders } from '@intake24/common/prompts';
87
import type { Prompt } from '@intake24/common/prompts';
9-
import { fromMealTime } from '@intake24/common/surveys';
108
import type { ExportField as BaseExportField } from '@intake24/common/surveys';
9+
import { fromTime } from '@intake24/common/util';
1110
import type { SurveyScheme } from '@intake24/db';
1211
import {
1312
NutrientTableCsvMappingField,
@@ -171,6 +170,16 @@ function dataExportFields() {
171170
label: 'Submission DateTime',
172171
value: ({ food }: ExportRow) => food.meal?.submission?.submissionTime?.toISOString(),
173172
},
173+
{
174+
id: 'wakeUpTime',
175+
label: 'Wake-Up Time',
176+
value: ({ food }: ExportRow) => food.meal?.submission?.wakeUpTime,
177+
},
178+
{
179+
id: 'sleepTime',
180+
label: 'Sleep Time',
181+
value: ({ food }: ExportRow) => food.meal?.submission?.sleepTime,
182+
},
174183
{
175184
id: 'userAgent',
176185
label: 'User Agent',
@@ -244,7 +253,7 @@ function dataExportFields() {
244253
label: 'Meal time',
245254
value: ({ food }: ExportRow) =>
246255
food.meal
247-
? fromMealTime({ hours: food.meal.hours, minutes: food.meal.minutes })
256+
? fromTime({ hours: food.meal.hours, minutes: food.meal.minutes })
248257
: undefined,
249258
},
250259
{ id: 'duration', label: 'Meal duration', value: ({ food }: ExportRow) => food.meal?.duration },

apps/api/src/services/survey/survey-submission.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ function surveySubmissionService({
484484
];
485485

486486
await db.system.transaction(async (transaction) => {
487-
const { recallDate, startTime, endTime, userAgent } = state;
487+
const { recallDate, startTime, endTime, userAgent, wakeUpTime, sleepTime } = state;
488488
const submissionTime = state.submissionTime ?? new Date();
489489
const id = state.id ?? randomUUID();
490490

@@ -508,6 +508,8 @@ function surveySubmissionService({
508508
submissionTime,
509509
sessionId,
510510
userAgent,
511+
wakeUpTime,
512+
sleepTime,
511513
},
512514
{ transaction },
513515
);

apps/docs/admin/surveys/prompt-types.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,18 @@ Prompt to redirect user to external site with optional user identifier embedded
176176

177177
Prompt to detect foods that are the same as in the previous recalls / meals and offer to use the same portion-size estimation.
178178

179+
### Sleep schedule prompt
180+
181+
Prompt to collect sleep schedule information (wake-up time and bedtime).
182+
183+
#### Options
184+
185+
- `allowedMinutes` - allowed minutes for selection (`1`, `5`, `10`, `15`, `20`, `30`)
186+
187+
- `format` - time format (`am/pm` or `24h`)
188+
189+
- `AM/PM toggle` - `true` or `false` whether to show AM/PM toggle buttons
190+
179191
### Split food prompt
180192

181193
Prompt to identify foods that can potentially be split into separate foods.

apps/survey/src/components/elements/SurveyProgressBar.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
import type { PropType } from 'vue';
2020
import { defineComponent } from 'vue';
2121
22-
import { fromMealTime } from '@intake24/common/surveys';
23-
import type { MealState, MealTime } from '@intake24/common/surveys';
22+
import type { MealState } from '@intake24/common/surveys';
23+
import type { Time } from '@intake24/common/util';
24+
import { fromTime } from '@intake24/common/util';
2425
import { useI18n } from '@intake24/i18n';
2526
2627
export default defineComponent({
@@ -35,10 +36,10 @@ export default defineComponent({
3536
3637
setup() {
3738
const { translate } = useI18n();
38-
const stringTime = (time: MealTime | undefined): string => {
39+
const stringTime = (time: Time | undefined): string => {
3940
if (time === undefined)
4041
return '?';
41-
return fromMealTime(time);
42+
return fromTime(time);
4243
};
4344
4445
const color = (item: MealState) => {

apps/survey/src/components/elements/time-pickers/time-picker-digital.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
import type { PropType } from 'vue';
152152
import { computed, ref, watch } from 'vue';
153153
import type { Prompts } from '@intake24/common/prompts';
154-
import { fromMealTime, toMealTime } from '@intake24/common/surveys';
154+
import { fromTime, toTime } from '@intake24/common/util';
155155
import { usePromptUtils } from '@intake24/survey/composables';
156156
157157
const props = defineProps({
@@ -179,7 +179,7 @@ const promptI18n = computed(() => translatePrompt([
179179
'picker.pm',
180180
]));
181181
182-
const time = ref(toMealTime(props.modelValue ?? '00:00'));
182+
const time = ref(toTime(props.modelValue ?? '00:00'));
183183
const activeFormat = ref(props.prompt.format ?? '24hr');
184184
const isAmPm = computed(() => activeFormat.value === 'ampm');
185185
@@ -260,7 +260,7 @@ function updateTime(el: 'hours' | 'minutes', val: number) {
260260
}
261261
262262
watch(time, (val) => {
263-
const time = fromMealTime(val);
263+
const time = fromTime(val);
264264
emit('update:modelValue', time);
265265
}, { deep: true });
266266
</script>

0 commit comments

Comments
 (0)