Skip to content

Commit 9c43885

Browse files
committed
feat(time): add timeMin, timeMax and timeRange to question settings
Signed-off-by: GitHub <noreply@github.com> Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
1 parent 6c8a698 commit 9c43885

13 files changed

Lines changed: 128 additions & 10 deletions

docs/DataStructure.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,6 @@ Optional extra settings for some [Question Types](#question-types)
229229
| `dateMax` | `date` | Integer | - | Maximum allowed date to be chosen (as Unix timestamp) |
230230
| `dateMin` | `date` | Integer | - | Minimum allowed date to be chosen (as Unix timestamp) |
231231
| `dateRange` | `date` | Boolean | `true/false` | The date picker should query a date range |
232+
| `timeMin` | `time` | Integer | - | Maximum allowed time to be chosen (as `HH:mm` string) |
233+
| `timeMax` | `time` | Integer | - | Minimum allowed time to be chosen (as `HH:mm` string) |
234+
| `timeRange` | `time` | Boolean | `true/false` | The time picker should query a time range |

img/clock_arrow_down.svg

Lines changed: 1 addition & 0 deletions
Loading

img/clock_arrow_up.svg

Lines changed: 1 addition & 0 deletions
Loading

lib/Constants.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ class Constants {
153153
'dateRange' => ['boolean', 'NULL'],
154154
];
155155

156+
public const EXTRA_SETTINGS_TIME = [
157+
'timeMax' => ['string', 'NULL'],
158+
'timeMin' => ['string', 'NULL'],
159+
'timeRange' => ['boolean', 'NULL'],
160+
];
161+
156162
// should be in sync with FileTypes.js
157163
public const EXTRA_SETTINGS_ALLOWED_FILE_TYPES = [
158164
'image',

lib/ResponseDefinitions.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
* optionsLimitMax?: int,
3232
* optionsLimitMin?: int,
3333
* shuffleOptions?: bool,
34+
* timeMax?: string,
35+
* timeMin?: string,
36+
* timeRange?: bool,
3437
* validationRegex?: string,
3538
* validationType?: string
3639
* }

lib/Service/FormsService.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,9 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
632632
case Constants::ANSWER_TYPE_DATE:
633633
$allowed = Constants::EXTRA_SETTINGS_DATE;
634634
break;
635+
case Constants::ANSWER_TYPE_TIME:
636+
$allowed = Constants::EXTRA_SETTINGS_TIME;
637+
break;
635638
default:
636639
$allowed = [];
637640
}
@@ -656,6 +659,12 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType
656659
&& $extraSettings['dateMin'] > $extraSettings['dateMax']) {
657660
return false;
658661
}
662+
} elseif ($questionType === Constants::ANSWER_TYPE_TIME) {
663+
// Ensure timeMin and timeMax don't overlap
664+
if (isset($extraSettings['timeMin']) && isset($extraSettings['timeMax'])
665+
&& $extraSettings['timeMin'] > $extraSettings['timeMax']) {
666+
return false;
667+
}
659668
} elseif ($questionType === Constants::ANSWER_TYPE_MULTIPLE) {
660669
// Ensure limits are sane
661670
if (isset($extraSettings['optionsLimitMax']) && isset($extraSettings['optionsLimitMin'])

lib/Service/SubmissionService.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,15 +457,17 @@ private function validateDateTime(array $answers, string $format, ?string $text
457457
}
458458

459459
if ($previousDate !== null && $d < $previousDate) {
460-
throw new \InvalidArgumentException(sprintf('Dates for question "%s" must be in ascending order.', $text));
460+
throw new \InvalidArgumentException(sprintf('Dates/times for question "%s" must be in ascending order.', $text));
461461
}
462462
$previousDate = $d;
463463

464464
if ($extraSettings) {
465465
if ((isset($extraSettings['dateMin']) && $d < (new DateTime())->setTimestamp($extraSettings['dateMin'])) ||
466-
(isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax']))
466+
(isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax'])) ||
467+
(isset($extraSettings['timeMin']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMin'])) ||
468+
(isset($extraSettings['timeMax']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMax']))
467469
) {
468-
throw new \InvalidArgumentException(sprintf('Date is not in the allowed range for question "%s".', $text));
470+
throw new \InvalidArgumentException(sprintf('Date/time is not in the allowed range for question "%s".', $text));
469471
}
470472
}
471473
}

openapi.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,15 @@
448448
"shuffleOptions": {
449449
"type": "boolean"
450450
},
451+
"timeMax": {
452+
"type": "string"
453+
},
454+
"timeMin": {
455+
"type": "string"
456+
},
457+
"timeRange": {
458+
"type": "boolean"
459+
},
451460
"validationRegex": {
452461
"type": "string"
453462
},

src/components/Questions/QuestionDate.vue

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@
4242
</template>
4343
</NcActionInput>
4444
</template>
45+
<template v-else-if="answerType.pickerType === 'time'" #actions>
46+
<NcActionCheckbox v-model="timeRange">
47+
{{ t('forms', 'Use time range') }}
48+
</NcActionCheckbox>
49+
<NcActionInput
50+
v-model="timeMin"
51+
type="time"
52+
:label="t('forms', 'Earliest time')"
53+
hide-label
54+
:formatter="formatter"
55+
is-native-picker
56+
:max="timeMax">
57+
<template #icon>
58+
<NcIconSvgWrapper
59+
:svg="svgClockArrowUpIcon"
60+
:name="t('forms', 'Earliest time')" />
61+
</template>
62+
</NcActionInput>
63+
<NcActionInput
64+
v-model="timeMax"
65+
type="time"
66+
:label="t('forms', 'Latest time')"
67+
hide-label
68+
:formatter="formatter"
69+
is-native-picker
70+
:min="timeMin">
71+
<template #icon>
72+
<NcIconSvgWrapper
73+
:svg="svgClockArrowDownIcon"
74+
:name="t('forms', 'Latest time')" />
75+
</template>
76+
</NcActionInput>
77+
</template>
4578
<div class="question__content">
4679
<NcDateTimePicker
4780
:value="time"
@@ -60,6 +93,8 @@
6093
</template>
6194

6295
<script>
96+
import svgClockArrowDownIcon from '../../../img/clock_arrow_down.svg?raw'
97+
import svgClockArrowUpIcon from '../../../img/clock_arrow_up.svg?raw'
6398
import svgEventIcon from '../../../img/event.svg?raw'
6499
import svgTodayIcon from '../../../img/today.svg?raw'
65100
@@ -92,6 +127,8 @@ export default {
92127
stringify: this.stringifyDate,
93128
parse: this.parseTimestampToDate,
94129
},
130+
svgClockArrowDownIcon,
131+
svgClockArrowUpIcon,
95132
svgEventIcon,
96133
svgTodayIcon,
97134
}
@@ -100,11 +137,11 @@ export default {
100137
computed: {
101138
datetimePickerPlaceholder() {
102139
if (this.readOnly) {
103-
return this.extraSettings?.dateRange
140+
return this.extraSettings?.dateRange || this.extraSettings?.timeRange
104141
? this.answerType.submitPlaceholderRange
105142
: this.answerType.submitPlaceholder
106143
}
107-
return this.extraSettings?.dateRange
144+
return this.extraSettings?.dateRange || this.extraSettings?.timeRange
108145
? this.answerType.createPlaceholderRange
109146
: this.answerType.createPlaceholder
110147
},
@@ -122,7 +159,7 @@ export default {
122159
},
123160
124161
time() {
125-
if (this.extraSettings?.dateRange) {
162+
if (this.extraSettings?.dateRange || this.extraSettings?.timeRange) {
126163
return this.values
127164
? [this.parse(this.values[0]), this.parse(this.values[1])]
128165
: null
@@ -170,6 +207,47 @@ export default {
170207
this.onExtraSettingsChange({ dateRange: value === true ?? null })
171208
},
172209
},
210+
211+
/**
212+
* The maximum allowable time for the time input field
213+
*/
214+
timeMax: {
215+
get() {
216+
return this.extraSettings?.timeMax
217+
? this.parse(this.extraSettings.timeMax)
218+
: null
219+
},
220+
set(value) {
221+
this.onExtraSettingsChange({
222+
timeMax: this.stringify(value),
223+
})
224+
},
225+
},
226+
227+
/**
228+
* The minimum allowable time for the time input field
229+
*/
230+
timeMin: {
231+
get() {
232+
return this.extraSettings?.timeMin
233+
? this.parse(this.extraSettings.timeMax)
234+
: null
235+
},
236+
set(value) {
237+
this.onExtraSettingsChange({
238+
timeMin: this.stringify(value),
239+
})
240+
},
241+
},
242+
243+
timeRange: {
244+
get() {
245+
return this.extraSettings?.timeRange ?? false
246+
},
247+
set(value) {
248+
this.onExtraSettingsChange({ timeRange: value === true ?? null })
249+
},
250+
},
173251
},
174252
175253
methods: {

src/components/Results/ResultsSummary.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ export default {
179179
}
180180
181181
// Add text answers
182-
if (this.question.type === 'date' && answers.length === 2) {
182+
if (
183+
(this.question.type === 'date' ||
184+
this.question.type === 'time') &&
185+
answers.length === 2
186+
) {
183187
// Combine the first two answers in order for date range questions
184188
answersModels.push({
185189
id: `${answers[0].id}-${answers[1].id}`,

0 commit comments

Comments
 (0)