diff --git a/zmsadmin/js/page/availabilityDay/form/datepicker.js b/zmsadmin/js/page/availabilityDay/form/datepicker.js
index f81bfe4d0..22b13c683 100644
--- a/zmsadmin/js/page/availabilityDay/form/datepicker.js
+++ b/zmsadmin/js/page/availabilityDay/form/datepicker.js
@@ -174,10 +174,6 @@ class AvailabilityDatePicker extends Component
}
handleTimeChange(name, date) {
- if (!date) {
- this.closeDatePicker();
- return;
- }
if ('startDate' == name) {
if (this.state.availability.startTime != moment(date).format('HH:mm')) {
this.props.onChange("startTime", moment(date).format('HH:mm'));
diff --git a/zmsadmin/js/page/availabilityDay/form/errors.js b/zmsadmin/js/page/availabilityDay/form/errors.js
index 67f225527..7dfa3dfd4 100644
--- a/zmsadmin/js/page/availabilityDay/form/errors.js
+++ b/zmsadmin/js/page/availabilityDay/form/errors.js
@@ -1,15 +1,20 @@
-import React from 'react'
-import PropTypes from 'prop-types'
+import React from 'react';
+import PropTypes from 'prop-types';
-const renderErrors = errors => Object.keys(errors).map(key => {
- return (
+const renderErrors = (errors) =>
+ Object.keys(errors).map(key => (
{errors[key].itemList.map((item, index) => {
- return
{item[0].message}
- })}
+ if (Array.isArray(item)) {
+ return item.map((nestedItem, nestedIndex) => (
+
{nestedItem.message}
+ ));
+ } else {
+ return
{item.message}
;
+ }
+ })}
- )
-})
+ ));
const Errors = (props) => {
return (
@@ -18,15 +23,15 @@ const Errors = (props) => {
Folgende Fehler sind bei der Prüfung Ihrer Eingaben aufgetreten:
{renderErrors(props.errorList)}
: null
- )
-}
+ );
+};
Errors.defaultProps = {
- errorList: []
-}
+ errorList: {}
+};
Errors.propTypes = {
errorList: PropTypes.object
-}
+};
-export default Errors
+export default Errors;
diff --git a/zmsadmin/js/page/availabilityDay/form/validate.js b/zmsadmin/js/page/availabilityDay/form/validate.js
index 48c35e13c..daa4025e3 100644
--- a/zmsadmin/js/page/availabilityDay/form/validate.js
+++ b/zmsadmin/js/page/availabilityDay/form/validate.js
@@ -16,10 +16,8 @@ const validate = (data, props) => {
itemList: []
};
- // Add new validation functions for null and format checks
errorList.itemList.push(validateNullValues(data));
errorList.itemList.push(validateTimestampAndTimeFormats(data));
-
errorList.itemList.push(validateStartTime(today, tomorrow, selectedDate, data));
errorList.itemList.push(validateEndTime(today, yesterday, selectedDate, data));
errorList.itemList.push(validateOriginEndTime(today, yesterday, selectedDate, data));
@@ -35,7 +33,6 @@ const validate = (data, props) => {
};
};
-// Validate if startDate, endDate, startTime, and endTime are not null
function validateNullValues(data) {
let errorList = [];
@@ -70,50 +67,71 @@ function validateNullValues(data) {
return errorList;
}
-// Validate timestamps and time formats
function validateTimestampAndTimeFormats(data) {
let errorList = [];
- const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/; // HH:mm:ss or HH:mm
+ const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;
+
+ let isStartDateValid = isValidTimestamp(data.startDate);
+ let isEndDateValid = isValidTimestamp(data.endDate);
- // Validate startDate and endDate as valid timestamps
- if (!isValidTimestamp(data.startDate)) {
+ if (!isStartDateValid) {
errorList.push({
type: 'startDateInvalid',
message: 'Das Startdatum ist kein gültiger Zeitstempel.'
});
}
- if (!isValidTimestamp(data.endDate)) {
+ if (!isEndDateValid) {
errorList.push({
type: 'endDateInvalid',
message: 'Das Enddatum ist kein gültiger Zeitstempel.'
});
}
- // Validate startTime and endTime format
- if (data.startTime && !timeRegex.test(data.startTime)) {
+ if (data.startTime) {
+ if (!timeRegex.test(data.startTime)) {
+ errorList.push({
+ type: 'startTimeFormat',
+ message: 'Die Startzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
+ });
+ }
+ } else {
errorList.push({
- type: 'startTimeFormat',
- message: 'Die Startzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
+ type: 'startTimeMissing',
+ message: 'Die Startzeit darf nicht leer sein.'
});
}
- if (data.endTime && !timeRegex.test(data.endTime)) {
+ if (data.endTime) {
+ if (!timeRegex.test(data.endTime)) {
+ errorList.push({
+ type: 'endTimeFormat',
+ message: 'Die Endzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
+ });
+ }
+ } else {
errorList.push({
- type: 'endTimeFormat',
- message: 'Die Endzeit muss im Format "HH:mm:ss" oder "HH:mm" vorliegen.'
+ type: 'endTimeMissing',
+ message: 'Die Endzeit darf nicht leer sein.'
});
}
+ if (isStartDateValid && isEndDateValid) {
+ if (new Date(data.startDate) > new Date(data.endDate)) {
+ errorList.push({
+ type: 'dateOrderInvalid',
+ message: 'Das Startdatum darf nicht nach dem Enddatum liegen.'
+ });
+ }
+ }
+
return errorList;
}
-// Helper function to check if a timestamp is valid
function isValidTimestamp(timestamp) {
return !isNaN(timestamp) && moment.unix(timestamp).isValid();
}
-// Parse timestamp and time into moment objects
function parseTimestampAndTime(dateTimestamp, timeStr) {
const date = moment.unix(dateTimestamp);
if (!date.isValid()) return null;
@@ -122,48 +140,6 @@ function parseTimestampAndTime(dateTimestamp, timeStr) {
return date.set({ hour: hours, minute: minutes, second: seconds });
}
-// Example usage in validateStartTime and validateEndTime
-function validateStartTime(today, tomorrow, selectedDate, data) {
- let errorList = [];
- const startTime = parseTimestampAndTime(data.startDate, data.startTime);
- const isFuture = data.kind && data.kind === 'future';
-
- if (!startTime) {
- errorList.push({
- type: 'startTimeInvalid',
- message: 'Ungültige Startzeit oder Startdatum.'
- });
- } else if (!isFuture && startTime.isAfter(today, 'minute')) {
- errorList.push({
- type: 'startTimeFuture',
- message: `Das Startdatum der Öffnungszeit muss vor dem ${tomorrow.format('DD.MM.YYYY')} liegen.`
- });
- }
-
- return errorList;
-}
-
-function validateEndTime(today, yesterday, selectedDate, data) {
- let errorList = [];
- const endTime = parseTimestampAndTime(data.endDate, data.endTime);
- const startTime = parseTimestampAndTime(data.startDate, data.startTime);
-
- if (!endTime) {
- errorList.push({
- type: 'endTimeInvalid',
- message: 'Ungültige Endzeit oder Enddatum.'
- });
- } else if (startTime && endTime.isBefore(startTime)) {
- errorList.push({
- type: 'endTime',
- message: 'Das Enddatum darf nicht vor dem Startdatum liegen.'
- });
- }
-
- return errorList;
-}
-
-
function validateStartTime(today, tomorrow, selectedDate, data) {
let errorList = []
const startTime = moment(data.startDate, 'X').startOf('day');
@@ -181,15 +157,15 @@ function validateStartTime(today, tomorrow, selectedDate, data) {
message: `Das Startdatum der Öffnungszeit muss vor dem ${tomorrow.format('DD.MM.YYYY')} liegen.`
})
}
-/*
- if (isOrigin && startTime.isBefore(today.startOf('day'), 'day') && data.__modified) {
- errorList.push({
- type: 'startTimeOrigin',
- message: 'Öffnungszeiten in der Vergangenheit lassen sich nicht bearbeiten '
- + '(Der Terminanfang am "'+startDateTime.format('DD.MM.YYYY')+' liegt vor dem heutigen Tag").'
- })
- }
-*/
+ /*
+ if (isOrigin && startTime.isBefore(today.startOf('day'), 'day') && data.__modified) {
+ errorList.push({
+ type: 'startTimeOrigin',
+ message: 'Öffnungszeiten in der Vergangenheit lassen sich nicht bearbeiten '
+ + '(Der Terminanfang am "'+startDateTime.format('DD.MM.YYYY')+' liegt vor dem heutigen Tag").'
+ })
+ }
+ */
if ((startHour == "00" && startMinute == "00") || (endHour == "00" && endMinute == "00")) {
errorList.push({
@@ -218,12 +194,15 @@ function validateEndTime(today, yesterday, selectedDate, data) {
type: 'endTime',
message: 'Die Endzeit darf nicht vor der Startzeit liegen.'
})
- } else if (startTimestamp >= endTimestamp) {
+ }
+
+ if (startTimestamp >= endTimestamp) {
errorList.push({
type: 'endTime',
message: 'Das Enddatum darf nicht vor dem Startdatum liegen.'
})
}
+
return errorList;
}
diff --git a/zmsadmin/js/page/availabilityDay/index.js b/zmsadmin/js/page/availabilityDay/index.js
index b5d26c62e..2b610d878 100644
--- a/zmsadmin/js/page/availabilityDay/index.js
+++ b/zmsadmin/js/page/availabilityDay/index.js
@@ -179,7 +179,6 @@ class AvailabilityPage extends Component {
}
}
-
onRevertUpdates() {
this.isCreatingExclusion = false
this.setState(Object.assign({}, getInitialState(this.props), {
@@ -196,18 +195,14 @@ class AvailabilityPage extends Component {
const id = availability.id;
if (ok) {
- // Format the selected date
const selectedDate = formatTimestampDate(this.props.timestamp);
- // Prepare the data to send
const sendAvailability = Object.assign({}, availability);
- // Clean up temporary fields
if (sendAvailability.tempId) {
delete sendAvailability.tempId;
}
- // Include 'kind' and wrap it in 'availabilityList'
const payload = {
availabilityList: [
{
@@ -220,7 +215,6 @@ class AvailabilityPage extends Component {
console.log('Updating single availability', payload);
- // Make the AJAX request
$.ajax(`${this.props.links.includeurl}/availability/save/${id}/`, {
method: 'POST',
data: JSON.stringify(payload),
@@ -495,72 +489,110 @@ class AvailabilityPage extends Component {
return hasError || hasConflict;
}
- getValidationList(list = []) {
- const validateData = data => {
- let validationResult = validate(data, this.props)
- if (!validationResult.valid) {
- return validationResult.errorList
-
- }
- return [];
- }
-
- this.state.availabilitylist.map(availability => {
- list.push(validateData(availability))
- })
- list = list.filter(el => el.id)
-
- this.setState({
- errorList: list.length ? Object.assign({}, list) : {}
- }, () => {
- if (list.length) {
- this.errorElement.scrollIntoView()
- }
- })
- }
-
- getConflictList() {
- const requestOptions = {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(Object.assign({}, {
- availabilityList: this.state.availabilitylist,
- selectedDate: formatTimestampDate(this.props.timestamp),
- selectedAvailability: this.state.selectedAvailability
- }))
- };
- const url = `${this.props.links.includeurl}/availability/conflicts/`;
- fetch(url, requestOptions)
- .then(res => res.json())
- .then(
- (data) => {
- this.setState({
- conflictList: Object.assign({},
- {
- itemList: Object.assign({}, data.conflictList),
- conflictIdList: data.conflictIdList
- }
- )
- })
- if (data.conflictIdList.length > 0) {
- this.errorElement.scrollIntoView()
- }
+ getValidationList() {
+ return new Promise((resolve, reject) => {
+ const validateData = (data) => {
+ const validationResult = validate(data, this.props);
+ if (!validationResult.valid) {
+ return validationResult.errorList;
+ }
+ return [];
+ };
+
+ const list = this.state.availabilitylist
+ .map(validateData)
+ .flat();
+
+ console.log("Validation list:", list);
+
+ this.setState(
+ {
+ errorList: list.length ? list : [],
},
- (err) => {
- let isException = err.responseText.toLowerCase().includes('exception');
- if (err.status >= 500 && isException) {
- new ExceptionHandler($('.opened'), {
- code: err.status,
- message: err.responseText
- });
+ () => {
+ if (list.length > 0) {
+ console.warn("Validation failed with errors:", list);
+ this.errorElement?.scrollIntoView();
+ //reject(new Error("Validation failed")); // Reject with an error object
} else {
- console.log('conflict error', err);
+ console.log("Validation passed.");
+ resolve();
}
- hideSpinner();
}
- )
+ );
+ });
+ }
+
+ validateAvailabilityList(availabilitylist) {
+ const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;
+
+ const isValidTimestamp = (timestamp) => !isNaN(timestamp) && moment.unix(timestamp).isValid();
+
+ const invalidAvailabilities = availabilitylist.filter((availability) => {
+ const hasInvalidDates =
+ !isValidTimestamp(availability.startDate) || !isValidTimestamp(availability.endDate);
+ const hasInvalidTimes =
+ !timeRegex.test(availability.startTime) || !timeRegex.test(availability.endTime);
+
+ if (hasInvalidDates || hasInvalidTimes) {
+ console.warn("Invalid availability detected:", availability);
+ }
+
+ return hasInvalidDates || hasInvalidTimes;
+ });
+
+ return invalidAvailabilities;
}
+ getConflictList() {
+ this.getValidationList()
+ .then(() => {
+ const { availabilitylist, selectedAvailability } = this.state;
+ const { timestamp } = this.props;
+
+ console.log("Validation passed. Proceeding with /availability/conflicts/.");
+
+ selectedAvailability.startTime = moment(selectedAvailability.startTime, ['HH:mm:ss', 'HH:mm']).format('HH:mm');
+ selectedAvailability.endTime = moment(selectedAvailability.endTime, ['HH:mm:ss', 'HH:mm']).format('HH:mm');
+
+ const requestOptions = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ availabilityList: availabilitylist,
+ selectedDate: formatTimestampDate(timestamp),
+ selectedAvailability,
+ }),
+ };
+
+ const url = `${this.props.links.includeurl}/availability/conflicts/`;
+
+ fetch(url, requestOptions)
+ .then((res) => res.json())
+ .then(
+ (data) => {
+ console.log("Conflicts fetched successfully:", data);
+ this.setState({
+ conflictList: {
+ itemList: { ...data.conflictList },
+ conflictIdList: data.conflictIdList,
+ },
+ });
+ if (data.conflictIdList.length > 0) {
+ this.errorElement?.scrollIntoView();
+ }
+ },
+ (err) => {
+ console.error("Conflict fetch error:", err);
+ hideSpinner();
+ }
+ );
+ })
+ .catch((error) => {
+ console.warn("Validation failed. Conflict fetch aborted.", error);
+ });
+ }
+
renderTimeTable() {
const onSelect = data => {
this.onSelectAvailability(data)
@@ -586,41 +618,55 @@ class AvailabilityPage extends Component {
}
readCalculatedAvailabilityList() {
- $.ajax(`${this.props.links.includeurl}/availability/slots/`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- data: JSON.stringify({
- 'availabilityList': this.state.availabilitylist,
- 'busySlots': this.state.busyslots
- })
- }).done((responseData) => {
- let availabilityList = writeSlotCalculationIntoAvailability(
- this.state.availabilitylist,
- responseData['maxSlots'],
- responseData['busySlots']
- );
- this.setState({
- availabilitylistslices: availabilityList,
- maxWorkstationCount: parseInt(responseData['maxWorkstationCount']),
- })
- }).fail((err) => {
- if (err.status === 404) {
- console.log('404 error, ignored')
- } else {
- let isException = err.responseText.toLowerCase().includes('exception');
- if (err.status >= 500 && isException) {
- new ExceptionHandler($('.opened'), {
- code: err.status,
- message: err.responseText
+ this.getValidationList()
+ .then(() => {
+ const { availabilitylist, busyslots } = this.state;
+
+ console.log("Validation passed. Proceeding with /availability/slots/.");
+
+ $.ajax(`${this.props.links.includeurl}/availability/slots/`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ data: JSON.stringify({
+ availabilityList: availabilitylist,
+ busySlots: busyslots,
+ }),
+ })
+ .done((responseData) => {
+ console.log("Slots fetched successfully:", responseData);
+ const availabilityList = writeSlotCalculationIntoAvailability(
+ this.state.availabilitylist,
+ responseData['maxSlots'],
+ responseData['busySlots']
+ );
+ this.setState({
+ availabilitylistslices: availabilityList,
+ maxWorkstationCount: parseInt(responseData['maxWorkstationCount']),
});
- } else {
- console.log('reading calculated availability list error', err);
- }
- hideSpinner();
- }
- })
- }
-
+ })
+ .fail((err) => {
+ console.error("Error during /availability/slots/ fetch:", err);
+ if (err.status === 404) {
+ console.log("404 error ignored.");
+ } else {
+ const isException = err.responseText.toLowerCase().includes("exception");
+ if (err.status >= 500 && isException) {
+ new ExceptionHandler($(".opened"), {
+ code: err.status,
+ message: err.responseText,
+ });
+ }
+ }
+ hideSpinner();
+ });
+ })
+ .catch((error) => {
+ console.warn("Validation failed. Slot calculation fetch aborted.", error);
+ this.setState({ errorList: error });
+ this.errorElement?.scrollIntoView();
+ });
+ }
+
handleChange(data) {
if (data.__modified) {
clearTimeout(this.timer)
diff --git a/zmsentities/src/Zmsentities/Availability.php b/zmsentities/src/Zmsentities/Availability.php
index 5561db327..2dd33f895 100644
--- a/zmsentities/src/Zmsentities/Availability.php
+++ b/zmsentities/src/Zmsentities/Availability.php
@@ -450,8 +450,6 @@ public function hasDateBetween(\DateTimeInterface $startTime, \DateTimeInterface
} while ($startTime->getTimestamp() <= $stopTime->getTimestamp());
return false;
}
-
-
public function validateStartTime(\DateTimeInterface $today, \DateTimeInterface $tomorrow, \DateTimeInterface $startDate, \DateTimeInterface $endDate, \DateTimeInterface $selectedDate, String $kind)
{
$errorList = [];
@@ -482,9 +480,7 @@ public function validateStartTime(\DateTimeInterface $today, \DateTimeInterface
}
return $errorList;
- }
-
-
+ }
public function validateEndTime(\DateTimeInterface $today, \DateTimeInterface $yesterday, \DateTimeInterface $startDate, \DateTimeInterface $endDate, \DateTimeInterface $selectedDate)
{
$errorList = [];
@@ -497,8 +493,7 @@ public function validateEndTime(\DateTimeInterface $today, \DateTimeInterface $y
$dayMinutesEnd = ($endHour * 60) + $endMinute;
$startTimestamp = $startDate->getTimestamp();
$endTimestamp = $endDate->getTimestamp();
-
- // Check if end time is before start time
+
if ($dayMinutesEnd <= $dayMinutesStart) {
$errorList[] = [
'type' => 'endTime',
@@ -514,7 +509,6 @@ public function validateEndTime(\DateTimeInterface $today, \DateTimeInterface $y
return $errorList;
}
-
public function validateOriginEndTime(\DateTimeInterface $today, \DateTimeInterface $yesterday, \DateTimeInterface $startDate, \DateTimeInterface $endDate, \DateTimeInterface $selectedDate, String $kind)
{
$errorList = [];