mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 16:44:28 +00:00
Merge branch 'master' into feature-61672/svnr_aus_infocenter_tool_entfernen
This commit is contained in:
@@ -95,10 +95,9 @@ var FHC_DialogLib = {
|
||||
*/
|
||||
_displayDialog: function(heading, message, icon, colorClass)
|
||||
{
|
||||
var html = "<p class='dialogmessage'><i class='glyphicon glyphicon-"+icon+"'></i> "+message+"</p>";
|
||||
var html = "<p class='dialogmessage'>"+message+"</p>";
|
||||
FHC_DialogLib.alertDefault(heading, html);
|
||||
$(".ui-dialog-titlebar").addClass(colorClass+" text-center");
|
||||
$(".glyphicon-"+icon).addClass(colorClass);
|
||||
$(".ui-dialog-titlebar").addClass(colorClass+" alert text-center");
|
||||
FHC_DialogLib._formatShortDialog();
|
||||
},
|
||||
|
||||
@@ -113,7 +112,6 @@ var FHC_DialogLib = {
|
||||
$(".ui-dialog-buttonpane.ui-widget-content").css("padding", ".3em .4em .5em .4em");
|
||||
$(".ui-dialog .ui-dialog-content").css("padding", "0");
|
||||
$(".ui-dialog-buttonset button").css("margin", "0");
|
||||
$(".dialogmessage i.glyphicon").css("background-color", "transparent");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -6,5 +6,10 @@ export default {
|
||||
{ }
|
||||
);
|
||||
},
|
||||
|
||||
getAuthInfo() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/AuthInfo/getAuthInfo',
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -21,5 +21,11 @@ export default {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/AuthInfo/getAuthUID'
|
||||
};
|
||||
},
|
||||
getAuthInfo() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/AuthInfo/getAuthInfo'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -16,10 +16,13 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAllBetriebsmittel(type, id) {
|
||||
getAllBetriebsmittel(type, id, betriebsmitteltypes) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id,
|
||||
params: {
|
||||
betriebsmitteltypes
|
||||
}
|
||||
};
|
||||
},
|
||||
addNewBetriebsmittel(person_id, params) {
|
||||
@@ -48,10 +51,13 @@ export default {
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/deleteBetriebsmittel/' + betriebsmittelperson_id
|
||||
};
|
||||
},
|
||||
getTypenBetriebsmittel() {
|
||||
getTypenBetriebsmittel(betriebsmitteltypes) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/'
|
||||
url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/',
|
||||
params: {
|
||||
betriebsmitteltypes
|
||||
}
|
||||
};
|
||||
},
|
||||
loadInventarliste(query) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getHeader(person_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vertraege/vertraege/getHeader/' + person_id,
|
||||
};
|
||||
},
|
||||
getPersonAbteilung(mitarbeiter_uid){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vertraege/vertraege/getPersonAbteilung/' + mitarbeiter_uid,
|
||||
};
|
||||
},
|
||||
getLeitungOrg(oekurzbz){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vertraege/vertraege/getLeitungOrg/' + oekurzbz,
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export default {
|
||||
getOrgHeads() {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/getOrgHeads';
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
getOrgetsForCompany(unternehmen) {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/getOrgetsForCompany'
|
||||
+ '/' + unternehmen;
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
getAllOrgUnits(filterStudent) {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/getAllOrgUnits';
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
getAllUserFunctions(mitarbeiter_uid) {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/getAllUserFunctions'
|
||||
+ '/' + mitarbeiter_uid;
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
getAllFunctions() {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/getAllFunctions';
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
addFunction(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/funktionen/Funktionen/insertFunction/',
|
||||
params
|
||||
};
|
||||
},
|
||||
loadFunction(benutzerfunktion_id) {
|
||||
var url = 'api/frontend/v1/funktionen/Funktionen/loadFunction'
|
||||
+ '/' + benutzerfunktion_id;
|
||||
return {
|
||||
method: 'get',
|
||||
url,
|
||||
};
|
||||
},
|
||||
updateFunction(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/funktionen/Funktionen/updateFunction/',
|
||||
params
|
||||
};
|
||||
},
|
||||
deleteFunction(benutzerfunktion_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/funktionen/Funktionen/deleteFunction/' + benutzerfunktion_id
|
||||
};
|
||||
},
|
||||
getOes(head, searchString) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/funktionen/Funktionen/searchOes/' + head + '/' + searchString
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAll() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/language/get'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -18,49 +18,79 @@
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/getRoomplan',
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/getRoomplan',
|
||||
params: { ort_kurzbz, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStundenplan(start_date, end_date, lv_id) {
|
||||
getLvPlan(start_date, end_date, lv_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/getStundenplan',
|
||||
url: '/api/frontend/v1/LvPlan/getLvPlan',
|
||||
params: { start_date, end_date, lv_id }
|
||||
};
|
||||
},
|
||||
eventsPersonal(start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lvPlan/eventsPersonal',
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
eventsLv(lv_id, start_date, end_date) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/lvPlan/eventsLv',
|
||||
params: { lv_id, start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStunden() {
|
||||
// TODO(chris): seems to be called from nowhere?
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/Stunden'
|
||||
url: '/api/frontend/v1/LvPlan/Stunden'
|
||||
};
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/Reservierungen/${ort_kurzbz}`,
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/LvPlan/getReservierungen/${ort_kurzbz}`,
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
getStundenplanReservierungen(start_date, end_date) {
|
||||
getLvPlanReservierungen(start_date, end_date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Stundenplan/Reservierungen',
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/getReservierungen',
|
||||
params: { start_date, end_date }
|
||||
};
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/getLehreinheitStudiensemester/${lehreinheit_id}`
|
||||
url: `/api/frontend/v1/LvPlan/getLehreinheitStudiensemester/${lehreinheit_id}`
|
||||
};
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Stundenplan/studiensemesterDateInterval/${date}`
|
||||
url: `/api/frontend/v1/LvPlan/studiensemesterDateInterval/${date}`
|
||||
};
|
||||
},
|
||||
LvPlanEvents(start_date, end_date, lv_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/LvPlan/LvPlanEvents',
|
||||
params: {
|
||||
start_date: start_date,
|
||||
end_date: end_date,
|
||||
lv_id: lv_id
|
||||
}
|
||||
};
|
||||
},
|
||||
getLv(lehrveranstaltung_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/LvPlan/getLv/' + lehrveranstaltung_id
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getMessages(params) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getMessages/'
|
||||
+ params.id + '/'
|
||||
+ params.type + '/'
|
||||
+ params.size + '/'
|
||||
+ params.page
|
||||
};
|
||||
},
|
||||
getVorlagen(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getVorlagen/'
|
||||
};
|
||||
},
|
||||
getMsgVarsLoggedInUser(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getMsgVarsLoggedInUser/'
|
||||
};
|
||||
},
|
||||
getMessageVarsPerson(userParams){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/getMessageVarsPerson/' + userParams.id + '/' + userParams.type_id
|
||||
};
|
||||
},
|
||||
getMsgVarsPrestudent(userParams){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/getMsgVarsPrestudent/' + userParams.id + '/' + userParams.type_id
|
||||
};
|
||||
},
|
||||
getPersonId(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/getPersonId/' + params.id + '/' + params.type_id
|
||||
};
|
||||
},
|
||||
getUid(userParams){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getUid/' + userParams.id + '/' + userParams.type_id
|
||||
};
|
||||
},
|
||||
getVorlagentext(vorlage_kurzbz){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getVorlagentext/' + vorlage_kurzbz
|
||||
};
|
||||
},
|
||||
getNameOfDefaultRecipient(params){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getNameOfDefaultRecipient/' + params.id + '/' + params.type_id
|
||||
};
|
||||
},
|
||||
getPreviewText(userParams, params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/getPreviewText/' + userParams.id + '/' + userParams.type_id,
|
||||
params
|
||||
};
|
||||
},
|
||||
getReplyData(messageId){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/messages/messages/getReplyData/' + messageId
|
||||
};
|
||||
},
|
||||
sendMessageFromModalContext(id, params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/sendMessage/' + id,
|
||||
params
|
||||
};
|
||||
},
|
||||
sendMessage(id, params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/sendMessage/' + id,
|
||||
params
|
||||
};
|
||||
},
|
||||
deleteMessage(messageId){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/messages/messages/deleteMessage/' + messageId
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -83,5 +83,11 @@ export default {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/notiz/notizPerson/isBerechtigt/'
|
||||
};
|
||||
},
|
||||
getCountNotes(person_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/notiz/notizPerson/getCountNotes/' + person_id
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,10 +16,15 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
getView(uid) {
|
||||
|
||||
profilViewData(uid) {
|
||||
let url = "/api/frontend/v1/Profil/profilViewData";
|
||||
if(uid){
|
||||
url += `/${uid}`;
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Profil/getView/${uid}`
|
||||
url: url
|
||||
};
|
||||
},
|
||||
fotoSperre(value) {
|
||||
@@ -69,5 +74,5 @@ export default {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Profil/getAllNationen'
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -61,6 +61,13 @@ export default {
|
||||
params: dms
|
||||
};
|
||||
},
|
||||
updateProfilbild(dms) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: `/api/frontend/v1/ProfilUpdate/updateProfilbild`,
|
||||
params: dms
|
||||
};
|
||||
},
|
||||
getProfilRequestFiles(requestID) {
|
||||
return {
|
||||
method: 'get',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
export default {
|
||||
|
||||
loadRenderers() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/RendererLoader/GetRenderers',
|
||||
params: {
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -22,5 +22,19 @@ export default {
|
||||
url: '/api/frontend/v1/searchbar/search',
|
||||
params
|
||||
};
|
||||
},
|
||||
searchCis(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/searchbar/searchCis',
|
||||
params
|
||||
};
|
||||
},
|
||||
searchStv(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/api/frontend/v1/searchbar/searchStv',
|
||||
params
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -29,6 +29,7 @@ import exam from './stv/exam.js';
|
||||
import abschlusspruefung from './stv/abschlusspruefung.js';
|
||||
import grades from './stv/grades.js';
|
||||
import mobility from './stv/mobility.js';
|
||||
import admissionDates from './stv/admissionDates.js';
|
||||
|
||||
export default {
|
||||
app,
|
||||
@@ -44,5 +45,6 @@ export default {
|
||||
exam,
|
||||
abschlusspruefung,
|
||||
grades,
|
||||
mobility
|
||||
mobility,
|
||||
admissionDates
|
||||
};
|
||||
@@ -105,7 +105,19 @@ export default {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/abschlusspruefung/checkForExistingExams/',
|
||||
params: { uid }
|
||||
params: { uids }
|
||||
};
|
||||
},
|
||||
getAllMitarbeiter(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/abschlusspruefung/getAllMitarbeiter/'
|
||||
};
|
||||
},
|
||||
getAllPersons(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/abschlusspruefung/getAllPersons/'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAufnahmetermine(person_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/getAufnahmetermine/' + person_id,
|
||||
};
|
||||
},
|
||||
getListPlacementTests(prestudent_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/getListPlacementTests/' + prestudent_id,
|
||||
};
|
||||
},
|
||||
getListStudyPlans(person_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/getListStudyPlans/' + person_id,
|
||||
};
|
||||
},
|
||||
addNewPlacementTest(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/insertAufnahmetermin/',
|
||||
params
|
||||
};
|
||||
},
|
||||
loadPlacementTest(rt_person_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/loadAufnahmetermin/' + rt_person_id,
|
||||
};
|
||||
},
|
||||
updatePlacementTest(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/updateAufnahmetermin/',
|
||||
params
|
||||
};
|
||||
},
|
||||
deletePlacementTest(rt_person_id){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/deleteAufnahmetermin/' + rt_person_id
|
||||
};
|
||||
},
|
||||
loadDataRtPrestudent(prestudent_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/loadDataRtPrestudent/' + prestudent_id,
|
||||
};
|
||||
},
|
||||
saveDataRtPrestudent(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/insertOrUpdateDataRtPrestudent/',
|
||||
params
|
||||
};
|
||||
},
|
||||
loadAufnahmegruppen(params){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/loadAufnahmegruppen/',
|
||||
params
|
||||
};
|
||||
},
|
||||
getResultReihungstest(params){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/getResultReihungstest/',
|
||||
params
|
||||
};
|
||||
},
|
||||
loadFutureReihungstests(params){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/aufnahmetermine/getZukuenftigeReihungstestStg/',
|
||||
params
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
export default {
|
||||
getArchiv(person_id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/archiv/getArchiv',
|
||||
params: { person_id }
|
||||
};
|
||||
},
|
||||
getArchivVorlagen() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/archiv/getArchivVorlagen'
|
||||
};
|
||||
},
|
||||
archive(data) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/documents/archive',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
archiveSigned(data) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/documents/archiveSigned',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/archiv/update',
|
||||
params: data
|
||||
};
|
||||
},
|
||||
delete(akte_id, studiengang_kz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/archiv/delete',
|
||||
params: {akte_id, studiengang_kz}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getCourselist(params) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/LvTermine/getStundenplan/' + params.student_uid + '/'
|
||||
+ params.start_date + '/'
|
||||
+ params.end_date + '/'
|
||||
+ params.group_consecutiveHours + '/'
|
||||
+ params.dbStundenplanTable
|
||||
};
|
||||
},
|
||||
getStudiensemester(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/LvTermine/getStudiensemester/'
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -16,16 +16,22 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
get(prestudent_id) {
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/student/get/' + prestudent_id
|
||||
url: 'api/frontend/v1/stv/student/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
};
|
||||
},
|
||||
save(prestudent_id, params) {
|
||||
save(prestudent_id, studiensemester_kurzbz, params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/student/save/' + prestudent_id,
|
||||
url: 'api/frontend/v1/stv/student/save/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz),
|
||||
params
|
||||
};
|
||||
},
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getDocumentsUnaccepted(params) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/dokumente/getDocumentsUnaccepted/' + params.id + '/' + params.studiengang_kz
|
||||
};
|
||||
},
|
||||
getDocumentsAccepted(params) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/dokumente/getDocumentsAccepted/' + params.id + '/' + params.studiengang_kz
|
||||
};
|
||||
},
|
||||
deleteZuordnung(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/dokumente/deleteZuordnung/' + params.prestudent_id + '/' + params.dokument_kurzbz
|
||||
};
|
||||
},
|
||||
createZuordnung(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/dokumente/createZuordnung/' + params.prestudent_id + '/' + params.dokument_kurzbz
|
||||
};
|
||||
},
|
||||
loadAkte(akte_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/dokumente/loadAkte/' + akte_id
|
||||
};
|
||||
},
|
||||
getDoktypen(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/dokumente/getDoktypen/'
|
||||
};
|
||||
},
|
||||
updateFile(akte_id, params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/dokumente/updateAkte/' + akte_id,
|
||||
params
|
||||
};
|
||||
},
|
||||
deleteFile(akte_id){
|
||||
console.log("in deleteFile " + akte_id);
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/dokumente/deleteAkte/' + akte_id,
|
||||
};
|
||||
},
|
||||
uploadFile(prestudent_id, params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/dokumente/uploadDokument/' + prestudent_id,
|
||||
params
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAnrechnungen(id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/getAnrechnungen/' + id
|
||||
};
|
||||
},
|
||||
getLehrveranstaltungen(prestudent_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/getLehrveranstaltungen/' + prestudent_id
|
||||
};
|
||||
},
|
||||
getBegruendungen(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/getBegruendungen/'
|
||||
};
|
||||
},
|
||||
getLvsKompatibel(lv_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/getLvsKompatibel/' + lv_id
|
||||
};
|
||||
},
|
||||
getLektoren(studiengang_kz){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/getLektoren/' + studiengang_kz
|
||||
};
|
||||
},
|
||||
addNewAnrechnung(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/insertAnrechnung/',
|
||||
params
|
||||
};
|
||||
},
|
||||
loadAnrechnung(anrechnung_id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/loadAnrechnung/' + anrechnung_id
|
||||
};
|
||||
},
|
||||
editAnrechnung(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/updateAnrechnung/',
|
||||
params
|
||||
};
|
||||
},
|
||||
deleteAnrechnung(anrechnung_id){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/anrechnungen/deleteAnrechnung/' + anrechnung_id
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -22,25 +22,34 @@ export default {
|
||||
url: 'api/frontend/v1/stv/grades/list'
|
||||
};
|
||||
},
|
||||
getCertificate(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getCertificate(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getCertificate/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getCertificate/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
getTeacherProposal(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getTeacherProposal(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getTeacherProposal/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getTeacherProposal/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
getRepeaterGrades(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
getRepeaterGrades(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getRepeaterGrades/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/grades/getRepeaterGrades/' + prestudent_id + all
|
||||
url: url
|
||||
};
|
||||
},
|
||||
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) {
|
||||
@@ -86,11 +95,15 @@ export default {
|
||||
}
|
||||
};
|
||||
},
|
||||
getGradeFromPoints(points, lehrveranstaltung_id) {
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/grades/getGradeFromPoints',
|
||||
params: { points, lehrveranstaltung_id }
|
||||
params: {
|
||||
"points": points,
|
||||
"lehrveranstaltung_id": lehrveranstaltung_id,
|
||||
"studiensemester_kurzbz": studiensemester_kurzbz
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getGruppen(id) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/Gruppen/getGruppen/' + id
|
||||
};
|
||||
},
|
||||
deleteGroup(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/Gruppen/deleteGruppe/',
|
||||
params
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getStudies(uid) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getStudien/' + uid
|
||||
};
|
||||
},
|
||||
getTypenMobility(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getTypenMobility/'
|
||||
};
|
||||
},
|
||||
getStudiensemester(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getStudiensemester/'
|
||||
};
|
||||
},
|
||||
getStudyprograms(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getStudienprogramme/'
|
||||
};
|
||||
},
|
||||
getListPartner(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getPartnerfirmen/'
|
||||
};
|
||||
},
|
||||
getStatiPrestudent(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/getStatiPrestudent/'
|
||||
};
|
||||
},
|
||||
loadStudy(id){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/loadStudie/' + id
|
||||
};
|
||||
},
|
||||
insertStudy(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/insertStudie/',
|
||||
params
|
||||
};
|
||||
},
|
||||
updateStudy(params){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/updateStudie/',
|
||||
params
|
||||
};
|
||||
},
|
||||
deleteStudy(id){
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/GemeinsameStudien/deleteStudie/' + id
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -67,5 +67,11 @@ export default {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/address/getNations/'
|
||||
};
|
||||
},
|
||||
getAllFirmen(){
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/kontakt/getAllFirmen/'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -51,7 +51,8 @@ export default {
|
||||
deleteMobility(bisio_id) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/mobility/deleteMobility/' + bisio_id
|
||||
url: 'api/frontend/v1/stv/mobility/deleteMobility/',
|
||||
params: { bisio_id }
|
||||
};
|
||||
},
|
||||
getLVList(studiengang_kz) {
|
||||
@@ -100,7 +101,7 @@ export default {
|
||||
deleteMobilityPurpose(params) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/mobility/deleteMobilityPurpose/' + params.bisio_id,
|
||||
url: 'api/frontend/v1/stv/mobility/deleteMobilityPurpose/',
|
||||
params
|
||||
};
|
||||
},
|
||||
@@ -108,7 +109,7 @@ export default {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/mobility/addMobilityPurpose/' + params.bisio_id,
|
||||
params
|
||||
params: params
|
||||
};
|
||||
},
|
||||
deleteMobilitySupport(params) {
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
|
||||
export default {
|
||||
//------------- Prestudent.js------------------------------------------------------
|
||||
get(prestudent_id) {
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return {
|
||||
method: 'post',
|
||||
url: 'api/frontend/v1/stv/prestudent/get/' + prestudent_id
|
||||
url: 'api/frontend/v1/stv/prestudent/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
};
|
||||
},
|
||||
updatePrestudent(prestudent_id, params) {
|
||||
|
||||
@@ -16,22 +16,34 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
uid(uid) {
|
||||
uid(uid, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/uid/'
|
||||
+ encodeURIComponent(uid);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/uid/' + uid
|
||||
url: url
|
||||
};
|
||||
},
|
||||
prestudent(prestudent_id) {
|
||||
prestudent(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/prestudent/'
|
||||
+ encodeURIComponent(prestudent_id);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/prestudent/' + prestudent_id
|
||||
url: url
|
||||
};
|
||||
},
|
||||
person(person_id) {
|
||||
person(person_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/person/'
|
||||
+ encodeURIComponent(person_id);
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/stv/students/person/' + person_id
|
||||
url: url
|
||||
};
|
||||
},
|
||||
verband(relative_path) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (C) 2025 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getVorlagen() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vorlagen/vorlagen/getVorlagen/'
|
||||
};
|
||||
},
|
||||
getVorlagenByLoggedInUser() {
|
||||
return {
|
||||
method: 'get',
|
||||
url: 'api/frontend/v1/vorlagen/vorlagen/getVorlagenByLoggedInUser/'
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
@@ -22,7 +22,7 @@ import filter from "./filter.js";
|
||||
import studstatus from "./studstatus.js";
|
||||
import profil from "./profil.js";
|
||||
import profilUpdate from "./profilUpdate.js";
|
||||
import stundenplan from "./stundenplan.js";
|
||||
import lvPlan from "./lvPlan.js";
|
||||
import bookmark from "./bookmark.js";
|
||||
import stv from "./stv.js";
|
||||
import notiz from "./notiz.js";
|
||||
@@ -33,10 +33,14 @@ import ort from "./ort.js";
|
||||
import cms from "./cms.js";
|
||||
import lehre from "./lehre.js";
|
||||
import addons from "./addons.js";
|
||||
import messages from "./messages.js";
|
||||
import vorlagen from "./vorlagen.js";
|
||||
import studiengang from "./studiengang.js";
|
||||
import menu from "./menu.js";
|
||||
import dashboard from "./dashboard.js";
|
||||
import authinfo from "./authinfo.js";
|
||||
import studium from "./studium.js";
|
||||
import language from "./language.js";
|
||||
|
||||
export default {
|
||||
search,
|
||||
@@ -47,7 +51,7 @@ export default {
|
||||
studstatus,
|
||||
profil,
|
||||
profilUpdate,
|
||||
stundenplan,
|
||||
lvPlan,
|
||||
bookmark,
|
||||
stv,
|
||||
notiz,
|
||||
@@ -57,8 +61,13 @@ export default {
|
||||
ort,
|
||||
cms,
|
||||
lehre,
|
||||
addons,
|
||||
messages,
|
||||
vorlagen,
|
||||
addons,
|
||||
studiengang,
|
||||
menu,
|
||||
authinfo,
|
||||
studium,
|
||||
language
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (C) 2024 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default {
|
||||
getAll() {
|
||||
return this.$fhcApi.get('/api/frontend/v1/language/get');
|
||||
}
|
||||
};
|
||||
@@ -18,5 +18,80 @@ export default {
|
||||
`/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
getStudentProjektarbeiten(uid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getStudentProjektarbeiten/${uid}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
getStudentProjektabgaben(detail) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getStudentProjektabgaben`
|
||||
, {
|
||||
projektarbeit_id: detail.projektarbeit_id,
|
||||
student_uid: detail.student_uid
|
||||
}
|
||||
);
|
||||
},
|
||||
postStudentProjektarbeitEndupload(formData) {
|
||||
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitEndupload';
|
||||
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
|
||||
return this.$fhcApi.post(url, formData, headers)
|
||||
},
|
||||
postStudentProjektarbeitZwischenabgabe(formData) {
|
||||
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitZwischenabgabe';
|
||||
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
|
||||
return this.$fhcApi.post(url, formData, headers)
|
||||
},
|
||||
getStudentProjektarbeitAbgabeFile(paabgabe_id, student_uid) {
|
||||
const url = `/Cis/Abgabetool/getStudentProjektarbeitAbgabeFile?paabgabe_id=${paabgabe_id}&student_uid=${student_uid}`;
|
||||
|
||||
window.location = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url
|
||||
},
|
||||
getMitarbeiterProjektarbeiten(uid, all) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Lehre/getMitarbeiterProjektarbeiten?showall=${all}`
|
||||
, {}
|
||||
);
|
||||
},
|
||||
postProjektarbeitAbgabe(termin) {
|
||||
const payload = {
|
||||
paabgabe_id: termin.paabgabe_id,
|
||||
paabgabetyp_kurzbz: termin.bezeichnung.paabgabetyp_kurzbz,
|
||||
datum: termin.datum,
|
||||
fixtermin: termin.fixtermin,
|
||||
insertvon: termin.insertvon,
|
||||
kurzbz: termin.kurzbz,
|
||||
projektarbeit_id: termin.projektarbeit_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/postProjektarbeitAbgabe';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
|
||||
},
|
||||
deleteProjektarbeitAbgabe(paabgabe_id) {
|
||||
const payload = {
|
||||
paabgabe_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/deleteProjektarbeitAbgabe';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
},
|
||||
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids) {
|
||||
const payload = {
|
||||
datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/postSerientermin';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
},
|
||||
fetchDeadlines(person_id) {
|
||||
const payload = {
|
||||
person_id
|
||||
}
|
||||
const url = '/api/frontend/v1/Lehre/fetchDeadlines';
|
||||
|
||||
return this.$fhcApi.post(url, payload, null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
'/api/frontend/v1/LvPlan/getRoomplan',
|
||||
{ ort_kurzbz, start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStunden() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/LvPlan/Stunden',
|
||||
{}
|
||||
);
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
`/api/frontend/v1/LvPlan/getReservierungen/${ort_kurzbz}`,
|
||||
{ start_date, end_date}
|
||||
);
|
||||
},
|
||||
getLvPlanReservierungen(start_date, end_date) {
|
||||
return this.$fhcApi.post(
|
||||
'/api/frontend/v1/LvPlan/getReservierungen',
|
||||
{ start_date, end_date }
|
||||
);
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/getLehreinheitStudiensemester/${lehreinheit_id}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/studiensemesterDateInterval/${date}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
LvPlanEvents(start_date, end_date, lv_id) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/LvPlan/LvPlanEvents',
|
||||
{ start_date, end_date, lv_id }
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import person from "./messages/person.js";
|
||||
|
||||
export default {
|
||||
person
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
getMessages(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getMessages/' + params.id + '/' + params.type + '/' + params.size + '/' + params.page);
|
||||
},
|
||||
getVorlagen(){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getVorlagen/');
|
||||
},
|
||||
getMsgVarsLoggedInUser(){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getMsgVarsLoggedInUser/');
|
||||
},
|
||||
getMessageVarsPerson(params){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getMessageVarsPerson/' + params.id + '/' + params.type_id);
|
||||
},
|
||||
getMsgVarsPrestudent(params){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getMsgVarsPrestudent/' + params.id + '/' + params.type_id);
|
||||
},
|
||||
getPersonId(params){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getPersonId/'+ params.id + '/' + params.type_id);
|
||||
},
|
||||
getUid(params){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getUid/'+ params.id + '/' + params.type_id);
|
||||
},
|
||||
getVorlagentext(vorlage_kurzbz){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getVorlagentext/' + vorlage_kurzbz);
|
||||
},
|
||||
getNameOfDefaultRecipient(params){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getNameOfDefaultRecipient/' + params.id + '/' + params.type_id);
|
||||
},
|
||||
getPreviewText(params, data){
|
||||
return this.$fhcApi.post('api/frontend/v1/messages/messages/getPreviewText/' + params.id + '/' + params.type_id,
|
||||
data);
|
||||
},
|
||||
getReplyData(messageId){
|
||||
return this.$fhcApi.get('api/frontend/v1/messages/messages/getReplyData/' + messageId);
|
||||
},
|
||||
sendMessageFromModalContext(form, id, data) {
|
||||
return this.$fhcApi.post(form,'api/frontend/v1/messages/messages/sendMessage/' + id,
|
||||
data);
|
||||
},
|
||||
sendMessage(id, data) {
|
||||
return this.$fhcApi.post('api/frontend/v1/messages/messages/sendMessage/' + id,
|
||||
data);
|
||||
},
|
||||
deleteMessage(messageId){
|
||||
return this.$fhcApi.post('api/frontend/v1/messages/messages/deleteMessage/' + messageId);
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,19 @@ export default {
|
||||
{ ort_kurzbz: ort_kurbz }
|
||||
);
|
||||
},
|
||||
getRooms(datum, von, bis, typ, personenanzahl = 0) {
|
||||
return this.$fhcApi.get(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/api/frontend/v1/Ort/getRooms",
|
||||
{ datum, von, bis, typ, personenanzahl }
|
||||
);
|
||||
},
|
||||
getRoomTypes() {
|
||||
return this.$fhcApi.get(
|
||||
FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
"/api/frontend/v1/Ort/getTypes"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,16 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
search(searchsettings) {
|
||||
search(searchsettings, config) {
|
||||
const url = '/api/frontend/v1/searchbar/search';
|
||||
return this.$fhcApi.post(url, searchsettings, config);
|
||||
},
|
||||
searchAdvanced(searchsettings, config) {
|
||||
const url = '/api/frontend/v1/searchbar/searchAdvanced';
|
||||
return this.$fhcApi.post(url, searchsettings, config);
|
||||
},
|
||||
searchdummy(searchsettings) {
|
||||
const url = 'public/js/apps/api/dummyapi.php/Search';
|
||||
return this.$fhcApi.post(url, searchsettings);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export default {
|
||||
|
||||
getStudiensemester: function () {
|
||||
return this.$fhcApi.get(
|
||||
'/components/Cis/Mylv/Studiensemester',
|
||||
{}
|
||||
);
|
||||
},
|
||||
|
||||
getAllStudienSemester: function (studiensemester=undefined, studiengang=undefined, semester=undefined, studienplan=undefined) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Studium/getStudienAllSemester',
|
||||
{studiensemester, studiengang, semester, studienplan}
|
||||
);
|
||||
},
|
||||
|
||||
getStudiengaengeForStudienSemester: function (studiensemester) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getStudiengaengeForStudienSemester/${studiensemester}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
getStudienplaeneBySemester: function (studiengang, studiensemester) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getStudienplaeneBySemester`,
|
||||
{
|
||||
studiengang,
|
||||
studiensemester,
|
||||
}
|
||||
);
|
||||
},
|
||||
getLvPlanForStudiensemester: function (studiensemester, lvid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/LvPlan/getLvPlanForStudiensemester/${studiensemester}/${lvid}`,
|
||||
{
|
||||
}
|
||||
);
|
||||
},
|
||||
getLvEvaluierungInfo: function (studiensemester_kurzbz, lvid) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Studium/getLvEvaluierungInfo/${studiensemester_kurzbz}/${lvid}`,
|
||||
{
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
export default {
|
||||
getRoomInfo(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/getRoomplan',
|
||||
{ ort_kurzbz, start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStundenplan(start_date, end_date, lv_id) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/getStundenplan',
|
||||
{ start_date, end_date, lv_id }
|
||||
);
|
||||
},
|
||||
getStunden() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/Stunden',
|
||||
{}
|
||||
);
|
||||
},
|
||||
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/Reservierungen/${ort_kurzbz}`,
|
||||
{ start_date, end_date}
|
||||
);
|
||||
},
|
||||
getStundenplanReservierungen(start_date, end_date) {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/Stundenplan/Reservierungen',
|
||||
{ start_date, end_date }
|
||||
);
|
||||
},
|
||||
getLehreinheitStudiensemester(lehreinheit_id) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/getLehreinheitStudiensemester/${lehreinheit_id}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/studiensemesterDateInterval/${date}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import verband from './stv/verband.js';
|
||||
import students from './stv/students.js';
|
||||
import filter from './stv/filter.js';
|
||||
import konto from './stv/konto.js';
|
||||
import group from './stv/group.js';
|
||||
import kontakt from './stv/kontakt.js';
|
||||
import prestudent from './stv/prestudent.js';
|
||||
import status from './stv/status.js';
|
||||
@@ -10,12 +11,18 @@ import exam from './stv/exam.js';
|
||||
import abschlusspruefung from './stv/abschlusspruefung.js';
|
||||
import grades from './stv/grades.js';
|
||||
import mobility from './stv/mobility.js';
|
||||
import archiv from './stv/archiv.js';
|
||||
import documents from './stv/documents.js';
|
||||
import exemptions from './stv/exemptions.js';
|
||||
import jointstudies from "./stv/jointstudies.js";
|
||||
import courselist from './stv/courselist.js';
|
||||
|
||||
export default {
|
||||
verband,
|
||||
students,
|
||||
filter,
|
||||
konto,
|
||||
group,
|
||||
kontakt,
|
||||
prestudent,
|
||||
status,
|
||||
@@ -24,6 +31,11 @@ export default {
|
||||
abschlusspruefung,
|
||||
grades,
|
||||
mobility,
|
||||
archiv,
|
||||
documents,
|
||||
exemptions,
|
||||
jointstudies,
|
||||
courselist,
|
||||
configStudent() {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/config/student');
|
||||
},
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export default {
|
||||
tabulatorConfig(config, self) {
|
||||
config.ajaxURL = 'api/frontend/v1/stv/archiv/get';
|
||||
config.ajaxParams = () => {
|
||||
const params = {
|
||||
person_id: self.modelValue.person_id || self.modelValue.map(e => e.person_id)
|
||||
};
|
||||
return params;
|
||||
};
|
||||
config.ajaxRequestFunc = (url, config, params) => this.$fhcApi.post(url, params, config);
|
||||
config.ajaxResponse = (url, params, response) => response.data;
|
||||
|
||||
return config;
|
||||
},
|
||||
getArchivVorlagen() {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/archiv/getArchivVorlagen');
|
||||
},
|
||||
archive(data) {
|
||||
return this.$fhcApi.post(
|
||||
'api/frontend/v1/documents/archive',
|
||||
data
|
||||
);
|
||||
},
|
||||
archiveSigned(data) {
|
||||
return this.$fhcApi.post(
|
||||
'api/frontend/v1/documents/archiveSigned',
|
||||
data
|
||||
);
|
||||
},
|
||||
update(data) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/archiv/update', data);
|
||||
},
|
||||
delete({akte_id, studiengang_kz}) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/archiv/delete', {akte_id, studiengang_kz});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
getCourselist(url, config, params) {
|
||||
//corresponding logic controller Stundenplan.php
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/LvTermine/getStundenplan/'
|
||||
+ params.student_uid + '/'
|
||||
+ params.start_date + '/'
|
||||
+ params.end_date + '/'
|
||||
+ params.group_consecutiveHours + '/'
|
||||
+ params.dbStundenplanTable
|
||||
);
|
||||
},
|
||||
getStudiensemester(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/LvTermine/getStudiensemester/');
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
export default {
|
||||
get(prestudent_id) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/student/get/' + prestudent_id);
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/student/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz);
|
||||
return this.$fhcApi.post(url);
|
||||
},
|
||||
save(form, prestudent_id, data) {
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/student/save/' + prestudent_id,
|
||||
data
|
||||
);
|
||||
save(form, prestudent_id, studiensemester_kurzbz, data) {
|
||||
let url = 'api/frontend/v1/stv/student/save/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz);
|
||||
return this.$fhcApi.post(form, url, data);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
getDocumentsUnaccepted(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/dokumente/getDocumentsUnaccepted/' + params.id + '/' + params.studiengang_kz);
|
||||
},
|
||||
getDocumentsAccepted(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/dokumente/getDocumentsAccepted/' + params.id + '/' + params.studiengang_kz);
|
||||
},
|
||||
deleteZuordnung(params){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/dokumente/deleteZuordnung/' + params.prestudent_id + '/' + params.dokument_kurzbz);
|
||||
},
|
||||
createZuordnung(params){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/dokumente/createZuordnung/'
|
||||
+ params.prestudent_id + '/'
|
||||
+ params.dokument_kurzbz);
|
||||
},
|
||||
loadAkte(akte_id){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/dokumente/loadAkte/' + akte_id);
|
||||
},
|
||||
getDoktypen(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/dokumente/getDoktypen/');
|
||||
},
|
||||
updateFile(akte_id, data){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/dokumente/updateAkte/' + akte_id,
|
||||
data);
|
||||
},
|
||||
deleteFile(akte_id){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/dokumente/deleteAkte/' + akte_id);
|
||||
},
|
||||
uploadFile(prestudent_id, data){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/dokumente/uploadDokument/' + prestudent_id,
|
||||
data);
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
getAnrechnungen(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/getAnrechnungen/' + params.id);
|
||||
},
|
||||
getLehrveranstaltungen(prestudent_id){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/getLehrveranstaltungen/' + prestudent_id);
|
||||
},
|
||||
getBegruendungen(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/getBegruendungen/');
|
||||
},
|
||||
getLvsKompatibel(lv_id){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/getLvsKompatibel/' + lv_id);
|
||||
},
|
||||
getLektoren(studiengang_kz){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/getLektoren/' + studiengang_kz);
|
||||
},
|
||||
addNewAnrechnung(form, data){
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/anrechnungen/insertAnrechnung/', data);
|
||||
},
|
||||
loadAnrechnung(anrechnung_id){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/anrechnungen/loadAnrechnung/' + anrechnung_id);
|
||||
},
|
||||
editAnrechnung(form, data){
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/anrechnungen/updateAnrechnung/', data);
|
||||
},
|
||||
deleteAnrechnung(anrechnung_id){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/anrechnungen/deleteAnrechnung/' + anrechnung_id);
|
||||
},
|
||||
}
|
||||
+27
-11
@@ -2,17 +2,26 @@ export default {
|
||||
list() {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/list');
|
||||
},
|
||||
getCertificate(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getCertificate/' + prestudent_id + all);
|
||||
getCertificate(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getCertificate/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
getTeacherProposal(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getTeacherProposal/' + prestudent_id + all);
|
||||
getTeacherProposal(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getTeacherProposal/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
getRepeaterGrades(prestudent_id, all) {
|
||||
all = all ? '/all' : '';
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/grades/getRepeaterGrades/' + prestudent_id + all);
|
||||
getRepeaterGrades(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/grades/getRepeaterGrades/' + encodeURIComponent(prestudent_id);
|
||||
if (!!studiensemester_kurzbz) {
|
||||
url = url + '/' + encodeURIComponent(studiensemester_kurzbz);
|
||||
}
|
||||
return this.$fhcApi.get(url);
|
||||
},
|
||||
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) {
|
||||
return this.$fhcApi.post(
|
||||
@@ -65,8 +74,15 @@ export default {
|
||||
}
|
||||
);
|
||||
},
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, manualErrorHandling) {
|
||||
getGradeFromPoints(points, lehrveranstaltung_id, studiensemester_kurzbz, manualErrorHandling) {
|
||||
const config = manualErrorHandling ? {errorHandling: false} : {};
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/grades/getGradeFromPoints', {points, lehrveranstaltung_id}, config);
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/grades/getGradeFromPoints',
|
||||
{
|
||||
"points": points,
|
||||
"lehrveranstaltung_id": lehrveranstaltung_id,
|
||||
"studiensemester_kurzbz": studiensemester_kurzbz
|
||||
},
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
getGruppen(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/Gruppen/getGruppen/' + params.id);
|
||||
},
|
||||
deleteGroup(params) {
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/Gruppen/deleteGruppe/', params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
getStudies(url, config, params) {
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getStudien/' + params.id);
|
||||
},
|
||||
getTypenMobility(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getTypenMobility/');
|
||||
},
|
||||
getStudiensemester(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getStudiensemester/');
|
||||
},
|
||||
getStudyprograms(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getStudienprogramme/');
|
||||
},
|
||||
getListPartner(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getPartnerfirmen/');
|
||||
},
|
||||
getStatiPrestudent(){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/getStatiPrestudent/');
|
||||
},
|
||||
loadStudy(id){
|
||||
return this.$fhcApi.get('api/frontend/v1/stv/GemeinsameStudien/loadStudie/' + id);
|
||||
},
|
||||
insertStudy(form, data){
|
||||
return this.$fhcApi.post(form,'api/frontend/v1/stv/GemeinsameStudien/insertStudie/', data);
|
||||
},
|
||||
updateStudy(form, data){
|
||||
return this.$fhcApi.post(form,'api/frontend/v1/stv/GemeinsameStudien/updateStudie/', data);
|
||||
},
|
||||
deleteStudy(id){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/GemeinsameStudien/deleteStudie/' + id);
|
||||
},
|
||||
|
||||
}
|
||||
@@ -2,8 +2,13 @@ export default {
|
||||
|
||||
//------------- Prestudent.js------------------------------------------------------
|
||||
|
||||
get(prestudent_id){
|
||||
return this.$fhcApi.post('api/frontend/v1/stv/prestudent/get/' + prestudent_id);
|
||||
get(prestudent_id, studiensemester_kurzbz) {
|
||||
return this.$fhcApi.post(
|
||||
'api/frontend/v1/stv/prestudent/get/'
|
||||
+ encodeURIComponent(prestudent_id)
|
||||
+ '/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
);
|
||||
},
|
||||
updatePrestudent(form, prestudent_id, data){
|
||||
return this.$fhcApi.post(form, 'api/frontend/v1/stv/prestudent/updatePrestudent/' + prestudent_id,
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
export default {
|
||||
uid(uid) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/uid/' + uid);
|
||||
uid(uid, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/uid/'
|
||||
+ encodeURIComponent(uid);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
prestudent(prestudent_id) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/prestudent/' + prestudent_id);
|
||||
prestudent(prestudent_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/prestudent/'
|
||||
+ encodeURIComponent(prestudent_id);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
person(person_id) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/person/' + person_id);
|
||||
person(person_id, studiensemester_kurzbz) {
|
||||
let url = 'api/frontend/v1/stv/students/'
|
||||
+ encodeURIComponent(studiensemester_kurzbz)
|
||||
+ '/person/'
|
||||
+ encodeURIComponent(person_id);
|
||||
return this.$fhcApi.getUri(url);
|
||||
},
|
||||
verband(relative_path) {
|
||||
return this.$fhcApi.getUri('api/frontend/v1/stv/students/' + relative_path);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
getVorlagen() {
|
||||
return this.$fhcApi.get('api/frontend/v1/vorlagen/vorlagen/getVorlagen/');
|
||||
},
|
||||
getVorlagenByLoggedInUser() {
|
||||
return this.$fhcApi.get('api/frontend/v1/vorlagen/vorlagen/getVorlagenByLoggedInUser/');
|
||||
}
|
||||
}
|
||||
+37
-9
@@ -2,6 +2,7 @@ import FhcSearchbar from "../components/searchbar/searchbar.js";
|
||||
import CisMenu from "../components/Cis/Menu.js";
|
||||
import PluginsPhrasen from '../plugins/Phrasen.js';
|
||||
import ApiSearchbar from '../api/factory/searchbar.js';
|
||||
import Theme from "../plugins/Theme.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'CisApp',
|
||||
@@ -12,14 +13,17 @@ const app = Vue.createApp({
|
||||
data: function() {
|
||||
return {
|
||||
searchbaroptions: {
|
||||
origin: "cis",
|
||||
cssclass: "",
|
||||
calcheightonly: true,
|
||||
types: [
|
||||
"mitarbeiter",
|
||||
"student",
|
||||
"raum",
|
||||
"organisationunit"
|
||||
],
|
||||
types: {
|
||||
employee: Vue.computed(() => this.$p.t("search/type_employee")),
|
||||
student: Vue.computed(() => this.$p.t("search/type_student")),
|
||||
room: Vue.computed(() => this.$p.t("search/type_room")),
|
||||
organisationunit: Vue.computed(() => this.$p.t("search/type_organisationunit")),
|
||||
cms: Vue.computed(() => this.$p.t("search/type_cms")),
|
||||
dms: Vue.computed(() => this.$p.t("search/type_dms"))
|
||||
},
|
||||
actions: {
|
||||
employee: {
|
||||
defaultaction: {
|
||||
@@ -42,7 +46,7 @@ const app = Vue.createApp({
|
||||
},
|
||||
childactions: []
|
||||
},
|
||||
raum: {
|
||||
room: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
renderif: function(data) {
|
||||
@@ -104,14 +108,37 @@ const app = Vue.createApp({
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
}
|
||||
},
|
||||
cms: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
action: function (data) {
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
|
||||
'/CisVue/Cms/content/' + data.content_id;
|
||||
return link;
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
},
|
||||
dms: {
|
||||
defaultaction: {
|
||||
type: "link",
|
||||
action: function (data) {
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
|
||||
'cms/dms.php?id=' + data.dms_id;
|
||||
return link;
|
||||
}
|
||||
},
|
||||
childactions: []
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
searchfunction: function(searchsettings) {
|
||||
return this.$api.call(ApiSearchbar.search(searchsettings));
|
||||
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -122,4 +149,5 @@ app.use(primevue.config.default, {
|
||||
}
|
||||
})
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.mount('#cis-header');
|
||||
|
||||
+114
-50
@@ -1,20 +1,35 @@
|
||||
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import Theme from '../../plugins/Theme.js';
|
||||
import contrast from '../../directives/contrast.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
import Stundenplan, {DEFAULT_MODE_STUNDENPLAN} from "../../components/Cis/Stundenplan/Stundenplan.js";
|
||||
import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js";
|
||||
import MyLvPlan from "../../components/Cis/LvPlan/Personal.js";
|
||||
import MylvStudent from "../../components/Cis/Mylv/Student.js";
|
||||
import Profil from "../../components/Cis/Profil/Profil.js";
|
||||
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
|
||||
import CmsNews from "../../components/Cis/Cms/News.js";
|
||||
import CmsContent from "../../components/Cis/Cms/Content.js";
|
||||
import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
|
||||
import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js";
|
||||
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
|
||||
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
|
||||
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
|
||||
import Studium from "../../components/Cis/Studium/Studium.js";
|
||||
|
||||
import ApiRenderers from '../../api/factory/renderers.js';
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(`/${ciPath}`),
|
||||
routes: [
|
||||
{
|
||||
path: `/Cis/Studium`,
|
||||
name: 'Studium',
|
||||
component: Studium,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Profil/View/:uid`,
|
||||
name: 'ProfilView',
|
||||
@@ -27,7 +42,30 @@ const router = VueRouter.createRouter({
|
||||
component: Profil,
|
||||
props: true
|
||||
},
|
||||
|
||||
{
|
||||
path: `/Cis/Abgabetool/Student/:student_uid_prop?`,
|
||||
name: 'AbgabetoolStudent',
|
||||
component: AbgabetoolStudent,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Mitarbeiter`,
|
||||
name: 'AbgabetoolMitarbeiter',
|
||||
component: AbgabetoolMitarbeiter,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`,
|
||||
name: 'DeadlineOverview',
|
||||
component: DeadlineOverview,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: `/Cis/Raumsuche`,
|
||||
name: 'Raumsuche',
|
||||
component: Raumsuche,
|
||||
props: true
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
|
||||
@@ -113,59 +151,41 @@ const router = VueRouter.createRouter({
|
||||
},
|
||||
// Redirect old links to new format
|
||||
{
|
||||
// only trigger on first param being numeric to avoid paths like "Stundenplan/Month" entering here
|
||||
path: "/Cis/Stundenplan/:lv_id(\\d+)",
|
||||
name: "StundenplanOld",
|
||||
component: Stundenplan,
|
||||
redirect: (to) => {
|
||||
return { // redirect to longer Stundenplan url and map params
|
||||
name: "Stundenplan",
|
||||
// only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here
|
||||
path: "/Cis/LvPlan/:lv_id(\\d+)",
|
||||
name: "LvPlanOld",
|
||||
component: LvPlan,
|
||||
redirect(to) {
|
||||
const route = Vue.unref(router.currentRoute);
|
||||
const { mode, focus_date } = route.params; // keep mode and focus_date if available
|
||||
return { // redirect to longer LvPlan url and map params
|
||||
name: "LvPlan",
|
||||
params: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: to.params.lv_id
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `/Cis/Stundenplan/:mode?/:focus_date?/:lv_id?`,
|
||||
name: 'Stundenplan',
|
||||
component: Stundenplan,
|
||||
props: (route) => { // validate and set mode/focus date if for some reason missing
|
||||
const validModes = ["Month", "Week", "Day"];
|
||||
|
||||
// check mode string
|
||||
const mode = route.params.mode &&
|
||||
validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase())
|
||||
? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()
|
||||
: DEFAULT_MODE_STUNDENPLAN;
|
||||
|
||||
// default to today date if not provided or string forms invalid date
|
||||
const d = new Date(route.params.focus_date)
|
||||
const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0];
|
||||
// for consistency reasons format the props into one object but actually use a new name to we dont collide with
|
||||
// existing viewData declaration written from codeigniter 3 into routerview tag
|
||||
path: `/Cis/LvPlan/:mode?/:focus_date?/:lv_id?`,
|
||||
name: 'LvPlan',
|
||||
component: LvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: {
|
||||
mode,
|
||||
focus_date,
|
||||
lv_id: route.params.lv_id
|
||||
}
|
||||
propsViewData: route.params
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/Cis/MyLvPlan/:mode?/:focus_date?`,
|
||||
name: 'MyLvPlan',
|
||||
component: MyLvPlan,
|
||||
props(route) {
|
||||
return {
|
||||
propsViewData: route.params
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
// missing mode or focus_date -> set defaults
|
||||
if (!to.params.mode || !to.params.focus_date) {
|
||||
next({
|
||||
name: "Stundenplan",
|
||||
params: {
|
||||
mode: to.params.mode || DEFAULT_MODE_STUNDENPLAN,
|
||||
focus_date: to.params.focus_date || new Date().toISOString().split("T")[0],
|
||||
lv_id: to.params.lv_id
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -200,7 +220,8 @@ const router = VueRouter.createRouter({
|
||||
const app = Vue.createApp({
|
||||
name: 'FhcApp',
|
||||
data: () => ({
|
||||
appSideMenuEntries: {}
|
||||
appSideMenuEntries: {},
|
||||
renderers: null,
|
||||
}),
|
||||
components: {},
|
||||
computed: {
|
||||
@@ -210,7 +231,8 @@ const app = Vue.createApp({
|
||||
},
|
||||
provide() {
|
||||
return { // provide injectable & watchable language property
|
||||
language: Vue.computed(() => this.$p.user_language)
|
||||
language: Vue.computed(() => this.$p.user_language),
|
||||
renderers: Vue.computed(() => this.renderers),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -220,7 +242,8 @@ const app = Vue.createApp({
|
||||
},
|
||||
handleClick(event) {
|
||||
const target = event.target.closest('a');
|
||||
|
||||
|
||||
if(target?.id == 'skiplink') return
|
||||
if (target && this.isInternalRoute(target.href)) {
|
||||
const url = new URL(target.href)
|
||||
|
||||
@@ -247,6 +270,45 @@ const app = Vue.createApp({
|
||||
}
|
||||
}
|
||||
},
|
||||
async created(){
|
||||
await this.$api
|
||||
.call(ApiRenderers.loadRenderers())
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
for (let rendertype of Object.keys(data)) {
|
||||
let modalTitle = null;
|
||||
let modalContent = null;
|
||||
let calendarEvent = null;
|
||||
if (data[rendertype].modalTitle)
|
||||
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle)));
|
||||
if (data[rendertype].modalContent)
|
||||
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent)));
|
||||
if (data[rendertype].calendarEvent)
|
||||
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent)));
|
||||
|
||||
if (data[rendertype].calendarEventStyles){
|
||||
var head = document.head;
|
||||
if(!head.querySelector(`link[href="${data[rendertype].calendarEventStyles}"]`)){
|
||||
var link = document.createElement("link");
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
link.href = data[rendertype].calendarEventStyles;
|
||||
head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.renderers === null) {
|
||||
this.renderers = {};
|
||||
}
|
||||
if (!this.renderers[rendertype]) {
|
||||
this.renderers[rendertype] = {}
|
||||
}
|
||||
this.renderers[rendertype].modalTitle = modalTitle;
|
||||
this.renderers[rendertype].modalContent = modalContent;
|
||||
this.renderers[rendertype].calendarEvent = calendarEvent;
|
||||
}
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleClick);
|
||||
},
|
||||
@@ -264,6 +326,8 @@ app.use(primevue.config.default, {
|
||||
tooltip: 8000
|
||||
}
|
||||
})
|
||||
app.directive('tooltip', primevue.tooltip);
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(Theme);
|
||||
app.directive('contrast', contrast);
|
||||
app.mount('#fhccontent');
|
||||
app.mount('#fhccontent');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import DashboardAdmin from '../components/Dashboard/Admin.js';
|
||||
import Phrases from "../plugin/Phrasen.js"
|
||||
|
||||
Vue.createApp({
|
||||
name: 'DashboardAdminApp',
|
||||
@@ -12,4 +13,4 @@ Vue.createApp({
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}).mount('#main');
|
||||
}).use(Phrases).mount('#main');
|
||||
@@ -0,0 +1,27 @@
|
||||
//TODO Manu
|
||||
//use this instead of Nachrichten.js
|
||||
import NewMessage from "../../components/Messages/Details/NewMessage/NewDiv.js";
|
||||
import Phrasen from "../../plugin/Phrasen.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(),
|
||||
routes: [
|
||||
{ path: `/${ciPath}/NeueNachricht`, component: NewMessage },
|
||||
{ path: `/${ciPath}/NeueNachricht/:id`, component: NewMessage },
|
||||
{ path: `/${ciPath}/NeueNachricht/:id/:typeId`, component: NewMessage },
|
||||
]
|
||||
});
|
||||
|
||||
const app = Vue.createApp();
|
||||
|
||||
app
|
||||
.use(router)
|
||||
.use(primevue.config.default, {
|
||||
zIndex: {
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(Phrasen)
|
||||
.mount('#main');
|
||||
@@ -0,0 +1,26 @@
|
||||
import NewMessage from "../components/Messages/Details/NewMessage/NewDiv.js";
|
||||
|
||||
import Phrasen from "../plugin/Phrasen.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(),
|
||||
routes: [
|
||||
{ path: `/${ciPath}/NeueNachricht/:id/:typeId`, component: NewMessage },
|
||||
{ path: `/${ciPath}/NeueNachricht/:id/:typeId/:messageId`, component: NewMessage },
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
const app = Vue.createApp();
|
||||
|
||||
app
|
||||
.use(router)
|
||||
.use(primevue.config.default, {
|
||||
zIndex: {
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(Phrasen)
|
||||
.mount('#main');
|
||||
@@ -26,14 +26,148 @@ const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*
|
||||
const router = VueRouter.createRouter({
|
||||
history: VueRouter.createWebHistory(),
|
||||
routes: [
|
||||
{ path: `/${ciPath}/studentenverwaltung`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/prestudent/:prestudent_id`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/prestudent/:prestudent_id/:tab`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/student/:id`, component: FhcStudentenverwaltung },
|
||||
{ path: `/${ciPath}/studentenverwaltung/person/:person_id`, component: FhcStudentenverwaltung }
|
||||
{
|
||||
name: 'index',
|
||||
path: `/${ciPath}/studentenverwaltung`,
|
||||
component: FhcStudentenverwaltung
|
||||
},
|
||||
{
|
||||
name: 'studiensemester',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'studiengang',
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/:studiengang`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_studiengang: route.params.studiengang,
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
const isStudiengang = /^[A-Z]{3}/.test(to.params.studiengang);
|
||||
if (!isStudiengang) {
|
||||
return next({
|
||||
name: 'studiensemester',
|
||||
params: {
|
||||
studiensemester_kurzbz: to.params.studiensemester_kurzbz
|
||||
}
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id/:tab`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'prestudent',
|
||||
url_prestudent_id: route.params.prestudent_id,
|
||||
url_tab: route.params.tab
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/student/:id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'student',
|
||||
url_student_id: route.params.id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/person/:person_id`,
|
||||
component: FhcStudentenverwaltung,
|
||||
props: (route) => {
|
||||
return {
|
||||
url_studiensemester_kurzbz: route.params.studiensemester_kurzbz,
|
||||
url_mode: 'person',
|
||||
url_prestudent_id: route.params.person_id
|
||||
};
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz);
|
||||
if (!isSemester) {
|
||||
return next({name: 'index'});
|
||||
}
|
||||
next();
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: {
|
||||
name: 'index'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
router.afterEach((to, from, failure) => {
|
||||
let title = 'Studierendenverwaltung FH-Complete';
|
||||
if (to.params.studiengang) {
|
||||
title = to.params.studiengang + ' ' + title;
|
||||
}
|
||||
if (to.params.studiensemester_kurzbz) {
|
||||
title = to.params.studiensemester_kurzbz + ' ' + title;
|
||||
}
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'StudentenverwaltungApp'
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
:class="this.cModalClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog" :class="this.cModalDialogClass">
|
||||
<div class="modal-dialog modal-dialog-scrollable" :class="this.cModalDialogClass">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" :id="this.id + '_label'">
|
||||
|
||||
@@ -29,14 +29,24 @@ export default {
|
||||
uid: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
/** List of types to allow for creation */
|
||||
betriebsmittelTypes: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* If true: only show the types specified in 'betriebsmittelTypes'.
|
||||
* If false: show all available types.
|
||||
*/
|
||||
filterByProvidedTypes: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabulatorOptions: {
|
||||
ajaxURL: 'dummy',
|
||||
ajaxRequestFunc: () => this.$api.call(
|
||||
this.endpoint.getAllBetriebsmittel(this.typeId, this.id)
|
||||
this.endpoint.getAllBetriebsmittel(this.typeId, this.id, (this.filterByProvidedTypes ? this.betriebsmittelTypes : null))
|
||||
),
|
||||
ajaxResponse: (url, params, response) => response.data,
|
||||
columns: [
|
||||
@@ -137,8 +147,6 @@ export default {
|
||||
layout: 'fitColumns',
|
||||
layoutColumnsOnNewData: false,
|
||||
height: '550',
|
||||
selectableRangeMode: 'click',
|
||||
selectable: true,
|
||||
persistenceID: 'core-betriebsmittel'
|
||||
},
|
||||
tabulatorEvents: [
|
||||
@@ -296,7 +304,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
return this.$api
|
||||
.call(this.endpoint.getTypenBetriebsmittel())
|
||||
.call(this.endpoint.getTypenBetriebsmittel(this.betriebsmittelTypes))
|
||||
.then(result => {
|
||||
this.listBetriebsmitteltyp = result.data;
|
||||
})
|
||||
@@ -311,6 +319,7 @@ export default {
|
||||
table-only
|
||||
:side-menu="false"
|
||||
reload
|
||||
:reload-btn-infotext="this.$p.t('table', 'reload')"
|
||||
new-btn-show
|
||||
:new-btn-label="this.$p.t('ui', 'betriebsmittel')"
|
||||
@click:new="actionNewBetriebsmittel"
|
||||
@@ -427,6 +436,7 @@ export default {
|
||||
v-model="formData.ausgegebenam"
|
||||
auto-apply
|
||||
:enable-time-picker="false"
|
||||
text-input
|
||||
format="dd.MM.yyyy"
|
||||
preview-format="dd.MM.yyyy"
|
||||
:teleport="true"
|
||||
@@ -442,6 +452,7 @@ export default {
|
||||
v-model="formData.retouram"
|
||||
auto-apply
|
||||
:enable-time-picker="false"
|
||||
text-input
|
||||
format="dd.MM.yyyy"
|
||||
preview-format="dd.MM.yyyy"
|
||||
:teleport="true"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
export default {
|
||||
name: 'BootstrapModal',
|
||||
data: () => ({
|
||||
modal: null
|
||||
modal: null,
|
||||
fullscreen: false
|
||||
}),
|
||||
props: {
|
||||
backdrop: {
|
||||
@@ -23,9 +24,21 @@ export default {
|
||||
},
|
||||
noCloseBtn: Boolean,
|
||||
dialogClass: [String,Array,Object],
|
||||
headerClass: {
|
||||
type: [String,Array,Object],
|
||||
default: ''
|
||||
},
|
||||
bodyClass: {
|
||||
type: [String,Array,Object],
|
||||
default: 'px-4 py-5'
|
||||
},
|
||||
footerClass: {
|
||||
type: [String,Array,Object],
|
||||
default: ''
|
||||
},
|
||||
allowFullscreenExpand: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
@@ -50,6 +63,9 @@ export default {
|
||||
},
|
||||
toggle() {
|
||||
return this.modal.toggle();
|
||||
},
|
||||
toggleFullscreen() {
|
||||
this.fullscreen = !this.fullscreen
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -115,17 +131,23 @@ export default {
|
||||
});
|
||||
},
|
||||
template: `<div ref="modal" class="bootstrap-modal modal" tabindex="-1" @[\`hide.bs.modal\`]="$emit('hideBsModal')" @[\`hidden.bs.modal\`]="$emit('hiddenBsModal')" @[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal')" @[\`show.bs.modal\`]="$emit('showBsModal')" >
|
||||
<div class="modal-dialog" :class="dialogClass">
|
||||
<div class="modal-dialog" :class="fullscreen ? 'modal-fullscreen' : dialogClass">
|
||||
<div class="modal-content">
|
||||
<div v-if="$slots.title" class="modal-header">
|
||||
<div v-if="$slots.title" class="modal-header" :class="headerClass">
|
||||
<h5 class="modal-title"><slot name="title"/></h5>
|
||||
<slot name="popoutButton"></slot>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="d-flex align-items-center ms-auto">
|
||||
<button type="button" class="btn ms-auto" style="filter: invert(1)" v-if="allowFullscreenExpand" @click="toggleFullscreen">
|
||||
<i v-if="!fullscreen" class="fa-solid fa-expand"></i>
|
||||
<i v-else class="fa-solid fa-compress"></i>
|
||||
</button>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<slot name="modal-header-content"></slot>
|
||||
</div>
|
||||
<div class="modal-body" :class="bodyClass">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="modal-footer">
|
||||
<div v-if="$slots.footer" class="modal-footer" :class="footerClass">
|
||||
<slot name="footer"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import CalendarHeader from './Header.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarHeader
|
||||
},
|
||||
inject: [
|
||||
'date',
|
||||
'focusDate',
|
||||
'size'
|
||||
],
|
||||
emits: [
|
||||
'updateMode',
|
||||
'change:range',
|
||||
'input'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
import BaseDraganddrop from './Base/DragAndDrop.js';
|
||||
import BaseHeader from './Base/Header.js';
|
||||
import BaseSlider from './Base/Slider.js';
|
||||
import BsModal from '../Bootstrap/Modal.js';
|
||||
|
||||
import CalClick from '../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarBase",
|
||||
components: {
|
||||
BaseDraganddrop,
|
||||
BaseHeader,
|
||||
BaseSlider,
|
||||
BsModal
|
||||
},
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
locale: Vue.computed(() => this.locale),
|
||||
timezone: Vue.computed(() => this.timezone),
|
||||
timeGrid: Vue.computed(() => this.timeGrid),
|
||||
draggableEvents: Vue.computed(() => {
|
||||
if (!this.draggableEvents)
|
||||
return () => false;
|
||||
|
||||
if (Array.isArray(this.draggableEvents))
|
||||
return event => this.draggableEvents.includes(event.type);
|
||||
if (this.draggableEvents instanceof Function)
|
||||
return this.draggableEvents;
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
dropableEvents: Vue.computed(() => {
|
||||
if (!this.onDrop)
|
||||
return () => false;
|
||||
|
||||
if (Array.isArray(this.dropableEvents))
|
||||
return item => this.dropableEvents.includes(item.type);
|
||||
if (this.dropableEvents instanceof Function)
|
||||
return this.dropableEvents;
|
||||
|
||||
return () => true;
|
||||
}),
|
||||
hasDragoverFunc: Vue.computed(() => this.onDragover),
|
||||
mode: Vue.computed(() => this.mode)
|
||||
};
|
||||
},
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'de'
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: props => luxon.DateTime.now().setZone(props.timezone).startOf('day')
|
||||
},
|
||||
modes: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
// TODO(chris): verfication functions
|
||||
},
|
||||
mode: String,
|
||||
modeOptions: Object,
|
||||
events: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
backgrounds: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
showBtns: Boolean,
|
||||
btnMonth: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnWeek: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnDay: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
btnList: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
timeGrid: Array,
|
||||
draggableEvents: [Boolean, Array, Function],
|
||||
dropableEvents: [Boolean, Array, Function],
|
||||
onDragover: Function,
|
||||
onDrop: Function
|
||||
},
|
||||
emits: [
|
||||
"click:next",
|
||||
"click:prev",
|
||||
"click:mode",
|
||||
"click:event",
|
||||
"click:day",
|
||||
"click:week",
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range",
|
||||
"drop"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
internalView: null,
|
||||
internalDate: null,
|
||||
modalEvent: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
convertedEvents() {
|
||||
return this.events.map(orig => ({
|
||||
id: orig.type + orig[orig.type + '_id'],
|
||||
type: orig.type,
|
||||
start: luxon.DateTime.fromISO(orig.isostart).setZone(this.timezone),
|
||||
end: luxon.DateTime.fromISO(orig.isoend).setZone(this.timezone),
|
||||
orig
|
||||
}));
|
||||
},
|
||||
convertedBackgrounds() {
|
||||
return this.backgrounds.map(bg => {
|
||||
const res = { ...bg };
|
||||
if (res.start) {
|
||||
if (Number.isInteger(res.start))
|
||||
res.start = luxon.DateTime.fromMillis(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.start instanceof Date)
|
||||
res.start = luxon.DateTime.fromJSDate(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.start ===
|
||||
'string' || res.start instanceof String)
|
||||
res.start = luxon.DateTime.fromISO(res.start, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
if (res.end) {
|
||||
if (Number.isInteger(res.end))
|
||||
res.end = luxon.DateTime.fromMillis(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.end instanceof Date)
|
||||
res.end = luxon.DateTime.fromJSDate(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.end ===
|
||||
'string' || res.end instanceof String)
|
||||
res.end = luxon.DateTime.fromISO(res.end, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
return res;
|
||||
});
|
||||
},
|
||||
sDate() {
|
||||
if (this.date instanceof luxon.DateTime)
|
||||
return this.date;
|
||||
return luxon.DateTime.fromJSDate(new Date(this.date)).setZone(this.timezone);
|
||||
},
|
||||
cDate: {
|
||||
get() {
|
||||
const date = this.internalDate ? this.internalDate : this.sDate;
|
||||
return date.setLocale(this.locale);
|
||||
},
|
||||
set(value) {
|
||||
this.internalDate = value;
|
||||
this.$emit('update:date', value, this.cMode);
|
||||
}
|
||||
},
|
||||
sMode() {
|
||||
// choose default mode
|
||||
let mode = this.mode;
|
||||
if (mode)
|
||||
mode = mode.toLowerCase();
|
||||
if (!mode || !this.modes[mode])
|
||||
mode = Object.keys(this.modes).find(Boolean); // start with first entry as active mode
|
||||
return mode || '';
|
||||
},
|
||||
cMode: {
|
||||
get() {
|
||||
return this.internalView ? this.internalView : this.sMode;
|
||||
},
|
||||
set(value) {
|
||||
this.internalView = value;
|
||||
this.$emit('update:mode', value, this.cDate);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
sDate(n, o) {
|
||||
if (this.sDate.isValid && !this.sDate.hasSame(this.internalDate, 'day'))
|
||||
this.internalDate = this.sDate;
|
||||
},
|
||||
sMode() {
|
||||
if (this.sMode)
|
||||
this.internalView = this.sMode;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickPrev() {
|
||||
const evt = new Event('click:prev', {cancelable: true});
|
||||
this.$emit('click:prev', evt);
|
||||
if (evt.defaultPrevented)
|
||||
return;
|
||||
|
||||
// default: switch page
|
||||
this.$refs.mode.prevPage();
|
||||
},
|
||||
clickNext() {
|
||||
const evt = new Event('click:next', {cancelable: true});
|
||||
this.$emit('click:next', evt);
|
||||
if (evt.defaultPrevented)
|
||||
return;
|
||||
|
||||
// default: switch page
|
||||
this.$refs.mode.nextPage();
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
// TODO(chris): implement
|
||||
switch (evt.detail.source) {
|
||||
case 'day':
|
||||
if (this.cMode != 'day' && this.modes['day']) {
|
||||
evt.stopPropagation();
|
||||
this.cDate = evt.detail.value;
|
||||
this.cMode = 'day';
|
||||
}
|
||||
break;
|
||||
case 'week':
|
||||
if (this.cMode != 'week' && this.modes['week']) {
|
||||
evt.stopPropagation();
|
||||
this.cDate = luxon.DateTime.fromObject({
|
||||
localWeekNumber: evt.detail.value.number,
|
||||
localWeekYear: evt.detail.value.year
|
||||
}, {
|
||||
zone: this.cDate.zoneName,
|
||||
locale: this.cDate.locale
|
||||
});
|
||||
this.cMode = 'week';
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
onDropItem(evt, start, end) {
|
||||
this.$emit('drop', evt, start, end);
|
||||
},
|
||||
showEventModal(eventObj) {
|
||||
this.modalEvent = eventObj;
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
hideEventModal() {
|
||||
if (this.modalEvent)
|
||||
this.modalEvent.closeFn = undefined;
|
||||
this.$refs.modal.hide();
|
||||
this.modalEvent = null;
|
||||
},
|
||||
onModalHidden() {
|
||||
if (this.modalEvent.closeFn)
|
||||
this.modalEvent.closeFn();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.hideEventModal();
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-base h-100">
|
||||
<base-draganddrop
|
||||
class="card h-100"
|
||||
:events="convertedEvents"
|
||||
:backgrounds="convertedBackgrounds"
|
||||
@drop="onDropItem"
|
||||
v-cal-click:container
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-header
|
||||
class="card-header"
|
||||
v-model:date="cDate"
|
||||
v-model:mode="cMode"
|
||||
@prev="clickPrev"
|
||||
@next="clickNext"
|
||||
@click:mode="$emit('click:mode', $event)"
|
||||
:btn-day="!!modes['day'] && (btnDay || (showBtns && btnDay !== false))"
|
||||
:btn-week="!!modes['week'] && (btnWeek || (showBtns && btnWeek !== false))"
|
||||
:btn-month="!!modes['month'] && (btnMonth || (showBtns && btnMonth !== false))"
|
||||
:btn-list="!!modes['list'] && (btnList || (showBtns && btnList !== false))"
|
||||
:mode-options="modeOptions ? modeOptions[cMode] : undefined"
|
||||
>
|
||||
<slot name="actions" />
|
||||
</base-header>
|
||||
<component
|
||||
:is="modes ? modes[cMode] : null || 'div'"
|
||||
ref="mode"
|
||||
v-model:current-date="cDate"
|
||||
@update:range="$emit('update:range', $event)"
|
||||
@request-modal-open="showEventModal"
|
||||
@request-modal-close="hideEventModal"
|
||||
v-bind="modeOptions ? modeOptions[cMode] : null || {}"
|
||||
>
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</component>
|
||||
</base-draganddrop>
|
||||
<bs-modal ref="modal" dialog-class="modal-lg" body-class="" @hidden-bs-modal="onModalHidden">
|
||||
<template #title>
|
||||
<slot v-if="modalEvent" v-bind="{mode: 'eventheader', event: modalEvent.event}" />
|
||||
</template>
|
||||
<template #default>
|
||||
<slot v-if="modalEvent" v-bind="{mode: 'event', event: modalEvent.event}" />
|
||||
</template>
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import DragAndDrop from '../../../helpers/DragAndDrop.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
|
||||
/**
|
||||
* TODO(chris): this needs serious rework!
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "CalendarDragAndDrop",
|
||||
directives: {
|
||||
CalDnd
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
events: Vue.computed(() => this.correctedEvents),
|
||||
backgrounds: Vue.computed(() => this.backgrounds),
|
||||
dropAllowed: Vue.computed(() => this.dragging && this.dropAllowed)
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
mode: "mode",
|
||||
dropableEvents: "dropableEvents"
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
backgrounds: Array
|
||||
},
|
||||
emits: [
|
||||
"drop"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
allowed: false,
|
||||
draggedInternalEvent: null,
|
||||
draggedExternalEvent: null,
|
||||
targetTimestamp: 0,
|
||||
targetGridEnds: null,
|
||||
dropAllowed: false,
|
||||
|
||||
shadowPreview: false // TODO(chris): IMPLEMENT! (use background instead of event as preview)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
correctedEvents() {
|
||||
if (this.dragging) {
|
||||
if (this.draggedInternalEvent) {
|
||||
const index = this.events.findIndex(e => e.id == this.draggedInternalEvent.id);
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return this.events.toSpliced(index, 1, this.previewEvent);
|
||||
else
|
||||
return this.events.toSpliced(index, 1);
|
||||
}
|
||||
if (this.previewEvent && !this.shadowPreview)
|
||||
return [...this.events, this.previewEvent];
|
||||
}
|
||||
|
||||
return this.events;
|
||||
},
|
||||
correctedBackgrounds() {
|
||||
if (this.dragging) {
|
||||
if (this.shadowPreview) {
|
||||
// TODO(chris): how to get the length
|
||||
return [...this.backgrounds, {
|
||||
start: new Date(this.targetTimestamp),
|
||||
class: 'shadow-preview'
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return this.backgrounds;
|
||||
},
|
||||
previewEvent() {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return null;
|
||||
if (!this.targetTimestamp)
|
||||
return null;
|
||||
|
||||
const event = this.draggedInternalEvent || this.draggedExternalEvent;
|
||||
|
||||
if (!event)
|
||||
return null;
|
||||
|
||||
// TODO(chris): calculate length correctly from orig
|
||||
let length = event.end - event.start;
|
||||
if (this.targetGridEnds)
|
||||
length = this.targetGridEnds.find(end => end >= this.targetTimestamp + length) - this.targetTimestamp;
|
||||
|
||||
return {
|
||||
orig: event.orig,
|
||||
start: this.targetTimestamp,
|
||||
end: this.targetTimestamp + length
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDragstart(evt) {
|
||||
DragAndDrop.setTransferData(evt.detail.originalEvent, evt.detail.item.orig);
|
||||
this.draggedInternalEvent = evt.detail.item;
|
||||
},
|
||||
onDragend() {
|
||||
this.draggedInternalEvent = null;
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragenter(evt) {
|
||||
this.dragging = true;
|
||||
|
||||
if (!this.draggedInternalEvent) {
|
||||
const event = DragAndDrop.getValidTransferData(evt.detail.originalEvent);
|
||||
if (event) {
|
||||
this.draggedExternalEvent = {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
start: event.isostart
|
||||
? luxon.DateTime.fromISO(event.isostart).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
end: event.isoend
|
||||
? luxon.DateTime.fromISO(event.isoend).setZone(this.timezone)
|
||||
: luxon.DateTime.local().setZone(this.timezone),
|
||||
orig: event
|
||||
};
|
||||
} else {
|
||||
this.draggedExternalEvent = null;
|
||||
}
|
||||
this.dropAllowed = this.dropableEvents(event, this.mode);
|
||||
} else {
|
||||
this.dropAllowed = this.dropableEvents(this.draggedInternalEvent, this.mode);
|
||||
}
|
||||
},
|
||||
onDragleave() {
|
||||
this.dragging = false;
|
||||
},
|
||||
onDragchange(evt) {
|
||||
this.targetTimestamp = evt.detail.timestamp;
|
||||
|
||||
this.targetGridEnds = evt.detail.ends || null;
|
||||
},
|
||||
onDrop(evt) {
|
||||
if (!this.dragging || !this.dropAllowed)
|
||||
return;
|
||||
|
||||
this.$emit('drop', evt, this.previewEvent.start, this.previewEvent.end);
|
||||
this.dropAllowed = false;
|
||||
this.dragging = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-base-draganddrop"
|
||||
@calendar-dragstart="onDragstart"
|
||||
@calendar-dragend="onDragend"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="onDragenter"
|
||||
@calendar-dragleave="onDragleave"
|
||||
@calendar-dragchange="onDragchange"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
import GridLine from './Grid/Line.js';
|
||||
import GridLineEvent from './Grid/Line/Event.js';
|
||||
|
||||
import CalDnd from '../../../directives/Calendar/DragAndDrop.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarGrid",
|
||||
components: {
|
||||
GridLine,
|
||||
GridLineEvent
|
||||
},
|
||||
directives: {
|
||||
CalDnd
|
||||
},
|
||||
inject: {
|
||||
originalEvents: "events",
|
||||
originalBackgrounds: "backgrounds",
|
||||
dropAllowed: "dropAllowed"
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
flipAxis: Vue.computed(() => this.flipAxis),
|
||||
axisRow: Vue.computed(() => this.axisRow)
|
||||
};
|
||||
},
|
||||
props: {
|
||||
axisMain: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.every(item => item instanceof luxon.DateTime);
|
||||
}
|
||||
},
|
||||
axisParts: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return value.every(item =>
|
||||
item instanceof luxon.Duration
|
||||
|| Number.isInteger(item)
|
||||
|| (
|
||||
(
|
||||
item.start instanceof luxon.Duration
|
||||
|| Number.isInteger(item.start)
|
||||
) && (
|
||||
item.end instanceof luxon.Duration
|
||||
|| Number.isInteger(item.end)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
flipAxis: Boolean,
|
||||
allDayEvents: Boolean,
|
||||
axisMainCollapsible: Boolean,
|
||||
snapToGrid: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
resizeObserver: null,
|
||||
mutationObserver: null,
|
||||
userScroll: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
axisRow() {
|
||||
return this.flipAxis ? 'column' : 'row';
|
||||
},
|
||||
axisCol() {
|
||||
return this.flipAxis ? 'row' : 'column';
|
||||
},
|
||||
axisPartsWithBreaks() {
|
||||
return this.axisParts.reduce((res, tu, index) => {
|
||||
const start = tu.start || tu;
|
||||
const end = tu.end;
|
||||
|
||||
if (res.length) {
|
||||
const lastTuEnd = res.pop();
|
||||
if (Array.isArray(lastTuEnd)) {
|
||||
res.push({
|
||||
start: lastTuEnd[0],
|
||||
end: start,
|
||||
index: lastTuEnd[1]
|
||||
});
|
||||
} else if (lastTuEnd != start) {
|
||||
// add pause
|
||||
res.push({
|
||||
start: lastTuEnd,
|
||||
end: start
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
res.push([start, index]);
|
||||
} else {
|
||||
res.push({
|
||||
start,
|
||||
end,
|
||||
index
|
||||
});
|
||||
res.push(end);
|
||||
}
|
||||
return res;
|
||||
}, []).slice(0, -1);
|
||||
},
|
||||
axisPartsSave() {
|
||||
if (!this.axisParts[this.axisParts.length - 1].end)
|
||||
return this.axisParts.slice(0, -1);
|
||||
return this.axisParts;
|
||||
},
|
||||
start() {
|
||||
return this.axisPartsWithBreaks[0].start;
|
||||
},
|
||||
end() {
|
||||
return this.axisPartsWithBreaks[this.axisPartsWithBreaks.length - 1].end;
|
||||
},
|
||||
ends() {
|
||||
const ends = [];
|
||||
const partsEnds = this.axisPartsWithBreaks
|
||||
.filter(p => p.index !== undefined)
|
||||
.map(p => p.end);
|
||||
for (var date of this.axisMain)
|
||||
for (var part of partsEnds)
|
||||
ends.push(date.plus(part));
|
||||
|
||||
return ends;
|
||||
},
|
||||
axisMainBorders() {
|
||||
return this.axisMain.reduce(
|
||||
(res, curr) => res.concat([curr.plus(this.start), curr.plus(this.end)]),
|
||||
[]
|
||||
);
|
||||
},
|
||||
eventsAllDay() {
|
||||
if (!this.allDayEvents)
|
||||
return [];
|
||||
return this.mapIntoMainAxis(this.originalEvents.filter(event => event.orig.allDayEvent));
|
||||
},
|
||||
eventsNormal() {
|
||||
if (!this.allDayEvents)
|
||||
return this.events;
|
||||
return this.mapIntoMainAxis(this.originalEvents.filter(event => !event.orig.allDayEvent));
|
||||
},
|
||||
events() {
|
||||
return this.mapIntoMainAxis(this.originalEvents);
|
||||
},
|
||||
backgrounds() {
|
||||
return this.mapIntoMainAxis(this.originalBackgrounds);
|
||||
},
|
||||
hasValidEvents() {
|
||||
return this.events.find(e => e.length);
|
||||
},
|
||||
styleGridCols() {
|
||||
let cols = 'repeat(' + this.axisMain.length + ', 1fr)';
|
||||
if (this.axisMainCollapsible) {
|
||||
if (this.hasValidEvents)
|
||||
cols = this.events
|
||||
.map(e => e.length
|
||||
? '1fr'
|
||||
: 'var(--fhc-calendar-axis-collapsible, .5fr)')
|
||||
.join(' ');
|
||||
}
|
||||
return cols;
|
||||
},
|
||||
styleGridRows() {
|
||||
const gridlines = {};
|
||||
|
||||
this.axisPartsWithBreaks.forEach(part => {
|
||||
let ts = part.start.toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts];
|
||||
if (part.index !== undefined)
|
||||
gridlines[ts].push('ps_' + part.index);
|
||||
ts = part.end.toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts];
|
||||
if (part.index !== undefined)
|
||||
gridlines[ts].push('pe_' + part.index);
|
||||
});
|
||||
|
||||
this.eventsNormal.forEach((events, mainIndex) => {
|
||||
let day = this.axisMain[mainIndex];
|
||||
events.forEach(event => {
|
||||
if (!event.startsHere && !event.endsHere)
|
||||
return;
|
||||
|
||||
if (event.startsHere) {
|
||||
let ts = event.start.diff(day).toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts, 'e_' + ts];
|
||||
}
|
||||
if (event.endsHere) {
|
||||
let ts = event.end.diff(day).toMillis();
|
||||
if (!gridlines[ts])
|
||||
gridlines[ts] = ['t_' + ts, 'e_' + ts];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Object.keys(gridlines).sort((a,b) => parseInt(a)-parseInt(b)).map((start, i, keys) => {
|
||||
let end = keys[i + 1];
|
||||
if (!end) {
|
||||
gridlines[start].push('end');
|
||||
return '[' + gridlines[start].join(' ') + ']';
|
||||
}
|
||||
return '[' + gridlines[start].join(' ') + '] ' + (end - start) + 'fr';
|
||||
}).join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mapIntoMainAxis(target) {
|
||||
const result = Array.from({length: this.axisMain.length}, () => Array());
|
||||
|
||||
target.forEach(event => {
|
||||
const start = event.start || this.axisMainBorders[0].plus(-1);
|
||||
const end = event.end || this.axisMainBorders[this.axisMainBorders.length - 1].plus(1);
|
||||
|
||||
for (var i = 0; i < this.axisMain.length; i++) {
|
||||
let laneStart = this.axisMainBorders[i * 2];
|
||||
let laneEnd = this.axisMainBorders[i * 2 + 1];
|
||||
if (event.orig?.allDayEvent) {
|
||||
laneStart = laneStart.startOf('day');
|
||||
laneEnd = laneEnd.endOf('day');
|
||||
}
|
||||
if (start < laneEnd && end > laneStart) {
|
||||
const startsHere = start >= laneStart;
|
||||
const endsHere = end <= laneEnd;
|
||||
result[i].push({
|
||||
...event,
|
||||
startsHere,
|
||||
endsHere
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* DRAG AND DROP */
|
||||
getPageTop(el) {
|
||||
let pageTop = el.offsetTop;
|
||||
if (el.offsetParent)
|
||||
pageTop += this.getPageTop(el.offsetParent);
|
||||
return pageTop;
|
||||
},
|
||||
getPageLeft(el) {
|
||||
let pageLeft = el.offsetLeft;
|
||||
if (el.offsetParent)
|
||||
pageLeft += this.getPageLeft(el.offsetParent);
|
||||
return pageLeft;
|
||||
},
|
||||
getTimestampFromMouse(evt, dayTimestamp) {
|
||||
let mouse, mouseFrac;
|
||||
if (this.flipAxis) {
|
||||
mouse = evt.pageX - this.getPageLeft(this.$refs.body) + this.$refs.main.scrollLeft;
|
||||
mouseFrac = mouse / this.$refs.body.offsetWidth;
|
||||
} else {
|
||||
mouse = evt.pageY - this.getPageTop(this.$refs.body) + this.$refs.main.scrollTop;
|
||||
mouseFrac = mouse / this.$refs.body.offsetHeight;
|
||||
}
|
||||
|
||||
return dayTimestamp + this.start + Math.floor((this.end - this.start) * mouseFrac);
|
||||
},
|
||||
|
||||
/* SCROLLING */
|
||||
enableAutoScroll() {
|
||||
if (!this.resizeObserver)
|
||||
this.resizeObserver = new ResizeObserver(this.scrollToEarliestEvent);
|
||||
this.resizeObserver.observe(this.$refs.body);
|
||||
|
||||
if (!this.mutationObserver)
|
||||
this.mutationObserver = new MutationObserver(mutations => {
|
||||
if (mutations.some(m => m.addedNodes.length && [].some.call(m.addedNodes, el => el.matches && el.matches('.fhc-calendar-base-grid-line-event'))))
|
||||
this.scrollToEarliestEvent();
|
||||
});
|
||||
this.mutationObserver.observe(this.$refs.body, {
|
||||
subtree: true,
|
||||
childList: true
|
||||
});
|
||||
|
||||
this.scrollToEarliestEvent();
|
||||
},
|
||||
disableAutoScroll() {
|
||||
if (this.resizeObserver)
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
|
||||
if (this.mutationObserver)
|
||||
this.mutationObserver.disconnect();
|
||||
this.mutationObserver = null;
|
||||
},
|
||||
scrollToEarliestEvent() {
|
||||
const eventElements = this.$refs.scroller.querySelectorAll('.fhc-calendar-base-grid-line-event');
|
||||
|
||||
let earliestEventOffset = [0, null];
|
||||
for (var el of eventElements.values()) {
|
||||
const top = el.offsetTop;
|
||||
if (!earliestEventOffset[1] || top < earliestEventOffset[0])
|
||||
earliestEventOffset = [top, el];
|
||||
}
|
||||
|
||||
this.userScroll = false;
|
||||
if (earliestEventOffset[1]) {
|
||||
earliestEventOffset[1].scrollIntoView({ behavior: "smooth" });
|
||||
} else {
|
||||
this.$refs.scroller.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.disableAutoScroll();
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid"
|
||||
style="display:grid;width:100%;height:100%"
|
||||
:style="'grid-template-' + axisRow + 's:auto' + (allDayEvents ? ' auto ' : ' ') + '1fr;grid-template-' + axisCol + 's:auto ' + styleGridCols"
|
||||
>
|
||||
<div
|
||||
class="grid-header"
|
||||
style="display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
|
||||
>
|
||||
<div
|
||||
v-for="(date, index) in axisMain"
|
||||
:key="index"
|
||||
class="main-header"
|
||||
:class="{'collapsed-header': axisMainCollapsible && hasValidEvents && !events[index].length}"
|
||||
:style="'grid-' + axisCol + ':' + (2+index)"
|
||||
>
|
||||
<slot name="main-header" v-bind="{ index, date }" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="allDayEvents"
|
||||
class="grid-allday"
|
||||
style="display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-' + axisCol + ':1/-1'"
|
||||
>
|
||||
<div
|
||||
v-for="(events, index) in eventsAllDay"
|
||||
:key="index"
|
||||
class="all-day-events"
|
||||
:style="'grid-' + axisCol + ':' + (2+index)"
|
||||
>
|
||||
<grid-line-event
|
||||
v-for="(event, i) in events"
|
||||
:key="i"
|
||||
:event="event"
|
||||
>
|
||||
<template v-slot="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
</grid-line-event>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="scroller"
|
||||
@scrollend="userScroll ? disableAutoScroll() : userScroll = true"
|
||||
style="display:grid;overflow:auto"
|
||||
:style="'grid-' + axisCol + ':1/-1;grid-template-' + axisCol + 's:subgrid'"
|
||||
>
|
||||
<div
|
||||
ref="main"
|
||||
class="grid-main"
|
||||
style="position:relative;grid-column:1/-1;grid-row:1/-1;display:grid"
|
||||
:style="'grid-template-' + axisCol + 's:subgrid;grid-template-' + axisRow + 's:' + styleGridRows"
|
||||
>
|
||||
<div
|
||||
v-for="(part, index) in axisPartsSave"
|
||||
:key="index"
|
||||
class="part-header"
|
||||
:style="'grid-' + axisCol + ':1;grid-' + axisRow + ': ps_' + index + '/pe_' + index"
|
||||
>
|
||||
<slot name="part-header" v-bind="{ index, part }" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="body"
|
||||
class="grid-body"
|
||||
style="display:grid;grid-template-rows:subgrid;grid-template-columns:subgrid"
|
||||
:style="'grid-' + axisCol + ':2/-1;grid-' + axisRow + ':1/-1'"
|
||||
v-cal-dnd:dropcage
|
||||
@calendar-dragenter="dragging = true"
|
||||
@calendar-dragleave="dragging = false"
|
||||
@dragover="dropAllowed ? $event.preventDefault() : null"
|
||||
>
|
||||
<template
|
||||
v-for="(date, index) in axisMain"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
v-for="(part, i) in axisPartsSave"
|
||||
:key="i"
|
||||
class="part-body"
|
||||
style="position:relative"
|
||||
:style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i"
|
||||
>
|
||||
<slot name="part-body" v-bind="{ index, part }" />
|
||||
<div
|
||||
v-if="snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone.once="{date: date.plus(part.start || part), ends: ends.slice(ends.findIndex(end => end > date))}"
|
||||
></div>
|
||||
</div>
|
||||
<grid-line
|
||||
:start="date.plus(start)"
|
||||
:end="date.plus(end)"
|
||||
:date="date"
|
||||
:events="eventsNormal[index]"
|
||||
:backgrounds="backgrounds[index]"
|
||||
style="position:relative"
|
||||
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
|
||||
>
|
||||
<template #event="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
<template #dropzone>
|
||||
<div
|
||||
v-if="!snapToGrid && dragging"
|
||||
style="position:absolute;inset:0;z-index:1"
|
||||
v-cal-dnd:dropzone="evt => getTimestampFromMouse(evt, date)"
|
||||
></div>
|
||||
</template>
|
||||
</grid-line>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import LineEvent from './Line/Event.js';
|
||||
import LineBackground from './Line/Background.js';
|
||||
|
||||
/**
|
||||
* TODO(chris):
|
||||
* Event overflow for Month mode (more-button)
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "GridLine",
|
||||
components: {
|
||||
LineEvent,
|
||||
LineBackground
|
||||
},
|
||||
inject: {
|
||||
axisRow: "axisRow"
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
start: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
end: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
backgrounds: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
eventsWithRowInfo() {
|
||||
const events = [];
|
||||
this.events.forEach(event => {
|
||||
const rows = [1, -1];
|
||||
if (event.startsHere) {
|
||||
rows[0] = 't_' + event.start.diff(this.date).toMillis();
|
||||
}
|
||||
if (event.endsHere) {
|
||||
rows[1] = 't_' + event.end.diff(this.date).toMillis();
|
||||
}
|
||||
|
||||
events.push({
|
||||
...event,
|
||||
rows
|
||||
});
|
||||
});
|
||||
return events;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line"
|
||||
style="position:relative;display:grid;grid-auto-flow:dense"
|
||||
:style="'grid-template-' + axisRow + 's:subgrid'"
|
||||
>
|
||||
<line-background
|
||||
v-for="bg in backgrounds"
|
||||
:start="start"
|
||||
:end="end"
|
||||
:background="bg"
|
||||
></line-background>
|
||||
<line-event
|
||||
v-for="(event, i) in eventsWithRowInfo"
|
||||
:key="i"
|
||||
:style="'grid-' + axisRow + ': ' + event.rows.join('/')"
|
||||
:event="event"
|
||||
>
|
||||
<template v-slot="slot">
|
||||
<slot name="event" v-bind="slot" />
|
||||
</template>
|
||||
</line-event>
|
||||
<slot name="dropzone" />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export default {
|
||||
name: "GridLineBackground",
|
||||
inject: {
|
||||
flipAxis: "flipAxis"
|
||||
},
|
||||
props: {
|
||||
start: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
end: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
background: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator(value) {
|
||||
if (!value.start && !value.end)
|
||||
return false;
|
||||
if (value.start && !(value.start instanceof luxon.DateTime))
|
||||
return false;
|
||||
if (value.end && !(value.end instanceof luxon.DateTime))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
if (!this.background.endsHere && !this.background.startsHere)
|
||||
return this.background.style;
|
||||
|
||||
const perc = (this.end.ts - this.start.ts) / 100;
|
||||
|
||||
let border = {};
|
||||
if (this.background.startsHere)
|
||||
border[this.flipAxis ? 'left' : 'top'] = (this.background.start.diff(this.start)) / perc + '%';
|
||||
if (this.background.endsHere)
|
||||
border[this.flipAxis ? 'right' : 'bottom'] = (this.end.diff(this.background.end)) / perc + '%';
|
||||
|
||||
if (!this.background.style)
|
||||
return border;
|
||||
|
||||
return [this.background.style, border];
|
||||
},
|
||||
classes() {
|
||||
if (!this.background.endsHere && !this.background.startsHere)
|
||||
return this.background.class;
|
||||
|
||||
const result = [];
|
||||
if (this.background.class)
|
||||
result.push(this.background.class);
|
||||
if (this.background.startsHere)
|
||||
result.push('bg-begin');
|
||||
if (this.background.endsHere)
|
||||
result.push('bg-end');
|
||||
return result;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-background"
|
||||
:class="classes"
|
||||
style="position:absolute;inset:0;z-index:0"
|
||||
:style="styles"
|
||||
:title="background.title"
|
||||
>
|
||||
<span v-if="background.label">{{ background.label }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import CalDnd from '../../../../../directives/Calendar/DragAndDrop.js';
|
||||
import CalClick from '../../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "GridLineEvent",
|
||||
directives: {
|
||||
CalDnd,
|
||||
CalClick
|
||||
},
|
||||
inject: {
|
||||
draggableEvents: "draggableEvents",
|
||||
mode: "mode"
|
||||
},
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator(value) {
|
||||
return (value.start && value.end && value.orig);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isHeaderOrFooter() {
|
||||
return ['header', 'footer'].includes(this.event.orig);
|
||||
},
|
||||
draggable() {
|
||||
return !this.isHeaderOrFooter && this.draggableEvents(this.event.orig, this.mode);
|
||||
},
|
||||
classes() {
|
||||
const classes = [];
|
||||
if (this.isHeaderOrFooter) {
|
||||
classes.push('event-' + this.event.orig);
|
||||
} else {
|
||||
if (this.event.startsHere)
|
||||
classes.push('event-begin');
|
||||
if (this.event.endsHere)
|
||||
classes.push('event-end');
|
||||
}
|
||||
return classes
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-event event"
|
||||
:class="classes"
|
||||
style="z-index: 1"
|
||||
:draggable="draggable"
|
||||
v-cal-dnd:draggable="event"
|
||||
v-cal-click:event="isHeaderOrFooter ? event : event.orig"
|
||||
>
|
||||
<slot :event="isHeaderOrFooter ? event : event.orig">
|
||||
{{ event.orig }}
|
||||
</slot>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* TODO(chris): use click-directive
|
||||
*/
|
||||
import DatePicker from './Header/Datepicker.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarHeader",
|
||||
components: {
|
||||
DatePicker
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modeOptions: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
btnMonth: Boolean,
|
||||
btnWeek: Boolean,
|
||||
btnDay: Boolean,
|
||||
btnList: Boolean
|
||||
},
|
||||
emits: [
|
||||
"next",
|
||||
"prev",
|
||||
"click:mode",
|
||||
"update:date",
|
||||
"update:mode"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clickMode(evt, mode) {
|
||||
this.$emit('click:mode', evt);
|
||||
if (!evt.defaultPrevented)
|
||||
this.$emit('update:mode', mode);
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-base-header">
|
||||
<div class="header-actions">
|
||||
<div class="header-userdefined">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="header-modes">
|
||||
<div class="d-flex gap-1 justify-content-end" role="group">
|
||||
<button
|
||||
v-if="btnMonth"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'month'}"
|
||||
@click="clickMode($event, 'month')"
|
||||
>
|
||||
<i class="fa fa-calendar-days"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnWeek"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'week'}"
|
||||
@click="clickMode($event, 'week')"
|
||||
>
|
||||
<i class="fa fa-calendar-week"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnDay"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'day'}"
|
||||
@click="clickMode($event, 'day')"
|
||||
>
|
||||
<i class="fa fa-calendar-day"></i>
|
||||
</button>
|
||||
<button
|
||||
v-if="btnList"
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
:class="{active: mode === 'list'}"
|
||||
@click="clickMode($event, 'list')"
|
||||
>
|
||||
<i class="fa fa-table-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-picker">
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0"
|
||||
@click="$emit('prev')"
|
||||
:disabled="open"
|
||||
>
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</button>
|
||||
<date-picker
|
||||
:mode="mode"
|
||||
:date="date"
|
||||
@update:date="$emit('update:date', $event)"
|
||||
@open="open = true"
|
||||
@closed="open = false"
|
||||
:list-length="modeOptions.length"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0"
|
||||
@click="$emit('next')"
|
||||
:disabled="open"
|
||||
>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// TODO(chris): translate aria-labels
|
||||
|
||||
export default {
|
||||
name: "CalendarHeaderDatepicker",
|
||||
components: {
|
||||
VueDatePicker
|
||||
},
|
||||
inject: [
|
||||
"locale",
|
||||
"timezone"
|
||||
],
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
listLength: {
|
||||
type: Number,
|
||||
default: 7
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:date"
|
||||
],
|
||||
computed: {
|
||||
convertedDate() {
|
||||
// convert to target TZ then strip TZ Information
|
||||
// so the datepicker can work with local times
|
||||
return this.date.setZone(this.timezone).setZone('local', { keepLocalTime: true });
|
||||
},
|
||||
current() {
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
return {month: this.convertedDate.month-1, year: this.convertedDate.year};
|
||||
case "list":
|
||||
return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
|
||||
case "week":
|
||||
return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
|
||||
case "day":
|
||||
return this.convertedDate;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
title() {
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
return this.date.toLocaleString({ month: 'long', year: 'numeric' });
|
||||
case "week":
|
||||
var year = this.date.localWeekYear;
|
||||
var week = this.date.toFormat('nn');
|
||||
return this.$p.t('calendar/year_kw', { year, week });
|
||||
case "list":
|
||||
return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.listLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
|
||||
case "day":
|
||||
return this.date.toLocaleString(luxon.DateTime.DATE_FULL);
|
||||
default:
|
||||
return 'View not Supported';
|
||||
}
|
||||
},
|
||||
weekStart() {
|
||||
return luxon.Info.getStartOfWeek(this.date)%7;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update(value) {
|
||||
let date;
|
||||
switch (this.mode) {
|
||||
case "month":
|
||||
value.month++;
|
||||
date = luxon.DateTime.fromObject(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
case "list":
|
||||
case "week":
|
||||
date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
case "day":
|
||||
date = luxon.DateTime.fromJSDate(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
|
||||
break;
|
||||
default:
|
||||
return; // Don't update if the value is invalid!
|
||||
}
|
||||
this.$emit("update:date", date);
|
||||
},
|
||||
weekNumbers(date) {
|
||||
return luxon.DateTime.fromJSDate(date, { locale: this.locale }).localWeekNumber;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<vue-date-picker
|
||||
:model-value="current"
|
||||
@update:model-value="update"
|
||||
:format="() => title"
|
||||
:month-picker="mode == 'month'"
|
||||
:week-picker="mode == 'week'"
|
||||
:range="mode == 'list' ? { autoRange: listLength - 1 } : false"
|
||||
:text-input="mode == 'day'"
|
||||
:week-start="weekStart"
|
||||
:week-numbers="{ type: weekNumbers }"
|
||||
:clearable="false"
|
||||
:enable-time-picker="false"
|
||||
:config="{ keepActionRow: mode != 'month' }"
|
||||
:action-row="{ showSelect: false, showCancel: false, showNow: mode != 'month', showPreview: false }"
|
||||
auto-apply
|
||||
six-weeks
|
||||
teleport
|
||||
:locale="locale"
|
||||
:now-button-label="$p.t('calendar/today')"
|
||||
:week-num-name="$p.t('calendar/kw')"
|
||||
/>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelDay",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleFull() {
|
||||
return this.date.toLocaleString({day: 'numeric', month: 'long', year: 'numeric'});
|
||||
},
|
||||
titleLong() {
|
||||
return this.date.toLocaleString({day: '2-digit', month: '2-digit', year: 'numeric'});
|
||||
},
|
||||
titleShort() {
|
||||
return this.date.toLocaleString({day: 'numeric', month: 'numeric'});
|
||||
},
|
||||
titleNarrow() {
|
||||
return this.date.toLocaleString({day: 'numeric'});
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-label-day"
|
||||
v-cal-click:day="date"
|
||||
>
|
||||
<span class="full">{{ titleFull }}</span>
|
||||
<span class="long">{{ titleLong }}</span>
|
||||
<span class="short">{{ titleShort }}</span>
|
||||
<span class="narrow">{{ titleNarrow }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelDow",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
titleLong() {
|
||||
return this.date.toLocaleString({weekday: 'long'});
|
||||
},
|
||||
titleShort() {
|
||||
return this.date.toLocaleString({weekday: 'short'});
|
||||
},
|
||||
titleNarrow() {
|
||||
return this.date.toLocaleString({weekday: 'narrow'});
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-label-dow"
|
||||
v-cal-click:dow="date"
|
||||
>
|
||||
<b class="long">{{ titleLong }}</b>
|
||||
<b class="short">{{ titleShort }}</b>
|
||||
<b class="narrow">{{ titleNarrow }}</b>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
export default {
|
||||
name: "LabelTime",
|
||||
props: {
|
||||
part: {
|
||||
type: [luxon.Duration, Number, Object],
|
||||
required: true,
|
||||
validator(value) {
|
||||
if (value instanceof Object) {
|
||||
if (value instanceof luxon.Duration)
|
||||
return true;
|
||||
let start_ok = true;
|
||||
let end_ok = true;
|
||||
if (value.start) {
|
||||
start_ok = (
|
||||
value.start instanceof luxon.Duration
|
||||
|| Number.isInteger(value.start)
|
||||
);
|
||||
}
|
||||
if (value.end) {
|
||||
end_ok = (
|
||||
value.end instanceof luxon.Duration
|
||||
|| Number.isInteger(value.end)
|
||||
);
|
||||
}
|
||||
return start_ok && end_ok;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sanitizedTimestamps() {
|
||||
return this.part.start || this.part.end ? this.part : { start: this.part };
|
||||
},
|
||||
start() {
|
||||
if (!this.sanitizedTimestamps.start)
|
||||
return null;
|
||||
return this.formatTime(this.sanitizedTimestamps.start);
|
||||
},
|
||||
end() {
|
||||
if (!this.sanitizedTimestamps.end)
|
||||
return null;
|
||||
return this.formatTime(this.sanitizedTimestamps.end);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatTime(date) {
|
||||
return date.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-base-label-time">
|
||||
<span v-if="start">{{ start }}</span>
|
||||
<span v-if="end">-</span>
|
||||
<span v-if="end">{{ end }}</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "LabelWeek",
|
||||
directives: {
|
||||
CalClick
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
weeks() {
|
||||
const firstDay = this.date.startOf('week', { useLocaleWeeks: true });
|
||||
const lastDay = this.date.endOf('week', { useLocaleWeeks: true });
|
||||
|
||||
const weeks = [
|
||||
{ number: firstDay.localWeekNumber, year: firstDay.localWeekYear },
|
||||
{ number: lastDay.localWeekNumber, year: lastDay.localWeekYear }
|
||||
];
|
||||
if (weeks[0].number == weeks[1].number)
|
||||
weeks.pop();
|
||||
return weeks;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-base-label-week">
|
||||
<span
|
||||
v-for="week in weeks"
|
||||
v-cal-click:week="week"
|
||||
>
|
||||
{{ week.number }}
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
export default {
|
||||
name: 'CalendarSlider',
|
||||
inject: {
|
||||
time: {
|
||||
from: "sliderTime",
|
||||
default: ".3s"
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'slid'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
target: 0,
|
||||
extrasAfter: 0,
|
||||
extrasBefore: 0,
|
||||
running: false,
|
||||
promiseResolve: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemsAfter() {
|
||||
return [...Array(this.extrasAfter)].map((i, k) => 1+k);
|
||||
},
|
||||
itemsBefore() {
|
||||
return [...Array(this.extrasBefore)].map((i, k) => k-this.extrasBefore);
|
||||
},
|
||||
styleSlider() {
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
if (this.running) {
|
||||
style.left = (-this.target * 100) + '%';
|
||||
style.transition = 'left ' + this.time + ' ease-in-out';
|
||||
}
|
||||
return style;
|
||||
},
|
||||
styleBefore() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
right: '100%',
|
||||
width: (this.extrasBefore * 100) + '%'
|
||||
};
|
||||
},
|
||||
styleAfter() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
left: '100%',
|
||||
width: (this.extrasAfter * 100) + '%'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
return this.slidePages(-1);
|
||||
},
|
||||
nextPage() {
|
||||
return this.slidePages(1);
|
||||
},
|
||||
slidePages(dir) {
|
||||
return new Promise(resolve => {
|
||||
this.promiseResolve = resolve;
|
||||
this.running = true;
|
||||
const newTarget = this.target + dir;
|
||||
if (newTarget > 0) {
|
||||
if (this.extrasAfter < newTarget)
|
||||
this.extrasAfter = newTarget;
|
||||
} else if (newTarget < 0) {
|
||||
if (-this.extrasBefore > newTarget)
|
||||
this.extrasBefore = -newTarget;
|
||||
}
|
||||
this.target = newTarget;
|
||||
});
|
||||
},
|
||||
endSlide() {
|
||||
if (this.promiseResolve) {
|
||||
this.promiseResolve(this.target);
|
||||
this.promiseResolve = null;
|
||||
}
|
||||
this.$emit('slid', this.target);
|
||||
this.running = false;
|
||||
this.target = 0;
|
||||
this.extrasAfter = this.extrasBefore = 0;
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
class="fhc-calendar-base-slider h-100"
|
||||
style="position:relative;overflow:hidden"
|
||||
>
|
||||
<div
|
||||
:style="styleSlider"
|
||||
@transitionend="endSlide"
|
||||
>
|
||||
<div :style="styleBefore">
|
||||
<div
|
||||
v-for="i in itemsBefore"
|
||||
:key="i"
|
||||
style="height:100%;width:100%"
|
||||
>
|
||||
<slot :offset="i" />
|
||||
</div>
|
||||
</div>
|
||||
<div :style="styleAfter">
|
||||
<div
|
||||
v-for="i in itemsAfter"
|
||||
:key="i"
|
||||
style="height:100%;width:100%"
|
||||
>
|
||||
<slot :offset="i" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:100%;width:100%">
|
||||
<slot :offset="0" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
import CalendarMonth from './Month.js';
|
||||
import CalendarMonths from './Months.js';
|
||||
import CalendarYears from './Years.js';
|
||||
import CalendarWeek from './Week.js';
|
||||
import CalendarWeeks from './Weeks.js';
|
||||
import CalendarDay from './Day.js';
|
||||
import CalendarMinimized from './Minimized.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
import CalendarDates from '../../composables/CalendarDates.js';
|
||||
|
||||
const todayDate = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const today = todayDate.getTime()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalendarMonth,
|
||||
CalendarMonths,
|
||||
CalendarYears,
|
||||
CalendarWeek,
|
||||
CalendarWeeks,
|
||||
CalendarDay,
|
||||
CalendarMinimized,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
today,
|
||||
todayDate,
|
||||
date: this.date,
|
||||
focusDate: this.focusDate,
|
||||
size: Vue.computed({ get: () => this.size }),
|
||||
containerHeight: Vue.computed({ get: () => this.containerHeight }),
|
||||
containerWidth: Vue.computed({ get: () => this.containerWidth }),
|
||||
|
||||
events: Vue.computed(() => this.eventsPerDay),
|
||||
filteredEvents: Vue.computed(() => this.filteredEvents),
|
||||
minimized: Vue.computed({ get: () => this.minimized, set: v => this.$emit('update:minimized', v) }),
|
||||
showWeeks: this.showWeeks,
|
||||
noMonthView: this.noMonthView,
|
||||
noWeekView: this.noWeekView,
|
||||
eventsAreNull: Vue.computed(() => this.events === null),
|
||||
mode: Vue.computed(()=>this.mode),
|
||||
selectedEvent: Vue.computed(() => this.selectedEvent),
|
||||
setSelectedEvent: (event)=>{this.selectedEvent = event;},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
events: Array,
|
||||
initialDate: {
|
||||
type: [Date, String],
|
||||
default: new Date()
|
||||
},
|
||||
showWeeks: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
initialMode: {
|
||||
type: String,
|
||||
default: 'month'
|
||||
},
|
||||
minimized: Boolean,
|
||||
noWeekView: Boolean,
|
||||
noMonthView: Boolean
|
||||
},
|
||||
watch:{
|
||||
mode(newVal) {
|
||||
this.$emit('change:mode', newVal)
|
||||
},
|
||||
selectedEvent:{
|
||||
handler(newSelectedEvent) {
|
||||
this.$emit('selectedEvent', newSelectedEvent);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// scroll to the first event if the html element was found
|
||||
scrollTime({focusDate,scrollTime}){
|
||||
// return early if the scrollTime is not set
|
||||
if(!scrollTime) return;
|
||||
// scroll the Stundenplan to the closest event
|
||||
Vue.nextTick(()=>{
|
||||
let previousScrollAnchor = document.getElementById('scroll' + (scrollTime - 1) + this.focusDate.d + this.focusDate.w)
|
||||
let scrollAnchor = document.getElementById('scroll' + scrollTime + this.focusDate.d + this.focusDate.w);
|
||||
if (previousScrollAnchor) {
|
||||
previousScrollAnchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
else {
|
||||
if (scrollAnchor) {
|
||||
scrollAnchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'select:day',
|
||||
'select:event',
|
||||
'change:range',
|
||||
'update:minimized',
|
||||
'selectedEvent',
|
||||
'change:offset'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
header: '',
|
||||
prevMode: null,
|
||||
currMode: null,
|
||||
date: new CalendarDate(),
|
||||
focusDate: new CalendarDate(),
|
||||
size: 0,
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
selectedEvent:null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizeClass() {
|
||||
// mainly determines calendar font-size
|
||||
return 'fhc-calendar-' + ['xs', 'sm', 'md', 'lg'][this.size];
|
||||
},
|
||||
mode: {
|
||||
get() { return this.minimized ? 'minimized' : this.currMode; },
|
||||
set(v) {
|
||||
if (!v && this.prevMode) {
|
||||
this.currMode = this.prevMode;
|
||||
this.prevMode = null;
|
||||
} else {
|
||||
this.prevMode = this.currMode;
|
||||
this.currMode = v;
|
||||
}
|
||||
}
|
||||
},
|
||||
eventsPerDay() {
|
||||
if (!this.events)
|
||||
return {};
|
||||
return this.events.reduce((result, event) => {
|
||||
let days = Math.ceil((event.end - event.start) / 86400000) || 1;
|
||||
while (days-- > 0) {
|
||||
let day = (new Date(event.start.getFullYear(), event.start.getMonth(), event.start.getDate() + days)).toDateString();
|
||||
if (!result[day])
|
||||
result[day] = [];
|
||||
result[day].push(event);
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
},
|
||||
// returns the hour of the earliest event, used to scroll to the events in the calendar (week / day view)
|
||||
scrollTime() {
|
||||
// return the first beginning time of the filtered events
|
||||
if(this.filteredEvents && Array.isArray(this.filteredEvents) && this.filteredEvents.length > 0)
|
||||
{
|
||||
let scrollTime = parseInt(this.filteredEvents.sort((a, b) => parseInt(a.beginn) - parseInt(b.beginn))[0].beginn);
|
||||
// to ensure that the scrollTime watcher triggers even if the scrollTime doesn't change, it returns both the scrollTime and the focusDate
|
||||
return { focusDate: this.focusDate, scrollTime };
|
||||
}
|
||||
// there is no event that matches the current view mode constraints
|
||||
else
|
||||
{
|
||||
return { focusDate: this.focusDate, scrollTime: null };
|
||||
}
|
||||
},
|
||||
// filters the events based on the current calendar view mode
|
||||
// week view - filter events based on their week
|
||||
// day view - filter events based on their day and week
|
||||
// month view - does not filter the events
|
||||
filteredEvents: function(){
|
||||
if (this.events && Array.isArray(this.events) && this.events.length > 0) {
|
||||
let filteredEvents = this.events.filter(event => {
|
||||
let eventDate = new CalendarDate(new Date(event.datum));
|
||||
if (this.mode == 'week' || this.mode == 'Week')
|
||||
{
|
||||
// week view filters the elements only for the same week
|
||||
return this.focusDate.w == eventDate.w;
|
||||
}
|
||||
else if (this.mode == 'day' || this.mode == 'Day')
|
||||
{
|
||||
// day view filters the elements for the same day and the same week
|
||||
return this.focusDate.d == eventDate.d && this.focusDate.w == eventDate.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
// returns all the events, does not filter the events
|
||||
return true;
|
||||
}
|
||||
})
|
||||
|
||||
return filteredEvents;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setMode(mode) {
|
||||
this.mode = mode
|
||||
},
|
||||
handleInput(day) {
|
||||
this.$emit(day[0], day[1]);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const initMode = this.initialMode.toLowerCase()
|
||||
const allowedInitialModes = ['day'];
|
||||
if (!this.noWeekView)
|
||||
allowedInitialModes.push('week');
|
||||
if (!this.noMonthView)
|
||||
allowedInitialModes.push('month');
|
||||
this.mode = allowedInitialModes[allowedInitialModes.indexOf(initMode)] || allowedInitialModes.pop();
|
||||
this.date.set(new Date(this.initialDate));
|
||||
this.focusDate.set(this.date);
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.container) {
|
||||
new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
const w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
|
||||
const h = entry.contentBoxSize ? entry.contentBoxSize[0].blockSize : entry.contentRect.height;
|
||||
|
||||
// https://getbootstrap.com/docs/5.0/layout/breakpoints/
|
||||
// bootstrap breakpoints watch window size and this function monitors container size of calendar itself.
|
||||
// calendar is using bootstrap breakpoints which influence layout, which retriggers this function
|
||||
// -> some width constellations will loop so we dont use values around bs5 breakpoints
|
||||
// ['xs', 'sm', 'md', 'lg'][this.size]
|
||||
if (w >= 600)
|
||||
this.size = 3;
|
||||
else if (w >= 350)
|
||||
this.size = 2;
|
||||
else if (w >= 250)
|
||||
this.size = 1;
|
||||
else
|
||||
this.size = 0;
|
||||
|
||||
this.containerWidth = w
|
||||
this.containerHeight = h
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
},
|
||||
unmounted(){
|
||||
CalendarDates.cleanup();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="container" class="fhc-calendar card h-100" :class="sizeClass">
|
||||
<component :is="'calendar-' + mode" @updateMode="mode = $event" @change:range="$emit('change:range',$event)"
|
||||
@input="handleInput" @change:offset="$emit('change:offset', $event)">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads" ></slot>
|
||||
</template>
|
||||
<template #monthPage="{event,day}">
|
||||
<slot name="monthPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
<template #weekPage="{event,day}">
|
||||
<slot name="weekPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
<template #dayPage="{event,day,mobile}">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="mobile"></slot>
|
||||
</template>
|
||||
<template #pageMobilContent="{lvMenu}">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu"></slot>
|
||||
</template>
|
||||
<template #pageMobilContentEmpty>
|
||||
<slot name="pageMobilContentEmpty" ></slot>
|
||||
</template>
|
||||
<template #minimizedPage="{event,day}">
|
||||
<slot name="minimizedPage" :event="event" :day="day"></slot>
|
||||
</template>
|
||||
</component>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarDayPage from './Day/Page.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarDayPage,
|
||||
CalendarPane
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.wYear + ' KW ' + this.focusDate.w;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
let previousDate = new CalendarDate(this.focusDate);
|
||||
this.focusDate.d += dir;
|
||||
this.emitRangeChanged(previousDate);
|
||||
},
|
||||
emitRangeChanged(previousDate) {
|
||||
this.$emit('change:range', { start: previousDate, end:this.focusDate });
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: -1 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: 1 });
|
||||
},
|
||||
selectEvent(event) {
|
||||
this.$emit('input', ['select:event', event]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'week')">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-day-page :active="slot.active" :year="focusDate.y" :week="focusDate.w+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" >
|
||||
<template #dayPage="{event,day,mobile}">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="mobile" ></slot>
|
||||
</template>
|
||||
<template #pageMobilContent="{lvMenu}">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu" ></slot>
|
||||
</template>
|
||||
<template #pageMobilContentEmpty>
|
||||
<slot name="pageMobilContentEmpty" ></slot>
|
||||
</template>
|
||||
</calendar-day-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
import LvModal from "../../../components/Cis/Mylv/LvModal.js";
|
||||
|
||||
import ApiStundenplan from '../../../api/factory/stundenplan.js';
|
||||
import ApiAddons from '../../../api/factory/addons.js';
|
||||
|
||||
function ggt(m, n) {
|
||||
return n == 0 ? m : ggt(n, m % n);
|
||||
}
|
||||
|
||||
function kgv(m, n) {
|
||||
return (m * n) / ggt(m, n);
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DayPage',
|
||||
components: {
|
||||
LvModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hourPosition: null,
|
||||
curHourPosition: null,
|
||||
hourPositionTime: null,
|
||||
lvMenu: null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'noMonthView',
|
||||
'filteredEvents',
|
||||
'isSliding',
|
||||
'calendarScrollTop',
|
||||
'calendarClientHeight',
|
||||
'setSelectedEvent',
|
||||
'selectedEvent',
|
||||
'rowMinHeight'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
week: Number,
|
||||
active: Boolean,
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
watch: {
|
||||
//TODO: on first render non of the day-page components are active and the watcher on selectedEvent does not fetch the lvMenu
|
||||
//TODO: workaround is to watch the active state and refetch in case the lvMenu is empty
|
||||
active: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
if (!this.lvMenu) {
|
||||
this.fetchLvMenu(this.selectedEvent);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
eventsPerDayAndHour: {
|
||||
handler(newEvents) {
|
||||
// if no event is selected, select the first event of the day
|
||||
if (this.selectedEvent == null && newEvents[this.day.toDateString()]?.events.length > 0) {
|
||||
let events = newEvents[this.day.toDateString()]?.events;
|
||||
if (Array.isArray(events) && events.length > 0) {
|
||||
this.setSelectedEvent(events[0].orig);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
selectedEvent: {
|
||||
handler(event) {
|
||||
// return early if the day-page component is not the active carousel item
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
this.lvMenu = null;
|
||||
this.fetchLvMenu(event);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isSliding: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.setSelectedEvent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
overlayStyle() {
|
||||
return {
|
||||
'background-color': '#F5E9D7',
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': 2,
|
||||
height: this.getDayTimePercent + '%',
|
||||
opacity: 0.5,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
},
|
||||
pageHeaderStyle() {
|
||||
return {
|
||||
'z-index': 4,
|
||||
'grid-template-columns': 'repeat(' + this.day.length + ', 1fr)',
|
||||
'grid-template-rows': 1,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}
|
||||
},
|
||||
dayText(){
|
||||
if(!this.day)return {};
|
||||
return {
|
||||
heading: this.day.toLocaleString(this.$p.user_locale.value, { dateStyle: 'short' }),
|
||||
tag: this.day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 2 ? 'narrow' : (this.size < 3 ? 'short' : 'long') }),
|
||||
datum: this.day.toLocaleString(this.$p.user_locale.value, [{ day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { dateStyle: 'short' }][this.size]),
|
||||
}
|
||||
},
|
||||
noLvStyle() {
|
||||
return {
|
||||
top: (this.calendarScrollTop + 100) + 'px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
'text-align': 'center',
|
||||
width: '100%',
|
||||
'z-index': 1,
|
||||
}
|
||||
},
|
||||
indicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '3.5rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.hourPosition + 'px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
curTime() {
|
||||
const now = new Date();
|
||||
return String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
|
||||
},
|
||||
curIndicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '7rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.getDayTimePercent + '%',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
noEventsCondition() {
|
||||
return !this.isSliding && (this.filteredEvents?.length === 0 || !this.filteredEvents);
|
||||
},
|
||||
hours() {
|
||||
// returns an array with elements starting at 7 and ending at 24
|
||||
return [...Array(24).keys()].filter(hour => hour >= 7 && hour <= 24);
|
||||
},
|
||||
day() {
|
||||
return new Date(this.focusDate.y, this.focusDate.m, this.focusDate.d);
|
||||
},
|
||||
eventsPerDayAndHour() {
|
||||
// return early if the calendar pane is sliding
|
||||
if (this.isSliding) return {};
|
||||
|
||||
const res = {};
|
||||
|
||||
let key = this.day.toDateString();
|
||||
|
||||
let nextDay = new Date(this.day);
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
nextDay.setMilliseconds(nextDay.getMilliseconds() - 1);
|
||||
let d = {events: [], lanes: 1};
|
||||
if (this.events[key]) {
|
||||
this.events[key].forEach(evt => {
|
||||
let event = {
|
||||
orig: evt,
|
||||
lane: 1,
|
||||
maxLane: 1,
|
||||
start: evt.start < this.day ? this.day : evt.start,
|
||||
end: evt.end > nextDay ? nextDay : evt.end,
|
||||
shared: [],
|
||||
setSharedMaxRecursive(doneItems) {
|
||||
this.maxLane = Math.max(doneItems[0].maxLane, this.maxLane);
|
||||
doneItems.push(this);
|
||||
this.shared.filter(other => !doneItems.includes(other)).forEach(i => i.setSharedMaxRecursive(doneItems));
|
||||
}
|
||||
};
|
||||
event.shared = d.events.filter(other => other.start < event.end && other.end > event.start);
|
||||
event.shared.forEach(other => other.shared.push(event));
|
||||
let occupiedLanes = event.shared.map(other => other.lane);
|
||||
while (occupiedLanes.includes(event.lane))
|
||||
event.lane++;
|
||||
event.maxLane = Math.max(...[event.lane], ...occupiedLanes);
|
||||
if (event.maxLane > 1) {
|
||||
event.setSharedMaxRecursive([event]);
|
||||
}
|
||||
d.events.push(event);
|
||||
});
|
||||
d.lanes = d.events.map(e => e.maxLane).reduce((res, i) => kgv(res, i), 1);
|
||||
}
|
||||
res[key] = d;
|
||||
|
||||
return res;
|
||||
},
|
||||
smallestTimeFrame() {
|
||||
return [30, 15, 10, 5][this.size];
|
||||
},
|
||||
lookingAtToday() {
|
||||
return this.date.compare(this.todayDate)
|
||||
},
|
||||
getDayTimePercent() {
|
||||
const now = new Date(Date.now())
|
||||
const currentMinutes = now.getMinutes() + now.getHours() * 60
|
||||
let timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
|
||||
return timePercentage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dayGridStyle(day) {
|
||||
const styleObj = {
|
||||
'grid-template-columns': '1 1fr',
|
||||
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
|
||||
}
|
||||
|
||||
if(this.date.compare(this.todayDate)) {
|
||||
styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)'
|
||||
styleObj['border-color'] = '#E8E8E8';
|
||||
// styleObj.opacity = 0.5; // would opaque the whole column
|
||||
}
|
||||
|
||||
return styleObj
|
||||
},
|
||||
fetchLvMenu(event) {
|
||||
if (event && event.type == 'lehreinheit') {
|
||||
this.$api
|
||||
.call(ApiStundenplan.getLehreinheitStudiensemester(event.lehreinheit_id[0]))
|
||||
.then(res => res.data)
|
||||
.then(studiensemester_kurzbz => this.$api.call(
|
||||
ApiAddons.getLvMenu(
|
||||
event.lehreinheit_id,
|
||||
studiensemester_kurzbz
|
||||
)
|
||||
))
|
||||
.then(res => {
|
||||
if (res.data) {
|
||||
this.lvMenu = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
hourGridIdentifier(hour) {
|
||||
// this is the id attribute that is responsible to scroll the calender to the first event
|
||||
return 'scroll' + hour + this.focusDate.d + this.week;
|
||||
},
|
||||
hourGridStyle(hour) {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
top: this.getAbsolutePositionForHour(hour),
|
||||
left: 0,
|
||||
right: 0,
|
||||
'z-index': 0,
|
||||
}
|
||||
},
|
||||
eventGridStyle(day, event) {
|
||||
return {
|
||||
'z-index': 1,
|
||||
'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane,
|
||||
'grid-column-end': 1 + event.lane * day.lanes / event.maxLane,
|
||||
'grid-row-start': this.dateToMinutesOfDay(event.start),
|
||||
'grid-row-end': this.dateToMinutesOfDay(event.end),
|
||||
'background-color': event.orig.color,
|
||||
'--test': this.dateToMinutesOfDay(event.end),
|
||||
}
|
||||
},
|
||||
eventClick(evt) {
|
||||
let event = evt.orig;
|
||||
this.setSelectedEvent(event);
|
||||
this.$emit('input', event);
|
||||
},
|
||||
calcHourPosition(event) {
|
||||
let height = this.$refs.eventcontainer.getBoundingClientRect().height;
|
||||
let top = this.$refs.eventcontainer.getBoundingClientRect().top;
|
||||
let position = event.clientY - top;
|
||||
// position percentage of total height
|
||||
let timePercentage = (position / height) * 100;
|
||||
// minute percentage of total minutes
|
||||
let result = (this.hours.length * 60) * (timePercentage / 100);
|
||||
// calculate time in float
|
||||
let currentMinutes = ((result + (this.hours[0] * 60)) / 60);
|
||||
// get hour part of time
|
||||
let currentHour = Math.floor(currentMinutes);
|
||||
// get float part of time
|
||||
let minutePercentage = currentMinutes % currentHour;
|
||||
// calculate minutes from float part of time
|
||||
let minute = Math.round(60 * minutePercentage);
|
||||
// convert minutes to 5 minute interval
|
||||
if (minute % 5 != 0) {
|
||||
minute = Math.round(minute / 5) * 5;
|
||||
}
|
||||
// in case the rounding made the minutes 60, increase the hour and reset the minutes
|
||||
if (minute == 60) {
|
||||
currentHour++;
|
||||
minute = 0;
|
||||
}
|
||||
|
||||
// ## after rounding the time to the nearest 5 Minute interval, we have to convert the time back to the relative position
|
||||
// convert current time in minutes
|
||||
currentMinutes = currentHour * 60 + minute;
|
||||
// calculate the minutes percentage of the total minutes
|
||||
timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
// calculate the relative position of the time percentage
|
||||
position = height * (timePercentage / 100);
|
||||
this.hourPosition = position;
|
||||
|
||||
// add padding to minutes that consist of only one digit
|
||||
minute.toString().length == 1 ? minute = "0" + minute : minute;
|
||||
this.hourPositionTime = currentHour + ":" + minute;
|
||||
},
|
||||
getAbsolutePositionForHour(hour) {
|
||||
// used for the absolute positioning of the gutters of hours
|
||||
return (100 / this.hours.length) * (hour - (24 - this.hours.length)) + '%';
|
||||
},
|
||||
changeToMonth(day) {
|
||||
if (!this.noMonthView) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'month');
|
||||
}
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
return Math.floor(((day.getHours() - 7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
}
|
||||
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day-page h-100">
|
||||
<div class="row m-0 h-100">
|
||||
<div class="col-12 col-xl-6 p-0 h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div ref="header" class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle">
|
||||
<div type="button" class="flex-grow-1" :title="dayText.heading" @click.prevent="changeToMonth(day)">
|
||||
<div class="fw-bold">{{dayText.tag}}</div>
|
||||
<a href="#" class="small text-secondary text-decoration-none" >{{dayText.datum}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scroll g-0" style="height: 100%; overflow-y: scroll;">
|
||||
|
||||
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
|
||||
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
|
||||
|
||||
<Transition>
|
||||
<div v-if="hourPosition && !noEventsCondition" class="position-absolute border-top small" :style="indicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition>
|
||||
<div v-if="lookingAtToday && !noEventsCondition" class="position-absolute border-top small" :style="curIndicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{curTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div>
|
||||
<h1 v-if="noEventsCondition" class="m-0 text-secondary" ref="noEventsText" :style="noLvStyle">{{ $p.t('lehre/noLvFound') }}</h1>
|
||||
<div :class="{'fhc-calendar-no-events-overlay':noEventsCondition, 'events':true}">
|
||||
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" :style="'min-height:' + rowMinHeight " :key="hour" class="text-muted text-end small" :ref="'hour' + hour">{{hour}}:00</div>
|
||||
</div>
|
||||
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle(day)">
|
||||
<div v-if="lookingAtToday && !noEventsCondition" :style="overlayStyle"></div>
|
||||
<div v-for="event in day.events" :key="event" :style="eventGridStyle(day,event)" v-contrast
|
||||
:selected="event.orig == selectedEvent" class="fhc-entry mx-2 small rounded overflow-hidden" >
|
||||
<!-- desktop version of the page template, parent receives slotProp mobile = false -->
|
||||
<div class="d-none d-xl-block h-100 " @click.prevent="eventClick(event)">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="false">
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</div>
|
||||
<!-- mobile version of the page template, parent receives slotProp mobile = true -->
|
||||
<div class="d-block d-xl-none h-100" @click.prevent="eventClick(event)">
|
||||
<slot name="dayPage" :event="event" :day="day" :mobile="true">
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-xl-block col-xl-6 p-4 d-none" style="max-height: 100%">
|
||||
<div style="z-index:0; max-height: 100%" class="sticky-top d-flex justify-content-center align-items-center flex-column">
|
||||
<div style="max-height: 100%; overflow-y:auto;" class="w-100">
|
||||
<template v-if="selectedEvent && lvMenu">
|
||||
<slot name="pageMobilContent" :lvMenu="lvMenu" >
|
||||
<p>this is a slot placeholder</p>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-else-if="noEventsCondition">
|
||||
<slot name="pageMobilContentEmpty" >
|
||||
<h3>This is an slot placeholder</h3>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="p-4 d-flex w-100 justify-content-center align-items-center">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
export default {
|
||||
data(){
|
||||
return{
|
||||
selected: this.mode,
|
||||
modes:{
|
||||
day: { mode_bezeichnung: "day", icon: "fa-calendar-day" , condition:true},
|
||||
week: { mode_bezeichnung: "week", icon: "fa-calendar-week", condition: !this.noWeekView },
|
||||
month: { mode_bezeichnung: "month", icon: "fa-calendar-days", condition: !this.noMonthView },
|
||||
},
|
||||
headerPadding:null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'eventsAreNull',
|
||||
'size',
|
||||
'mode',
|
||||
'noWeekView',
|
||||
'noMonthView',
|
||||
'containerWidth'
|
||||
],
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'prev',
|
||||
'next',
|
||||
'click'
|
||||
],
|
||||
computed: {
|
||||
getHeaderClassSide() {
|
||||
return this.containerWidth > 780 ? 'col-3' : 'col-12'
|
||||
},
|
||||
getHeaderClassMiddle() {
|
||||
return this.containerWidth > 780 ? 'col-6' : 'col-12'
|
||||
}
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="calendar-header card-header w-100">
|
||||
<div class="row align-items-center ">
|
||||
<div :class="getHeaderClassSide" class="d-flex justify-content-center justify-content-md-start align-items-center">
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</div>
|
||||
<div :class="getHeaderClassMiddle" :style="{'padding-left':headerPadding}">
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-auto p-2">
|
||||
<button class="btn btn-outline-secondary border-0" :class="{'btn-sm':!this.size}" @click="$emit('prev')"><i class="fa fa-chevron-left"></i></button>
|
||||
</div>
|
||||
<div class="justify-content-center text-center col-auto">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<button class="btn btn-link link-secondary text-decoration-none" :class="{'btn-sm': !this.size}" @click="$emit('click')">
|
||||
{{ title }}
|
||||
<i v-if="eventsAreNull" class="fa fa-spinner fa-pulse"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto p-2">
|
||||
<button class="btn btn-outline-secondary border-0" :class="{'btn-sm': !this.size}" @click="$emit('next')"><i class="fa fa-chevron-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="viewButtons" v-if="!noWeekView && !noMonthView" :class="getHeaderClassSide" class="d-flex justify-content-center justify-content-md-end align-items-center" style="pointer-events: none;">
|
||||
<div style="pointer-events: all;">
|
||||
<div role="group" aria-label="Kalender Modus">
|
||||
<button type="button" :class="{'active':mode_kurzbz.toLowerCase() === mode.toLowerCase()}" style="margin-right: 4px;" @click.prevent="$emit('updateMode',mode_kurzbz)" class="btn btn-outline-secondary" v-for="({mode_bezeichnung,icon,condition},mode_kurzbz) in modes">
|
||||
<i v-if="condition" class="fa" :class="icon" ></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import FhcCalendar from "./Base.js";
|
||||
|
||||
import ApiLvPlan from '../../api/factory/lvPlan.js';
|
||||
|
||||
import { useEventLoader } from '../../composables/EventLoader.js';
|
||||
|
||||
import ModeDay from './Mode/Day.js';
|
||||
import ModeWeek from './Mode/Week.js';
|
||||
import ModeMonth from './Mode/Month.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarLvPlan",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
inject: [
|
||||
"renderers"
|
||||
],
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: luxon.DateTime.local()
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'Week'
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
modes: {
|
||||
day: Vue.markRaw(ModeDay),
|
||||
week: Vue.markRaw(ModeWeek),
|
||||
month: Vue.markRaw(ModeMonth)
|
||||
},
|
||||
modeOptions: {
|
||||
day: {
|
||||
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
|
||||
emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound'))
|
||||
},
|
||||
week: {
|
||||
collapseEmptyDays: false
|
||||
}
|
||||
},
|
||||
teachingunits: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
backgrounds() {
|
||||
let now = luxon.DateTime.now().setZone(this.timezone);
|
||||
|
||||
if (this.mode == 'Month')
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now.startOf('day')
|
||||
}
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
class: 'background-past',
|
||||
end: now,
|
||||
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
eventStyle(event) {
|
||||
if (!event.farbe)
|
||||
return undefined;
|
||||
return '--event-bg:#' + event.farbe;
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.rangeInterval = rangeInterval;
|
||||
this.$emit('update:range', rangeInterval);
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
|
||||
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
|
||||
Vue.watch(lv, newValue => {
|
||||
context.emit('update:lv', newValue);
|
||||
});
|
||||
|
||||
return {
|
||||
rangeInterval,
|
||||
events,
|
||||
lv
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$api
|
||||
.call(ApiLvPlan.getStunden())
|
||||
.then(res => {
|
||||
return this.teachingunits = res.data.map(el => ({
|
||||
id: el.stunde,
|
||||
start: el.beginn,
|
||||
end: el.ende
|
||||
}));
|
||||
});
|
||||
},
|
||||
template: /* html */`
|
||||
<fhc-calendar
|
||||
ref="calendar"
|
||||
class="fhc-calendar-lvplan"
|
||||
:date="date"
|
||||
:modes="modes"
|
||||
:mode-options="modeOptions"
|
||||
:mode="mode"
|
||||
:timezone="timezone"
|
||||
:locale="$p.user_locale.value"
|
||||
:events="events || []"
|
||||
:backgrounds="backgrounds"
|
||||
:time-grid="teachingunits"
|
||||
show-btns
|
||||
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
|
||||
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
|
||||
@update:range="updateRange"
|
||||
>
|
||||
<template v-slot="{ event, mode }">
|
||||
<div
|
||||
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
|
||||
:type="mode == 'day' ? 'button' : undefined"
|
||||
:style="eventStyle(event)"
|
||||
>
|
||||
<component
|
||||
v-if="mode == 'event'"
|
||||
:is="renderers[event.type]?.modalContent"
|
||||
:event="event"
|
||||
></component>
|
||||
<component
|
||||
v-else-if="mode == 'eventheader'"
|
||||
:is="renderers[event.type]?.modalTitle"
|
||||
:event="event"
|
||||
></component>
|
||||
<component
|
||||
v-else
|
||||
:is="renderers[event.type]?.calendarEvent"
|
||||
:event="event"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<slot />
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'minimized',
|
||||
'date',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
start: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
maximize() {
|
||||
this.minimized = false;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-minimized h-100 d-flex flex-column">
|
||||
<slot name="minimizedPage"></slot>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import DayView from './Day/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeDay",
|
||||
components: {
|
||||
BaseSlider,
|
||||
DayView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen",
|
||||
"requestModalClose"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('day');
|
||||
let last = this.focusDate.endOf('day');
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ days: this.rangeOffset });
|
||||
} else {
|
||||
last = last.plus({ days: this.rangeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(days) {
|
||||
const newFocusDate = this.focusDate.plus({ days });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
viewAttrs(days) {
|
||||
const day = this.focusDate.plus({ days });
|
||||
return { ...this.$attrs, day };
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-day flex-grow-1 position-relative"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<day-view
|
||||
ref="view"
|
||||
v-bind="viewAttrs(slot.offset)"
|
||||
@request-modal-open="$emit('requestModalOpen', $event)"
|
||||
@request-modal-close="$emit('requestModalClose', $event)"
|
||||
>
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</day-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelTime from '../../Base/Label/Time.js';
|
||||
|
||||
import { useResizeObserver } from '../../../../composables/Responsive.js';
|
||||
|
||||
export default {
|
||||
name: "DayView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelDay,
|
||||
LabelDow,
|
||||
LabelTime
|
||||
},
|
||||
inject: {
|
||||
timeGrid: "timeGrid",
|
||||
originalEvents: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
emptyMessage: String,
|
||||
emptyMessageDetails: String
|
||||
},
|
||||
emits: [
|
||||
"requestModalOpen",
|
||||
"requestModalClose"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
chosenEvent: null,
|
||||
gridMainRef: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
axisMain() {
|
||||
return [this.day.startOf('day')];
|
||||
},
|
||||
axisParts() {
|
||||
if (this.timeGrid) {
|
||||
// create {start, end} array
|
||||
return this.timeGrid.map(tu => {
|
||||
return {
|
||||
start: luxon.Duration.fromISOTime(tu.start),
|
||||
end: luxon.Duration.fromISOTime(tu.end)
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// create 07:00-23:00
|
||||
return Array.from({ length: 17 }, (e, i) => luxon.Duration.fromObject({ hours: i + 7 }));
|
||||
}
|
||||
},
|
||||
events() {
|
||||
return this.originalEvents
|
||||
.filter(event => event.start < this.day.plus({ days: 1 }) && event.end > this.day)
|
||||
.sort((a, b) => a.start.ts - b.start.ts)
|
||||
.map(evt => evt.orig);
|
||||
},
|
||||
currentEvent() {
|
||||
if (this.chosenEvent) {
|
||||
if (this.events.find(e => e == this.chosenEvent))
|
||||
return this.chosenEvent;
|
||||
}
|
||||
let first = null;
|
||||
if (this.events)
|
||||
first = this.events.find(Boolean); // undefined => none found
|
||||
|
||||
if (first && first.type == 'loading')
|
||||
return null; // null => loading
|
||||
|
||||
return first;
|
||||
},
|
||||
isToday() {
|
||||
return this.day.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
compact() {
|
||||
if (this.compact) {
|
||||
if (this.chosenEvent) {
|
||||
this.$emit('requestModalOpen', {
|
||||
event: this.chosenEvent,
|
||||
closeFn: () => { this.chosenEvent = null; }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.$emit('requestModalClose');
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickDefaults(evt) {
|
||||
if (evt.detail.source == 'event') {
|
||||
this.chosenEvent = evt.detail.value;
|
||||
if (this.compact) {
|
||||
this.$emit('requestModalOpen', {
|
||||
event: this.chosenEvent,
|
||||
closeFn: () => { this.chosenEvent = null; }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const container = Vue.ref(null); // use useTemplateRef when updating to Vue 3.5
|
||||
const { compact } = useResizeObserver(container, 750);
|
||||
|
||||
return {
|
||||
container, // must be exposed or it won't be set in Vue < 3.5
|
||||
compact
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.gridMainRef = this.$refs.grid.$refs.main;
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
ref="container"
|
||||
class="fhc-calendar-mode-day-view d-flex h-100"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<calendar-grid
|
||||
ref="grid"
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
:snap-to-grid="!!timeGrid"
|
||||
all-day-events
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<div :class="{ today: isToday }">
|
||||
<label-dow
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
<label-day
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-time v-bind="{ part }" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<div v-if="slot.event.type == 'loading'" class="placeholder-glow h-100 opacity-50">
|
||||
<span class="placeholder w-100 h-100" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" mode="day" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
<Teleport :disabled="!gridMainRef" :to="gridMainRef">
|
||||
<div
|
||||
v-if="emptyMessage && currentEvent !== null && !currentEvent"
|
||||
class="fhc-calendar-no-events-overlay"
|
||||
style="position:absolute;inset:0"
|
||||
>
|
||||
{{ emptyMessage }}
|
||||
</div>
|
||||
</Teleport>
|
||||
<div class="event-details" v-if="!compact">
|
||||
<div
|
||||
v-if="currentEvent === null"
|
||||
class="p-4 d-flex w-100 justify-content-center align-items-center"
|
||||
>
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
<h3 v-else-if="!currentEvent">{{ emptyMessageDetails }}</h3>
|
||||
<slot v-else :event="currentEvent" mode="event" />
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import ListView from './List/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeList",
|
||||
components: {
|
||||
BaseSlider,
|
||||
ListView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
default: 7
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate;
|
||||
let last = first.plus({ days: this.length });
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ days: this.rangeOffset });
|
||||
} else {
|
||||
last = first.plus({ days: this.rangeOffset + this.length });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('day').diff(this.focusDate.startOf('day'), 'days').days;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(offset) {
|
||||
const newFocusDate = this.focusDate.plus({ days: offset });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
viewAttrs(offset) {
|
||||
const day = this.focusDate.plus({ days: offset });
|
||||
return { day, length: this.length };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-list flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<list-view v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" /></template>
|
||||
</list-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
|
||||
import CalDnd from '../../../../directives/Calendar/DragAndDrop.js';
|
||||
import CalClick from '../../../../directives/Calendar/Click.js';
|
||||
|
||||
// TODO(chris): drag and drop
|
||||
|
||||
export default {
|
||||
name: "ListView",
|
||||
components: {
|
||||
LabelDay,
|
||||
LabelDow
|
||||
},
|
||||
directives: {
|
||||
CalDnd,
|
||||
CalClick
|
||||
},
|
||||
inject: {
|
||||
draggableEvents: "draggableEvents",
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chosenEvent: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
days() {
|
||||
return Array.from({ length: this.length }, (e, days) => this.day.plus({ days }));
|
||||
},
|
||||
eventsPerDay() {
|
||||
const eventsPerDay = this.days.map(day => {
|
||||
return {
|
||||
day,
|
||||
events: this.events
|
||||
.filter(event => event.start < day.plus({ days: 1 }) && event.end > day)
|
||||
.sort((a, b) => a.start.ts - b.start.ts)
|
||||
};
|
||||
});
|
||||
return eventsPerDay.filter(day => day.events.length);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
draggable(event) {
|
||||
return this.draggableEvents(event.orig, 'list');
|
||||
},
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-list-view h-100 overflow-auto">
|
||||
<div v-if="!eventsPerDay.length" class="h-100">
|
||||
<slot :event="undefined" mode="list" />
|
||||
</div>
|
||||
<div v-for="{ day, events } in eventsPerDay">
|
||||
<div
|
||||
class="text-center"
|
||||
:class="{ today: isToday(day) }"
|
||||
>
|
||||
<label-dow
|
||||
:date="day"
|
||||
class="d-inline"
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
/>
|
||||
,
|
||||
<label-day :date="day" class="d-inline" />
|
||||
</div>
|
||||
<div v-for="event in events">
|
||||
<div v-if="event.type == 'loading'" class="placeholder-glow opacity-50">
|
||||
<span class="placeholder w-100" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="event"
|
||||
:draggable="draggable(event)"
|
||||
v-cal-dnd:draggable="event"
|
||||
v-cal-click:event="event.orig"
|
||||
>
|
||||
<slot :event="event.orig" mode="list" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import MonthView from './Month/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeMonth",
|
||||
components: {
|
||||
BaseSlider,
|
||||
MonthView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
let last = first.plus({ days: 41 }).endOf('day'); // NOTE(chris): 6 weeks minus 1 day
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
const nextFocusDate = this.focusDate.plus({ months: this.rangeOffset});
|
||||
const nextRangeStart = nextFocusDate.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
if (this.rangeOffset < 0) {
|
||||
first = nextRangeStart;
|
||||
} else {
|
||||
last = nextRangeStart.plus({ days: 41 }).endOf('day');
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('month').diff(this.focusDate.startOf('month'), 'months').months;
|
||||
if (this.rangeOffset) {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(months) {
|
||||
const newFocusDate = this.focusDate.plus({ months });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
viewAttrs(months) {
|
||||
const day = this.focusDate.plus({ months });
|
||||
return { day };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'week':
|
||||
// default: Move to week if not in month
|
||||
let dayInWeek = luxon.DateTime.fromObject({
|
||||
localWeekNumber: evt.detail.value.number,
|
||||
localWeekYear: evt.detail.value.year
|
||||
}, {
|
||||
zone: this.currentDate.zoneName,
|
||||
locale: this.currentDate.locale
|
||||
});
|
||||
|
||||
if (!this.focusDate.hasSame(dayInWeek.startOf('week', { useLocaleWeeks: true }), 'month')) {
|
||||
this.$emit('update:currentDate', dayInWeek.startOf('week', { useLocaleWeeks: true }));
|
||||
} else if (!this.focusDate.hasSame(dayInWeek.endOf('week', { useLocaleWeeks: true }), 'month')) {
|
||||
this.$emit('update:currentDate', dayInWeek.endOf('week', { useLocaleWeeks: true }));
|
||||
}
|
||||
break;
|
||||
case 'day':
|
||||
// default: Set current-date
|
||||
this.$emit('update:currentDate', evt.detail.value);
|
||||
break;
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-month flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<month-view v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" mode="month" /></template>
|
||||
</month-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelWeek from '../../Base/Label/Week.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
|
||||
export default {
|
||||
name: "MonthView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelWeek,
|
||||
LabelDow,
|
||||
LabelDay
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
// NOTE(chris): snap events to day
|
||||
events: Vue.computed(() => {
|
||||
//const events = [];
|
||||
const events = this.events.map(event => {
|
||||
const start = event.start.startOf('day');
|
||||
const end = event.end.plus({ days: 1 }).startOf('day');
|
||||
return {
|
||||
...event,
|
||||
start,
|
||||
end
|
||||
};
|
||||
});
|
||||
for (var w = 5; w > -1; w--) {
|
||||
for (var d = 6; d > -1; d--) {
|
||||
const startdate = this.axisMain[w].plus(this.axisParts[d]);
|
||||
events.unshift({
|
||||
start: startdate,
|
||||
end: startdate.plus({ days: 1 }),
|
||||
orig: 'header'
|
||||
});
|
||||
}
|
||||
}
|
||||
return events;
|
||||
})
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
axisMain() {
|
||||
const start = this.day.startOf('month').startOf('week', { useLocaleWeeks: true });
|
||||
return Array.from({ length: 6 }, (e, i) => start.plus({ weeks: i }));
|
||||
},
|
||||
axisParts() {
|
||||
return Array.from({ length: 8 }, (e, i) => luxon.Duration.fromObject({ days: i }));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-month-view h-100">
|
||||
<calendar-grid
|
||||
flip-axis
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
|
||||
snap-to-grid
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<label-week v-bind="{ date }" />
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-dow :date="axisMain[0].plus(part)" class="text-center" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<label-day
|
||||
v-if="slot.event.orig == 'header'"
|
||||
:date="slot.event.start"
|
||||
:class="{ disabled: !day.hasSame(slot.event.start, 'month'), today: isToday(slot.event.start) }"
|
||||
/>
|
||||
<div v-else-if="slot.event.type == 'loading'" class="placeholder-glow opacity-50">
|
||||
<span class="placeholder w-100 fs-1" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import BaseSlider from '../Base/Slider.js';
|
||||
import WeekView from './Week/View.js';
|
||||
|
||||
export default {
|
||||
name: "ModeWeek",
|
||||
components: {
|
||||
BaseSlider,
|
||||
WeekView
|
||||
},
|
||||
props: {
|
||||
currentDate: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"update:currentDate",
|
||||
"update:range",
|
||||
"click",
|
||||
"requestModalOpen"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
focusDate: this.currentDate,
|
||||
rangeOffset: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
let first = this.focusDate.startOf('week', { useLocaleWeeks: true });
|
||||
let last = this.focusDate.endOf('week', { useLocaleWeeks: true });
|
||||
|
||||
if (this.rangeOffset != 0) {
|
||||
if (this.rangeOffset < 0) {
|
||||
first = first.plus({ weeks: this.rangeOffset });
|
||||
} else {
|
||||
last = last.plus({ weeks: this.rangeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
return luxon.Interval.fromDateTimes(first, last);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentDate() {
|
||||
if (this.currentDate.locale != this.focusDate.locale) {
|
||||
this.focusDate = this.currentDate;
|
||||
this.$emit('update:range', this.range);
|
||||
} else {
|
||||
this.rangeOffset = this.currentDate.startOf('week', { useLocaleWeeks: true }).diff(this.focusDate.startOf('week', { useLocaleWeeks: true }), 'weeks').weeks;
|
||||
if (this.rangeOffset) {
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
prevPage() {
|
||||
this.rangeOffset = this.$refs.slider.target - 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.prevPage().then(this.updatePage);
|
||||
},
|
||||
nextPage() {
|
||||
this.rangeOffset = this.$refs.slider.target + 1;
|
||||
this.$refs.view.$refs.grid.disableAutoScroll();
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.slider.nextPage().then(this.updatePage);
|
||||
},
|
||||
updatePage(weeks) {
|
||||
const newFocusDate = this.focusDate.plus({ weeks });
|
||||
this.focusDate = newFocusDate;
|
||||
this.rangeOffset = 0;
|
||||
this.$emit('update:currentDate', this.focusDate);
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
viewAttrs(weeks) {
|
||||
const day = this.focusDate.plus({ weeks });
|
||||
return { ...this.$attrs, day };
|
||||
},
|
||||
handleClickDefaults(evt) {
|
||||
switch (evt.detail.source) {
|
||||
case 'day':
|
||||
// default: Set current-date
|
||||
this.$emit('update:currentDate', evt.detail.value);
|
||||
break;
|
||||
case 'event':
|
||||
// default: Request Modal
|
||||
this.$emit('requestModalOpen', { event: evt.detail.value });
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('update:range', this.range);
|
||||
this.$refs.view.$refs.grid.enableAutoScroll();
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="fhc-calendar-mode-week flex-grow-1 position-relative"
|
||||
@cal-click-default.capture="handleClickDefaults"
|
||||
>
|
||||
<base-slider ref="slider" v-slot="slot">
|
||||
<week-view ref="view" v-bind="viewAttrs(slot.offset)">
|
||||
<template v-slot="slot"><slot v-bind="slot" mode="week" /></template>
|
||||
</week-view>
|
||||
</base-slider>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import CalendarGrid from '../../Base/Grid.js';
|
||||
import LabelDay from '../../Base/Label/Day.js';
|
||||
import LabelDow from '../../Base/Label/Dow.js';
|
||||
import LabelTime from '../../Base/Label/Time.js';
|
||||
|
||||
export default {
|
||||
name: "WeekView",
|
||||
components: {
|
||||
CalendarGrid,
|
||||
LabelDay,
|
||||
LabelDow,
|
||||
LabelTime
|
||||
},
|
||||
inject: {
|
||||
timeGrid: "timeGrid",
|
||||
timezone: "timezone"
|
||||
},
|
||||
props: {
|
||||
day: {
|
||||
type: luxon.DateTime,
|
||||
required: true
|
||||
},
|
||||
collapseEmptyDays: Boolean
|
||||
},
|
||||
computed: {
|
||||
start() {
|
||||
return this.day.startOf('week', { useLocaleWeeks: true });
|
||||
},
|
||||
axisMain() {
|
||||
return Array.from({ length: 7 }, (e, i) => this.start.plus({ days: i }));
|
||||
},
|
||||
axisParts() {
|
||||
if (this.timeGrid) {
|
||||
// create {start, end} array
|
||||
return this.timeGrid.map(tu => {
|
||||
return {
|
||||
start: luxon.Duration.fromISOTime(tu.start),
|
||||
end: luxon.Duration.fromISOTime(tu.end)
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// create 07:00-23:00
|
||||
return Array.from({ length: 17 }, (e, i) => luxon.Duration.fromObject({ hours: i + 7 }));
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
|
||||
}
|
||||
},
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-week-view h-100">
|
||||
<calendar-grid
|
||||
ref="grid"
|
||||
:axis-main="axisMain"
|
||||
:axis-parts="axisParts"
|
||||
:axis-main-collapsible="collapseEmptyDays"
|
||||
:snap-to-grid="!!timeGrid"
|
||||
all-day-events
|
||||
>
|
||||
<template #main-header="{ date }">
|
||||
<div :class="{ today: isToday(date) }">
|
||||
<label-dow
|
||||
v-bind="{ date }"
|
||||
@cal-click="evt => evt.detail.source = 'day'"
|
||||
/>
|
||||
<label-day
|
||||
v-bind="{ date }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #part-header="{ part }">
|
||||
<label-time v-bind="{ part }" />
|
||||
</template>
|
||||
<template #event="slot">
|
||||
<div v-if="slot.event.type == 'loading'" class="placeholder-glow h-100 opacity-50">
|
||||
<span class="placeholder w-100 h-100" />
|
||||
</div>
|
||||
<slot v-else v-bind="slot" />
|
||||
</template>
|
||||
</calendar-grid>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarMonthPage from './Month/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarMonthPage,
|
||||
CalendarPane
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
syncOnNextChange: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({month: ['short','long','long','long'][this.size], year: 'numeric'}, this.$p.user_locale.value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
if (this.syncOnNextChange) {
|
||||
this.syncOnNextChange = false;
|
||||
this.focusDate.set(this.date);
|
||||
} else {
|
||||
this.focusDate.moveMonthInDirection(dir)
|
||||
}
|
||||
this.$emit('change:range', {
|
||||
start: new Date(this.focusDate.y, this.focusDate.m, 1),
|
||||
end: new Date(this.focusDate.y, this.focusDate.m+1, 0)
|
||||
});
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
this.$emit('change:offset', { y: 0, m: -1, d: 0 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 1, d: 0 });
|
||||
},
|
||||
selectDay(day) {
|
||||
let m = day.getMonth();
|
||||
if (this.focusDate.m != m) {
|
||||
this.syncOnNextChange = true;
|
||||
if (this.focusDate.m-1 == m || (m == 11 && !this.focusDate.m))
|
||||
this.$refs.pane.prev();
|
||||
else
|
||||
this.$refs.pane.next();
|
||||
} else {
|
||||
this.focusDate.set(this.date);
|
||||
}
|
||||
this.$emit('input', ['select:day',day])
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('change:range', {
|
||||
start: new Date(this.focusDate.y, this.focusDate.m, 1),
|
||||
end: new Date(this.focusDate.y, this.focusDate.m+1, 0)
|
||||
});
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-month">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'months')" >
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-month-page :year="focusDate.y" :month="focusDate.m+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectDay" >
|
||||
<template #monthPage="{event,day}">
|
||||
<slot name="monthPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
</calendar-month-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
|
||||
export default {
|
||||
name: 'MonthPage',
|
||||
data(){
|
||||
return{
|
||||
highlightedWeek: null,
|
||||
highlightedDay: null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'showWeeks',
|
||||
'noWeekView',
|
||||
'selectedEvent',
|
||||
'setSelectedEvent'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
month: Number
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input'
|
||||
],
|
||||
computed: {
|
||||
dayText(){
|
||||
if (!this.size || !this.weeks[0]?.days) return {};
|
||||
let dayTextMap ={};
|
||||
this.weeks[0].days.forEach((day)=>{
|
||||
dayTextMap[day] = day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 1 ? 'narrow' : (this.size < 3 ? 'short' : 'long') });
|
||||
});
|
||||
return dayTextMap;
|
||||
},
|
||||
weeks() {
|
||||
let firstDayOfMonth = new CalendarDate(this.year, this.month, 1);
|
||||
let startDay = firstDayOfMonth.firstDayOfCalendarMonth;
|
||||
let endDay = firstDayOfMonth.lastDayOfCalendarMonth;
|
||||
|
||||
let res = [];
|
||||
let week = {no:0,y:0,days:[]};
|
||||
while (startDay <= endDay) {
|
||||
week.days.push(new Date(startDay));
|
||||
|
||||
if (week.days.length == 7) {
|
||||
let d = new CalendarDate(week.days[5]);
|
||||
week.no = d.w;
|
||||
week.y = d.y;
|
||||
res.push(week);
|
||||
week = {no:0,y:0,days:[]};
|
||||
}
|
||||
startDay.setDate(startDay.getDate() + 1);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
getDayClass(week, day) {
|
||||
let classstring = 'fhc-calendar-month-page-day text-decoration-none overflow-hidden'
|
||||
const isHighlightedWeek = this.isHighlightedWeek(week)
|
||||
const isHighlightedDay = this.isHighlightedDay(day)
|
||||
const isThisDate = this.date.compare(day)
|
||||
const isNotThisMonth = day.getMonth() != this.month
|
||||
const isInThePast = day.getTime() < this.today // this.date is just the focusDate but not the initial Date
|
||||
|
||||
if(isHighlightedWeek) classstring += ' fhc-highlight-week'
|
||||
if(isHighlightedDay) classstring += ' fhc-highlight-day'
|
||||
|
||||
if(isNotThisMonth) classstring += ' opacity-25'
|
||||
if(isInThePast) classstring += ' fhc-calendar-past'
|
||||
return classstring
|
||||
},
|
||||
selectDay(day) {
|
||||
this.date.set(day);
|
||||
this.$emit('input', day);
|
||||
},
|
||||
changeToWeek(week) {
|
||||
if (!this.noWeekView) {
|
||||
if (!this.focusDate.isInWeek(week.no, week.y))
|
||||
this.focusDate.set(week.days[0]);
|
||||
this.$emit('updateMode', 'week');
|
||||
}
|
||||
},
|
||||
highlight(week, day){
|
||||
this.highlightedWeek = week.no;
|
||||
this.highlightedDay = day;
|
||||
},
|
||||
isHighlightedDay(day) {
|
||||
return day == this.highlightedDay
|
||||
},
|
||||
isHighlightedWeek(week) {
|
||||
return week.no == this.highlightedWeek
|
||||
},
|
||||
clickEvent(day,week) {
|
||||
if(!this.noWeekView)
|
||||
{
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'day');
|
||||
}
|
||||
this.selectDay(day);
|
||||
},
|
||||
getNumberStyle(day) {
|
||||
|
||||
const styleObj = {}
|
||||
styleObj.display = 'inline-block';
|
||||
styleObj.height = '32px';
|
||||
styleObj['line-height'] = '32px';
|
||||
styleObj['text-align'] = 'center';
|
||||
styleObj['font-weight'] = 'bold';
|
||||
styleObj['font-size'] = '14px';
|
||||
|
||||
if(day.getDate() === this.todayDate.getDate()
|
||||
&& day.getMonth() === this.todayDate.getMonth()
|
||||
&& day.getFullYear() === this.todayDate.getFullYear()) {
|
||||
styleObj['background-color'] = '#00649c'; // fh blau
|
||||
styleObj.color = 'white';
|
||||
}
|
||||
|
||||
return styleObj
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) container.style['overflow-y'] = 'auto'
|
||||
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-month-page" :class="{'show-weeks': showWeeks}">
|
||||
<div v-if="showWeeks" class=" bg-light fw-bold border-top border-bottom text-center"></div>
|
||||
<div v-for="day in weeks[0].days" :key="day" class="bg-light fw-bold border-top border-bottom text-center">
|
||||
{{dayText[day]}}
|
||||
</div>
|
||||
<template v-for="week in weeks"
|
||||
:key="week.no">
|
||||
<a href="#" v-if="showWeeks" class="fhc-calendar-month-page-weekday text-decoration-none text-end opacity-25"
|
||||
@click.prevent="changeToWeek(week)">{{week.no}}</a>
|
||||
<a href="#"
|
||||
@click="clickEvent(day,week)"
|
||||
@mouseover="highlight(week,day)"
|
||||
@mouseleave="highlightedWeek = null; highlightedDay = null"
|
||||
v-for="day in week.days"
|
||||
:key="day"
|
||||
:class="getDayClass(week, day)"
|
||||
>
|
||||
<span @click="clickEvent(day,week)" class="no" :style="getNumberStyle(day)">{{day.getDate()}}</span>
|
||||
<span v-if="events[day.toDateString()] && events[day.toDateString()].length" class="events">
|
||||
<div v-for="event in events[day.toDateString()]" :key="event.id"
|
||||
:style="{'background-color': event.color}" class="fhc-entry" :selected="event == selectedEvent" v-contrast >
|
||||
<slot name="monthPage" :event="event" :day="day" >
|
||||
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
|
||||
</slot>
|
||||
</div>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
monthIndices: [...Array(12).keys()]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.format({year: 'numeric'});
|
||||
},
|
||||
months() {
|
||||
return this.monthIndices.map(i => (new Date(0, i, 1)).toLocaleString(this.$p.user_locale.value, {month: this.size < 2 ? 'short' : 'long'}));
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-months">
|
||||
<calendar-header :title="title" @prev="focusDate.y--" @next="focusDate.y++" @click="$emit('updateMode', 'years')" @updateMode="$emit('updateMode', $event)" />
|
||||
<div class="d-flex flex-wrap">
|
||||
<div v-for="(month, key) in months" :key="key" class="d-grid col-4">
|
||||
<button @click="focusDate.m = key; $emit('updateMode', 'month')" class="btn btn-outline-secondary" :class="{'border-0': key != focusDate.m}">
|
||||
{{month}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
export default {
|
||||
name: 'Pane',
|
||||
emits: [
|
||||
'slid'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
carousel: null,
|
||||
queue: 0,
|
||||
offset: 0,
|
||||
slideAnimation:false,
|
||||
scrollTop:null,
|
||||
clientHeight:null,
|
||||
carouselItems:null,
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
isSliding: Vue.computed(() => this.slideAnimation),
|
||||
calendarScrollTop: Vue.computed(() =>this.scrollTop),
|
||||
calendarClientHeight: Vue.computed(() => this.clientHeight),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
offsets() {
|
||||
return [...Array(3).keys()].map(i => (3+i-this.offset)%3-1);
|
||||
},
|
||||
activeCarouselItemIndex() {
|
||||
if (Array.isArray(this.carouselItems) && this.carouselItems.length > 0) {
|
||||
for(let index=0; index < this.carouselItems.length; index++){
|
||||
if (this.carouselItems[index] == true){
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
scrollCalendar(event){
|
||||
this.scrollTop = this.$refs.calendarContainer.scrollTop;
|
||||
this.clientHeight = this.$refs.calendarContainer.clientHeight;
|
||||
},
|
||||
prev() {
|
||||
if (!this.queue--)
|
||||
this.carousel.prev();
|
||||
},
|
||||
next() {
|
||||
if (!this.queue++)
|
||||
this.carousel.next();
|
||||
},
|
||||
slid(evt) {
|
||||
let dir = evt.direction == 'left' ? 1 : -1;
|
||||
this.queue -= dir;
|
||||
this.$emit('slid', dir);
|
||||
this.offset = (3+this.offset+dir)%3;
|
||||
if (this.queue) {
|
||||
if (this.queue > 0)
|
||||
this.carousel.next();
|
||||
else
|
||||
this.carousel.prev();
|
||||
}
|
||||
this.carouselItems = this.$refs.carouselItems.map((item) => { return item.classList.contains('active') });
|
||||
this.slideAnimation = false;
|
||||
},
|
||||
slide(evt) {
|
||||
this.slideAnimation = true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.carousel) {
|
||||
this.$refs.carousel.children[0].children[1].classList.add('active');
|
||||
this.carousel = new window.bootstrap.Carousel(this.$refs.carousel, {
|
||||
interval: false
|
||||
});
|
||||
}
|
||||
this.carouselItems = this.$refs.carouselItems.map((item)=>{return item.classList.contains('active')});
|
||||
this.scrollTop = this.$refs.calendarContainer.scrollTop;
|
||||
this.clientHeight = this.$refs.calendarContainer.clientHeight;
|
||||
},
|
||||
template: `
|
||||
<div ref="carousel" class="calendar-pane carousel slide" @[\`slide.bs.carousel\`]="slide" @[\`slid.bs.carousel\`]="slid" :data-queue="queue">
|
||||
<div id="calendarContainer" @scroll="scrollCalendar" ref="calendarContainer" class="carousel-inner " style="height:var(--fhc-calendar-pane-height);">
|
||||
<div ref="carouselItems" v-for="i in [...Array(3).keys()]" :key="i" class="carousel-item" style="height:var(--fhc-calendar-pane-height);">
|
||||
<slot :active="i == activeCarouselItemIndex" :index="i" :offset="offsets[i]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarWeekPage from './Week/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarWeekPage,
|
||||
CalendarPane
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.focusDate.wYear + ' KW ' + this.focusDate.w;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
this.focusDate.d += dir * 7;
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
emitRangeChanged() {
|
||||
let start = this.focusDate.firstDayOfWeek;
|
||||
let end = this.focusDate.lastDayOfWeek;
|
||||
this.$emit('change:range', { start, end });
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: -7 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 0, m: 0, d: 7 });
|
||||
},
|
||||
selectEvent(event) {
|
||||
this.$emit('input', ['select:event',event]);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.emitRangeChanged();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-week">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'weeks')">
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</template>
|
||||
</calendar-header>
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-week-page :year="focusDate.wYear" :week="focusDate.w+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" >
|
||||
<template #weekPage="{event,day}">
|
||||
<slot name="weekPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
</calendar-week-page>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
import CalendarDate from '../../../composables/CalendarDate.js';
|
||||
|
||||
function ggt(m,n) { return n==0 ? m : ggt(n, m%n); }
|
||||
function kgv(m,n) { return (m*n) / ggt(m,n); }
|
||||
|
||||
export default {
|
||||
name: 'WeekPage',
|
||||
data(){
|
||||
return{
|
||||
hourPosition:null,
|
||||
hourPositionTime:null,
|
||||
resizeObserver: null,
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
'events',
|
||||
'noMonthView',
|
||||
'isSliding',
|
||||
'selectedEvent',
|
||||
'setSelectedEvent',
|
||||
'rowMinHeight'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
week: Number
|
||||
},
|
||||
emits: [
|
||||
'updateMode',
|
||||
'page:back',
|
||||
'page:forward',
|
||||
'input',
|
||||
],
|
||||
computed: {
|
||||
getGridStyle() {
|
||||
return {
|
||||
'min-height': this.rowMinHeight,
|
||||
// this.size is the magic number anyway which directs font-size,
|
||||
// which in turn influences a lot of layout
|
||||
width: '42px'
|
||||
}
|
||||
},
|
||||
laneWidth() {
|
||||
return (this.width - 42) / this.days.length
|
||||
},
|
||||
curTime() {
|
||||
const now = new Date();
|
||||
return String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
|
||||
},
|
||||
pageHeaderStyle(){
|
||||
return {
|
||||
'z-index': 4,
|
||||
'grid-template-columns': 'repeat(' + this.days.length + ', 1fr)',
|
||||
'grid-template-rows': 1,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
}
|
||||
},
|
||||
overlayStyle() {
|
||||
return {
|
||||
'background-color': '#F5E9D7',
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': 2,
|
||||
height: this.getDayTimePercent + '%',
|
||||
width: this.laneWidth + 'px',
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
indicatorStyle() {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '3.5rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.hourPosition + 'px',
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
},
|
||||
hours(){
|
||||
// returns an array with elements starting at 7 and ending at 24
|
||||
return [...Array(24).keys()].filter(hour => hour >= 7 && hour <= 24);
|
||||
},
|
||||
dayText() {
|
||||
if (!this.size || !this.days) return {};
|
||||
let dayTextMap ={};
|
||||
this.days.forEach((day)=>{
|
||||
dayTextMap[day] = {
|
||||
heading: day.toLocaleString(this.$p.user_locale.value, { dateStyle: 'short' }),
|
||||
tag: day.toLocaleString(this.$p.user_locale.value, { weekday: this.size < 2 ? 'narrow' : (this.size < 3 ? 'short' : 'long') }),
|
||||
datum: day.toLocaleString(this.$p.user_locale.value, [{ day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { day: 'numeric', month: 'numeric' }, { dateStyle: 'short' }][this.size]),
|
||||
};
|
||||
});
|
||||
return dayTextMap;
|
||||
},
|
||||
days() {
|
||||
|
||||
let tmpDate = new CalendarDate(this.year,1,1); // NOTE(chris): somewhere in the middle of the year
|
||||
tmpDate.w = this.week;
|
||||
let startDay = tmpDate.firstDayOfWeek;
|
||||
let result = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
result.push(new Date(startDay.getFullYear(), startDay.getMonth(), startDay.getDate() + i));
|
||||
}
|
||||
return result;
|
||||
|
||||
},
|
||||
eventsPerDayAndHour() {
|
||||
// return early if the calendar pane is sliding
|
||||
if (this.isSliding) return {};
|
||||
const res = {};
|
||||
this.days.forEach(day => {
|
||||
let key = day.toDateString();
|
||||
|
||||
let nextDay = new Date(day);
|
||||
nextDay.setDate(nextDay.getDate()+1);
|
||||
nextDay.setMilliseconds(nextDay.getMilliseconds()-1);
|
||||
let d = {events:[],lanes:1, isPast: false};
|
||||
d.isPast = nextDay.getTime() < this.today
|
||||
d.isToday = nextDay.getFullYear() === this.todayDate.getFullYear() && nextDay.getMonth() === this.todayDate.getMonth() && nextDay.getDate() === this.todayDate.getDate()
|
||||
if (this.events[key]) {
|
||||
this.events[key].forEach(evt => {
|
||||
let event = {orig:evt,lane:1,maxLane:1,start: evt.start < day ? day : evt.start, end: evt.end > nextDay ? nextDay : evt.end,shared:[],setSharedMaxRecursive(doneItems) {
|
||||
this.maxLane = Math.max(doneItems[0].maxLane, this.maxLane);
|
||||
doneItems.push(this);
|
||||
this.shared.filter(other => !doneItems.includes(other)).forEach(i => i.setSharedMaxRecursive(doneItems));
|
||||
}};
|
||||
event.shared = d.events.filter(other => other.start < event.end && other.end > event.start);
|
||||
event.shared.forEach(other => other.shared.push(event));
|
||||
let occupiedLanes = event.shared.map(other => other.lane);
|
||||
while (occupiedLanes.includes(event.lane))
|
||||
event.lane++;
|
||||
event.maxLane = Math.max(...[event.lane], ...occupiedLanes);
|
||||
if (event.maxLane > 1) {
|
||||
event.setSharedMaxRecursive([event]);
|
||||
}
|
||||
d.events.push(event);
|
||||
});
|
||||
d.lanes = d.events.map(e => e.maxLane).reduce((res, i) => kgv(res, i), 1);
|
||||
}
|
||||
res[key] = d;
|
||||
});
|
||||
return res;
|
||||
},
|
||||
smallestTimeFrame() {
|
||||
return [30,15,10,5][this.size];
|
||||
},
|
||||
lookingAtToday() {
|
||||
return this.days.some(d =>
|
||||
d.getFullYear() === this.todayDate.getFullYear() &&
|
||||
d.getMonth() === this.todayDate.getMonth() &&
|
||||
d.getDate() === this.todayDate.getDate()
|
||||
)
|
||||
},
|
||||
curIndicatorStyle() {
|
||||
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
'padding-left': '1rem',
|
||||
'margin-top': '-1px',
|
||||
'z-index': 2,
|
||||
'border-color': '#00649C!important',
|
||||
top: this.getDayTimePercent + '%',
|
||||
width: this.laneWidth + 'px'
|
||||
}
|
||||
},
|
||||
getDayTimePercent() {
|
||||
const now = new Date(Date.now())
|
||||
const currentMinutes = now.getMinutes() + now.getHours() * 60
|
||||
let timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
|
||||
return timePercentage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hourGridIdentifier(hour) {
|
||||
// this is the id attribute that is responsible to scroll the calender to the first event
|
||||
return 'scroll' + hour + this.focusDate.d + this.week;
|
||||
},
|
||||
hourGridStyle(hour) {
|
||||
return {
|
||||
'pointer-events': 'none',
|
||||
top: this.getAbsolutePositionForHour(hour),
|
||||
left: 0,
|
||||
right: 0,
|
||||
'z-index': 0
|
||||
}
|
||||
},
|
||||
dayGridStyle(day) {
|
||||
const styleObj = {
|
||||
'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)',
|
||||
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
|
||||
}
|
||||
|
||||
if(day.isPast) {
|
||||
styleObj['background-color'] = '#F5E9D7'
|
||||
styleObj['border-color'] = '#E8E8E8';
|
||||
styleObj.opacity = 0.5;
|
||||
} else if (day.isToday) {
|
||||
|
||||
// styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)'
|
||||
// styleObj['border-color'] = '#E8E8E8';
|
||||
// styleObj.opacity = 0.5;
|
||||
}
|
||||
|
||||
return styleObj
|
||||
},
|
||||
eventGridStyle(day, event) {
|
||||
return {
|
||||
'z-index': 1,
|
||||
'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane,
|
||||
'grid-column-end': 1 + event.lane * day.lanes / event.maxLane,
|
||||
'grid-row-start': this.dateToMinutesOfDay(event.start),
|
||||
'grid-row-end': this.dateToMinutesOfDay(event.end),
|
||||
'background-color': event.orig.color,
|
||||
'max-height': '75px'
|
||||
}
|
||||
},
|
||||
calcHourPosition(event) {
|
||||
let height = this.$refs.eventcontainer.getBoundingClientRect().height;
|
||||
let top = this.$refs.eventcontainer.getBoundingClientRect().top;
|
||||
let position = event.clientY - top;
|
||||
// position percentage of total height
|
||||
let timePercentage = (position / height) * 100;
|
||||
// minute percentage of total minutes
|
||||
let result = (this.hours.length * 60) * (timePercentage / 100);
|
||||
// calculate time in float
|
||||
let currentMinutes = ((result + (this.hours[0] * 60)) / 60);
|
||||
// get hour part of time
|
||||
let currentHour = Math.floor(currentMinutes);
|
||||
// get float part of time
|
||||
let minutePercentage = currentMinutes % currentHour;
|
||||
// calculate minutes from float part of time
|
||||
let minute = Math.round(60 * minutePercentage);
|
||||
// convert minutes to 5 minute interval
|
||||
if (minute % 5 != 0) {
|
||||
minute = Math.round(minute / 5) * 5;
|
||||
}
|
||||
// in case the rounding made the minutes 60, increase the hour and reset the minutes
|
||||
if (minute == 60) {
|
||||
currentHour++;
|
||||
minute = 0;
|
||||
}
|
||||
|
||||
// ## after rounding the time to the nearest 5 Minute interval, we have to convert the time back to the relative position
|
||||
// convert current time in minutes
|
||||
currentMinutes = currentHour * 60 + minute;
|
||||
// calculate the minutes percentage of the total minutes
|
||||
timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
|
||||
// calculate the relative position of the time percentage
|
||||
position = height * (timePercentage / 100);
|
||||
this.hourPosition = position;
|
||||
|
||||
// add padding to minutes that consist of only one digit
|
||||
minute.toString().length == 1 ? minute = "0" + minute : minute;
|
||||
this.hourPositionTime = currentHour + ":" + minute;
|
||||
},
|
||||
getAbsolutePositionForHour(hour){
|
||||
// used for the absolute positioning of the gutters of hours
|
||||
return (100 / this.hours.length) * (hour - (24-this.hours.length)) + '%';
|
||||
},
|
||||
changeToMonth(day) {
|
||||
if (!this.noMonthView) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'month');
|
||||
}
|
||||
},
|
||||
changeToDay(day) {
|
||||
this.date.set(day);
|
||||
this.focusDate.set(day);
|
||||
this.$emit('updateMode', 'day');
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
// subtract 7 from the total hours because the hours range from 7 to 24
|
||||
return Math.floor(((day.getHours()-7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
},
|
||||
weekPageClick(event, day) {
|
||||
this.setSelectedEvent(event);
|
||||
this.focusDate.set(new CalendarDate(new Date(event.datum)));
|
||||
this.$emit('input', event)
|
||||
},
|
||||
initResizeObserver() {
|
||||
const events = this.$refs['eventsRef'+this.week];
|
||||
if (!events) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
if(width > 0) this.width = width
|
||||
}
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(events);
|
||||
},
|
||||
destroyResizeObserver() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
setTimeout(() => this.$refs.eventcontainer.scrollTop = this.$refs.eventcontainer.scrollHeight / 3 + 1, 0);
|
||||
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) {
|
||||
container.style['overflow-y'] = 'scroll'
|
||||
container.style['overflow-x'] = 'auto'
|
||||
}
|
||||
|
||||
this.initResizeObserver();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.destroyResizeObserver();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div ref="page" class="fhc-calendar-week-page" style="min-width: 700px;">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle" >
|
||||
<div type="button" v-for="day in days" :key="day" class="flex-grow-1" :title="dayText[day]?.heading" @click.prevent="changeToDay(day)">
|
||||
<div class="fw-bold">{{dayText[day]?.tag}}</div>
|
||||
<a href="#" class="small text-secondary text-decoration-none" >{{dayText[day]?.datum}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
|
||||
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
|
||||
<Transition>
|
||||
<div v-if="hourPosition" class="position-absolute border-top small" :style="indicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<div class="events" :ref="'eventsRef'+week">
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" :style="getGridStyle" :key="hour" class="text-muted text-end small" :ref="'hour' + hour">{{hour}}:00</div>
|
||||
</div>
|
||||
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle(day)">
|
||||
<Transition>
|
||||
<div v-if="day.isToday" class="position-absolute border-top small" :style="curIndicatorStyle">
|
||||
<span class="border border-top-0 px-2 bg-white">{{curTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div v-if="day.isToday" :style="overlayStyle"></div>
|
||||
<div v-for="event in day.events" :key="event" @click.prevent="weekPageClick(event.orig, day)"
|
||||
:selected="event.orig == selectedEvent"
|
||||
:style="eventGridStyle(day,event)"
|
||||
class="mx-2 small rounded overflow-hidden fhc-entry "
|
||||
v-contrast >
|
||||
<slot name="weekPage" :event="event" :day="day">
|
||||
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
inject: [
|
||||
'size',
|
||||
'focusDate'
|
||||
],
|
||||
computed: {
|
||||
weeks(){
|
||||
return [...Array(this.focusDate.numWeeks).keys()].map(i => i + 1);
|
||||
},
|
||||
title() {
|
||||
return this.focusDate.format({year: 'numeric'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setWeek(week) {
|
||||
// TODO(chris): test is there a week jump on year select? => yes there is if the same month/day are in different weeks ... should we prevent that?
|
||||
this.focusDate.w = week;
|
||||
this.$emit('updateMode', 'week');
|
||||
},
|
||||
prev(){
|
||||
this.focusDate.y--;
|
||||
this.focusDate._clean();
|
||||
},
|
||||
next() {
|
||||
this.focusDate.y++;
|
||||
this.focusDate._clean();
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-weeks h-100">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @click="$emit('updateMode', 'years')" @updateMode="$emit('updateMode', $event)" />
|
||||
<div class="d-flex flex-wrap">
|
||||
<div v-for="(week, key) in weeks" :key="key" class="d-grid col-2">
|
||||
<button @click="setWeek(week)" class="btn btn-outline-secondary" :class="{'border-0': week != focusDate.w}">
|
||||
{{week}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import FhcCalendar from "./Base.js";
|
||||
|
||||
import { useEventLoader } from '../../composables/EventLoader.js';
|
||||
|
||||
import ModeList from '../Calendar/Mode/List.js';
|
||||
|
||||
export default {
|
||||
name: "CalendarWidget",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
inject: [
|
||||
"renderers"
|
||||
],
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
now: luxon.DateTime.now().setZone(this.timezone),
|
||||
modes: {
|
||||
list: Vue.markRaw(ModeList)
|
||||
},
|
||||
modeOptions: {
|
||||
list: {
|
||||
length: 7
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
eventStyle(event) {
|
||||
const styles = {};
|
||||
if (event.farbe)
|
||||
styles['--event-bg'] = '#' + event.farbe;
|
||||
else if (event.type == 'reservierung')
|
||||
styles['--event-bg'] = '#ffffff';
|
||||
else
|
||||
styles['--event-bg'] = '#cccccc';
|
||||
|
||||
const eventEnd = luxon.DateTime.fromISO(event.isoend, { zone: this.timezone });
|
||||
if (eventEnd < this.now)
|
||||
styles['opacity'] = .5;
|
||||
|
||||
return styles;
|
||||
},
|
||||
updateRange(rangeInterval) {
|
||||
this.rangeInterval = rangeInterval;
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const rangeInterval = Vue.ref(null);
|
||||
|
||||
const { events } = useEventLoader(rangeInterval, props.getPromiseFunc);
|
||||
|
||||
return {
|
||||
rangeInterval,
|
||||
events
|
||||
};
|
||||
},
|
||||
template: /* html */`
|
||||
<fhc-calendar
|
||||
:modes="modes"
|
||||
:mode-options="modeOptions"
|
||||
:timezone="timezone"
|
||||
:locale="$p.user_locale.value"
|
||||
:events="events || []"
|
||||
@update:range="updateRange"
|
||||
>
|
||||
<template v-slot="{ event, mode }">
|
||||
<div
|
||||
v-if="!event"
|
||||
class="h-100 d-flex justify-content-center align-items-center"
|
||||
>
|
||||
{{ $p.t('lehre/noLvFound') }}
|
||||
</div>
|
||||
<component
|
||||
v-else-if="mode == 'eventheader'"
|
||||
:is="renderers[event.type]?.modalTitle"
|
||||
:event="event"
|
||||
></component>
|
||||
<component
|
||||
v-else-if="mode == 'event'"
|
||||
:is="renderers[event.type]?.modalContent"
|
||||
:event="event"
|
||||
></component>
|
||||
<div
|
||||
v-else
|
||||
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
|
||||
:style="eventStyle(event)"
|
||||
>
|
||||
<component
|
||||
:is="renderers[event.type]?.calendarEvent"
|
||||
:event="event"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
</fhc-calendar>`
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import CalendarAbstract from './Abstract.js';
|
||||
import CalendarPane from './Pane.js';
|
||||
import CalendarYearsPage from './Years/Page.js';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
CalendarAbstract
|
||||
],
|
||||
components: {
|
||||
CalendarYearsPage,
|
||||
CalendarPane
|
||||
},
|
||||
inject: [
|
||||
'size'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
start: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
range() {
|
||||
switch (this.size) {
|
||||
case 3:
|
||||
// eslint-disable-next-line
|
||||
case 2:
|
||||
return 24;
|
||||
}
|
||||
return 12;
|
||||
},
|
||||
end() {
|
||||
return this.start + this.range - 1;
|
||||
},
|
||||
title() {
|
||||
return this.start + ' - ' + this.end;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
paneChanged(dir) {
|
||||
this.start += this.range * dir;
|
||||
},
|
||||
prev() {
|
||||
this.$refs.pane.prev();
|
||||
this.$emit('change:offset', { y: -1, m: 0, d: 0 });
|
||||
},
|
||||
next() {
|
||||
this.$refs.pane.next();
|
||||
this.$emit('change:offset', { y: 1, m: 0, d: 0 });
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.start = this.focusDate.y - this.focusDate.y%this.range;
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-years">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" />
|
||||
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
|
||||
<calendar-years-page :data-test="slot.index" :start="start+range*slot.offset" :end="start+range*slot.offset+range" @updateMode="$emit('updateMode')"/>
|
||||
</calendar-pane>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
export default {
|
||||
inject: [
|
||||
'focusDate'
|
||||
],
|
||||
props: {
|
||||
start: Number,
|
||||
end: Number
|
||||
},
|
||||
emits: [
|
||||
'updateMode'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
years() {
|
||||
return [...Array(this.end - this.start).keys()].map(i => i + this.start);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) {
|
||||
container.style['overflow-y'] = 'scroll'
|
||||
container.style['overflow-x'] = 'auto'
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-years-page d-flex flex-wrap">
|
||||
<div v-for="year in years" :key="year" class="d-grid col-4">
|
||||
<button class="btn btn-outline-secondary" :class="{'border-0': year != focusDate.y}" @click="focusDate.y = year; $emit('updateMode')">
|
||||
{{year}}
|
||||
</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import BsModal from '../../Bootstrap/Modal.js';
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
|
||||
const today = new Date()
|
||||
export const AbgabeMitarbeiterDetail = {
|
||||
name: "AbgabeMitarbeiterDetail",
|
||||
components: {
|
||||
BsModal,
|
||||
InputNumber: primevue.inputnumber,
|
||||
Checkbox: primevue.checkbox,
|
||||
Dropdown: primevue.dropdown,
|
||||
Textarea: primevue.textarea,
|
||||
VueDatePicker
|
||||
},
|
||||
props: {
|
||||
projektarbeit: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
oldPaBeurteilungLink: 'https://moodle.technikum-wien.at/mod/page/view.php?id=1005052', // TODO: inject from app & app provide link from config
|
||||
eidAkzeptiert: false,
|
||||
enduploadTermin: null,
|
||||
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
|
||||
// TODO: fetch types
|
||||
allAbgabeTypes: [
|
||||
{
|
||||
paabgabetyp_kurzbz: 'abstract',
|
||||
bezeichnung: 'Entwurf'
|
||||
},
|
||||
{
|
||||
paabgabetyp_kurzbz: 'zwischen',
|
||||
bezeichnung: 'Zwischenabgabe'
|
||||
},
|
||||
{
|
||||
paabgabetyp_kurzbz: 'note',
|
||||
bezeichnung: 'Benotung'
|
||||
},
|
||||
{
|
||||
paabgabetyp_kurzbz: 'end',
|
||||
bezeichnung: 'Endupload'
|
||||
},
|
||||
{
|
||||
paabgabetyp_kurzbz: 'enda',
|
||||
bezeichnung: 'Endabgabe im Sekretariat'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openZusatzdatenModal(termin) {
|
||||
|
||||
},
|
||||
saveTermin(termin) {
|
||||
const paabgabe_id = termin.paabgabe_id
|
||||
this.$fhcApi.factory.lehre.postProjektarbeitAbgabe(termin).then( (res) => {
|
||||
if(res?.meta?.status == 'success') {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui/gespeichert'))
|
||||
|
||||
if(paabgabe_id === -1) { // new abgabe has been inserted
|
||||
termin.paabgabe_id = res?.data?.retval
|
||||
|
||||
this.projektarbeit.abgabetermine.push({ // new abgatermin row
|
||||
|
||||
'paabgabe_id': -1,
|
||||
'projektarbeit_id': this.projektarbeit.projektarbeit_id,
|
||||
'fixtermin': false,
|
||||
'kurzbz': '',
|
||||
'datum': new Date().toISOString().split('T')[0],
|
||||
'paabgabetyp_kurzbz': '',
|
||||
'bezeichnung': '',
|
||||
'abgabedatum': null,
|
||||
'insertvon': this.viewData?.uid ?? '',
|
||||
'allowedToSave': true,
|
||||
'allowedToDelete': true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
} else if(res?.meta?.status == 'error'){
|
||||
this.$fhcAlert.alertError()
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
deleteTermin(termin) {
|
||||
this.$fhcApi.factory.lehre.deleteProjektarbeitAbgabe(termin.paabgabe_id).then( (res) => {
|
||||
if(res?.meta?.status == 'success') {
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui/genericDeleted', [this.$p.t('abgabetool/abgabe')]))
|
||||
// this.$p.t('global/tooltipLektorDeleteKontrolle', [this.$entryParams.permissions.kontrolleDeleteMaxReach ])
|
||||
const deletedTerminIndex = this.projektarbeit.abgabetermine.findIndex(t => t.paabgabe_id === termin.paabgabe_id)
|
||||
this.projektarbeit.abgabetermine.splice(deletedTerminIndex, 1)
|
||||
|
||||
|
||||
} else if(res?.meta?.status == 'error'){
|
||||
this.$fhcAlert.alertError()
|
||||
}
|
||||
})
|
||||
},
|
||||
validate: function(termin) {
|
||||
if(!termin.file.length) {
|
||||
this.$fhcAlert.alertWarning(this.$p.t('global/warningChooseFile'));
|
||||
return false
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
downloadAbgabe(termin) {
|
||||
this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
|
||||
},
|
||||
dateDiffInDays(datum, today){
|
||||
const oneDayMs = 1000 * 60 * 60 * 24
|
||||
return Math.round((new Date(datum) - new Date(today)) / oneDayMs)
|
||||
},
|
||||
getDateStyle(termin) {
|
||||
const datum = new Date(termin.datum)
|
||||
const abgabedatum = new Date(termin.abgabedatum)
|
||||
|
||||
// todo: rework styling but keep the color pattern logic
|
||||
// https://wiki.fhcomplete.info/doku.php?id=cis:abgabetool_fuer_studierende
|
||||
let color = 'white'
|
||||
let fontColor = 'black'
|
||||
if (termin.abgabedatum === null) {
|
||||
if(datum < today) {
|
||||
color = 'red'
|
||||
fontColor = 'white'
|
||||
} else if (datum > today && this.dateDiffInDays(datum, today) <= 12) {
|
||||
color = 'yellow'
|
||||
}
|
||||
} else if(abgabedatum > datum) {
|
||||
color = 'pink' // aka "hellrot"
|
||||
fontColor = 'white'
|
||||
} else {
|
||||
color = 'green'
|
||||
}
|
||||
|
||||
return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
|
||||
},
|
||||
openBeurteilungLink(link) {
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
getOptionLabelSprache(option) {
|
||||
return option.sprache
|
||||
},
|
||||
getOptionLabelAbgabetyp(option){
|
||||
return option.bezeichnung
|
||||
},
|
||||
formatDate(dateParam) {
|
||||
const date = new Date(dateParam)
|
||||
// handle missing leading 0
|
||||
const padZero = (num) => String(num).padStart(2, '0');
|
||||
|
||||
const month = padZero(date.getMonth() + 1); // Months are zero-based
|
||||
const day = padZero(date.getDate());
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day}.${month}.${year}`;
|
||||
},
|
||||
openStudentPage() {
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router
|
||||
+ '/Cis/Abgabetool/Student/' + this.projektarbeit?.student_uid
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
openPlagiatcheck() {
|
||||
// todo: hardcoded turnitin link?
|
||||
const link = "https://technikum-wien.turnitin.com/sso/sp/redwood/saml/5IyfmBr2OcSIaWQTKlFCGj/start"
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
openBenotung() {
|
||||
const path = this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter' ? 'ProjektarbeitsbeurteilungZweitbegutachter' : 'ProjektarbeitsbeurteilungErstbegutachter'
|
||||
const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/' + path
|
||||
window.open(link, '_blank')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getEid() {
|
||||
return this.$p.t('abgabetool/c4eidesstattlicheErklaerung')
|
||||
},
|
||||
getEnduploadErlaubt() {
|
||||
return !this.eidAkzeptiert
|
||||
},
|
||||
getSemesterBenotbar(){
|
||||
return this.projektarbeit?.isCurrent ?? false
|
||||
},
|
||||
endUploadVorhanden(){
|
||||
return this.projektarbeit?.abgabetermine.find(abgabe => abgabe.paabgabetyp_kurzbz === 'end' && abgabe.abgabedatum !== null)
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
template: `
|
||||
<div v-if="projektarbeit">
|
||||
|
||||
|
||||
<h5>{{$p.t('abgabetool/c4abgabeMitarbeiterbereich')}}</h5>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<p> {{projektarbeit?.student}}</p>
|
||||
<p> {{projektarbeit?.titel}}</p>
|
||||
<p v-if="projektarbeit?.zweitbegutachter"> {{projektarbeit?.zweitbegutachter}}</p>
|
||||
</div>
|
||||
<div class="col-4 d-flex">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<button :disabled="!getSemesterBenotbar || !endUploadVorhanden" class="btn btn-secondary border-0" @click="openBenotung" style="width: 80%;">
|
||||
benoten
|
||||
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row" style="width: 90%;">
|
||||
<span v-if="!getSemesterBenotbar" v-html="$p.t('abgabetool/c4aeltereParbeitBenoten', oldPaBeurteilungLink)"></span>
|
||||
<span v-else-if="!endUploadVorhanden">Kein Endupload vorhanden!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<button v-if="projektarbeit?.betreuerart_kurzbz !== 'Zweitbegutachter'" class="btn btn-secondary border-0" @click="openPlagiatcheck" style="width: 80%;">
|
||||
zur Plagiatsprüfung
|
||||
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<button class="btn btn-secondary border-0" @click="openStudentPage" style="width: 80%;">
|
||||
Studentenansicht
|
||||
<i style="margin-left: 8px" class="fa-solid fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="uploadWrapper">
|
||||
<div class="row" style="margin-bottom: 12px;">
|
||||
<div style="width: 100px">{{$p.t('abgabetool/c4fixtermin')}}</div>
|
||||
<div class="col-2">{{$p.t('abgabetool/c4zieldatum')}}</div>
|
||||
<div class="col-2">{{$p.t('abgabetool/c4abgabetyp')}}</div>
|
||||
<div class="col-4">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
|
||||
<div class="col-1">{{$p.t('abgabetool/c4abgabedatum')}}</div>
|
||||
<div class="col">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!projektarbeit?.abgabetermine?.length">keine Termine gefunden!</div>
|
||||
<div class="row" v-for="termin in projektarbeit.abgabetermine">
|
||||
<div style="width: 100px" class="d-flex justify-content-center align-items-center">
|
||||
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
|
||||
</div>
|
||||
<div class="col-2 d-flex justify-content-center align-items-center">
|
||||
<div :style="getDateStyle(termin)">
|
||||
<VueDatePicker
|
||||
style="width: 95%;"
|
||||
v-model="termin.datum"
|
||||
:clearable="false"
|
||||
:disabled="!termin.allowedToSave"
|
||||
:enable-time-picker="false"
|
||||
:format="formatDate"
|
||||
:text-input="true"
|
||||
auto-apply>
|
||||
</VueDatePicker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-2 d-flex justify-content-center align-items-center">
|
||||
<Dropdown
|
||||
:style="{'width': '100%'}"
|
||||
:disabled="!termin.allowedToSave"
|
||||
v-model="termin.bezeichnung"
|
||||
:options="allAbgabeTypes"
|
||||
:optionLabel="getOptionLabelAbgabetyp">
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div class="col-4 d-flex justify-content-center align-items-center">
|
||||
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="3" cols="60" :disabled="!termin.allowedToSave"></Textarea>
|
||||
</div>
|
||||
<div class="col-1 d-flex justify-content-center align-items-center">
|
||||
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
|
||||
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
|
||||
<i class="fa-solid fa-file-pdf"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-2 align-content-center">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<button v-if="termin.allowedToSave" class="btn btn-primary border-0" @click="saveTermin(termin)">
|
||||
Speichern
|
||||
<i style="margin-left: 8px" class="fa-solid fa-floppy-disk"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button v-if="termin.allowedToDelete && termin.paabgabe_id > 0" class="btn btn-primary border-0" @click="deleteTermin(termin)">
|
||||
Löschen
|
||||
<i style="margin-left: 8px" class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`,
|
||||
};
|
||||
|
||||
export default AbgabeMitarbeiterDetail;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user