add prefiltering on validity period form component

This commit is contained in:
Ivymaster
2026-04-24 14:08:11 +02:00
parent 869a7e3335
commit 7307c1fa44
20 changed files with 1181 additions and 282 deletions
+22 -2
View File
@@ -11,7 +11,7 @@
}
.fhc-resize-vertical {
cursor: ns-resize;
cursor: ns-resize;
}
.fhc-drag-handle:hover {
@@ -21,4 +21,24 @@
.fhc-w-fit {
width: fit-content;
}
}
.tabulator-group-level-0 {
background-color: #c6c6c6 !important;
display: flex;
align-items: center;
}
.tabulator-group-level-1 {
background-color: #f5f5f5 !important;
display: flex;
align-items: center;
}
.tabulator-cell {
background-color: transparent !important;
}
div[role="row"] {
background-color: white;
}
+6
View File
@@ -22,6 +22,12 @@ export default {
url: "/api/frontend/v1/ClassScheduleApi/getAllClassTimeValidityPeriods",
};
},
getAllClassTimeValidityPeriodsPerOrganizationalUnit(organizationUnitId) {
return {
method: "get",
url: `/api/frontend/v1/ClassScheduleApi/getAllClassTimeValidityPeriodsPerOrganizationalUnit/${organizationUnitId}`,
};
},
getClassTimeValidityPeriod(classTimeSlotValidityPeriodId) {
return {
method: "get",
+24 -9
View File
@@ -5,12 +5,27 @@ export default {
url: "api/frontend/v1/organisation/studienplan/getAllStudyPlans",
};
},
getStudienplaeneBySemester(studiengang_kz, studiensemester_kurzbz, ausbildungssemester, orgform_kurzbz)
{
return {
method: 'get',
url: 'api/frontend/v1/organisation/studienplan/getBySemester',
params: { studiengang_kz, studiensemester_kurzbz, ausbildungssemester, orgform_kurzbz },
};
}
}
getStudyPlansByOrganizationalUnitAndSemesterDates(organizationalUnitShortCode, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studienplan/getStudyPlansByOrganizationalUnitAndSemesterDates/${organizationalUnitShortCode}?filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
getStudienplaeneBySemester(
studiengang_kz,
studiensemester_kurzbz,
ausbildungssemester,
orgform_kurzbz,
) {
return {
method: "get",
url: "api/frontend/v1/organisation/studienplan/getBySemester",
params: {
studiengang_kz,
studiensemester_kurzbz,
ausbildungssemester,
orgform_kurzbz,
},
};
},
};
+13 -1
View File
@@ -29,5 +29,17 @@ export default {
url: 'api/frontend/v1/organisation/studiensemester/getAll',
params: { order, start }
};
}
},
getStudySemestersByOrganizationalUnitAndDates(organizationalUnitShortCode, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studiensemester/getStudySemestersByOrganizationalUnitAndDates/${organizationalUnitShortCode}?filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
getStudySemestersByStudyPlanAndDates(studyPlanId, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studiensemester/getStudySemestersByStudyPlanAndDates?filter[studyPlanId]=${studyPlanId}&filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
};
+14
View File
@@ -17,6 +17,7 @@
import ClassScheduleOverview from "../../components/ClassSchedule/ClassScheduleOverview.js";
import ClassScheduleValidityPeriodOverview from "../../components/ClassSchedule/ClassScheduleValidityPeriodOverview.js";
import ClassScheduleOrgUnitGroupedValidityPeriodsOverview from "../../components/ClassSchedule/ClassScheduleOrgUnitGroupedValidityPeriodsOverview.js";
import FhcAlert from "../../plugins/FhcAlert.js";
import Phrasen from "../../plugins/Phrasen.js";
@@ -34,6 +35,18 @@ const router = VueRouter.createRouter({
path: `/${ciPath}/lehre/ClassSchedule`,
component: ClassScheduleOverview,
},
{
name: "validityPeriodsOverviewByOrganizationUnit",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/groupedOverviewByOrganizationUnit/:organizationalUnitShortCode`,
component: ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
params: true,
},
{
name: "validityPeriodsOverviewByOrganizationUnitAndStudyPlan",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/groupedOverviewByOrganizationUnit/:organizationalUnitShortCode/studyPlan/:studyPlanId`,
component: ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
params: true,
},
{
name: "validityPeriodOverview",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/:classTimeSlotValidityPeriodId`,
@@ -47,6 +60,7 @@ const app = Vue.createApp({
components: {
ClassScheduleOverview,
ClassScheduleValidityPeriodOverview,
ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
},
});
@@ -1,12 +1,8 @@
import draggable from "../../directives/draggable.js";
import drop from "../../directives/drop.js";
import FormInput from "../Form/Input.js";
export default {
name: "ClassScheduleCalendarSelector",
components: {
FormInput,
},
directives: {
draggable,
drop,
@@ -30,91 +26,91 @@ export default {
emits: ["overlaysChanged"],
watch: {
editedOverlays: {
handler(newVal) {
setTimeout(() => {
this.overlays = [];
async handler(newVal) {
await this.$nextTick();
this.$refs.calendarContainer
.querySelectorAll("div[id^='overlay-']")
.forEach((element) => {
element.remove();
});
newVal
.map((slot) => {
return {
...slot,
startingTimeSlot: this.timeSlotsInDay.find((timeSlot) =>
timeSlot.startsWith(slot.startTime.substr(0, 5)),
),
endingTimeSlot: this.timeSlotsInDay.find((timeSlot) =>
timeSlot.endsWith(slot.endTime.substr(0, 5)),
),
};
})
.forEach((overlay) => {
let firstElementDataNumber =
(overlay.weekday - 1) * this.timeSlotsInDay.length +
this.timeSlotsInDay.indexOf(overlay.startingTimeSlot);
let lastElementDataNumber =
(overlay.weekday - 1) * this.timeSlotsInDay.length +
this.timeSlotsInDay.indexOf(overlay.endingTimeSlot);
let firstSelectedElement =
this.$refs.calendarSelectorContainer.querySelector(
"div[data-number='" + firstElementDataNumber + "']",
);
let lastSelectedElement =
this.$refs.calendarSelectorContainer.querySelector(
"div[data-number='" + lastElementDataNumber + "']",
);
if (!firstSelectedElement || !lastSelectedElement) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotLoadingErrorMessage"),
);
return;
}
firstSelectedElement.style.backgroundColor =
this.selectedTimeSlotLabelColor;
lastSelectedElement.style.backgroundColor =
this.selectedTimeSlotLabelColor;
this.currentFirstSelectedElementNumber = firstSelectedElement
? parseInt(firstSelectedElement.getAttribute("data-number"))
: null;
this.currentLastSelectedElementNumber = lastSelectedElement
? parseInt(lastSelectedElement.getAttribute("data-number"))
: null;
this.createOverlay();
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
.forEach((child) => {
child.style.backgroundColor = this.defaultTimeSlotLabelColor;
});
this.currentFirstSelectedElementNumber = null;
this.currentLastSelectedElementNumber = null;
});
this.overlays = this.overlays.map((existingOverlay, index) => {
let existingOverlayInNewVal = newVal[index];
existingOverlay.databaseId = existingOverlayInNewVal.databaseId;
existingOverlay.type = existingOverlayInNewVal.type;
existingOverlay.hexColor =
this.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz ===
existingOverlayInNewVal.type,
)?.hintergrundfarbe || null;
return {
...existingOverlay,
};
this.overlays = [];
await this.$nextTick();
this.$refs.calendarContainer
.querySelectorAll("div[id^='overlay-']")
.forEach((element) => {
element.remove();
});
}, 500);
newVal
.map((slot) => {
return {
...slot,
startingTimeSlot: this.timeSlotsInDay.find((timeSlot) =>
timeSlot.startsWith(slot.startTime.substr(0, 5)),
),
endingTimeSlot: this.timeSlotsInDay.find((timeSlot) =>
timeSlot.endsWith(slot.endTime.substr(0, 5)),
),
};
})
.forEach((overlay) => {
let firstElementDataNumber =
(overlay.weekday - 1) * this.timeSlotsInDay.length +
this.timeSlotsInDay.indexOf(overlay.startingTimeSlot);
let lastElementDataNumber =
(overlay.weekday - 1) * this.timeSlotsInDay.length +
this.timeSlotsInDay.indexOf(overlay.endingTimeSlot);
let firstSelectedElement =
this.$refs.calendarSelectorContainer.querySelector(
"div[data-number='" + firstElementDataNumber + "']",
);
let lastSelectedElement =
this.$refs.calendarSelectorContainer.querySelector(
"div[data-number='" + lastElementDataNumber + "']",
);
if (!firstSelectedElement || !lastSelectedElement) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotLoadingErrorMessage"),
);
return;
}
firstSelectedElement.style.backgroundColor =
this.selectedTimeSlotLabelColor;
lastSelectedElement.style.backgroundColor =
this.selectedTimeSlotLabelColor;
this.currentFirstSelectedElementNumber = firstSelectedElement
? parseInt(firstSelectedElement.getAttribute("data-number"))
: null;
this.currentLastSelectedElementNumber = lastSelectedElement
? parseInt(lastSelectedElement.getAttribute("data-number"))
: null;
this.createOverlay();
this.$refs.calendarSelectorContainer
.querySelectorAll("div[class*='part-body']")
.forEach((child) => {
child.style.backgroundColor = this.defaultTimeSlotLabelColor;
});
this.currentFirstSelectedElementNumber = null;
this.currentLastSelectedElementNumber = null;
});
this.overlays = this.overlays.map((existingOverlay, index) => {
let existingOverlayInNewVal = newVal[index];
existingOverlay.databaseId = existingOverlayInNewVal.databaseId;
existingOverlay.type = existingOverlayInNewVal.type;
existingOverlay.hexColor =
this.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz ===
existingOverlayInNewVal.type,
)?.hintergrundfarbe || null;
return {
...existingOverlay,
};
});
},
deep: true,
immediate: true,
@@ -129,13 +125,13 @@ export default {
data() {
return {
daysInWeek: [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
this.$p.t("ui", "monday"),
this.$p.t("ui", "tuesday"),
this.$p.t("ui", "wednesday"),
this.$p.t("ui", "thursday"),
this.$p.t("ui", "friday"),
this.$p.t("ui", "saturday"),
this.$p.t("ui", "sunday"),
],
timeSlotsInDay: [
"08:00-08:45",
@@ -173,6 +169,8 @@ export default {
x: null,
y: null,
},
currentlyEditedOverlayId: null,
visiblePopover: null,
};
},
computed: {
@@ -209,15 +207,9 @@ export default {
createOverlay() {
let overlayElement;
if (!this.$props.isPreviewMode) {
overlayElement = this.$refs.calendarSelectorContainer.querySelector(
"#overlays-container",
).children[0];
} else {
overlayElement = this.$refs.calendarSelectorContainer.querySelector(
"#overlays-container-preview",
).children[0];
}
overlayElement = this.$refs.calendarSelectorContainer.querySelector(
"#overlays-container",
).children[0];
let firstSelectedChild =
this.$refs.calendarSelectorContainer.querySelector(
@@ -709,11 +701,15 @@ export default {
let dropzoneItem = event.target;
if (!dropzoneItem) return;
if (!dropzoneItem.getAttribute("data-time")) {
let dropzoneOverlay = this.overlays.find((overlay) => overlay.id === dropzoneItem.id);
let dropzoneOverlay = this.overlays.find(
(overlay) => overlay.id === dropzoneItem.id,
);
if (!dropzoneOverlay) {
console.error("Could not find overlay for dropzone item with id " + dropzoneItem.id);
console.error(
"Could not find overlay for dropzone item with id " +
dropzoneItem.id,
);
return;
}
@@ -726,7 +722,7 @@ export default {
let startElementNumber = dropzoneOverlay.startingTimeSlotElementNumber;
let startElement = this.$refs.calendarSelectorContainer.querySelector(
`[data-number='${startElementNumber}']`
`[data-number='${startElementNumber}']`,
);
// get mouse position
@@ -742,9 +738,10 @@ export default {
// add delta Y to top of the start element to get new top for the dropzone item
const newTop = startElementTop + deltaY;
//find which item has the closest top to the new top and get its data-number attribute
const partBodies = this.$refs.calendarSelectorContainer.querySelectorAll(
"div[data-weekday='" + dropzoneOverlay.weekday + "']",
);
const partBodies =
this.$refs.calendarSelectorContainer.querySelectorAll(
"div[data-weekday='" + dropzoneOverlay.weekday + "']",
);
// see which item has the new top in between its top and bottom and get its data-number attribute
let closestPartBody = null;
@@ -756,7 +753,7 @@ export default {
});
if (!closestPartBody) return;
dropzoneItem = closestPartBody;
dropzoneItem = closestPartBody;
}
let newStartTimeSlot =
@@ -1351,6 +1348,101 @@ export default {
overlay.endingTimeSlotElementNumber
);
},
getOverlayClassScheduleTypeTitle(overlayId) {
let overlay = this.overlays.find((overlay) => overlay.id === overlayId);
if (!overlay) return "";
let typeDescriptions =
this.classTimeSlotTypes.find(
(type) => type.unterrichtszeitentyp_kurzbz === overlay.type,
)?.bezeichnung_mehrsprachig || "";
if (!typeDescriptions) return "";
return typeDescriptions[0].value || "";
},
handleChangeClassTimeSlotTypeForOverlay(newType) {
let classTimeSlotType = this.classTimeSlotTypes.find(
(type) => type.unterrichtszeitentyp_kurzbz === newType,
);
if (!classTimeSlotType) {
console.error(
"Could not find class time slot type for newType: " + newType,
);
return;
}
this.overlays = this.overlays.map((overlay) => {
if (overlay.id === this.currentlyEditedOverlayId) {
return {
...overlay,
type: classTimeSlotType.unterrichtszeitentyp_kurzbz,
hexColor: classTimeSlotType.hintergrundfarbe,
};
}
return overlay;
});
this.currentlyEditedOverlayId = null;
},
showOverlayClassTimeTypePopover(overlayId) {
let overlayElement = this.$refs.calendarSelectorContainer.querySelector(
`#${overlayId}`,
);
if (!overlayElement) return;
if (this.visiblePopover) {
this.visiblePopover.dispose();
this.visiblePopover = null;
return;
}
this.visiblePopover = new bootstrap.Popover(overlayElement, {
title: this.$p.t("ui", "classTimeSlotType"),
html: true,
content: this.$refs.classScheduleTypeSelectorContainer.innerHTML,
placement: "left",
});
this.visiblePopover.show();
setTimeout(() => {
document
.querySelectorAll(".class-schedule-type-selector-option")
.forEach((option) => {
option.addEventListener("click", (event) => {
let selectedTypeDescription = event.currentTarget.innerText;
let selectedType = this.classTimeSlotTypes.find((type) =>
type.bezeichnung_mehrsprachig.some(
(desc) => desc.value === selectedTypeDescription,
),
);
if (!selectedType) {
console.error(
"Could not find class time slot type for selected description: " +
selectedTypeDescription,
);
return;
}
this.overlays = this.overlays.map((overlay) => {
if (overlay.id === overlayId) {
return {
...overlay,
type: selectedType.unterrichtszeitentyp_kurzbz,
hexColor: selectedType.hintergrundfarbe,
};
}
return overlay;
});
this.visiblePopover.dispose();
this.visiblePopover = null;
});
});
}, 10);
},
},
unmounted() {
this.visiblePopover?.dispose();
this.visiblePopover = null;
},
template: /*html*/ `
<div ref="calendarSelectorContainer" >
@@ -1601,21 +1693,34 @@ export default {
</div>
</div>
</div>
<div v-if="!isPreviewMode" id="overlays-container" class='d-none'>
<div id="overlays-container" class='d-none'>
<div
v-for="(index) in 50"
v-draggable:copyLink.noimage="selectedDragObject"
@mousedown='overlaySelectionChanged($event, "overlay-item-" + index)'
@mouseover="handleMouseOverOnOverlay('overlay-item-' + index)"
:id="'overlay-item-' + index"
:class="{
'fhc-drag-handle': !$props.isPreviewMode,
}"
:style="{ backgroundColor: this.overlays.find(overlay => overlay.id === 'overlay-item-' + index)?.hexColor || this.defaultOverlayColor }"
class="d-none fhc-pointer-events-all flex-column justify-content-between align-items-center shadow rounded-1 fhc-drag-handle"
draggable='true'
:title="getOverlayClassScheduleTypeTitle('overlay-item-' + index)"
class="d-none fhc-pointer-events-all flex-column justify-content-between align-items-center shadow rounded-1"
:draggable='!$props.isPreviewMode ? "true" : "false"'
>
<div
class="d-flex flex-column justify-content-center align-items-center gap-1 p-2 overflow-scroll"
>
<a
v-if="!$props.isPreviewMode"
@mousedown.stop="showOverlayClassTimeTypePopover('overlay-item-' + index)"
:title="$p.t('ui', 'bearbeiten')"
class="position-absolute top-0 start-0 p-1 d-flex gap-1 fhc-cursor-pointer"
>
<i class="fa fa-edit text-primary fs-6"></i>
</a>
<a
v-if="!$props.isPreviewMode"
@mousedown.stop="deleteOverlay('overlay-item-' + index)"
:title="$p.t('global', 'loeschen')"
class="position-absolute top-0 end-0 p-1 d-flex gap-1 fhc-cursor-pointer"
@@ -1623,43 +1728,20 @@ export default {
<i class="fa fa-trash text-danger fs-6"></i>
</a>
<p
v-if="!isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-' + index))"
:style="'color: ' + this.overlayTextColor"
class="bg-transparent rounded-1 py-0 m-0 "
>
{{ getOverlayTimeSlotSpan("overlay-item-" + index) }}
</p>
<div v-if="this.overlays.length">
<form-input
:modelValue="this.overlays.find(overlay => overlay.id === 'overlay-item-' + index)?.type"
@mousedown.stop
@change="(event) => {
let overlay = this.overlays.find(overlay => overlay.id === 'overlay-item-' + index);
if (!overlay) return;
overlay.type = event.target.value;
overlay.hexColor = this.classTimeSlotTypes.find(type => type.unterrichtszeitentyp_kurzbz === event.target.value)?.hintergrundfarbe;
}"
:class="{
'w-75': isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-' + index)),
}"
type="select"
name="classTimeSlotType"
class="p-0 m-0 fs-6"
>
<option
v-for="type in $props.classTimeSlotTypes"
:key="type.unterrichtszeitentyp_kurzbz"
:value="type.unterrichtszeitentyp_kurzbz"
selected
>
{{type.unterrichtszeitentyp_kurzbz}}
</option>
</form-input>
</div>
<span
v-if="!isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-' + index))"
class="badge badge-pill bg-light text-dark"
>
{{ this.getOverlayClassScheduleTypeTitle('overlay-item-' + index) }}
</span>
</div>
<span
<span
v-if="!$props.isPreviewMode"
@mousedown.stop="handleMouseDownOnResizer($event, 'overlay-item-resizer-' + index, 'overlay-item-' + index)"
:id="'overlay-item-resizer-' + index"
:class="{
@@ -1671,34 +1753,17 @@ export default {
</span>
</div>
</div>
<div v-if="isPreviewMode" id="overlays-container-preview" class='d-none'>
<div
v-for="(index) in 50"
:id="'overlay-item-preview-' + index"
:style="{ backgroundColor: this.overlays.find(overlay => overlay.id === 'overlay-item-preview-' + index)?.hexColor || this.defaultOverlayColor }"
:class="isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-preview-' + index)) ? 'justify-content-center' : 'justify-content-between'"
class="d-flex flex-column align-items-center shadow rounded-1 "
<div ref="classScheduleTypeSelectorContainer" class="d-none">
<div class='d-flex flex-column gap-2'>
<span v-for="(type, index) in classTimeSlotTypes"
:key="index"
:data-type="type.unterrichtszeitentyp_kurzbz"
class="btn btn-sm btn-outline-dark class-schedule-type-selector-option"
>
<div
:class="{
'p-2': !isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-preview-' + index)),
}"
class="d-flex flex-column justify-content-center align-items-center gap-0"
>
<span
v-if="this.$props.isPreviewMode"
class="badge badge-pill bg-light text-dark"
>
{{ this.overlays.find(overlay => overlay.id === 'overlay-item-preview-' + index)?.type }}
</span>
<p
v-if="!isOverlayMinimallySized(this.overlays.find(overlay => overlay.id === 'overlay-item-preview-' + index))"
:style="'color: ' + this.overlayTextColor"
class="bg-transparent rounded-1 py-0 m-0 "
>{{ getOverlayTimeSlotSpan("overlay-item-preview-" + index) }}</p>
</div>
{{ type.bezeichnung_mehrsprachig[0].value }}
</span>
</div>
</div>
</div>
</div>
`,
};
@@ -0,0 +1,115 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ClassScheduleValidityPeriodPreview from "./ClassScheduleValidityPeriodPreview.js";
export default {
name: "ClassScheduleOrgUnitGroupedValidityPeriodsOverview",
components: {
ClassScheduleValidityPeriodPreview,
},
data: () => {
return {
organizationalUnitShortCode: null,
studyPlanId: null,
classScheduleValidityPeriods: [],
classScheduleTimeSlots: [],
classTimeSlotTypes: [],
};
},
computed: {},
methods: {},
async created() {
this.organizationalUnitShortCode = this.$route.params.organizationalUnitShortCode;
this.studyPlanId = parseInt(this.$route.params.studyPlanId);
let getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse =
await this.$api.call(
ApiClassSchedule.getAllClassTimeValidityPeriodsPerOrganizationalUnit(
this.organizationalUnitShortCode,
),
);
if (
getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse.meta
.status === "success"
) {
this.classScheduleValidityPeriods =
getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse.data.filter(
(period) => {
if (!this.studyPlanId) return true;
return period.studienplan_id === this.studyPlanId;
}
);
} else {
console.error(
"Error fetching class time slot validity periods:",
getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse.meta
.message,
);
}
for (let validityPeriod of this.classScheduleValidityPeriods) {
if (this.studyPlanId && validityPeriod.studienplan_id !== this.studyPlanId) {
continue;
}
let getClassTimeSlotsForValidityPeriodResponse = await this.$api.call(
ApiClassSchedule.getClassTimeSlotsForValidityPeriod(
validityPeriod.unterrichtszeitengueltigkeit_id,
),
);
if (
getClassTimeSlotsForValidityPeriodResponse.meta.status === "success"
) {
this.classScheduleTimeSlots = [
...this.classScheduleTimeSlots,
...getClassTimeSlotsForValidityPeriodResponse.data,
];
} else {
console.error(
`Error fetching class time slots for validity period ${validityPeriod.unterrichtszeitengueltigkeit_id}:`,
getClassTimeSlotsForValidityPeriodResponse.meta.message,
);
}
}
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
);
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [];
for (let item of type.bezeichnung_mehrsprachig) {
let [lang, value] = item.split(":");
descriptions.push({ lang, value });
}
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
console.error(
"Error fetching class time slot types:",
getAllClassTimeSlotTypesResponse.meta.message,
);
}
},
template: /* html */ `
<div class="container mt-4">
<div class="my-2">
<h2 v-if="!this.studyPlanId">{{ $p.t("ui", "classScheduleOrgUnitGroupedValidityPeriodsOverviewTitle") }}</h2>
<h2 v-else>{{ $p.t("ui", "classScheduleStudyPlanGroupedValidityPeriodsOverviewTitle") }}</h2>
</div>
<class-schedule-validity-period-preview
v-for="validityPeriod in classScheduleValidityPeriods"
:key="validityPeriod.unterrichtszeitengueltigkeit"
:class-schedule-validity-period="validityPeriod"
:class-time-slots="classScheduleTimeSlots.filter(slot => slot.unterrichtszeitengueltigkeit_id === validityPeriod.unterrichtszeitengueltigkeit_id)"
:class-time-slot-types="classTimeSlotTypes"
class="shadow"
/>
</div>
`,
};
@@ -38,34 +38,48 @@ export default {
tabulatorOptions() {
const options = {
ajaxURL: "dummy",
ajaxRequestFunc: () =>
this.$api.call(ApiClassSchedule.getAllClassTimeValidityPeriods()),
ajaxResponse: (url, params, response) => response.data,
ajaxRequestFunc: async () =>
await this.getParsedClassTimeSlotValidityPeriodData(),
ajaxResponse: (url, params, response) => response,
persistenceID: "core_class_schedule_validity_periods",
selectableRows: true,
columns: [
{
title: "gueltig von",
field: "gueltig_von",
title: this.$p.t("ui", "zeitraum"),
formatter: (cell, formatterParams, onRendered) => {
const data = cell.getData();
const validFrom = new Date(data.gueltig_von).toLocaleDateString(
"de-AT",
{
day: "2-digit",
month: "2-digit",
year: "numeric",
},
);
data.gueltig_von;
const validTo = new Date(data.gueltig_bis).toLocaleDateString(
"de-AT",
{
day: "2-digit",
month: "2-digit",
year: "numeric",
},
);
return `${validFrom ? validFrom : "?"} - ${validTo ? validTo : "?"}`;
},
},
{
title: this.$p.t("global", "typ"),
field: "unterrichtszeiten_typ_bezeichnung_mehrsprachig",
width: 150,
},
{ title: "gueltig bis", field: "gueltig_bis", width: 150 },
{ title: "orgform kurzbz", field: "orgform_kurzbz", width: 150 },
{
title: "ausbildungssemester",
title: this.$p.t("lehre", "sem"),
field: "ausbildungssemester",
width: 150,
},
{
title: "typ",
field: "unterrichtszeitentyp_kurzbz",
},
{
field: "unterrichtszeitengueltigkeit_id",
visible: false,
},
{
title: "Aktionen",
title: this.$p.t("global", "actions"),
field: "actions",
minWidth: 150,
maxWidth: 150,
@@ -135,38 +149,102 @@ export default {
frozen: true,
},
],
groupBy: [
"organisationseinheit_bezeichnung_extended",
"studienplan_bezeichnung",
],
groupHeader: [
function (value, count, data) {
let container = document.createElement("span");
container.className =
"d-flex align-items-center justify-content-between";
container.style.display = "inline-block";
container.style.width = "100%";
let label = document.createElement("span");
label.textContent =
value + " (" + count + " item" + (count > 1 ? "s" : "") + ")";
container.append(label);
let button = document.createElement("button");
button.className =
"btn btn-sm btn-outline-secondary fhc-btn-for-org-unit-grouping";
button.style.marginLeft = "10px";
button.innerHTML = '<i class="fa fa-eye"></i>';
button.dataset.organizationalUnitShortCode = data[0].oe_kurzbz;
container.append(button);
return container;
},
function (value, count, data) {
let container = document.createElement("span");
container.className =
"d-flex align-items-center justify-content-between";
container.style.display = "inline-block";
container.style.width = "100%";
let label = document.createElement("span");
label.textContent =
value + " (" + count + " item" + (count > 1 ? "s" : "") + ")";
container.append(label);
let button = document.createElement("button");
button.className =
"btn btn-sm btn-outline-secondary fhc-btn-for-org-unit-and-study-plan-grouping";
button.style.marginLeft = "10px";
button.innerHTML = '<i class="fa fa-eye"></i>';
button.title = 22;
button.dataset.organizationalUnitShortCode = data[0].oe_kurzbz;
button.dataset.studyPlanId = data[0].studienplan_id;
container.append(button);
return container;
},
],
};
return options;
},
tabulatorEvents() {
const events = [
{
event: "tableBuilt",
event: "renderComplete",
handler: async () => {
const setHeader = (field, text) => {
const col =
this.$refs.classTimeSlotValidityPeriodsTable.tabulator.getColumn(
field,
);
if (!col) return;
document
.querySelectorAll(".fhc-btn-for-org-unit-grouping")
.forEach((button) => {
button.addEventListener("click", (e) => {
let organizationalUnitShortCode =
button.dataset.organizationalUnitShortCode;
const el = col.getElement();
if (!el || !el.querySelector) return;
this.$router.push({
name: "validityPeriodsOverviewByOrganizationUnit",
params: {
organizationalUnitShortCode,
},
});
});
});
const titleEl = el.querySelector(".tabulator-col-title");
if (titleEl) {
titleEl.textContent = text;
}
};
setHeader("nummer", this.$p.t("wawi", "nummer"));
setHeader("anmerkung", this.$p.t("global", "anmerkung"));
setHeader("retouram", this.$p.t("wawi", "retourdatum"));
setHeader("beschreibung", this.$p.t("global", "beschreibung"));
setHeader("kaution", this.$p.t("infocenter", "kaution"));
setHeader("ausgegebenam", this.$p.t("wawi", "ausgabedatum"));
setHeader("person_id", this.$p.t("person", "person_id"));
setHeader("uid", this.$p.t("person", "uid"));
document
.querySelectorAll(".fhc-btn-for-org-unit-and-study-plan-grouping")
.forEach((button) => {
button.addEventListener("click", (e) => {
let organizationalUnitShortCode =
button.dataset.organizationalUnitShortCode;
let studyPlanId = button.dataset.studyPlanId;
this.$router.push({
name: "validityPeriodsOverviewByOrganizationUnitAndStudyPlan",
params: {
organizationalUnitShortCode,
studyPlanId,
},
});
});
});
},
},
];
@@ -174,6 +252,41 @@ export default {
},
},
methods: {
test() {
alert("test");
},
async getParsedClassTimeSlotValidityPeriodData() {
let getAllClassTimeValidityPeriodsResponse = await this.$api.call(
ApiClassSchedule.getAllClassTimeValidityPeriods(),
);
if (getAllClassTimeValidityPeriodsResponse.meta.status === "success") {
let generalWord = this.$p.t("ui", "general");
return getAllClassTimeValidityPeriodsResponse.data.map(
function (period) {
period.organisationseinheit_bezeichnung_extended =
period.organisationseinheit_bezeichnung +
" - " +
period.organisationseinheit_organisationseinheittyp_kurzbz;
if (!period.studienplan_bezeichnung) {
period.studienplan_bezeichnung = generalWord;
}
period.unterrichtszeiten_typ_bezeichnung_mehrsprachig =
period.unterrichtszeiten_typ_bezeichnung_mehrsprachig[0]?.split(
":",
)[1];
return {
...period,
};
},
);
} else {
console.error(
"Error fetching class time slot validity periods:",
getAllClassTimeValidityPeriodsResponse.meta.message,
);
}
},
showClassTimeSlotValidityPeriodModal() {
this.isClassTimeSlotValidityPeriodModalVisible = true;
},
@@ -227,12 +340,12 @@ export default {
},
mounted() {
this.$p
.loadCategory(["global", "lehre", "ui", "gruppenmanagement"])
.loadCategory(["global", "lehre", "ui", "gruppenmanagement", "core"])
.then(() => {
this.phrasesLoaded = true;
});
},
template: /* html */`
template: /* html */ `
<div class="container mt-4">
<h1 class='mb-5'>{{ $p.t("ui", "classScheduleOverviewHeading") }}</h1>
<div class="row mb-3">
@@ -265,7 +378,7 @@ export default {
table-only
:side-menu="false"
:tabulator-options="tabulatorOptions"
:tabulator-events="[{ event: 'cellEdited'}]"
:tabulator-events="tabulatorEvents"
:new-btn-label="$p.t('ui', 'addClassTimeSlotValidityPeriodButton')"
new-btn-show
reload
@@ -21,6 +21,7 @@ export default {
required: true,
},
},
emits: ["hideBsModal", "classTimeSlotTypeCreated", "classTimeSlotTypeUpdated"],
watch: {
isVisible(newValue) {
if (newValue) {
@@ -1,6 +1,8 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiStudienPlan from "../../../js/api/factory/studienplan.js";
import ApiStudienSemester from "../../../js/api/factory/studiensemester.js";
import ApiOrganizationalUnit from "../../../js/api/factory/organizationalUnit.js";
import { formatDate } from "../../helpers/DateHelpers.js";
import BsModal from "../Bootstrap/Modal.js";
import CoreForm from "../Form/Form.js";
@@ -66,6 +68,35 @@ export default {
this.$fhcAlert.handleSystemError(error);
});
},
async "classTimeSlotValidityPeriodFormData.organizationalUnitShortCode"(
newValue,
) {
if (!newValue) {
this.classTimeSlotValidityPeriodFormData.studyPlanId = null;
return;
}
this.refetchFilterableOptions();
},
async "classTimeSlotValidityPeriodFormData.validityPeriodFrom"(newValue) {
if (!newValue) {
this.classTimeSlotValidityPeriodFormData.studyPlanId = null;
return;
}
this.refetchFilterableOptions();
},
async "classTimeSlotValidityPeriodFormData.validityPeriodTo"(newValue) {
if (!newValue) {
this.classTimeSlotValidityPeriodFormData.studyPlanId = null;
return;
}
this.refetchFilterableOptions();
},
async "classTimeSlotValidityPeriodFormData.studyPlanId"(newValue) {
this.refetchFilterableOptions();
},
},
data: () => {
return {
@@ -73,6 +104,8 @@ export default {
isEditInProgress: false,
organizationalUnits: [],
studyPlans: [],
studySemesters: [],
studySemestersByNumber: [],
classTimeSlotTypes: [],
classTimeSlotValidityPeriodFormData: {
id: null,
@@ -86,7 +119,157 @@ export default {
},
};
},
computed: {
isStudyPlanSelectDisabled() {
return (
!this.classTimeSlotValidityPeriodFormData.organizationalUnitShortCode ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodTo
);
},
formattedValidityPeriodFrom() {
if (!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom)
return null;
return formatDate(
this.classTimeSlotValidityPeriodFormData.validityPeriodFrom,
);
},
formattedValidityPeriodTo() {
if (!this.classTimeSlotValidityPeriodFormData.validityPeriodTo)
return null;
return formatDate(
this.classTimeSlotValidityPeriodFormData.validityPeriodTo,
);
},
},
methods: {
async refetchFilterableOptions() {
this.studyPlans = await this.getTargetedStudyPlans(
this.classTimeSlotValidityPeriodFormData.organizationalUnitShortCode,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
let studySemestersByDates =
await this.getStudySemestersByOrganizationalUnitAndDates(
this.classTimeSlotValidityPeriodFormData.organizationalUnitShortCode,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
let studySemesters = new Array(
...new Set(
studySemestersByDates
.map((s) => s.semester_numbers)
.flat()
.sort((a, b) => a - b),
),
);
this.studySemestersByNumber = studySemesters;
if (this.classTimeSlotValidityPeriodFormData.studyPlanId) {
let studySemestersByStudyProgramId =
await this.getStudySemestersByStudyPlanAndDates(
this.classTimeSlotValidityPeriodFormData.studyPlanId,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
studySemestersByStudyProgramId = new Array(
...new Set(
studySemestersByStudyProgramId
.map((s) => s.semester_numbers)
.flat()
.sort((a, b) => a - b),
),
);
studySemesters = studySemestersByStudyProgramId;
}
this.studySemestersByNumber = studySemesters;
},
async getTargetedStudyPlans(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
) {
if (
!organizationalUnitShortCode ||
!validityPeriodFrom ||
!validityPeriodTo
) {
return [];
}
let getAllStudyPlansResponse = await this.$api.call(
ApiStudienPlan.getStudyPlansByOrganizationalUnitAndSemesterDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
),
);
if (getAllStudyPlansResponse.meta.status !== "success") {
console.error(
"Error fetching study plans:",
getAllStudyPlansResponse.meta.message,
);
}
return getAllStudyPlansResponse.data?.length
? getAllStudyPlansResponse.data
: [];
},
async getStudySemestersByOrganizationalUnitAndDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
) {
if (
!organizationalUnitShortCode ||
!validityPeriodFrom ||
!validityPeriodTo
) {
return [];
}
let getStudySemestersResponse = await this.$api.call(
ApiStudienSemester.getStudySemestersByOrganizationalUnitAndDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
),
);
if (getStudySemestersResponse.meta.status !== "success") {
console.error(
"Error fetching study semesters by organizational unit and dates:",
getStudySemestersResponse.meta.message,
);
}
return getStudySemestersResponse.data?.length
? getStudySemestersResponse.data
: [];
},
async getStudySemestersByStudyPlanAndDates(studyProgramId, validityPeriodFrom, validityPeriodTo) {
if (!studyProgramId || !validityPeriodFrom || !validityPeriodTo) {
return [];
}
let getStudySemestersResponse = await this.$api.call(
ApiStudienSemester.getStudySemestersByStudyPlanAndDates(studyProgramId, validityPeriodFrom, validityPeriodTo),
);
if (getStudySemestersResponse.meta.status !== "success") {
console.error(
"Error fetching study semesters:",
getStudySemestersResponse.meta.message,
);
}
return getStudySemestersResponse.data?.length
? getStudySemestersResponse.data
: [];
},
createClassTimeSlotValidityPeriod() {
return this.$refs.classTimeSlotValidityPeriodData
.call(
@@ -153,7 +336,9 @@ export default {
ApiOrganizationalUnit.getAllOrganizationalUnits(),
);
if (getAllOrganizationalUnitsResponse.meta.status === "success") {
this.organizationalUnits = getAllOrganizationalUnitsResponse.data;
this.organizationalUnits = getAllOrganizationalUnitsResponse.data.sort(
(a, b) => a.bezeichnung.localeCompare(b.bezeichnung),
);
} else {
console.error(
"Error fetching organizational units:",
@@ -197,7 +382,7 @@ export default {
);
}
},
template: `
template: /*html*/ `
<bs-modal ref="classTimeSlotValidityPeriodModal" @hideBsModal="() => { $emit('hideBsModal'); resetClassTimeSlotValidityPeriodModal(); }" size="md">
<template #title>
<p v-if="!classTimeSlotValidityPeriodFormData.id" class="fw-bold mt-3">{{$p.t('ui', 'addClassTimeSlotValidityPeriodModalTitle')}}</p>
@@ -207,33 +392,82 @@ export default {
<form-validation />
<div class="row mb-3">
<form-input
v-model="classTimeSlotValidityPeriodFormData.organizationalUnitShortCode"
:label="$p.t('lehre/organisationseinheit') + ' *'"
type="select"
name="organizationalUnitShortCode"
:label="$p.t('lehre/organisationseinheit') + ' *'"
v-model="classTimeSlotValidityPeriodFormData.organizationalUnitShortCode"
>
<option
v-for="organizationalUnit in organizationalUnits"
:key="organizationalUnit.oe_kurzbz"
:value="organizationalUnit.oe_kurzbz"
>
{{organizationalUnit.bezeichnung}}
{{organizationalUnit.bezeichnung}} - {{ organizationalUnit.organisationseinheittyp_kurzbz }}
</option>
</form-input>
</div>
<div class="row mb-3">
<div class="col-12 mb-3">
<label>{{$p.t('ui', 'validityPeriod')}}</label>
</div>
<div class="col">
<form-input
v-model="classTimeSlotValidityPeriodFormData.validityPeriodFrom"
:label="$p.t('ui/von') + ' *'"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodFrom"
format="dd.MM.yyyy"
auto-apply
/>
</div>
<div class="col">
<form-input
v-model="classTimeSlotValidityPeriodFormData.validityPeriodTo"
:label="$p.t('global/bis') + ' *'"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodTo"
format="dd.MM.yyyy"
auto-apply
/>
</div>
</div>
<div class="row mb-3">
<form-input
v-model="classTimeSlotValidityPeriodFormData.studyPlanId"
:label="$p.t('lehre/studienplan')"
:disabled="isStudyPlanSelectDisabled"
type="select"
name="studyPlan"
>
<option :value="null"> - </option>
<option
v-for="studyPlan in studyPlans"
:key="studyPlan.studienplan_id"
:value="studyPlan.studienplan_id"
>
{{studyPlan.bezeichnung}} - {{studyPlan.studienplan_id}}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="select"
name="studyPlan"
:label="$p.t('lehre/studienplan')"
v-model="classTimeSlotValidityPeriodFormData.studyPlanId"
id="ausbildungssemester"
name="semester"
:disabled="isStudyPlanSelectDisabled"
:label="$p.t('lehre', 'ausbildungssemester')+ '*'"
v-model="classTimeSlotValidityPeriodFormData.semester"
>
<option
v-for="studyPlan in studyPlans"
:key="studyPlan.studienplan_id"
:value="studyPlan.studienplan_id"
v-for="studySemester in studySemestersByNumber"
:key="studySemester"
:value="studySemester"
>
{{studyPlan.orgform_kurzbz}} - {{studyPlan.sprache}} - {{studyPlan.semesterwochen}}
{{studySemester}}
</option>
</form-input>
</div>
@@ -249,42 +483,10 @@ export default {
:key="classTimeSlotType.unterrichtszeitentyp_kurzbz"
:value="classTimeSlotType.unterrichtszeitentyp_kurzbz"
>
{{classTimeSlotType.unterrichtszeitentyp_kurzbz}} - {{classTimeSlotType.bezeichnung_mehrsprachig[0].value}} / ({{classTimeSlotType.bezeichnung_mehrsprachig[1].value}})
{{classTimeSlotType.bezeichnung_mehrsprachig[0].value}} / ({{classTimeSlotType.bezeichnung_mehrsprachig[1].value}})
</option>
</form-input>
</div>
<div class="row mb-3">
<div class="col-12 mb-3">
<label>{{$p.t('ui', 'validityPeriod')}}</label>
</div>
<div class="col">
<form-input
type="date"
name="validityPeriodFrom"
:label="$p.t('ui/von') + ' *'"
v-model="classTimeSlotValidityPeriodFormData.validityPeriodFrom"
/>
</div>
<div class="col">
<form-input
type="date"
name="validityPeriodTo"
:label="$p.t('global/bis') + ' *'"
v-model="classTimeSlotValidityPeriodFormData.validityPeriodTo"
/>
</div>
</div>
<div class="row mb-3">
<form-input
type="select"
id="ausbildungssemester"
name="semester"
:label="$p.t('lehre', 'ausbildungssemester')+ '*'"
v-model="classTimeSlotValidityPeriodFormData.semester"
>
<option v-for="sem in Array.from({length:8}).map((u,i) => i+1)" :key="sem" :value="sem">{{sem}}. Semester</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="textarea"
@@ -248,6 +248,7 @@ export default {
</div>
</div>
<h2>{{ classScheduleValidityPeriodStartDate }} - {{ classScheduleValidityPeriodEndDate }}</h2>
<p>{{ $p.t("lehre", "ausbildungssemester") }}: {{ classTimeSlotValidityPeriod?.ausbildungssemester }}</p>
<p v-html="classTimeSlotValidityPeriod?.anmerkung"></p>
</div>
<div>
@@ -0,0 +1,114 @@
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
export default {
name: "ClassScheduleValidityPeriodPreview",
components: {
ClassScheduleCalendarSelector,
},
props: {
classScheduleValidityPeriod: {
type: Object,
required: true,
},
classTimeSlots: {
type: Array,
required: true,
},
classTimeSlotTypes: {
type: Array,
required: true,
},
},
data: () => {
return {};
},
computed: {
classScheduleValidityPeriodStartDate() {
if (!this.$props.classScheduleValidityPeriod) return null;
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_von
.split("-")
.reverse();
return dateParts.join("/");
},
classScheduleValidityPeriodEndDate() {
if (!this.$props.classScheduleValidityPeriod) return null;
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_bis
.split("-")
.reverse();
return dateParts.join("/");
},
classScheduleValidityPeriodStudyPlan() {
return this.$props.classScheduleValidityPeriod.studienplan_bezeichnung;
},
parsedClassTimeSlots() {
let classTimeSlotsGroupedByWeek = [];
this.$props.classTimeSlots.forEach((slot) => {
let groupIdentifikator = slot["unterrichtszeit_gruppe_identifikator"];
let existingGroup = classTimeSlotsGroupedByWeek.find(
(group) => group.groupIdentifikator === groupIdentifikator,
);
if (existingGroup) {
existingGroup.slots.push(slot);
} else {
classTimeSlotsGroupedByWeek.push({
groupIdentifikator,
slots: [slot],
});
}
});
return classTimeSlotsGroupedByWeek;
},
},
methods: {
showClassScheduleValidityPeriod(classScheduleValidityPeriodId) {
this.$router.push({
name: "validityPeriodOverview",
params: {
classTimeSlotValidityPeriodId: classScheduleValidityPeriodId,
},
});
},
},
async created() {},
template: /* html */ `
<div class="container mt-4">
<div class='py-3 d-flex align-items-center justify-content-between'>
<div>
<h2>{{ classScheduleValidityPeriodStartDate }} - {{ classScheduleValidityPeriodEndDate }}</h2>
<h4>{{ classScheduleValidityPeriodStudyPlan }}</h4>
</div>
<a
@click="showClassScheduleValidityPeriod($props.classScheduleValidityPeriod.unterrichtszeitengueltigkeit_id)"
class="ml-auto btn btn-link"
><i class="fa fa-eye fs-5"></i></a>
</div>
<div class='py-3 mb-4'>
<div v-if="parsedClassTimeSlots && parsedClassTimeSlots.length > 0">
<div v-for="(classTimeSlotsPerWeek, index) in parsedClassTimeSlots" :key="index" class="row border-top rounded pt-1 pb-5">
<class-schedule-calendar-selector
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="classTimeSlotsPerWeek.slots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: slot.wochentag,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
};
})"
:isPreviewMode="true"
/>
</div>
</div>
<div v-else class="d-flex align-items-center justify-content-center border rounded p-2">
<p>{{ $p.t("ui", "noClassScheduleValidityPeriodTimeSlotsFound") }}</p>
</div>
</div>
</div>
`,
};
+1 -1
View File
@@ -37,7 +37,7 @@ export function formatDate(d) {
// parameter is of type Date
if(d instanceof Date)
{
if (isNaN(date.valueOf())) {
if (isNaN(d.valueOf())) {
return 'N/A';
}
// if the date is an invalid string then creating a date from the string will fail and N/A is returned