Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/DataStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,6 @@ Optional extra settings for some [Question Types](#question-types)
| `dateMax` | `date` | Integer | - | Maximum allowed date to be chosen (as Unix timestamp) |
| `dateMin` | `date` | Integer | - | Minimum allowed date to be chosen (as Unix timestamp) |
| `dateRange` | `date` | Boolean | `true/false` | The date picker should query a date range |
| `timeMin` | `time` | Integer | - | Maximum allowed time to be chosen (as `HH:mm` string) |
| `timeMax` | `time` | Integer | - | Minimum allowed time to be chosen (as `HH:mm` string) |
| `timeRange` | `time` | Boolean | `true/false` | The time picker should query a time range |
1 change: 1 addition & 0 deletions img/clock_arrow_down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/clock_arrow_up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ class Constants {
'dateRange' => ['boolean', 'NULL'],
];

public const EXTRA_SETTINGS_TIME = [
'timeMax' => ['string', 'NULL'],
'timeMin' => ['string', 'NULL'],
'timeRange' => ['boolean', 'NULL'],
];

// should be in sync with FileTypes.js
public const EXTRA_SETTINGS_ALLOWED_FILE_TYPES = [
'image',
Expand Down
3 changes: 3 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
* optionsLimitMax?: int,
* optionsLimitMin?: int,
* shuffleOptions?: bool,
* timeMax?: string,
* timeMin?: string,
* timeRange?: bool,
* validationRegex?: string,
* validationType?: string
* }
Expand Down
9 changes: 9 additions & 0 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,9 @@
case Constants::ANSWER_TYPE_DATE:
$allowed = Constants::EXTRA_SETTINGS_DATE;
break;
case Constants::ANSWER_TYPE_TIME:
$allowed = Constants::EXTRA_SETTINGS_TIME;
break;

Check warning on line 637 in lib/Service/FormsService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/FormsService.php#L635-L637

Added lines #L635 - L637 were not covered by tests
default:
$allowed = [];
}
Expand All @@ -656,6 +659,12 @@
&& $extraSettings['dateMin'] > $extraSettings['dateMax']) {
return false;
}
} elseif ($questionType === Constants::ANSWER_TYPE_TIME) {
// Ensure timeMin and timeMax don't overlap
if (isset($extraSettings['timeMin']) && isset($extraSettings['timeMax'])
&& $extraSettings['timeMin'] > $extraSettings['timeMax']) {
return false;

Check warning on line 666 in lib/Service/FormsService.php

View check run for this annotation

Codecov / codecov/patch

lib/Service/FormsService.php#L664-L666

Added lines #L664 - L666 were not covered by tests
}
} elseif ($questionType === Constants::ANSWER_TYPE_MULTIPLE) {
// Ensure limits are sane
if (isset($extraSettings['optionsLimitMax']) && isset($extraSettings['optionsLimitMin'])
Expand Down
8 changes: 5 additions & 3 deletions lib/Service/SubmissionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,15 +457,17 @@ private function validateDateTime(array $answers, string $format, ?string $text
}

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

if ($extraSettings) {
if ((isset($extraSettings['dateMin']) && $d < (new DateTime())->setTimestamp($extraSettings['dateMin'])) ||
(isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax']))
(isset($extraSettings['dateMax']) && $d > (new DateTime())->setTimestamp($extraSettings['dateMax'])) ||
(isset($extraSettings['timeMin']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMin'])) ||
(isset($extraSettings['timeMax']) && $d > (new DateTime())->setTimestamp($extraSettings['timeMax']))
) {
throw new \InvalidArgumentException(sprintf('Date is not in the allowed range for question "%s".', $text));
throw new \InvalidArgumentException(sprintf('Date/time is not in the allowed range for question "%s".', $text));
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@
"shuffleOptions": {
"type": "boolean"
},
"timeMax": {
"type": "string"
},
"timeMin": {
"type": "string"
},
"timeRange": {
"type": "boolean"
},
"validationRegex": {
"type": "string"
},
Expand Down
84 changes: 81 additions & 3 deletions src/components/Questions/QuestionDate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,39 @@
</template>
</NcActionInput>
</template>
<template v-else-if="answerType.pickerType === 'time'" #actions>
<NcActionCheckbox v-model="timeRange">
{{ t('forms', 'Use time range') }}
</NcActionCheckbox>
<NcActionInput
v-model="timeMin"
type="time"
:label="t('forms', 'Earliest time')"
hide-label
:formatter="formatter"
is-native-picker
:max="timeMax">
<template #icon>
<NcIconSvgWrapper
:svg="svgClockArrowUpIcon"
:name="t('forms', 'Earliest time')" />
</template>
</NcActionInput>
<NcActionInput
v-model="timeMax"
type="time"
:label="t('forms', 'Latest time')"
hide-label
:formatter="formatter"
is-native-picker
:min="timeMin">
<template #icon>
<NcIconSvgWrapper
:svg="svgClockArrowDownIcon"
:name="t('forms', 'Latest time')" />
</template>
</NcActionInput>
</template>
<div class="question__content">
<NcDateTimePicker
:value="time"
Expand All @@ -60,6 +93,8 @@
</template>

<script>
import svgClockArrowDownIcon from '../../../img/clock_arrow_down.svg?raw'
import svgClockArrowUpIcon from '../../../img/clock_arrow_up.svg?raw'
import svgEventIcon from '../../../img/event.svg?raw'
import svgTodayIcon from '../../../img/today.svg?raw'

Expand Down Expand Up @@ -92,6 +127,8 @@ export default {
stringify: this.stringifyDate,
parse: this.parseTimestampToDate,
},
svgClockArrowDownIcon,
svgClockArrowUpIcon,
svgEventIcon,
svgTodayIcon,
}
Expand All @@ -100,11 +137,11 @@ export default {
computed: {
datetimePickerPlaceholder() {
if (this.readOnly) {
return this.extraSettings?.dateRange
return this.extraSettings?.dateRange || this.extraSettings?.timeRange
? this.answerType.submitPlaceholderRange
: this.answerType.submitPlaceholder
}
return this.extraSettings?.dateRange
return this.extraSettings?.dateRange || this.extraSettings?.timeRange
? this.answerType.createPlaceholderRange
: this.answerType.createPlaceholder
},
Expand All @@ -122,7 +159,7 @@ export default {
},

time() {
if (this.extraSettings?.dateRange) {
if (this.extraSettings?.dateRange || this.extraSettings?.timeRange) {
return this.values
? [this.parse(this.values[0]), this.parse(this.values[1])]
: null
Expand Down Expand Up @@ -170,6 +207,47 @@ export default {
this.onExtraSettingsChange({ dateRange: value === true ?? null })
},
},

/**
* The maximum allowable time for the time input field
*/
timeMax: {
get() {
return this.extraSettings?.timeMax
? this.parse(this.extraSettings.timeMax)
: null
},
set(value) {
this.onExtraSettingsChange({
timeMax: this.stringify(value),
})
},
},

/**
* The minimum allowable time for the time input field
*/
timeMin: {
get() {
return this.extraSettings?.timeMin
? this.parse(this.extraSettings.timeMax)
: null
},
set(value) {
this.onExtraSettingsChange({
timeMin: this.stringify(value),
})
},
},

timeRange: {
get() {
return this.extraSettings?.timeRange ?? false
},
set(value) {
this.onExtraSettingsChange({ timeRange: value === true ?? null })
},
},
},

methods: {
Expand Down
6 changes: 5 additions & 1 deletion src/components/Results/ResultsSummary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ export default {
}

// Add text answers
if (this.question.type === 'date' && answers.length === 2) {
if (
(this.question.type === 'date' ||
this.question.type === 'time') &&
answers.length === 2
) {
// Combine the first two answers in order for date range questions
answersModels.push({
id: `${answers[0].id}-${answers[1].id}`,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Results/Submission.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export default {
}
}),
})
} else if (question.type === 'date') {
} else if (question.type === 'date' || question.type === 'time') {
const squashedAnswers = answers
.map((answer) => answer.text)
.join(' - ')
Expand Down
2 changes: 2 additions & 0 deletions src/models/AnswerTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ export default {

titlePlaceholder: t('forms', 'Time question title'),
createPlaceholder: t('forms', 'People can pick a time'),
createPlaceholderRange: t('forms', 'People can pick a time range'),
submitPlaceholder: t('forms', 'Pick a time'),
submitPlaceholderRange: t('forms', 'Pick a time range'),
warningInvalid: t('forms', 'This question needs a title!'),

pickerType: 'time',
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/Service/SubmissionServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ public function dataValidateSubmission() {
'1' => ['2025-03-24']
],
// Expected Result
'Date is not in the allowed range for question "q1".',
'Date/time is not in the allowed range for question "q1".',
],
'valid-date-range' => [
// Questions
Expand Down Expand Up @@ -849,7 +849,7 @@ public function dataValidateSubmission() {
'1' => ['2023-12-31', '2023-01-01']
],
// Expected Result
'Dates for question "q1" must be in ascending order.',
'Dates/times for question "q1" must be in ascending order.',
],
'valid-single-date' => [
// Questions
Expand Down
Loading