Compare commits

...

9 Commits

Author SHA1 Message Date
Harald Bamberger 63ff8dd4c4 Merge branch 'cis40_2026-05_ma_rc' into demo-cis40 2026-05-29 13:40:02 +02:00
Harald Bamberger 6019489ef1 Merge branch 'cis40_2026-02_rc' into cis40_2026-05_ma_rc 2026-05-29 13:38:58 +02:00
adisposkofh fc79f92796 added 'add reservation'/'reservation not allowed' tooltips in calendar 2026-05-27 15:35:45 +02:00
adisposkofh 8e569a9ccd displaying forbidden room reservation slots 2026-05-27 10:43:28 +02:00
adisposkofh d7b2964e4e obsolete use of viewData object from cisRouterView 2026-05-26 17:33:38 +02:00
adisposkofh fa91e204f0 always displaying timeslot on calendar event in list view 2026-05-26 12:02:08 +02:00
adisposkofh 23506430b1 initializing stg org lv plan with provided url params 2026-05-26 09:30:50 +02:00
adisposkofh fa58635a22 positioning of profile cards 'quick links' and 'calendar sync' 2026-05-26 09:29:10 +02:00
Harald Bamberger 7f630f24d5 StundenplanLib: check array index access before 2026-05-26 07:54:41 +02:00
14 changed files with 130 additions and 26 deletions
@@ -25,12 +25,6 @@ class ProjektabgabeUebersicht extends Auth_Controller
*/ */
public function index() public function index()
{ {
// TODO create permission $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'ProjektabgabeUebersicht']);
$viewData = array(
'uid' => getAuthUID(),
'showEdit' => true
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'ProjektabgabeUebersicht']);
} }
} }
@@ -27,11 +27,12 @@ class PaabgabeUebersicht extends FHCAPI_Controller
public function __construct() public function __construct()
{ {
parent::__construct([ parent::__construct([
'viewData' => self::PERM_LOGGED,
'getPaAbgaben' => array('lehre/abgabetool:r'), 'getPaAbgaben' => array('lehre/abgabetool:r'),
'getStudiengaenge' => array('lehre/abgabetool:r'), 'getStudiengaenge' => array('lehre/abgabetool:r'),
'getTermine' => array('lehre/abgabetool:r'), 'getTermine' => array('lehre/abgabetool:r'),
'getPaAbgabetypen' => array('lehre/abgabetool:r'), 'getPaAbgabetypen' => array('lehre/abgabetool:r'),
'downloadZip' => array('lehre/abgabetool:r') 'downloadZip' => array('lehre/abgabetool:r'),
//'downloadProjektarbeit' => array('lehre/abgabetool:r') //'downloadProjektarbeit' => array('lehre/abgabetool:r')
]); ]);
@@ -45,6 +46,17 @@ class PaabgabeUebersicht extends FHCAPI_Controller
]); ]);
} }
public function viewData()
{
$viewData = [
"uid" => getAuthUID(),
// TODO create permission
"showEdit" => true,
];
$this->terminateWithSuccess($viewData);
}
/** /**
* Get Projektabgaben for search criteria. * Get Projektabgaben for search criteria.
*/ */
+4 -1
View File
@@ -361,7 +361,10 @@ class StundenplanLib
if (isError($ort_content_object)) { if (isError($ort_content_object)) {
return error(getData($ort_content_object)); return error(getData($ort_content_object));
} }
$ort_content_object = getData($ort_content_object)[0]; $ort_content_object_data = getData($ort_content_object);
$ort_content_object = (is_array($ort_content_object_data) && count($ort_content_object_data) > 0)
? $ort_content_object_data[0]
: null;
if($ort_content_object) { if($ort_content_object) {
$item->ort_content_id = $ort_content_object->content_id; $item->ort_content_id = $ort_content_object->content_id;
} }
@@ -26,5 +26,11 @@ export default {
method: 'get', method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgabetypen' url: '/api/frontend/v1/education/PaabgabeUebersicht/getPaAbgabetypen'
}; };
},
getViewData() {
return {
method: 'get',
url: '/api/frontend/v1/education/PaabgabeUebersicht/viewData'
};
} }
}; };
+8 -2
View File
@@ -19,7 +19,8 @@ export default {
originalBackgrounds: "backgrounds", originalBackgrounds: "backgrounds",
dropAllowed: "dropAllowed", dropAllowed: "dropAllowed",
timezone: "timezone", timezone: "timezone",
reservierbar: "isReservierbar" reservierbar: "isReservierbar",
reservierbarMap: "reservierbarMap",
}, },
provide() { provide() {
return { return {
@@ -328,6 +329,9 @@ export default {
return !dayEvents.some(ev => ev.start < end && ev.end > start); return !dayEvents.some(ev => ev.start < end && ev.end > start);
} }
}, },
created() {
this.$p.loadCategory(["LvPlan"]);
},
beforeUnmount() { beforeUnmount() {
this.disableAutoScroll(); this.disableAutoScroll();
}, },
@@ -424,9 +428,11 @@ export default {
class="fhc-calendar-empty-slot" class="fhc-calendar-empty-slot"
style="position:absolute; inset:0; z-index:1" style="position:absolute; inset:0; z-index:1"
v-cal-click:slot="{ date, part }" v-cal-click:slot="{ date, part }"
:title="this.reservierbarMap?.[date.toISODate()] ? $p.t('LvPlan/add_reservation') : $p.t('LvPlan/reservation_not_allowed')"
> >
<div class="fhc-calendar-empty-slot-plus"> <div class="fhc-calendar-empty-slot-plus">
<i class="fa-solid fa-plus"></i> <i v-if="this.reservierbarMap?.[date.toISODate()]" class="fa-solid fa-plus"></i>
<i v-else class="fa-solid fa-ban" style="color: red;"></i>
</div> </div>
</div> </div>
+3
View File
@@ -230,6 +230,9 @@ export default {
:is="renderers[event.type]?.calendarEvent" :is="renderers[event.type]?.calendarEvent"
:event="event" :event="event"
@delete-event="(event) => $emit('delete-event', event)" @delete-event="(event) => $emit('delete-event', event)"
:timeSlotDisplayBehavior="
$props.mode.toLowerCase() === 'list' ? 'always' : 'default'
"
></component> ></component>
</div> </div>
</template> </template>
+1
View File
@@ -97,6 +97,7 @@ export default {
<component <component
:is="renderers[event.type]?.calendarEvent" :is="renderers[event.type]?.calendarEvent"
:event="event" :event="event"
:timeSlotDisplayBehavior="'always'"
></component> ></component>
</div> </div>
</template> </template>
@@ -340,7 +340,7 @@ export default {
<div class="row"> <div class="row">
<div class="d-md-none col-12"> <div class="d-md-none col-12">
<!-- Bearbeiten Button --> <!-- Bearbeiten Button -->
<div v-if="isEditable" class="row mb-4"> <div v-if="isEditable" class="row mb-3 ">
<div class="col"> <div class="col">
<button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" > <button @click="()=>showEditProfilModal()" type="button" class="text-start card w-100 btn btn-outline-secondary" >
<div class="row"> <div class="row">
@@ -176,6 +176,7 @@ export const Profil = {
this.data = data.profil_data.data; this.data = data.profil_data.data;
this.calendarSyncUrls = data.calendar_sync_urls ?? []; this.calendarSyncUrls = data.calendar_sync_urls ?? [];
this.authPermissions = data.permissions; this.authPermissions = data.permissions;
console.log(data.profil_data);
}, },
zustellAdressenCount() { zustellAdressenCount() {
if (!this.data || !this.data.adressen) { if (!this.data || !this.data.adressen) {
@@ -4,9 +4,6 @@ import Loader from "../../Loader.js";
export const ProjektabgabeUebersicht = { export const ProjektabgabeUebersicht = {
name: "ProjektabgabeUebersicht", name: "ProjektabgabeUebersicht",
props: {
viewData: Object // NOTE: this is inherited from router-view
},
components: { components: {
CoreFilterCmpt, CoreFilterCmpt,
Loader Loader
@@ -60,7 +57,7 @@ export const ProjektabgabeUebersicht = {
}); });
container.append(downloadButton); container.append(downloadButton);
if (this.viewData.showEdit) if (this.showEdit)
{ {
let editButton = document.createElement('button'); let editButton = document.createElement('button');
editButton.className = 'btn btn-outline-secondary'; editButton.className = 'btn btn-outline-secondary';
@@ -116,7 +113,9 @@ export const ProjektabgabeUebersicht = {
this.tableBuiltResolve() this.tableBuiltResolve()
} }
} }
]}; ],
showEdit: null,
};
}, },
methods: { methods: {
tableResolve(resolve) { tableResolve(resolve) {
@@ -220,7 +219,12 @@ export const ProjektabgabeUebersicht = {
if (this.selectedTermin) url.searchParams.append('abgabedatum', this.selectedTermin); if (this.selectedTermin) url.searchParams.append('abgabedatum', this.selectedTermin);
if (this.personSearchString) url.searchParams.append('personSearchString', this.personSearchString); if (this.personSearchString) url.searchParams.append('personSearchString', this.personSearchString);
window.open(url.toString(), '_blank'); window.open(url.toString(), '_blank');
} },
async getViewData() {
const viewDataResponse = await this.$api.call(ApiPaabgabe.getViewData());
const viewData = viewDataResponse.data;
this.showEdit = viewData.showEdit;
},
}, },
computed: { computed: {
isDarkMode() { isDarkMode() {
@@ -236,7 +240,8 @@ export const ProjektabgabeUebersicht = {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/api/frontend/v1/education/PaabgabeUebersicht/downloadZip'; return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/api/frontend/v1/education/PaabgabeUebersicht/downloadZip';
} }
}, },
created() { async created() {
await this.getViewData();
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global', 'person', 'ui']); this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global', 'person', 'ui']);
this.phrasenPromise.then(()=> {this.phrasenResolved = true}); this.phrasenPromise.then(()=> {this.phrasenResolved = true});
}, },
@@ -3,6 +3,11 @@ export default {
event: { event: {
type: Object, type: Object,
required: true required: true
},
timeSlotDisplayBehavior: {
type: String,
default: "default",
// options: default, always, never
} }
}, },
computed:{ computed:{
@@ -50,7 +55,17 @@ export default {
return luxon.Duration return luxon.Duration
.fromISOTime(this.event.ende) .fromISOTime(this.event.ende)
.toISOTime({ suppressSeconds: true }); .toISOTime({ suppressSeconds: true });
} },
timeSlotDisplayClasses() {
switch (this.$props.timeSlotDisplayBehavior) {
case "always":
return "d-grid";
case "never":
return "d-none";
default:
return "d-none d-xl-grid";
}
},
}, },
template: /*html*/` template: /*html*/`
<div <div
@@ -58,8 +73,9 @@ export default {
@wheel.stop @wheel.stop
> >
<div <div
v-if="!event.allDayEvent && event?.beginn && event?.ende" v-if="!event?.allDayEvent && event?.beginn && event?.ende"
class="event-time d-none d-xl-grid h-100" :class="timeSlotDisplayClasses"
class="event-time h-100"
> >
<span>{{ start }}</span> <span>{{ start }}</span>
<span>{{ end }}</span> <span>{{ end }}</span>
@@ -3,6 +3,11 @@ export default {
event: { event: {
type: Object, type: Object,
required: true required: true
},
timeSlotDisplayBehavior: {
type: String,
default: "default",
// options: default, always, never
} }
}, },
inject: { inject: {
@@ -63,14 +68,25 @@ export default {
methods: { methods: {
handleDelete() { handleDelete() {
this.$emit('delete-event', this.event); this.$emit('delete-event', this.event);
} },
timeSlotDisplayClasses() {
switch (this.$props.timeSlotDisplayBehavior) {
case "always":
return "d-grid";
case "never":
return "d-none";
default:
return "d-none d-xl-grid";
}
},
}, },
template: /* html */` template: /* html */`
<div <div
class="cis-renderer-reservierungen-calendar-event calendar-event-default h-100 w-100 p-1 position-relative"> class="cis-renderer-reservierungen-calendar-event calendar-event-default h-100 w-100 p-1 position-relative">
<div <div
v-if="!event.allDayEvent && event?.beginn && event?.ende" v-if="!event?.allDayEvent && event?.beginn && event?.ende"
class="event-time d-grid h-100" :class="timeSlotDisplayClasses"
class="event-time h-100"
> >
<span>{{ start }}</span> <span>{{ start }}</span>
<span>{{ end }}</span> <span>{{ end }}</span>
@@ -86,6 +102,7 @@ export default {
</button> </button>
<span class="event-topic">{{ event.topic }}</span> <span class="event-topic">{{ event.topic }}</span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
<span <span
v-for="lektor in event.lektor.slice(0, 3)" v-for="lektor in event.lektor.slice(0, 3)"
class="event-lectors" class="event-lectors"
@@ -98,7 +115,6 @@ export default {
> >
... +{{ event.lektor.length - 3 }} ... +{{ event.lektor.length - 3 }}
</span> </span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
</div> </div>
</div> </div>
`, `,
@@ -75,6 +75,7 @@ export default {
}, },
methods: { methods: {
switchFilter(evt) { switchFilter(evt) {
console.log(evt);
this.$emit('switchFilter', evt.currentTarget.value); this.$emit('switchFilter', evt.currentTarget.value);
}, },
applyFilterConfig() { applyFilterConfig() {
+40
View File
@@ -29924,6 +29924,46 @@ array(
) )
) )
), ),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'add_reservation',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reservierung hinzufügen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Add reservation',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'reservation_not_allowed',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reservierung nicht erlaubt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reservation not allowed',
'description' => '',
'insertvon' => 'system'
)
)
),
// LvPlan Phrasen ende // LvPlan Phrasen ende
//ProfilUpdate Phrasen start //ProfilUpdate Phrasen start
array( array(