mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-25 07:59:28 +00:00
Merge branch 'feature-25999/C4_cleanup' into merge_FHC4_C4
This commit is contained in:
@@ -25,9 +25,9 @@ class Stundenplan extends Auth_Controller
|
||||
*/
|
||||
public function index($lv_id = null)
|
||||
{
|
||||
|
||||
$viewData = array(
|
||||
'lv_id' => $lv_id
|
||||
'lv_id' => $lv_id,
|
||||
'uid'=>getAuthUID(),
|
||||
);
|
||||
|
||||
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Stundenplan']);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
if (!defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
class AuthInfo extends FHCAPI_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Object initialization
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'getAuthUID' => self::PERM_LOGGED,
|
||||
]);
|
||||
|
||||
$this->uid = getAuthUID();
|
||||
$this->pid = getAuthPersonID();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* returns the uid of the currently logged in user
|
||||
* @access public
|
||||
*
|
||||
*/
|
||||
public function getAuthUID()
|
||||
{
|
||||
$this->terminateWithSuccess(['uid'=>$this->uid]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class Stundenplan extends FHCAPI_Controller
|
||||
'Reservierungen' => self::PERM_LOGGED,
|
||||
'getStundenplan' => self::PERM_LOGGED,
|
||||
'getLehreinheitStudiensemester' => self::PERM_LOGGED,
|
||||
'studiensemesterDateInterval' => self::PERM_LOGGED,
|
||||
]);
|
||||
|
||||
$this->load->library('LogLib');
|
||||
@@ -56,6 +57,15 @@ class Stundenplan extends FHCAPI_Controller
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
//TODO: delete this function if we don't use the old calendar export endpoints anymore
|
||||
public function studiensemesterDateInterval($date){
|
||||
$this->load->model('organisation/Studiensemester_model','StudiensemesterModel');
|
||||
$studiensemester =$this->StudiensemesterModel->getByDate(date_format(date_create($date),'Y-m-d'));
|
||||
$studiensemester =current($this->getDataOrTerminateWithError($studiensemester));
|
||||
$this->terminateWithSuccess($studiensemester);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fetches Stunden layout from database
|
||||
* @access public
|
||||
@@ -545,7 +555,7 @@ class Stundenplan extends FHCAPI_Controller
|
||||
private function studienSemesterErmitteln($start_date,$end_date){
|
||||
|
||||
// gets all studiensemester from the student from start_date to end_date
|
||||
$semester_range = $this->StudiensemesterModel->getByDate($start_date,$end_date);
|
||||
$semester_range = $this->StudiensemesterModel->getByDateRange($start_date,$end_date);
|
||||
$semester_range = array_map(
|
||||
function($sem)
|
||||
{
|
||||
|
||||
@@ -170,13 +170,31 @@ class Studiensemester_model extends DB_Model
|
||||
return $this->execQuery($query, array($studiensemester_kurzbz, $studiengang_kz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Studiensemester for a date
|
||||
* @param $date
|
||||
* @return string
|
||||
*/
|
||||
public function getByDate($date)
|
||||
{
|
||||
// gets the studiensemster of a date or the next closest previous studiensemester if a date is not within a studiensemester
|
||||
$query = "
|
||||
SELECT studiensemester_kurzbz, start, ende
|
||||
FROM public.tbl_studiensemester
|
||||
WHERE ( ende >= ?::date AND start <= ?::date ) OR ( ende >= ?::date + '-45 days'::interval AND start <= ?::date + '-45 days'::interval )
|
||||
ORDER BY start DESC
|
||||
LIMIT 1";
|
||||
|
||||
return $this->execQuery($query, array($date,$date,$date,$date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all Studiensemester between two dates
|
||||
* @param $from
|
||||
* @param $to
|
||||
* @return array|null
|
||||
*/
|
||||
public function getByDate($from, $to)
|
||||
public function getByDateRange($from, $to)
|
||||
{
|
||||
if (date_format(date_create($from), 'Y-m-d') > (date_format(date_create($to), 'Y-m-d')))
|
||||
return success(array());
|
||||
|
||||
@@ -10,7 +10,7 @@ $this->load->view(
|
||||
);
|
||||
?>
|
||||
|
||||
<iframe style="width:100%; height:100%;" id="Infoterminal" src="<?php echo base_url() . 'cis/infoterminal/'; ?>" name="Infoterminal" frameborder="0" >
|
||||
<iframe style="width:100%; height:100%;" id="Infoterminal" src="<?php echo base_url() . 'cis/infoterminal/?forcelogin=true'; ?>" name="Infoterminal" frameborder="0" >
|
||||
No iFrames
|
||||
</iframe>
|
||||
<?php $this->load->view('templates/CISVUE-Footer', $includesArray); ?>
|
||||
|
||||
@@ -38,6 +38,14 @@ require_once('../../include/authentication.class.php');
|
||||
require_once('../../include/addon.class.php');
|
||||
require_once('../../include/'.EXT_FKT_PATH.'/serviceterminal.inc.php');
|
||||
|
||||
// 2025-02-05 ma0080 add query parameter to force login e.g. when used in iframe in CIS4.0 begin
|
||||
if( isset($_GET['forcelogin']) && !isset($_SERVER['PHP_AUTH_USER']) ) {
|
||||
header('WWW-Authenticate: Basic Realm="' . AUTH_NAME . '"');
|
||||
http_response_code(401);
|
||||
die();
|
||||
}
|
||||
// 2025-02-05 ma0080 add query parameter to force login e.g. when used in iframe in CIS4.0 end
|
||||
|
||||
if (!$db = new basis_db())
|
||||
$db=false;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.sprachen-entry{
|
||||
background-color: var(--fhc-cis-primary);
|
||||
background-color: var(--fhc-cis-primary-hover);
|
||||
}
|
||||
|
||||
[selected="true"].sprachen-entry {
|
||||
background-color: var(--fhc-cis-primary-hover);
|
||||
background-color: var(--fhc-cis-primary);
|
||||
}
|
||||
|
||||
.sprachen-entry.btn {
|
||||
|
||||
@@ -131,19 +131,19 @@
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day.active {
|
||||
border-color: var(--bs-secondary);
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day .events,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day .events {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.fhc-calendar-lg .fhc-calendar-month-page-day .events span,
|
||||
.fhc-calendar-md .fhc-calendar-month-page-day .events span {
|
||||
display: block;
|
||||
margin: 0.2em;
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 0.1em;
|
||||
}
|
||||
/*.fhc-calendar-lg .fhc-calendar-month-page-day .events,*/
|
||||
/*.fhc-calendar-md .fhc-calendar-month-page-day .events {*/
|
||||
/* display: block;*/
|
||||
/* overflow: auto;*/
|
||||
/* font-size: 0.7em;*/
|
||||
/*}*/
|
||||
/*.fhc-calendar-lg .fhc-calendar-month-page-day .events span,*/
|
||||
/*.fhc-calendar-md .fhc-calendar-month-page-day .events span {*/
|
||||
/* display: block;*/
|
||||
/* margin: 0.2em;*/
|
||||
/* padding: 0.1em 0.4em;*/
|
||||
/* border-radius: 0.1em;*/
|
||||
/*}*/
|
||||
.fhc-calendar-lg .fhc-calendar-years .col-4,
|
||||
.fhc-calendar-md .fhc-calendar-years .col-4 {
|
||||
padding: 0.09375em 0;
|
||||
@@ -185,8 +185,25 @@
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fhc-calendar-past {
|
||||
background-color:#F5E9D7;
|
||||
border-color: #E8E8E8;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.fhc-calendar-month-page-day-highlight {
|
||||
background-color: #f5f5f5;
|
||||
/*background-color: #f5f5f5;*/
|
||||
/*background-color: red;*/
|
||||
}
|
||||
.fhc-highlight-week {
|
||||
/*border-color: black !important;*/
|
||||
}
|
||||
|
||||
.fhc-highlight-day {
|
||||
border-width: 2px !important;
|
||||
border-color: black !important;
|
||||
}
|
||||
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day.active .no,
|
||||
@@ -200,25 +217,25 @@
|
||||
background-color: rgba(var(--bs-secondary-rgb), 0.25);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .no,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .no {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: 10%;
|
||||
}
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .events,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .events {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 10%;
|
||||
width: 80%;
|
||||
height: 10%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
/*.fhc-calendar-sm .fhc-calendar-month-page-day .no,*/
|
||||
/*.fhc-calendar-xs .fhc-calendar-month-page-day .no {*/
|
||||
/* display: flex;*/
|
||||
/* align-items: center;*/
|
||||
/* justify-content: center;*/
|
||||
/* width: 80%;*/
|
||||
/* height: 80%;*/
|
||||
/* margin: 10%;*/
|
||||
/*}*/
|
||||
/*.fhc-calendar-sm .fhc-calendar-month-page-day .events,*/
|
||||
/*.fhc-calendar-xs .fhc-calendar-month-page-day .events {*/
|
||||
/* position: absolute;*/
|
||||
/* bottom: 0;*/
|
||||
/* left: 10%;*/
|
||||
/* width: 80%;*/
|
||||
/* height: 10%;*/
|
||||
/* overflow: hidden;*/
|
||||
/* display: flex;*/
|
||||
/*}*/
|
||||
.fhc-calendar-sm .fhc-calendar-month-page-day .events span,
|
||||
.fhc-calendar-xs .fhc-calendar-month-page-day .events span {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
export default {
|
||||
getAuthUID() {
|
||||
return this.$fhcApi.get(
|
||||
'/api/frontend/v1/AuthInfo/getAuthUID',
|
||||
{ }
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
@@ -36,6 +36,7 @@ import addons from "./addons.js";
|
||||
import studiengang from "./studiengang.js";
|
||||
import menu from "./menu.js";
|
||||
import dashboard from "./dashboard.js";
|
||||
import authinfo from "./authinfo.js";
|
||||
|
||||
export default {
|
||||
search,
|
||||
@@ -59,4 +60,5 @@ export default {
|
||||
addons,
|
||||
studiengang,
|
||||
menu,
|
||||
authinfo,
|
||||
};
|
||||
|
||||
@@ -36,4 +36,10 @@ export default {
|
||||
{}
|
||||
);
|
||||
},
|
||||
studiensemesterDateInterval(date) {
|
||||
return this.$fhcApi.get(
|
||||
`/api/frontend/v1/Stundenplan/studiensemesterDateInterval/${date}`,
|
||||
{}
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -78,6 +78,11 @@ const app = Vue.createApp({
|
||||
appSideMenuEntries: {}
|
||||
}),
|
||||
components: {},
|
||||
computed: {
|
||||
isMobile() {
|
||||
return /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isInternalRoute(href) {
|
||||
const internalBase = window.location.origin
|
||||
@@ -97,6 +102,13 @@ const app = Vue.createApp({
|
||||
if(!res?.matched?.length) return
|
||||
|
||||
event.preventDefault(); // Prevent browser navigation
|
||||
|
||||
if(this.isMobile) { // toggle the menu
|
||||
const navMain = document.getElementById('nav-main');
|
||||
// fix unwanted toggle from off to on for some links on mobile
|
||||
if(navMain.classList.contains('show')) document.getElementById('nav-main-btn').click();
|
||||
}
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import CalendarMinimized from './Minimized.js';
|
||||
import CalendarDate from '../../composables/CalendarDate.js';
|
||||
import CalendarDates from '../../composables/CalendarDates.js';
|
||||
|
||||
// TODO(chris): week/month toggle
|
||||
|
||||
const todayDate = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
const today = todayDate.getTime()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -22,9 +24,14 @@ export default {
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
today,
|
||||
todayDate,
|
||||
date: this.date,
|
||||
focusDate: this.focusDate,
|
||||
size: Vue.computed({ get: () => this.size, set: v => this.size = v }),
|
||||
size: Vue.computed({ get: () => this.size }),
|
||||
calendarHeight: Vue.computed({ get: () => this.calendarHeight }),
|
||||
calendarWidth: Vue.computed({ get: () => this.calendarWidth }),
|
||||
|
||||
events: Vue.computed(() => this.eventsPerDay),
|
||||
filteredEvents: Vue.computed(() => this.filteredEvents),
|
||||
minimized: Vue.computed({ get: () => this.minimized, set: v => this.$emit('update:minimized', v) }),
|
||||
@@ -32,11 +39,9 @@ export default {
|
||||
noMonthView: this.noMonthView,
|
||||
noWeekView: this.noWeekView,
|
||||
eventsAreNull: Vue.computed(() => this.events === null),
|
||||
classHeader: this.classHeader,
|
||||
mode: Vue.computed(()=>this.mode),
|
||||
selectedEvent: Vue.computed(() => this.selectedEvent),
|
||||
setSelectedEvent: (event)=>{this.selectedEvent = event;},
|
||||
widget: this.widget
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -53,17 +58,9 @@ export default {
|
||||
type: String,
|
||||
default: 'month'
|
||||
},
|
||||
classHeader: {
|
||||
type: [String, Object, Array],
|
||||
default: ''
|
||||
},
|
||||
minimized: Boolean,
|
||||
noWeekView: Boolean,
|
||||
noMonthView: Boolean,
|
||||
widget: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
noMonthView: Boolean
|
||||
},
|
||||
watch:{
|
||||
selectedEvent:{
|
||||
@@ -106,11 +103,14 @@ export default {
|
||||
date: new CalendarDate(),
|
||||
focusDate: new CalendarDate(),
|
||||
size: 0,
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
selectedEvent:null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sizeClass() {
|
||||
sizeClass() {
|
||||
// mainly determines calendar font-size
|
||||
return 'fhc-calendar-' + ['xs', 'sm', 'md', 'lg'][this.size];
|
||||
},
|
||||
mode: {
|
||||
@@ -206,16 +206,25 @@ export default {
|
||||
if (this.$refs.container) {
|
||||
new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
let w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
|
||||
// TODO(chris): rework sizing
|
||||
if (w > 600)
|
||||
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)
|
||||
else if (w >= 350)
|
||||
this.size = 2;
|
||||
else if (w > 250)
|
||||
else if (w >= 250)
|
||||
this.size = 1;
|
||||
else
|
||||
this.size = 0;
|
||||
|
||||
this.containerWidth = w
|
||||
this.containerHeight = h
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
@@ -227,6 +236,9 @@ export default {
|
||||
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" >
|
||||
<template #calendarDownloads>
|
||||
<slot name="calendarDownloads" ></slot>
|
||||
</template>
|
||||
<template #monthPage="{event,day}">
|
||||
<slot name="monthPage" :event="event" :day="day" ></slot>
|
||||
</template>
|
||||
|
||||
@@ -40,7 +40,11 @@ export default {
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'week')"/>
|
||||
<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}">
|
||||
|
||||
@@ -17,11 +17,14 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
hourPosition: null,
|
||||
curHourPosition: null,
|
||||
hourPositionTime: null,
|
||||
lvMenu: null,
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
@@ -100,19 +103,13 @@ export default {
|
||||
}
|
||||
},
|
||||
dayText(){
|
||||
if(!this.size || !this.day)return {};
|
||||
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]),
|
||||
}
|
||||
},
|
||||
dayGridStyle() {
|
||||
return {
|
||||
'grid-template-columns': '1 1fr',
|
||||
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
|
||||
}
|
||||
},
|
||||
noLvStyle() {
|
||||
return {
|
||||
top: (this.calendarScrollTop + 100) + 'px',
|
||||
@@ -135,6 +132,22 @@ export default {
|
||||
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;
|
||||
},
|
||||
@@ -192,8 +205,32 @@ export default {
|
||||
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.$fhcApi.factory.stundenplan.getLehreinheitStudiensemester(event.lehreinheit_id[0]).then(
|
||||
@@ -270,6 +307,7 @@ export default {
|
||||
// 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
|
||||
console.log('height: ', height)
|
||||
position = height * (timePercentage / 100);
|
||||
this.hourPosition = position;
|
||||
|
||||
@@ -290,13 +328,9 @@ export default {
|
||||
},
|
||||
dateToMinutesOfDay(day) {
|
||||
return Math.floor(((day.getHours() - 7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const container = document.getElementById("calendarContainer")
|
||||
if(container) container.style.overflow = 'hidden'
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-day-page h-100">
|
||||
<div class="row m-0 h-100">
|
||||
@@ -308,7 +342,7 @@ export default {
|
||||
<a href="#" class="small text-secondary text-decoration-none" >{{dayText.datum}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scroll g-0" style="height: 100%; overflow: scroll;">
|
||||
<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>
|
||||
@@ -318,6 +352,11 @@ export default {
|
||||
<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">Keine Lehrveranstaltungen</h1>
|
||||
<div :class="{'fhc-calendar-no-events-overlay':noEventsCondition, 'events':true}">
|
||||
@@ -325,7 +364,7 @@ export default {
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" style="min-height:100px" :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">
|
||||
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle(day)">
|
||||
<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)">
|
||||
@@ -348,7 +387,7 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-xl-block col-xl-6 p-4" style="max-height: 100%">
|
||||
<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">
|
||||
|
||||
@@ -13,11 +13,9 @@ export default {
|
||||
inject: [
|
||||
'eventsAreNull',
|
||||
'size',
|
||||
'classHeader',
|
||||
'mode',
|
||||
'noWeekView',
|
||||
'noMonthView',
|
||||
'widget'
|
||||
],
|
||||
props: {
|
||||
title: String
|
||||
@@ -28,30 +26,13 @@ export default {
|
||||
'next',
|
||||
'click'
|
||||
],
|
||||
computed: {
|
||||
getHeaderOffsetClass() {
|
||||
return 'col offset-0' + (this.widget ? '' : ' offset-md-3')
|
||||
},
|
||||
myClassHeader() {
|
||||
// TODO(chris): + {'btn-sm': !this.size}
|
||||
let c = this.classHeader;
|
||||
if (Array.isArray(c)) {
|
||||
if (!this.size)
|
||||
c.push('btn-sm');
|
||||
} else if (typeof c === 'string' || c instanceof String) {
|
||||
if (!this.size)
|
||||
c += ' btn-sm';
|
||||
} else {
|
||||
c['btn-sm'] = !this.size;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="calendar-header card-header w-100" :class="classHeader">
|
||||
<div class="calendar-header card-header w-100">
|
||||
<div class="row align-items-center ">
|
||||
<div :class="getHeaderOffsetClass" :style="{'padding-left':headerPadding}">
|
||||
<div class=" col-12 col-md-3 d-flex justify-content-center justify-content-md-start align-items-center">
|
||||
<slot name="calendarDownloads"></slot>
|
||||
</div>
|
||||
<div class="col-12 col-md-6" :style="{'padding-left':headerPadding}">
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-auto ">
|
||||
<button class="btn btn-outline-secondary border-0" :class="{'btn-sm':!this.size}" @click="$emit('prev')"><i class="fa fa-chevron-left"></i></button>
|
||||
|
||||
@@ -8,7 +8,6 @@ export default {
|
||||
'size',
|
||||
'minimized',
|
||||
'date',
|
||||
'classHeader'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -61,7 +61,11 @@ export default {
|
||||
},
|
||||
template: `
|
||||
<div class="fhc-calendar-month">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'months')" />
|
||||
<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}">
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
@@ -16,7 +18,7 @@ export default {
|
||||
'showWeeks',
|
||||
'noWeekView',
|
||||
'selectedEvent',
|
||||
'setSelectedEvent',
|
||||
'setSelectedEvent'
|
||||
],
|
||||
props: {
|
||||
year: Number,
|
||||
@@ -63,14 +65,17 @@ export default {
|
||||
methods: {
|
||||
getDayClass(week, day) {
|
||||
let classstring = 'fhc-calendar-month-page-day text-decoration-none overflow-hidden'
|
||||
const isHighlighted = this.isHighlighted(week, day)
|
||||
const isHighlightedWeek = this.isHighlightedWeek(week)
|
||||
const isHighlightedDay = this.isHighlightedDay(day)
|
||||
const isThisDate = this.date.compare(day)
|
||||
const isNotThisMonth = day.getMonth() != this.month
|
||||
const isInThePast = this.date.isInPast(day)
|
||||
if(isHighlighted) classstring += ' fhc-calendar-month-page-day-highlight'
|
||||
if(isThisDate) classstring += ' active'
|
||||
if(isNotThisMonth || isInThePast) classstring += ' opacity-50'
|
||||
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) {
|
||||
@@ -85,12 +90,14 @@ export default {
|
||||
}
|
||||
},
|
||||
highlight(week, day){
|
||||
console.log('highlight method')
|
||||
this.highlightedWeek = week.no;
|
||||
this.highlightedDay = day;
|
||||
},
|
||||
isHighlighted(week, day) {
|
||||
return this.noWeekView ? day == this.highlightedDay : week.no == this.highlightedWeek;
|
||||
isHighlightedDay(day) {
|
||||
return day == this.highlightedDay
|
||||
},
|
||||
isHighlightedWeek(week) {
|
||||
return week.no == this.highlightedWeek
|
||||
},
|
||||
clickEvent(day,week) {
|
||||
if(!this.noWeekView)
|
||||
@@ -99,11 +106,31 @@ export default {
|
||||
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 = 'scroll'
|
||||
if(container) container.style['overflow-y'] = 'auto'
|
||||
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-month-page" :class="{'show-weeks': showWeeks}">
|
||||
@@ -113,7 +140,8 @@ export default {
|
||||
</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="#" 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.prevent="clickEvent(day,week)"
|
||||
@mouseover="highlight(week,day)"
|
||||
@@ -122,9 +150,10 @@ export default {
|
||||
:key="day"
|
||||
:class="getDayClass(week, day)"
|
||||
>
|
||||
<span class="no">{{day.getDate()}}</span>
|
||||
<span class="no" :style="getNumberStyle(day)">{{day.getDate()}}</span>
|
||||
<span v-if="events[day.toDateString()] && events[day.toDateString()].length" class="events">
|
||||
<div @click="setSelectedEvent(event);" v-for="event in events[day.toDateString()]" :key="event.id" :style="{'background-color': event.color}" class="fhc-entry" :selected="event == selectedEvent" v-contrast >
|
||||
<div @click="setSelectedEvent(event);" 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>
|
||||
@@ -132,5 +161,6 @@ export default {
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>`
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -40,7 +40,11 @@ export default {
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-week">
|
||||
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'weeks')"/>
|
||||
<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}">
|
||||
|
||||
@@ -9,9 +9,13 @@ export default {
|
||||
return{
|
||||
hourPosition:null,
|
||||
hourPositionTime:null,
|
||||
resizeObserver: null,
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
inject: [
|
||||
'today',
|
||||
'todayDate',
|
||||
'date',
|
||||
'focusDate',
|
||||
'size',
|
||||
@@ -32,6 +36,13 @@ export default {
|
||||
'input',
|
||||
],
|
||||
computed: {
|
||||
laneWidth() {
|
||||
return this.width / 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,
|
||||
@@ -91,7 +102,9 @@ export default {
|
||||
let nextDay = new Date(day);
|
||||
nextDay.setDate(nextDay.getDate()+1);
|
||||
nextDay.setMilliseconds(nextDay.getMilliseconds()-1);
|
||||
let d = {events:[],lanes: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) {
|
||||
@@ -118,6 +131,32 @@ export default {
|
||||
},
|
||||
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' // todo: manage the real value of 1fr somehow
|
||||
}
|
||||
},
|
||||
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: {
|
||||
@@ -135,10 +174,23 @@ export default {
|
||||
}
|
||||
},
|
||||
dayGridStyle(day) {
|
||||
return {
|
||||
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 {
|
||||
@@ -208,17 +260,44 @@ export default {
|
||||
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 = 'scroll'
|
||||
if(container) {
|
||||
container.style['overflow-y'] = 'scroll'
|
||||
container.style['overflow-x'] = 'auto'
|
||||
}
|
||||
|
||||
this.initResizeObserver();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.destroyResizeObserver();
|
||||
},
|
||||
template: /*html*/`
|
||||
<div class="fhc-calendar-week-page" style="min-width: 700px;">
|
||||
<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="changeToMonth(day)">
|
||||
@@ -233,19 +312,25 @@ export default {
|
||||
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="events">
|
||||
|
||||
<div class="events" :ref="'eventsRef'+week">
|
||||
<div class="hours">
|
||||
<div v-for="hour in hours" style="min-height:100px" :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-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">
|
||||
<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>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -10,19 +10,45 @@ export const Stundenplan = {
|
||||
return {
|
||||
events: null,
|
||||
calendarDate: new CalendarDate(new Date()),
|
||||
eventCalendarDate: new CalendarDate(new Date()),
|
||||
currentlySelectedEvent: null,
|
||||
currentDay: new Date(),
|
||||
minimized: false,
|
||||
viewData: JSON.parse(this.viewDataString ?? '{}'),
|
||||
studiensemester_kurzbz:null,
|
||||
studiensemester_start:null,
|
||||
studiensemester_ende:null,
|
||||
uid:null,
|
||||
}
|
||||
},
|
||||
props: [
|
||||
"viewDataString"
|
||||
"viewData",
|
||||
],
|
||||
watch: {
|
||||
weekFirstDay: {
|
||||
handler: async function (newValue) {
|
||||
let data = await this.fetchStudiensemesterDetails(newValue);
|
||||
let { studiensemester_kurzbz, start, ende } = data.data;
|
||||
this.studiensemester_kurzbz = studiensemester_kurzbz;
|
||||
this.studiensemester_start = start;
|
||||
this.studiensemester_ende = ende;
|
||||
},
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FhcCalendar, LvModal, LvMenu, LvInfo
|
||||
},
|
||||
computed:{
|
||||
downloadLinks: function(){
|
||||
if(!this.studiensemester_start || !this.studiensemester_ende || !this.uid )return;
|
||||
let start = new Date(this.studiensemester_start);
|
||||
start = Math.floor(start.getTime()/1000);
|
||||
let ende = new Date(this.studiensemester_ende);
|
||||
ende = Math.floor(ende.getTime() / 1000);
|
||||
|
||||
let download_link = (format, version = "", target = "") => `${FHC_JS_DATA_STORAGE_OBJECT.app_root}cis/private/lvplan/stpl_kalender.php?type=student&pers_uid=${this.uid}&begin=${start}&ende=${ende}&format=${format}${version ? '&version=' + version : ''}${target ? '&target=' + target : ''}`;
|
||||
return [{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link('excel') }, { title: "csv", icon: 'fa-solid fa-file-csv', link: download_link('csv') }, { title: "ical1", icon: 'fa-regular fa-calendar', link: download_link('ical', '1', 'ical') }, { title: "ical2", icon: 'fa-regular fa-calendar', link: download_link('ical', '2', 'ical') }];
|
||||
},
|
||||
lv_id() { // computed so we can theoretically change path/lva selection and reload without page refresh
|
||||
const pathParts = window.location.pathname.split('/').filter(Boolean);
|
||||
const id = pathParts[pathParts.length - 1];
|
||||
@@ -35,14 +61,17 @@ export const Stundenplan = {
|
||||
return this.calendarDateToString(this.calendarDate.cdLastDayOfWeek);
|
||||
},
|
||||
monthFirstDay: function () {
|
||||
return this.calendarDateToString(this.calendarDate.cdFirstDayOfCalendarMonth);
|
||||
return this.calendarDateToString(this.eventCalendarDate.cdFirstDayOfCalendarMonth);
|
||||
},
|
||||
monthLastDay: function () {
|
||||
return this.calendarDateToString(this.calendarDate.cdLastDayOfCalendarMonth);
|
||||
return this.calendarDateToString(this.eventCalendarDate.cdLastDayOfCalendarMonth);
|
||||
},
|
||||
|
||||
},
|
||||
methods:{
|
||||
fetchStudiensemesterDetails: async function (date) {
|
||||
return this.$fhcApi.factory.stundenplan.studiensemesterDateInterval(date);
|
||||
},
|
||||
convertTime: function([hour,minute]){
|
||||
let date = new Date();
|
||||
date.setHours(hour);
|
||||
@@ -66,14 +95,15 @@ export const Stundenplan = {
|
||||
updateRange: function ({start,end}) {
|
||||
|
||||
let checkDate = (date) => {
|
||||
return date.m != this.calendarDate.m || date.y != this.calendarDate.y;
|
||||
return date.m != this.eventCalendarDate.m || date.y != this.eventCalendarDate.y;
|
||||
}
|
||||
this.calendarDate = new CalendarDate(end);
|
||||
|
||||
// only load month data if the month or year has changed
|
||||
if (checkDate(new CalendarDate(start)) && checkDate(new CalendarDate(end))){
|
||||
// reset the events before querying the new events to activate the loading spinner
|
||||
this.events = null;
|
||||
this.calendarDate = new CalendarDate(end);
|
||||
this.eventCalendarDate = new CalendarDate(end);
|
||||
Vue.nextTick(() => {
|
||||
this.loadEvents();
|
||||
});
|
||||
@@ -120,13 +150,30 @@ export const Stundenplan = {
|
||||
},
|
||||
created()
|
||||
{
|
||||
this.$fhcApi.factory.authinfo.getAuthUID().then((res) => res.data)
|
||||
.then(data=>{
|
||||
this.uid = data.uid;
|
||||
})
|
||||
this.loadEvents();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if(this.$refs.lvmodal) this.$refs.lvmodal.hide()
|
||||
},
|
||||
template:/*html*/`
|
||||
<h2>{{$p.t('lehre/stundenplan')}}</h2>
|
||||
<hr>
|
||||
<lv-modal v-if="currentlySelectedEvent" :event="currentlySelectedEvent" ref="lvmodal" />
|
||||
<fhc-calendar @selectedEvent="setSelectedEvent" :initial-date="currentDay" @change:range="updateRange" :events="events" initial-mode="week" show-weeks @select:day="selectDay" v-model:minimized="minimized">
|
||||
<template #calendarDownloads>
|
||||
<div v-for="{title,icon,link} in downloadLinks">
|
||||
<a :href="link" :title="title" class="py-1 px-2 m-1 btn btn-outline-secondary">
|
||||
<div class="d-flex flex-column">
|
||||
<i :class="icon"></i>
|
||||
<span class="small">{{title}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #monthPage="{event,day}">
|
||||
<span class="fhc-entry" >
|
||||
{{event.topic}}
|
||||
|
||||
@@ -120,8 +120,6 @@ export default {
|
||||
.catch((err) => {
|
||||
console.error("ERROR: ", err);
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
mounted() {
|
||||
@@ -141,10 +139,13 @@ export default {
|
||||
}
|
||||
}).observe(this.$refs.container);
|
||||
}
|
||||
this.carouselInstance = new bootstrap.Carousel(this.$refs.carousel, {
|
||||
wrap: false, // keep this off even though it actually wraps
|
||||
interval: false
|
||||
});
|
||||
Vue.nextTick(()=>{
|
||||
this.carouselInstance = new bootstrap.Carousel(this.$refs.carousel, {
|
||||
wrap: false, // keep this off even though it actually wraps
|
||||
interval: false
|
||||
});
|
||||
})
|
||||
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div ref="container" class="widgets-news h-100" :class="sizeClass" :style="getNewsWidgetStyle">
|
||||
@@ -177,9 +178,8 @@ export default {
|
||||
<div class="container h-100" style="padding: 0px;" ref="carocontainer">
|
||||
<div id="FhcCarouselContainer" style="height: 100%;" ref="carousel" class="carousel slide fhc-carousel" data-bs-ride="carousel" data-bs-interval="false">
|
||||
|
||||
<div class="carousel-inner" ref="carouselInner" style="height: 100%; max-width: 100%;">
|
||||
<div ref="carouselItems" v-for="(news, index) in newsList" class="carousel-item " style="overflow-y: auto; height: 100%;" :id="'card-'+news.news_id" v-html="news.content_obj.content">
|
||||
</div>
|
||||
<div class="carousel-inner" ref="carouselInner" style="height: 100%; max-width: 100%;">
|
||||
<div ref="carouselItems" v-for="(news, index) in newsList" class="carousel-item " style="overflow-y: auto; overflow-x: hidden; height: 100%;" :id="'card-'+news.news_id" v-html="news.content_obj.content"/>
|
||||
</div>
|
||||
<button @click="setPrev" @focus="$event.target.blur()" style="z-index: 100; color: black; overflow: hidden; margin-left: 10px; width:35px;" data-bs-target="#FhcCarouselContainer" class="carousel-control-prev" type="button">
|
||||
<div class="border rounded-circle" style="padding-left: 0.4rem; padding-right: 0.4rem; background-color:rgba(138,138,138,0.4)">
|
||||
|
||||
@@ -178,7 +178,7 @@ export default {
|
||||
<div v-if="events === null" class="d-flex h-100 justify-content-center align-items-center">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
<template ref="allWeek" v-else-if="allEventsGrouped.size" v-for="([key, value], index) in allEventsGrouped" :key="index" style="margin-top: 8px;">
|
||||
<template v-else-if="allEventsGrouped.size" v-for="([key, value], index) in allEventsGrouped" :key="index" style="margin-top: 8px;">
|
||||
<div class="card-header d-grid p-0">
|
||||
<button class="btn btn-link link-secondary text-decoration-none" @click="setCalendarMaximized">{{ key.format({dateStyle: "full"})}}</button>
|
||||
</div>
|
||||
|
||||
@@ -199,11 +199,7 @@ class CalendarDate {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
isInPast(d) {
|
||||
if (this.isDate(d))
|
||||
return (this.y > d.getFullYear() || this.m > d.getMonth() || this.d > d.getDate());
|
||||
return false
|
||||
}
|
||||
|
||||
setLocale(locale) {
|
||||
this.weekStart = CalendarDate.getWeekStart(locale);
|
||||
}
|
||||
@@ -261,5 +257,4 @@ CalendarDate.getWeekStart = function(locale) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default CalendarDate
|
||||
@@ -1,16 +1,18 @@
|
||||
<?php
|
||||
|
||||
if (!$result = @$db->db_query("SELECT LC_Time FROM public.tbl_sprache WHERE LIMIT 1")) {
|
||||
if ($result = $db->db_query("SELECT * FROM information_schema.columns WHERE column_name = 'lc_time' AND table_name = 'tbl_sprache' AND table_schema = 'public';")) {
|
||||
if ($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "
|
||||
ALTER TABLE public.tbl_sprache ADD lc_time VARCHAR(255) ;
|
||||
UPDATE public.tbl_sprache SET lc_time = 'en-GB' where locale ='en-US';
|
||||
UPDATE public.tbl_sprache SET lc_time = 'de-AT' where locale ='de-AT';
|
||||
";
|
||||
|
||||
$qry = "
|
||||
ALTER TABLE public.tbl_sprache ADD LC_Time VARCHAR(255) ;
|
||||
UPDATE public.tbl_sprache SET LC_Time = 'en-GB' where locale ='en-US';
|
||||
UPDATE public.tbl_sprache SET LC_Time = 'de-AT' where locale ='de-AT';
|
||||
";
|
||||
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>public.tbl_sprache: ' . $db->db_last_error() . '</strong><br>';
|
||||
else
|
||||
echo '<br>public.tbl_sprache: column LC_Time was successfully added';
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>public.tbl_sprache: ' . $db->db_last_error() . '</strong><br>';
|
||||
else
|
||||
echo '<br>public.tbl_sprache: column lc_time was successfully added';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user