mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-20 18:59:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99445d2372 | ||
|
|
0ab6e3d00d | ||
|
|
385b262be8 | ||
|
|
b4206e1e48 | ||
|
|
e09555908a | ||
|
|
38060c60fb | ||
|
|
40516b34a2 | ||
|
|
ea97e3a192 | ||
|
|
f34481ae60 | ||
|
|
fcf3cbac6c | ||
|
|
40b28b88b9 | ||
|
|
11f2f0ad75 | ||
|
|
4abe6f0d24 | ||
|
|
9fbbd6b855 | ||
|
|
60b2c89c66 | ||
|
|
59b4b2bac9 | ||
|
|
5f546fca2a | ||
|
|
f74b885253 | ||
|
|
3ab402adef | ||
|
|
52f24645fb | ||
|
|
4ac108174b | ||
|
|
6378b80d87 | ||
|
|
658c8d6bd4 | ||
|
|
bf29faa5a7 | ||
|
|
2ada167557 | ||
|
|
cc9d1f983e | ||
|
|
57c48f2c4a |
@@ -167,7 +167,9 @@ class Studium extends FHCAPI_Controller
|
||||
if($aktuelles_studienplan){
|
||||
$lehrveranstaltungen = $this->computeStudienplanLehrveranstaltungen($aktuelles_studienplan->studienplan_id, $aktuelles_semester);
|
||||
foreach($lehrveranstaltungen as $lehrv){
|
||||
foreach($lehrv->lehrveranstaltungen as $lv){
|
||||
// a lehrveranstaltung without a modul has no children and gets its lektoren directly
|
||||
$lvs = isset($lehrv->lehrveranstaltungen) ? $lehrv->lehrveranstaltungen : [$lehrv];
|
||||
foreach($lvs as $lv){
|
||||
$lvLektoren =$this->computeLektorenFromLehrveranstaltung($lv->lehrveranstaltung_id,$aktuelles_semester, $aktuelles_studiengang->studiengang_kz, $aktuelles_studiensemester->studiensemester_kurzbz);
|
||||
$lv->lektoren = $lvLektoren;
|
||||
}
|
||||
@@ -286,6 +288,7 @@ SELECT tbl_lehrveranstaltung.*,
|
||||
$lehrveranstaltungen = $this->LehrveranstaltungModel->execReadOnlyQuery($query,[$studienplan_id, $semester]);
|
||||
|
||||
$lehrveranstaltungen = $this->getDataOrTerminateWithError($lehrveranstaltungen);
|
||||
|
||||
usort($lehrveranstaltungen, function($a, $b){
|
||||
if($a->lehrtyp_kurzbz == "modul"){
|
||||
return -1;
|
||||
@@ -302,12 +305,17 @@ SELECT tbl_lehrveranstaltung.*,
|
||||
}
|
||||
else{
|
||||
$parent =array_filter($carry, function($item)use($lehrv){
|
||||
return $item->studienplan_lehrveranstaltung_id == $lehrv->studienplan_lehrveranstaltung_id_parent;
|
||||
return isset($item->lehrveranstaltungen)
|
||||
&& $item->studienplan_lehrveranstaltung_id == $lehrv->studienplan_lehrveranstaltung_id_parent;
|
||||
});
|
||||
$parent = current($parent);
|
||||
if($parent){
|
||||
$parent->lehrveranstaltungen[] = $lehrv;
|
||||
}
|
||||
else{
|
||||
// lehrveranstaltung without a modul: keep it on the top level
|
||||
array_push($carry, $lehrv);
|
||||
}
|
||||
}
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
@@ -108,6 +108,7 @@ class Students extends FHCAPI_Controller
|
||||
'studiensemester_kurzbz' => $studiensemester_kurzbz
|
||||
]);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
$this->studentlistlib->addJoin(
|
||||
"(
|
||||
@@ -153,7 +154,8 @@ class Students extends FHCAPI_Controller
|
||||
$this->addMeta('ci_params', [
|
||||
'studiensemester_kurzbz' => $studiensemester_kurzbz
|
||||
]);
|
||||
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
$this->studentlistlib->addJoin(
|
||||
"(
|
||||
@@ -213,6 +215,7 @@ class Students extends FHCAPI_Controller
|
||||
'studiensemester_kurzbz' => $studiensemester_kurzbz
|
||||
]);
|
||||
|
||||
$studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz);
|
||||
|
||||
$this->studentlistlib->addJoin(
|
||||
"(
|
||||
|
||||
@@ -16,7 +16,7 @@ $query = '
|
||||
anrechnung.begruendung_id,
|
||||
anrechnung.dms_id,
|
||||
CASE
|
||||
WHEN stg.typ || stg.kurzbz IN (' . $ORGANISATIONSEINHEITEN_SCHREIBBERECHTIGT . ') THEN TRUE
|
||||
WHEN stg.oe_kurzbz IN (' . $ORGANISATIONSEINHEITEN_SCHREIBBERECHTIGT . ') THEN TRUE
|
||||
ELSE FALSE
|
||||
END "schreibberechtigt",
|
||||
anrechnung.studiensemester_kurzbz,
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="row<?php if ($lockedbyother) echo ' alert-danger' ?>">
|
||||
<div class="col-lg-8">
|
||||
<h3 class="page-header">
|
||||
Infocenter Details: <a target="_blank" title="Studentenverwaltung" href="<?php echo site_url('/Studentenverwaltung/' . $studiensemester . '/person/' . $stammdaten->person_id) ?>"><?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?> <i class="fa fa-external-link" style="font-size:small"></i></a>
|
||||
Infocenter Details: <a target="_blank" title="Studentenverwaltung" href="<?php echo site_url('/studvw/stdsem/' . $studiensemester . '/person/' . $stammdaten->person_id) ?>"><?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?> <i class="fa fa-external-link" style="font-size:small"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
|
||||
@@ -351,7 +351,7 @@ if (!$berechtigung->isBerechtigt('admin')
|
||||
{
|
||||
$worksheet->write($lines,3,$elem->vorname);
|
||||
}
|
||||
$worksheet->write($lines,4,$elem->semester.$elem->verband.$elem->gruppe);
|
||||
$worksheet->writeString($lines,4,$elem->semester.$elem->verband.$elem->gruppe);
|
||||
$worksheet->write($lines,5,trim($elem->matrikelnr),$format_highlight);
|
||||
$worksheet->write($lines,6, $note, $format_highlightright);
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ class vertrag extends basis_db
|
||||
JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
|
||||
WHERE
|
||||
vertrag_id=tbl_vertrag.vertrag_id
|
||||
AND studiensemester_kurzbz in (SELECT studiensemester_kurzbz FROM public.tbl_studiensemester WHERE start>=".$this->db_add_param($datum).")
|
||||
AND studiensemester_kurzbz in (SELECT studiensemester_kurzbz FROM public.tbl_studiensemester WHERE start>".$this->db_add_param($datum).")
|
||||
)
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js";
|
||||
|
||||
import ApiRouteInfo from '../../api/factory/routeinfo.js';
|
||||
import {capitalize} from "../../helpers/StringHelpers.js";
|
||||
import { getTodayISO, toDateTime } from "../../helpers/DateHelpers.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
const isMobile = window.matchMedia("(max-width: 767px)").matches;
|
||||
@@ -89,7 +88,7 @@ const router = VueRouter.createRouter({
|
||||
params: { // in this case always populate other params since they are not optional
|
||||
ort_kurzbz: to.params.ort_kurzbz,
|
||||
mode: isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP,
|
||||
focus_date: getTodayISO()
|
||||
focus_date: new Date().toISOString().split("T")[0]
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -108,7 +107,8 @@ const router = VueRouter.createRouter({
|
||||
: (isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP);
|
||||
|
||||
// default to today date if not provided
|
||||
const focus_date = toDateTime(route.params.focus_date) ? route.params.focus_date : getTodayISO();
|
||||
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
|
||||
@@ -127,7 +127,7 @@ const router = VueRouter.createRouter({
|
||||
name: "RoomInformation",
|
||||
params: {
|
||||
mode: to.params.mode || DEFAULT_MODE_RAUMINFO,
|
||||
focus_date: to.params.focus_date || getTodayISO(),
|
||||
focus_date: to.params.focus_date || new Date().toISOString().split("T")[0],
|
||||
ort_kurzbz: route.params.ort_kurzbz
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import BsModal from '../../Bootstrap/Modal.js';
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import { getDateStyleClass } from "./getDateStyleClass.js";
|
||||
import { compareISODateValues, formatDate, formatDateTime, getTodayISO } from "../../../helpers/DateHelpers.js";
|
||||
import {compareISODateValues, formatDateTime, formatISODate, getViennaTodayISO} from "./dateUtils.js";
|
||||
|
||||
|
||||
export const AbgabeMitarbeiterDetail = {
|
||||
@@ -174,7 +174,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates
|
||||
'datum': getTodayISO(),
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': false,
|
||||
@@ -351,7 +351,9 @@ export const AbgabeMitarbeiterDetail = {
|
||||
}
|
||||
}
|
||||
},
|
||||
formatDate,
|
||||
formatDate(dateParam) {
|
||||
return formatISODate(dateParam)
|
||||
},
|
||||
openCreateNewAbgabeModal() {
|
||||
if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') {
|
||||
// maybe alert that this is not allowed or smth
|
||||
@@ -368,7 +370,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': getTodayISO(),
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': typ.upload_allowed_default,
|
||||
@@ -402,7 +404,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': getTodayISO(),
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': false,
|
||||
@@ -601,7 +603,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
'fixtermin': false,
|
||||
'invertedFixtermin': true,
|
||||
'kurzbz': '',
|
||||
'datum': getTodayISO(),
|
||||
'datum': getViennaTodayISO(),
|
||||
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
|
||||
'beurteilungsnotiz': '',
|
||||
'upload_allowed': typ.upload_allowed_default,
|
||||
|
||||
@@ -3,7 +3,7 @@ import BsModal from '../../Bootstrap/Modal.js';
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { formatDate, getTodayISO } from "../../../helpers/DateHelpers.js";
|
||||
import { formatISODate, getViennaTodayISO } from "./dateUtils.js";
|
||||
import { validateThesisTitle } from './titleValidation.js'
|
||||
|
||||
|
||||
@@ -248,7 +248,9 @@ export const AbgabeStudentDetail = {
|
||||
|
||||
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
|
||||
},
|
||||
formatDate,
|
||||
formatDate(dateParam) {
|
||||
return formatISODate(dateParam)
|
||||
},
|
||||
async upload(termin) {
|
||||
|
||||
if (! await this.validate(termin))
|
||||
@@ -284,7 +286,7 @@ export const AbgabeStudentDetail = {
|
||||
if(res.meta.status == "success") {
|
||||
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3')))
|
||||
|
||||
termin.abgabedatum = getTodayISO();
|
||||
termin.abgabedatum = getViennaTodayISO();
|
||||
if(res?.data?.signatur !== undefined) {
|
||||
termin.signatur = res.data.signatur
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AbgabeterminStatusLegende from "./StatusLegende.js";
|
||||
import { formatDateTime } from "../../../helpers/DateHelpers.js";
|
||||
import {formatDateTime} from "./dateUtils.js";
|
||||
|
||||
export const AbgabeStudentTimeline = {
|
||||
name: "AbgabeStudentTimeline",
|
||||
|
||||
@@ -11,7 +11,7 @@ import AbgabeStudentTimeline from "./AbgabeStudentTimeline.js";
|
||||
import { splitMailsHelper } from "../../../helpers/EmailHelpers.js"
|
||||
import { getDateStyleClass} from "./getDateStyleClass.js";
|
||||
import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
|
||||
import { compareISODateValues, formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js";
|
||||
import { compareISODateValues, formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
|
||||
|
||||
export const AbgabetoolAssistenz = {
|
||||
name: "AbgabetoolAssistenz",
|
||||
@@ -114,7 +114,7 @@ export const AbgabetoolAssistenz = {
|
||||
fixtermin: false,
|
||||
},
|
||||
serienTermin: Vue.reactive({
|
||||
datum: getTodayISO(),
|
||||
datum: getViennaTodayISO(),
|
||||
bezeichnung: {
|
||||
paabgabetyp_kurzbz: 'zwischen',
|
||||
bezeichnung: 'Zwischenabgabe'
|
||||
@@ -643,7 +643,7 @@ export const AbgabetoolAssistenz = {
|
||||
upload_allowed: false,
|
||||
fixtermin: false,
|
||||
}
|
||||
this.serienEdit.datum = getTodayISO()
|
||||
this.serienEdit.datum = getViennaTodayISO()
|
||||
this.serienEdit.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen')
|
||||
this.serienEdit.kurzbz = ''
|
||||
this.serienEdit.upload_allowed = false
|
||||
@@ -1091,9 +1091,23 @@ export const AbgabetoolAssistenz = {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rowDate = toDateTime(rowVal);
|
||||
const von = toDateTime(filterVal[0]);
|
||||
const bis = toDateTime(filterVal[1]);
|
||||
const toLuxon = (val) => {
|
||||
if (!val) return null;
|
||||
let dt;
|
||||
if (val instanceof Date) {
|
||||
dt = luxon.DateTime.fromJSDate(val);
|
||||
} else if (typeof val === "string") {
|
||||
dt = toViennaDate(val);
|
||||
} else { // fallback
|
||||
dt = luxon.DateTime.fromMillis(Number(val));
|
||||
}
|
||||
|
||||
return dt.isValid ? dt : null;
|
||||
};
|
||||
|
||||
const rowDate = toLuxon(rowVal);
|
||||
const von = toLuxon(filterVal[0]);
|
||||
const bis = toLuxon(filterVal[1]);
|
||||
|
||||
// specific day
|
||||
if (von && !bis) {
|
||||
@@ -1114,8 +1128,22 @@ export const AbgabetoolAssistenz = {
|
||||
|
||||
const rowDate = rowVal.luxonDate;
|
||||
|
||||
const von = toDateTime(filterVal[0]);
|
||||
const bis = toDateTime(filterVal[1]);
|
||||
const toLuxon = (val) => {
|
||||
if (!val) return null;
|
||||
let dt;
|
||||
if (val instanceof Date) {
|
||||
dt = luxon.DateTime.fromJSDate(val);
|
||||
} else if (typeof val === "string") {
|
||||
dt = toViennaDate(val);
|
||||
} else { // fallback
|
||||
dt = luxon.DateTime.fromMillis(Number(val));
|
||||
}
|
||||
|
||||
return dt.isValid ? dt : null;
|
||||
};
|
||||
|
||||
const von = toLuxon(filterVal[0]);
|
||||
const bis = toLuxon(filterVal[1]);
|
||||
|
||||
// specific day
|
||||
if (von && !bis) {
|
||||
@@ -1344,7 +1372,7 @@ export const AbgabetoolAssistenz = {
|
||||
|
||||
},
|
||||
checkAbgabetermineProjektarbeit(projekt) {
|
||||
const now = getNow()
|
||||
const now = luxon.DateTime.now()
|
||||
|
||||
// calculate Abgabetermin time diff to now and assign last and next to projekt
|
||||
projekt.abgabetermine.forEach(termin => {
|
||||
@@ -1359,7 +1387,7 @@ export const AbgabetoolAssistenz = {
|
||||
// while already looping through each termin, calculate datestyle beforehand
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
|
||||
const date = toDateTime(termin.datum).endOf('day')
|
||||
const date = toViennaDate(termin.datum).endOf('day')
|
||||
termin.luxonDate = date
|
||||
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
|
||||
|
||||
@@ -1626,7 +1654,9 @@ export const AbgabetoolAssistenz = {
|
||||
getNotenFilterOptionLabel(option) {
|
||||
return option.bezeichnung
|
||||
},
|
||||
formatDate,
|
||||
formatDate(dateParam) {
|
||||
return formatISODate(dateParam);
|
||||
},
|
||||
formAction(cell) {
|
||||
const actionButtons = document.createElement('div');
|
||||
actionButtons.className = "d-flex gap-3";
|
||||
@@ -1821,7 +1851,9 @@ export const AbgabetoolAssistenz = {
|
||||
return str
|
||||
},
|
||||
isPastDate(date) {
|
||||
return getNow() > toDateTime(date).endOf("day");
|
||||
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
|
||||
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
|
||||
return nowInVienna > deadline;
|
||||
},
|
||||
setDetailComponent(details){
|
||||
const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id)
|
||||
|
||||
@@ -7,7 +7,7 @@ import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { getDateStyleClass } from "./getDateStyleClass.js";
|
||||
import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
|
||||
import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
|
||||
import { formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js";
|
||||
import { formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
|
||||
|
||||
export const AbgabetoolMitarbeiter = {
|
||||
name: "AbgabetoolMitarbeiter",
|
||||
@@ -54,7 +54,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
allowedNotenOptions: null,
|
||||
notenOptionsNonFinal: null,
|
||||
serienTermin: Vue.reactive({
|
||||
datum: getTodayISO(),
|
||||
datum: getViennaTodayISO(),
|
||||
bezeichnung: {
|
||||
paabgabetyp_kurzbz: 'zwischen',
|
||||
bezeichnung: 'Zwischenabgabe'
|
||||
@@ -668,8 +668,22 @@ export const AbgabetoolMitarbeiter = {
|
||||
|
||||
const rowDate = rowVal.luxonDate;
|
||||
|
||||
const von = toDateTime(filterVal[0]);
|
||||
const bis = toDateTime(filterVal[1]);
|
||||
const toLuxon = (val) => {
|
||||
if (!val) return null;
|
||||
let dt;
|
||||
if (val instanceof Date) {
|
||||
dt = luxon.DateTime.fromJSDate(val);
|
||||
} else if (typeof val === "string") {
|
||||
dt = toViennaDate(val);
|
||||
} else { // fallback
|
||||
dt = luxon.DateTime.fromMillis(Number(val));
|
||||
}
|
||||
|
||||
return dt.isValid ? dt : null;
|
||||
};
|
||||
|
||||
const von = toLuxon(filterVal[0]);
|
||||
const bis = toLuxon(filterVal[1]);
|
||||
|
||||
// specific day
|
||||
if (von && !bis) {
|
||||
@@ -885,14 +899,14 @@ export const AbgabetoolMitarbeiter = {
|
||||
}
|
||||
},
|
||||
checkAbgabetermineProjektarbeit(projekt) {
|
||||
const now = getNow()
|
||||
const now = luxon.DateTime.now()
|
||||
// calculate Abgabetermin time diff to now and assign last and next to projekt
|
||||
projekt.abgabetermine.forEach(termin => {
|
||||
|
||||
// while already looping through each termin, calculate datestyle beforehand
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
|
||||
const date = toDateTime(termin.datum).endOf('day')
|
||||
const date = toViennaDate(termin.datum).endOf('day')
|
||||
termin.luxonDate = date
|
||||
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
|
||||
|
||||
@@ -1003,7 +1017,9 @@ export const AbgabetoolMitarbeiter = {
|
||||
getOptionLabelAbgabetyp(option){
|
||||
return this.$p.t('abgabetool/c4paatyp' + option.paabgabetyp_kurzbz)
|
||||
},
|
||||
formatDate,
|
||||
formatDate(dateParam) {
|
||||
return formatISODate(dateParam);
|
||||
},
|
||||
undoSelection(cell) {
|
||||
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
|
||||
// but actually just revert it after the fact
|
||||
@@ -1088,7 +1104,9 @@ export const AbgabetoolMitarbeiter = {
|
||||
return str
|
||||
},
|
||||
isPastDate(date) {
|
||||
return getNow() > toDateTime(date).endOf("day");
|
||||
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
|
||||
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
|
||||
return nowInVienna > deadline;
|
||||
},
|
||||
setDetailComponent(details){
|
||||
this.loading=true
|
||||
|
||||
@@ -4,7 +4,6 @@ import ApiAuthinfo from '../../../api/factory/authinfo.js';
|
||||
import BsModal from "../../Bootstrap/Modal.js";
|
||||
import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { getDateStyleClass} from "./getDateStyleClass.js";
|
||||
import { getNow, toDateTime } from "../../../helpers/DateHelpers.js";
|
||||
import { validateThesisTitle } from './titleValidation.js'
|
||||
|
||||
export const AbgabetoolStudent = {
|
||||
@@ -172,7 +171,9 @@ export const AbgabetoolStudent = {
|
||||
return qgate1positiv && qgate2positiv
|
||||
},
|
||||
isPastDate(date) {
|
||||
return getNow() > toDateTime(date).endOf("day");
|
||||
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
|
||||
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
|
||||
return nowInVienna > deadline;
|
||||
},
|
||||
setDetailComponent(details){
|
||||
this.loading = true
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const zone = 'Europe/Vienna';
|
||||
|
||||
export function getViennaTodayISO() {
|
||||
return luxon.DateTime.now().setZone(zone).toISODate();
|
||||
}
|
||||
|
||||
export function formatISODate(dateParam) {
|
||||
if (!dateParam) return '';
|
||||
|
||||
const date = luxon.DateTime.fromISO(String(dateParam), { zone });
|
||||
return date.isValid ? date.toFormat('dd.MM.yyyy') : '';
|
||||
}
|
||||
|
||||
export function formatDateTime(dateParam) {
|
||||
if (!dateParam) return '';
|
||||
|
||||
const date = luxon.DateTime.fromSQL(dateParam);
|
||||
return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : '';
|
||||
}
|
||||
|
||||
export function toViennaDate(dateParam) {
|
||||
if (!dateParam) return null;
|
||||
|
||||
return luxon.DateTime.fromISO(String(dateParam), { zone });
|
||||
}
|
||||
|
||||
export function compareISODateValues(a, b) {
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
import { getNow, toDateTime } from "../../../helpers/DateHelpers.js";
|
||||
const zone = 'Europe/Vienna';
|
||||
|
||||
export function getDateStyleClass(termin, notenOptions) {
|
||||
const today = getNow();
|
||||
const datum = toDateTime(termin.datum)?.endOf('day');
|
||||
const abgabedatum = toDateTime(termin.abgabedatum);
|
||||
termin.diffindays = datum ? datum.diff(today, 'days').days : NaN;
|
||||
const isLate = abgabedatum && datum && abgabedatum > datum;
|
||||
const today = luxon.DateTime.now().setZone(zone);
|
||||
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
|
||||
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
|
||||
termin.diffindays = datum.diff(today, 'days').days;
|
||||
const isLate = abgabedatum && abgabedatum > datum;
|
||||
|
||||
// GRADE STATUS
|
||||
if (termin.note) {
|
||||
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
urlMatchRankings:[],
|
||||
navUserDropdown:null,
|
||||
menuOpen:true,
|
||||
openMenuHierarchy: [],
|
||||
};
|
||||
},
|
||||
inject: ["isNarrow", "isMobile"],
|
||||
@@ -35,7 +36,7 @@ export default {
|
||||
return{
|
||||
setActiveEntry: this.setActiveEntry,
|
||||
addUrlCount: this.addUrlCount,
|
||||
makeParentContentActive: this.makeParentContentActive,
|
||||
setOpenMenuHierarchy: this.setOpenMenuHierarchy,
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
@@ -82,19 +83,6 @@ export default {
|
||||
handleHideNavUser(){
|
||||
document.removeEventListener("click", this.checkSettingsVisibility);
|
||||
},
|
||||
makeParentContentActive(content_id, collection=this.entries, parent=null){
|
||||
if(!collection) return;
|
||||
if (typeof collection == 'object' && !Array.isArray(collection) && Object.entries(collection).length > 0) {
|
||||
collection = Object.values(collection);
|
||||
}
|
||||
for(let entry of collection){
|
||||
if(entry.content_id == content_id){
|
||||
this.activeEntry = parent;
|
||||
}
|
||||
this.makeParentContentActive(content_id, entry.childs, entry.content_id);
|
||||
}
|
||||
|
||||
},
|
||||
addUrlCount(count){
|
||||
this.urlMatchRankings.push(count);
|
||||
},
|
||||
@@ -102,6 +90,9 @@ export default {
|
||||
setActiveEntry(content_id){
|
||||
this.activeEntry = content_id;
|
||||
},
|
||||
setOpenMenuHierarchy(openMenuHierarchy) {
|
||||
this.openMenuHierarchy = openMenuHierarchy;
|
||||
},
|
||||
},
|
||||
created(){
|
||||
this.fetchMenu();
|
||||
@@ -192,7 +183,14 @@ export default {
|
||||
<div class="offcanvas-body p-0">
|
||||
<div id="nav-main-menu" class="nav-menu-collapse collapse collapse-horizontal show">
|
||||
<div class="flex-grow-1">
|
||||
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeEntry" v-for="entry in entries" :key="entry.content_id" :entry="entry" />
|
||||
<cis-menu-entry
|
||||
v-for="entry in entries"
|
||||
:key="entry.content_id"
|
||||
:highestMatchingUrlCount="highestMatchingUrlCount"
|
||||
:activeContent="activeEntry"
|
||||
:entry="entry"
|
||||
:openMenuHierarchy="openMenuHierarchy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,11 @@ export default {
|
||||
},
|
||||
activeContent: [String, Number],
|
||||
highestMatchingUrlCount: Number,
|
||||
openMenuHierarchy: Array,
|
||||
precedingMenuHierarchy: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
@@ -15,7 +20,7 @@ export default {
|
||||
urlCount:0,
|
||||
}
|
||||
},
|
||||
inject: ['makeParentContentActive', 'setActiveEntry','addUrlCount'],
|
||||
inject: ['setActiveEntry', 'setOpenMenuHierarchy', 'addUrlCount'],
|
||||
watch:{
|
||||
highestMatchingUrlCount: function(newValue)
|
||||
{
|
||||
@@ -25,49 +30,27 @@ export default {
|
||||
this.setActiveEntry(this.entry.content_id);
|
||||
}
|
||||
},
|
||||
activeContent: function(newValue){
|
||||
if(newValue == this.entry.content_id){
|
||||
// wenn der Menupunkt nicht bereits offen ist
|
||||
if (!this.entry.menu_open){
|
||||
this.entry.menu_open = true;
|
||||
}
|
||||
|
||||
}else{
|
||||
if (this.searchRecursiveChild(this.entry, 'content_id',newValue)) {
|
||||
this.entry.menu_open = true;
|
||||
} else {
|
||||
this.entry.menu_open = false;
|
||||
}
|
||||
openMenuHierarchy() {
|
||||
if (!this.hasChilds) return;
|
||||
|
||||
if (this.$props.openMenuHierarchy.length && this.$props.openMenuHierarchy[0] === this.$props.entry.content_id) {
|
||||
this.$props.entry.menu_open = true;
|
||||
} else {
|
||||
this.$props.entry.menu_open = false;
|
||||
}
|
||||
},
|
||||
'entry.menu_open': function (newValue,oldValue) {
|
||||
if (newValue)
|
||||
{
|
||||
// only invokes .show if this.collapse is not null
|
||||
this.collapse && this.collapse.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
// only invokes .hide if this.collapse is not null
|
||||
this.collapse && this.collapse.hide();
|
||||
if (this.activeContent == this.entry.content_id)
|
||||
{
|
||||
this.makeParentContentActive(this.entry.content_id);
|
||||
}
|
||||
'entry.menu_open': function (isMenuOpen) {
|
||||
if (!this.collapse)
|
||||
return;
|
||||
|
||||
if (isMenuOpen) {
|
||||
this.collapse.show();
|
||||
} else {
|
||||
this.collapse.hide();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
active: function () {
|
||||
if (this.entry.menu_open){
|
||||
return true;
|
||||
}
|
||||
else if (this.activeContent) {
|
||||
return this.activeContent == this.entry.content_id;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
hasFullLink() {
|
||||
return this.entry.url.startsWith(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router)
|
||||
},
|
||||
@@ -89,25 +72,34 @@ export default {
|
||||
},
|
||||
hasChilds() {
|
||||
return this.entry.childs && this.entry.childs.length !== 0;
|
||||
}
|
||||
},
|
||||
menuHierarchy() {
|
||||
return [...this.$props.precedingMenuHierarchy, this.$props.entry.content_id];
|
||||
},
|
||||
menuNodeHref() {
|
||||
if (this.hasChilds) {
|
||||
return this.$props.entry.menu_open && this.hasFullLink
|
||||
? this.$props.entry.url
|
||||
: null;
|
||||
} else {
|
||||
return this.$props.entry.url;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getUrlMatchPoints(url,link){
|
||||
let splitted_link = link.split('/');
|
||||
let splitted_url = url.href.split('/');
|
||||
|
||||
let count = 0;
|
||||
|
||||
for(let part_url of splitted_url)
|
||||
{
|
||||
for (let part_link of splitted_link)
|
||||
{
|
||||
if(part_url == part_link)
|
||||
{
|
||||
for (let part_url of splitted_url) {
|
||||
for (let part_link of splitted_link) {
|
||||
if (part_url === part_link) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.urlCount = count;
|
||||
this.addUrlCount(count);
|
||||
},
|
||||
@@ -119,14 +111,11 @@ export default {
|
||||
let url_hash = url.hash;
|
||||
url_hash = url_hash.replace(url_hash_spaceSymbol_regex, " ").replace(url_hash_sharpSymbol_regex,"");
|
||||
|
||||
// if the url hash contains the titel of the menu
|
||||
// or if the url equals the link of a menu
|
||||
// then set the menu active
|
||||
if (url_hash == this.entry.titel || url.href == this.entry.url) {
|
||||
this.setActiveEntry(this.entry.content_id);
|
||||
this.setOpenMenuHierarchy(this.menuHierarchy);
|
||||
}
|
||||
},
|
||||
// searches the childs of an entry recursively based on the value of a property
|
||||
searchRecursiveChild(entry,property,value){
|
||||
if (typeof entry.childs == 'object' && !Array.isArray(entry.childs) && Object.entries(entry.childs).length > 0){
|
||||
entry.childs = Object.values(entry.childs);
|
||||
@@ -143,16 +132,23 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
toggleCollapse(evt) {
|
||||
if (this.active)
|
||||
{
|
||||
this.makeParentContentActive(this.entry.content_id);
|
||||
toggleCollapse() {
|
||||
if (!this.$props.entry.menu_open) {
|
||||
this.setOpenMenuHierarchy(this.menuHierarchy);
|
||||
} else {
|
||||
this.setOpenMenuHierarchy(this.precedingMenuHierarchy);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setActiveEntry(this.entry.content_id);
|
||||
},
|
||||
handleClickOnMenuNode() {
|
||||
if (this.hasFullLink) {
|
||||
this.setActiveEntry(this.$props.entry.content_id);
|
||||
if (this.hasChilds) {
|
||||
this.setOpenMenuHierarchy(this.menuHierarchy);
|
||||
}
|
||||
} else if (this.hasChilds) {
|
||||
this.toggleCollapse();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.children) {
|
||||
@@ -168,41 +164,41 @@ export default {
|
||||
INCLUDE
|
||||
</div>
|
||||
<template v-else>
|
||||
<template v-if="hasChilds">
|
||||
<div class="btn-group w-100">
|
||||
<a :target="target"
|
||||
:href="(entry.menu_open && hasFullLink) ? entry.url : null"
|
||||
@click="toggleCollapse"
|
||||
:class="{
|
||||
'btn btn-default rounded-0 text-start': true,
|
||||
['btn-level-' + level]: true,
|
||||
'fw-bold':active
|
||||
}">
|
||||
{{ entry.titel }}
|
||||
</a>
|
||||
<button @click.prevent="toggleCollapse" :aria-expanded="entry.menu_open"
|
||||
:class="{
|
||||
'btn btn-default rounded-0 dropdown-toggle dropdown-toggle-split flex-grow-0': true,
|
||||
collapsed: !entry.menu_open
|
||||
}">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul ref="children"
|
||||
class="nav w-100 collapse">
|
||||
<cis-menu-entry :highestMatchingUrlCount="highestMatchingUrlCount" :activeContent="activeContent" v-for="child in entry.childs" :key="child" :entry="child" :level="level + 1"/>
|
||||
</ul>
|
||||
</template>
|
||||
<a v-else
|
||||
:href="entry.url"
|
||||
:target="target"
|
||||
:class="{
|
||||
'btn btn-default rounded-0 w-100 text-start': true,
|
||||
['btn-level-' + level]: true,
|
||||
'fw-bold':active
|
||||
}"
|
||||
@mouseup="setActiveEntry(entry.content_id)">
|
||||
{{ entry.titel }}
|
||||
</a>
|
||||
<div class="btn-group w-100">
|
||||
<a
|
||||
@click="handleClickOnMenuNode($event)"
|
||||
:href="menuNodeHref"
|
||||
:target="target"
|
||||
class="btn btn-default rounded-0 text-start"
|
||||
:class="{
|
||||
['btn-level-' + level]: true,
|
||||
'fw-bold': $props.activeContent === $props.entry.content_id
|
||||
}"
|
||||
:style="'padding-left: calc(var(--bs-btn-padding-x) * ' + $props.level + ');'"
|
||||
>
|
||||
{{ entry.titel }}
|
||||
</a>
|
||||
<button
|
||||
v-if="hasChilds"
|
||||
@click.prevent="toggleCollapse()"
|
||||
:aria-expanded="entry.menu_open"
|
||||
class="btn btn-default rounded-0 dropdown-toggle dropdown-toggle-split flex-grow-0"
|
||||
:class="{ collapsed: !entry.menu_open }"
|
||||
>
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul v-if="hasChilds" ref="children" class="nav w-100 collapse" >
|
||||
<cis-menu-entry
|
||||
v-for="child in entry.childs"
|
||||
:key="child"
|
||||
:highestMatchingUrlCount="highestMatchingUrlCount"
|
||||
:activeContent="activeContent"
|
||||
:entry="child"
|
||||
:level="level + 1"
|
||||
:openMenuHierarchy="$props.openMenuHierarchy.slice(1)"
|
||||
:precedingMenuHierarchy="menuHierarchy"
|
||||
/>
|
||||
</ul>
|
||||
</template>`
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatDate, formatTime } from "../../../helpers/DateHelpers.js"
|
||||
import { numberPadding, formatDate } from "../../../helpers/DateHelpers.js"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -25,14 +25,27 @@ export default {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + `/CisVue/Cms/content/${this.event.ort_content_id}`
|
||||
},
|
||||
start_time: function () {
|
||||
return this.event.start ? formatTime(this.event.start) : 'N/A';
|
||||
if (!this.event.start) return 'N/A';
|
||||
if (!this.event.start instanceof Date) {
|
||||
return this.event.start;
|
||||
}
|
||||
return numberPadding(this.event.start.getHours()) + ":" + numberPadding(this.event.start.getMinutes());
|
||||
},
|
||||
end_time: function () {
|
||||
return this.event.end ? formatTime(this.event.end) : 'N/A';
|
||||
if (!this.event.end) return 'N/A';
|
||||
if (!this.event.end instanceof Date) {
|
||||
return this.event.end;
|
||||
}
|
||||
return numberPadding(this.event.end.getHours()) + ":" + numberPadding(this.event.end.getMinutes());
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
methodFormatDate: formatDate,
|
||||
mehtodNumberPadding: function (number) {
|
||||
return numberPadding(number);
|
||||
},
|
||||
methodFormatDate: function (d) {
|
||||
return formatDate(d);
|
||||
},
|
||||
},
|
||||
template:/*html*/`
|
||||
<table class="table table-hover mb-4">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import FhcCalendar from "../../Calendar/LvPlan.js";
|
||||
|
||||
import ApiLvPlan from '../../../api/factory/lvPlan.js';
|
||||
import { getTodayISO } from "../../../helpers/DateHelpers.js";
|
||||
|
||||
export const DEFAULT_MODE_RAUMINFO_MOBILE = 'List';
|
||||
export const DEFAULT_MODE_RAUMINFO_DESKTOP = 'Week';
|
||||
@@ -18,7 +17,7 @@ export default {
|
||||
inject: ["isMobile"],
|
||||
computed: {
|
||||
currentDay() {
|
||||
return this.propsViewData?.focus_date || getTodayISO();
|
||||
return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate();
|
||||
},
|
||||
currentMode() {
|
||||
const defaultMode = this.isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
|
||||
import VueDatePicker from '../../vueDatepicker.js.php';
|
||||
import ApiOrt from '../../../api/factory/ort.js'
|
||||
import { getNow, getTodayISO } from "../../../helpers/DateHelpers.js";
|
||||
export const Raumsuche = {
|
||||
name: "Raumsuche",
|
||||
components: {
|
||||
@@ -11,8 +10,6 @@ export const Raumsuche = {
|
||||
},
|
||||
inject: ["isMobile"],
|
||||
data() {
|
||||
const now = getNow();
|
||||
|
||||
return {
|
||||
phrasenPromise: null,
|
||||
phrasenResolved: false,
|
||||
@@ -26,14 +23,14 @@ export const Raumsuche = {
|
||||
},
|
||||
anzahl: 1,
|
||||
selectedType: null,
|
||||
datum: getTodayISO(),
|
||||
datum: new Date(),
|
||||
von: Vue.ref({
|
||||
hours: now.hour,
|
||||
minutes: now.minute
|
||||
hours: new Date().getHours(),
|
||||
minutes: new Date().getMinutes()
|
||||
}),
|
||||
bis: Vue.ref({
|
||||
hours: now.plus({ hours: 1 }).hour,
|
||||
minutes: now.minute
|
||||
hours: new Date().getHours() + 1,
|
||||
minutes: new Date().getMinutes()
|
||||
}),
|
||||
datepickerTextInputOptions: {
|
||||
enterSubmit: true,
|
||||
@@ -139,7 +136,7 @@ export const Raumsuche = {
|
||||
})
|
||||
},
|
||||
loadRooms() {
|
||||
this.$api.call(ApiOrt.getRooms(this.datum, this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
|
||||
this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
|
||||
.then(res => {
|
||||
if(res?.data?.retval) this.setupData(res.data.retval)
|
||||
})
|
||||
@@ -153,6 +150,17 @@ export const Raumsuche = {
|
||||
setRoute(val) {
|
||||
// TODO: router push
|
||||
},
|
||||
dateFormat(date) {
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
return `${day}.${month}.${year}`
|
||||
},
|
||||
timeFormat(date) {
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
async setupMounted() {
|
||||
|
||||
this.tableBuiltPromise = new Promise(this.tableResolve)
|
||||
@@ -191,8 +199,7 @@ export const Raumsuche = {
|
||||
:dark="isDarkMode"
|
||||
:clearable="false"
|
||||
:enable-time-picker="false"
|
||||
format="dd.MM.yyyy"
|
||||
model-type="yyyy-MM-dd"
|
||||
:format="dateFormat"
|
||||
:text-input="datepickerTextInputOptions"
|
||||
:min-date="new Date()"
|
||||
date-picker
|
||||
@@ -206,7 +213,7 @@ export const Raumsuche = {
|
||||
v-model="von"
|
||||
:dark="isDarkMode"
|
||||
:clearable="false"
|
||||
format="HH:mm"
|
||||
:format="timeFormat"
|
||||
:text-input="timepickerTextInputOptions"
|
||||
:is-24="true"
|
||||
time-picker
|
||||
@@ -220,7 +227,7 @@ export const Raumsuche = {
|
||||
v-model="bis"
|
||||
:dark="isDarkMode"
|
||||
:clearable="false"
|
||||
format="HH:mm"
|
||||
:format="timeFormat"
|
||||
:text-input="timepickerTextInputOptions"
|
||||
:is-24="true"
|
||||
time-picker
|
||||
|
||||
@@ -206,6 +206,8 @@ export default {
|
||||
});
|
||||
|
||||
this.lehrveranstaltungen.forEach((lehrveranstaltung)=>{
|
||||
// a lehrveranstaltung without a modul has no children
|
||||
if(!Array.isArray(lehrveranstaltung.lehrveranstaltungen)) return;
|
||||
lehrveranstaltung.lehrveranstaltungen.sort((lv1,lv2)=>{
|
||||
if (lv1.bezeichnung.toLowerCase() > lv2.bezeichnung.toLowerCase()) {
|
||||
return 1;
|
||||
@@ -393,6 +395,16 @@ export default {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" v-else-if="!Array.isArray(lehrveranstaltung.lehrveranstaltungen)">
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="d-flex list-group-item">
|
||||
<a class="fhc-link-color d-block me-auto" href="#" @click="openLvUebersicht(lehrveranstaltung)">{{isGermanLanguage ? lehrveranstaltung.bezeichnung : lehrveranstaltung.bezeichnung_english}}</a>
|
||||
<p>{{lehrveranstaltung.lehrform_kurzbz}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { formatDate, formatDateTime } from "../../helpers/DateHelpers.js";
|
||||
|
||||
export default {
|
||||
props: [
|
||||
"config",
|
||||
@@ -32,7 +30,14 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDateTime,
|
||||
getDate: formatDate
|
||||
formatDateTime: function(dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear() + ' | '
|
||||
+ dt.getHours() + ':' + dt.getMinutes();
|
||||
},
|
||||
getDate: function(dateTime){
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AbstractWidget from './Abstract.js';
|
||||
import BsModal from '../Bootstrap/Modal.js';
|
||||
import { formatDate, formatTime } from '../../helpers/DateHelpers.js';
|
||||
import { numberPadding } from '../../helpers/DateHelpers.js';
|
||||
import ApiCms from '../../api/factory/cms.js';
|
||||
|
||||
const MAX_LOADED_NEWS = 30;
|
||||
@@ -84,8 +84,14 @@ export default {
|
||||
});
|
||||
})
|
||||
},
|
||||
formatDate,
|
||||
formatTime,
|
||||
formatDate: function (dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return numberPadding(dt.getDate()) + '.' + numberPadding((dt.getMonth() + 1)) + '.' + dt.getFullYear();
|
||||
},
|
||||
formatTime: function (dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes());
|
||||
},
|
||||
isString(value){
|
||||
return Object.prototype.toString.call(value) === '[object String]';
|
||||
},
|
||||
|
||||
@@ -255,7 +255,7 @@ export default {
|
||||
deleteButton.className = 'btn btn-outline-secondary';
|
||||
const icon = document.createElement('i');
|
||||
icon.className = 'fa fa-trash';
|
||||
icon.title = this.$p.t('ui/loeschen');
|
||||
icon.title = this.$p.t('lehre/info_deleteMarkEntry');
|
||||
deleteButton.append(icon);
|
||||
deleteButton.addEventListener('click', evt => {
|
||||
evt.stopPropagation();
|
||||
|
||||
@@ -822,18 +822,33 @@ export default {
|
||||
@headerFilterOn="handleHeaderFilter"
|
||||
>
|
||||
|
||||
<!--
|
||||
<template #actions>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-outline-success sm mb-1"
|
||||
:title="'Export ' + selectedPrestudents.length + ' prestudent(s) to Excel'"
|
||||
>
|
||||
<i class="fas fa-file-excel fa-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<template #actions>
|
||||
<!-- <div>
|
||||
<button
|
||||
class="btn btn-outline-success sm mb-1"
|
||||
:title="'Export ' + selectedPrestudents.length + ' prestudent(s) to Excel'"
|
||||
>
|
||||
<i class="fas fa-file-excel fa-xl"></i>
|
||||
</button>
|
||||
</div> -->
|
||||
<template v-if="filter.length || headerFilterActive">
|
||||
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
|
||||
<p class="text-danger mb-0">
|
||||
<strong>{{$p.t('filter','filterActive')}}</strong>
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-danger sm"
|
||||
:title="$p.t('filter/filterDelete')"
|
||||
@click="resetFilter"
|
||||
>
|
||||
<span class="fa-solid fa-filter-circle-xmark"></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
||||
<template #additional>
|
||||
<div class="pe-1">
|
||||
@@ -873,22 +888,7 @@ export default {
|
||||
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="filter.length || headerFilterActive">
|
||||
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
|
||||
<p class="text-danger mb-0">
|
||||
<strong>{{$p.t('filter','filterActive')}}</strong>
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-danger sm"
|
||||
:title="$p.t('filter/filterDelete')"
|
||||
@click="resetFilter"
|
||||
>
|
||||
<span class="fa-solid fa-filter-circle-xmark"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template #filter>
|
||||
|
||||
@@ -1,69 +1,61 @@
|
||||
|
||||
// Everything parses and formats with luxon in the server timezone. new Date("2026-09-02")
|
||||
// reads UTC midnight and shows the day before west of Greenwich.
|
||||
// HELPER FILE -- that contains multiple functions which create, handle and manipulate js dates
|
||||
|
||||
|
||||
function getZone() {
|
||||
return globalThis.FHC_JS_DATA_STORAGE_OBJECT?.timezone || 'Europe/Vienna';
|
||||
// custom Error class for DateHelpers.js misuse
|
||||
class DateHelperError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "DateHelperError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a date value into a luxon DateTime in the server timezone
|
||||
* adds padding to a number
|
||||
*
|
||||
* @param {string|number|Date|Object} value - ISO string, SQL string, milliseconds, Date or luxon DateTime.
|
||||
* @returns {Object|null} luxon DateTime, or null if the value is empty or unparsable.
|
||||
* @param {number|string} number - the number on which padding should be added.
|
||||
* @returns {string} number with padding.
|
||||
*/
|
||||
export function toDateTime(value) {
|
||||
if (!value) return null;
|
||||
|
||||
const zone = getZone();
|
||||
let date;
|
||||
|
||||
if (value instanceof luxon.DateTime) date = value.setZone(zone);
|
||||
else if (value instanceof Date) date = luxon.DateTime.fromJSDate(value, { zone });
|
||||
else if (typeof value === 'number') date = luxon.DateTime.fromMillis(value, { zone });
|
||||
// fromISO rejects the space that SQL puts between date and time
|
||||
else if (typeof value === 'string') date = value.includes(' ')
|
||||
? luxon.DateTime.fromSQL(value, { zone })
|
||||
: luxon.DateTime.fromISO(value, { zone });
|
||||
else return null;
|
||||
|
||||
return date.isValid ? date : null;
|
||||
}
|
||||
|
||||
// current time in the server timezone
|
||||
export function getNow() {
|
||||
return luxon.DateTime.now().setZone(getZone());
|
||||
}
|
||||
|
||||
// today in the server timezone, as yyyy-MM-dd
|
||||
export function getTodayISO() {
|
||||
return getNow().toISODate();
|
||||
}
|
||||
|
||||
// the three formatters return '' for an empty or unparsable value
|
||||
export function formatDate(value) {
|
||||
return toDateTime(value)?.toFormat('dd.MM.yyyy') ?? '';
|
||||
}
|
||||
|
||||
export function formatTime(value) {
|
||||
return toDateTime(value)?.toFormat('HH:mm') ?? '';
|
||||
}
|
||||
|
||||
export function formatDateTime(value) {
|
||||
return toDateTime(value)?.toFormat('dd.MM.yyyy HH:mm') ?? '';
|
||||
}
|
||||
|
||||
// tabulator sorter for ISO dates, sorts empty values last
|
||||
export function compareISODateValues(a, b) {
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
|
||||
// pads hours and minutes to two digits
|
||||
export function numberPadding(number) {
|
||||
return String(number).padStart(2, '0');
|
||||
if(typeof number !== "string" && typeof number !== "number")
|
||||
{
|
||||
throw new TypeError("function numberPadding in file DateHelpers.js is only usable with strings or numbers");
|
||||
}
|
||||
if(number.toString().length > 2)
|
||||
{
|
||||
throw new DateHelperError("The number on which the padding should be added should not be longer than to 2 characters, please refere to the function numberPadding in the helper file DateHelpers.js");
|
||||
}
|
||||
return number.toString().length == 1 ? '0' + number.toString() : number.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* formats date to dd.mm.yyyy
|
||||
*
|
||||
* @param {string|Date} d - the date that should be formatted.
|
||||
* @returns {string} formatted date string.
|
||||
*/
|
||||
export function formatDate(d) {
|
||||
// parameter is of type Date
|
||||
if(d instanceof Date)
|
||||
{
|
||||
if (isNaN(date.valueOf())) {
|
||||
return 'N/A';
|
||||
}
|
||||
// if the date is an invalid string then creating a date from the string will fail and N/A is returned
|
||||
return `${numberPadding(d.getDate())}.${numberPadding(d.getMonth() + 1)}.${d.getFullYear()}`;
|
||||
}
|
||||
// parameter is of type string
|
||||
else if (typeof d === "string")
|
||||
{
|
||||
let date = new Date(d);
|
||||
// if the date is an invalid string then creating a date from the string will fail and N/A is returned
|
||||
if (isNaN(date.valueOf())) {
|
||||
return 'N/A';
|
||||
}
|
||||
return `${numberPadding(date.getDate())}.${numberPadding(date.getMonth() + 1)}.${date.getFullYear()}`;
|
||||
}
|
||||
// parameter is not of type string or Date and an exception is thrown
|
||||
else
|
||||
{
|
||||
throw new TypeError("The parameter provided for this function is not a string or a Date object, please refere to the function formatDate in the DateHelpers.js file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62411,6 +62411,26 @@ I have been informed that I am under no obligation to consent to the transmissio
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'category' => 'lehre',
|
||||
'phrase' => 'info_deleteMarkEntry',
|
||||
'insertvon' => 'system',
|
||||
'phrases' => array(
|
||||
array(
|
||||
'sprache' => 'German',
|
||||
'text' => 'Noteneintrag entfernen',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
),
|
||||
array(
|
||||
'sprache' => 'English',
|
||||
'text' => 'Remove grade entry',
|
||||
'description' => '',
|
||||
'insertvon' => 'system'
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user