857f2baa16
* replace ember-cp-validations with custom validation logic for poll creation * refactor poll participation to not use ember-cp-validations * drop ember-cp-validations from create.settings route * remove unused leftovers from ember-cp-validations * fix potential leak * assertion thrown in CI tests (Firefox) indicate that listener is cleaned up automatically * synchronize translations
221 lines
5.3 KiB
JavaScript
221 lines
5.3 KiB
JavaScript
import Component from "@glimmer/component";
|
|
import { inject as service } from "@ember/service";
|
|
import { action } from "@ember/object";
|
|
import { tracked } from "@glimmer/tracking";
|
|
import { TrackedArray } from "tracked-built-ins";
|
|
import { DateTime } from "luxon";
|
|
import IntlMessage from "../utils/intl-message";
|
|
|
|
class FormDataOption {
|
|
formData;
|
|
|
|
// ISO 8601 date string: YYYY-MM-DD
|
|
day;
|
|
|
|
// ISO 8601 time string without seconds: HH:mm
|
|
@tracked time;
|
|
|
|
// helper property set by modifiers to track if input element is invalid
|
|
// because user only entered the time partly (e.g. "10:--").
|
|
@tracked isPartiallyFilled = false;
|
|
|
|
get timeValidation() {
|
|
const { isPartiallyFilled } = this;
|
|
if (isPartiallyFilled) {
|
|
return new IntlMessage(
|
|
"create.options-datetime.error.partiallyFilledTime"
|
|
);
|
|
}
|
|
|
|
// The same time must not be entered twice for a day.
|
|
// It should show a validation error if the same time has been entered for
|
|
// the same day already before. Only the second input field containing the
|
|
// duplicated time should show the validation error.
|
|
const { formData, day } = this;
|
|
const optionsForThisDay = formData.optionsGroupedByDay[day];
|
|
const isDuplicate = optionsForThisDay
|
|
.slice(0, optionsForThisDay.indexOf(this))
|
|
.any((option) => option.time == this.time);
|
|
if (isDuplicate) {
|
|
return new IntlMessage("create.options-datetime.error.duplicatedDate");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get datetime() {
|
|
const { day, time } = this;
|
|
const isoString = time === null ? day : `${day}T${time}`;
|
|
return DateTime.fromISO(isoString);
|
|
}
|
|
|
|
get jsDate() {
|
|
const { datetime } = this;
|
|
return datetime.toJSDate();
|
|
}
|
|
|
|
get isValid() {
|
|
const { timeValidation } = this;
|
|
return timeValidation === null;
|
|
}
|
|
|
|
constructor(formData, { day, time }) {
|
|
this.formData = formData;
|
|
this.day = day;
|
|
this.time = time;
|
|
}
|
|
}
|
|
|
|
class FormData {
|
|
@tracked options;
|
|
|
|
get optionsValidation() {
|
|
const { options } = this;
|
|
const allOptionsAreValid = options.every((option) => option.isValid);
|
|
if (!allOptionsAreValid) {
|
|
return IntlMessage("create.options-datetime.error.invalidTime");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get optionsGroupedByDay() {
|
|
const { options } = this;
|
|
const groupedOptions = {};
|
|
|
|
for (const option of options) {
|
|
const { day } = option;
|
|
|
|
if (!groupedOptions[day]) {
|
|
groupedOptions[day] = [];
|
|
}
|
|
|
|
groupedOptions[day].push(option);
|
|
}
|
|
|
|
return groupedOptions;
|
|
}
|
|
|
|
get hasMultipleDays() {
|
|
return Object.keys(this.optionsGroupedByDay).length > 1;
|
|
}
|
|
|
|
@action
|
|
addOption(position, day) {
|
|
this.options.splice(
|
|
position + 1,
|
|
0,
|
|
new FormDataOption(this, { day, time: null })
|
|
);
|
|
}
|
|
|
|
/*
|
|
* removes target option if it's not the only date for this day
|
|
* otherwise it deletes time for this date
|
|
*/
|
|
@action
|
|
deleteOption(option) {
|
|
const optionsForThisDay = this.optionsGroupedByDay[option.day];
|
|
|
|
if (optionsForThisDay.length > 1) {
|
|
this.options.splice(this.options.indexOf(option), 1);
|
|
} else {
|
|
option.time = null;
|
|
}
|
|
}
|
|
|
|
@action
|
|
adoptTimesOfFirstDay() {
|
|
const { optionsGroupedByDay } = this;
|
|
const days = Object.keys(optionsGroupedByDay).sort();
|
|
const firstDay = days[0];
|
|
const optionsForFirstDay = optionsGroupedByDay[firstDay];
|
|
|
|
const timesForFirstDayAreValid = optionsForFirstDay.every(
|
|
(option) => option.isValid
|
|
);
|
|
if (!timesForFirstDayAreValid) {
|
|
return false;
|
|
}
|
|
|
|
const timesForFirstDay = optionsForFirstDay.map((option) => option.time);
|
|
|
|
this.options = new TrackedArray(
|
|
days
|
|
.map((day) =>
|
|
timesForFirstDay.map(
|
|
(time) => new FormDataOption(this, { day, time })
|
|
)
|
|
)
|
|
.flat()
|
|
);
|
|
}
|
|
|
|
constructor(options) {
|
|
this.options = new TrackedArray(
|
|
options.map(({ day, time }) => new FormDataOption(this, { day, time }))
|
|
);
|
|
}
|
|
}
|
|
|
|
export default class CreateOptionsDatetime extends Component {
|
|
@service router;
|
|
|
|
formData = new FormData(this.args.dates);
|
|
|
|
@tracked errorMesage = null;
|
|
|
|
@action
|
|
adoptTimesOfFirstDay() {
|
|
const { formData } = this;
|
|
const successful = formData.adoptTimesOfFirstDay();
|
|
|
|
if (!successful) {
|
|
this.errorMesage =
|
|
"create.options-datetime.fix-validation-errors-first-day";
|
|
}
|
|
}
|
|
|
|
@action
|
|
previousPage() {
|
|
this.args.onPrevPage();
|
|
}
|
|
|
|
@action
|
|
submit() {
|
|
this.args.onNextPage();
|
|
}
|
|
|
|
// validate input field for being partially filled
|
|
@action
|
|
validateInput(option, event) {
|
|
const element = event.target;
|
|
|
|
// update partially filled time validation error
|
|
option.isPartiallyFilled = !element.checkValidity();
|
|
}
|
|
|
|
// remove partially filled validation error if user fixed it
|
|
@action
|
|
updateInputValidation(option, event) {
|
|
const element = event.target;
|
|
|
|
if (element.checkValidity() && option.isPartiallyFilled) {
|
|
option.isPartiallyFilled = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
handleTransition(transition) {
|
|
if (transition.from?.name === "create.options-datetime") {
|
|
this.args.updateOptions(this.formData.options);
|
|
this.router.off('routeWillChange', this.handleTransition);
|
|
}
|
|
}
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
this.router.on("routeWillChange", this.handleTransition);
|
|
}
|
|
}
|