mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-20 16:32:20 +00:00
Merge branch 'feature-24647/Konfigurierbare_Dashboards_widgets' into feature-24647/Konfigurierbare_Dashboards_api
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
|
||||
class Api extends Auth_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/admin:rw',
|
||||
'getNews' => 'dashboard/admin:rw',
|
||||
'getAmpeln' => 'dashboard/admin:rw',
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('AuthLib', null, 'AuthLib');
|
||||
|
||||
$this->_setAuthUID();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
echo 'Dashboard API Controller';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get News.
|
||||
*/
|
||||
public function getNews()
|
||||
{
|
||||
$limit = $this->input->get('limit');
|
||||
|
||||
$this->load->model('content/News_model', 'NewsModel');
|
||||
|
||||
$result = $this->NewsModel->getAll($limit);
|
||||
|
||||
if(hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->terminateWithJsonError('fehler entdeckt');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Ampeln.
|
||||
*/
|
||||
public function getAmpeln(){
|
||||
|
||||
$this->load->model('content/Ampel_model', 'AmpelModel');
|
||||
$result = $this->AmpelModel->getByUser($this->_uid);
|
||||
|
||||
if(hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->terminateWithJsonError('fehler entdeckt');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the UID of the logged user and checks if it is valid
|
||||
*/
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
*/
|
||||
class DashboardDemo extends Auth_Controller
|
||||
{
|
||||
private $_uid; // uid of the logged user
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Set required permissions
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'user:r',
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('AuthLib');
|
||||
$this->load->library('WidgetLib');
|
||||
|
||||
$this->_setAuthUID(); // sets property uid
|
||||
|
||||
$this->setControllerId(); // sets the controller id
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
public function index()
|
||||
{
|
||||
$this->load->view('dashboard/dashboard_demo.php', []);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Retrieve the UID of the logged user and checks if it is valid
|
||||
*/
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,4 +11,19 @@ class News_model extends DB_Model
|
||||
$this->dbTable = 'campus.tbl_news';
|
||||
$this->pk = 'news_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all News ordered by date. (most actual on top)
|
||||
* @param null $limit Amount of news.
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($limit = null)
|
||||
{
|
||||
return $this->loadWhere('
|
||||
text IS NOT NULL
|
||||
AND datum <= NOW() AND (datum_bis IS NULL OR datum_bis >= now()::date)
|
||||
ORDER BY datum DESC
|
||||
LIMIT '. $this->escape($limit)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
$this->load->view('templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'axios027' => true,
|
||||
'restclient' => true,
|
||||
'vue3' => true,
|
||||
'customJSModules' => ['public/js/apps/Dashboard.js'],
|
||||
'customCSSs' => [
|
||||
'public/css/components/dashboard.css'
|
||||
],
|
||||
'navigationcomponent' => true
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<core-navigation-cmpt :add-side-menu-entries="appSideMenuEntries"></core-navigation-cmpt>
|
||||
|
||||
<div id="content">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
</div>
|
||||
<core-dashboard dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></core-dashboard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $this->load->view('templates/FHC-Footer'); ?>
|
||||
@@ -0,0 +1,16 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import CoreDashboard from '../components/Dashboard/Dashboard.js';
|
||||
|
||||
Vue.createApp({
|
||||
data: () => ({
|
||||
appSideMenuEntries: {}
|
||||
}),
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
CoreDashboard
|
||||
/*,
|
||||
"CoreFilterCmpt": CoreFilterCmpt,
|
||||
"verticalsplit": verticalsplit,
|
||||
"searchbar": searchbar*/
|
||||
}
|
||||
}).mount('#main');
|
||||
@@ -0,0 +1,38 @@
|
||||
export default {
|
||||
name: "BaseModal",
|
||||
props: [
|
||||
"id",
|
||||
"cModalClass", // optional: add custom classes to modal class
|
||||
"cModalDialogClass" // optional: add custom classes to modal-dialog class
|
||||
],
|
||||
onMounted()
|
||||
{
|
||||
const modal = new bootstrap.Modal(this.$refs.modal, {})
|
||||
modal.show();
|
||||
},
|
||||
template: `
|
||||
<div class="modal fade"
|
||||
ref="modal"
|
||||
:class="this.cModalClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog" :class="this.cModalDialogClass">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" :id="this.id + '_label'">
|
||||
<slot name="title"></slot>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button><!-- default -->
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
name: "BaseOffcanvas",
|
||||
props: [
|
||||
"id",
|
||||
"cOffcanvasClass", // optional: add custom classes to offcanvas class
|
||||
"closeFunc"
|
||||
],
|
||||
computed: {
|
||||
OffcanvasClass() {
|
||||
return this.cOffcanvasClass || 'offcanvas-end'; // default: slide in from right to left
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="offcanvas"
|
||||
:class="this.OffcanvasClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'"
|
||||
tabindex="-1"
|
||||
data-bs-backdrop="false">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" :id="this.id + '_label'">
|
||||
<slot name="title"></slot>
|
||||
</h5>
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" @click="closeFunc" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
<h5 class="modal-title"><slot name="title"/></h5>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body px-4 py-5">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="modal-footer">
|
||||
|
||||
@@ -8,5 +8,23 @@ export default {
|
||||
emits: [
|
||||
"setConfig",
|
||||
"change" // TODO(chris): do we need this?
|
||||
]
|
||||
],
|
||||
computed: {
|
||||
apiurl() {
|
||||
const app_root = FHC_JS_DATA_STORAGE_OBJECT.app_root;
|
||||
const ci_router = FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
return app_root + ci_router;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDateTime: function(dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + dt.getMonth() + '.' + dt.getFullYear() + ' | '
|
||||
+ dt.getHours() + ':' + dt.getMinutes();
|
||||
},
|
||||
getDate: function(dateTime){
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + dt.getMonth() + '.' + dt.getFullYear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
import BaseOffcanvas from '../Base/Offcanvas';
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAmpel',
|
||||
components: { BaseOffcanvas },
|
||||
data: () => ({
|
||||
filter: '',
|
||||
source: '',
|
||||
ampeln: []
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
widgetAmpeln () {
|
||||
return this.ampeln.slice(0, 4); // show only newest 4 ampeln
|
||||
},
|
||||
offcanvasAmpeln ()
|
||||
{
|
||||
switch(this.filter)
|
||||
{
|
||||
case 'verpflichtend': return this.ampeln.filter(item => item.verpflichtend);
|
||||
case 'ueberfaellig': return this.ampeln.filter(item => (new Date() > new Date(item.deadline)) && !item.bestaetigt);
|
||||
default: return this.ampeln;
|
||||
}
|
||||
},
|
||||
count () {
|
||||
return {
|
||||
verpflichtend: this.ampeln.filter(item => item.verpflichtend).length,
|
||||
ueberfaellig: this.ampeln.filter(item => (new Date() > new Date(item.deadline)) && !item.bestaetigt).length,
|
||||
alle: this.ampeln.length
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeOffcanvasAmpeln()
|
||||
{
|
||||
for (let i = 0; i < this.offcanvasAmpeln.length; i++)
|
||||
{
|
||||
let ampelId = this.offcanvasAmpeln[i].ampel_id;
|
||||
this.$refs['ampelCollapse_' + ampelId][0].classList.remove('show');
|
||||
}
|
||||
},
|
||||
openOffcanvasAmpel(ampelId){
|
||||
// Close earlier opened Ampeln
|
||||
this.closeOffcanvasAmpeln();
|
||||
|
||||
// Show given Ampel
|
||||
this.$refs['ampelCollapse_' + ampelId][0].classList.add('show');
|
||||
},
|
||||
closeOffcanvas(){
|
||||
this.filter = '';
|
||||
},
|
||||
confirm(ampelId){
|
||||
let indexToRemove = this.ampeln.findIndex((obj => obj.ampel_id === ampelId));
|
||||
this.ampeln.splice(indexToRemove, 1);
|
||||
},
|
||||
changeDisplay(){
|
||||
this.filter = '';
|
||||
if (this.source == 'offen')
|
||||
{
|
||||
this.ampeln = TEST_OFFENE_AMPELN;
|
||||
}
|
||||
|
||||
if (this.source == 'alle')
|
||||
{
|
||||
this.ampeln = TEST_ALLE_AMPELN;
|
||||
// axios
|
||||
// .get(this.apiurl + '/dashboard/Api/getAmpeln')
|
||||
// .then(res => { this.ampeln = res.data })
|
||||
// .catch(err => { console.error('ERROR: ', err.response.data) });
|
||||
}
|
||||
},
|
||||
validateBtnTxt(buttontext){
|
||||
return buttontext == null ? 'Bestätigen' : buttontext;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('setConfig', false);
|
||||
this.ampeln = TEST_OFFENE_AMPELN;
|
||||
},
|
||||
template: `
|
||||
<div class="widgets-ampel w-100 h-100">
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<div class="d-flex">
|
||||
<header class="me-auto"><b>Neueste Ampeln</b></header>
|
||||
<div class="mb-2 text-danger"><a href="#allAmpelOffcanvas" data-bs-toggle="offcanvas">Alle Ampeln</a></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a v-if="count.ueberfaellig > 0" href="#allAmpelOffcanvas" data-bs-toggle="offcanvas" @click="filter = 'ueberfaellig'" class="text-decoration-none"><span class="badge bg-danger me-1"><i class="fa fa-solid fa-bolt"></i> Überfällig: <b>{{ count.ueberfaellig }}</b></span></a>
|
||||
</div>
|
||||
<div v-for="ampel in widgetAmpeln" :key="ampel.ampel_id" class="mt-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="position-relative">
|
||||
<div class="d-flex">
|
||||
<div class="text-muted small me-auto"><small>Deadline: {{ getDate(ampel.deadline) }}</small></div>
|
||||
<div v-if="(new Date() > new Date(ampel.deadline)) && !ampel.bestaetigt "><span class="badge bg-danger"><i class="fa fa-solid fa-bolt"></i></span></div>
|
||||
<div v-if="ampel.verpflichtend"><span class="badge bg-warning ms-1"><i class="fa fa-solid fa-triangle-exclamation"></i></span></div>
|
||||
<div v-if="ampel.bestaetigt"><span class="badge bg-success ms-1"><i class="fa fa-solid fa-circle-check"></i></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#allAmpelOffcanvas" data-bs-toggle="offcanvas" class="stretched-link" @click="openOffcanvasAmpel(ampel.ampel_id)">{{ ampel.kurzbz }}</a><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ampeln.length == 0" class="card card-body mt-4 p-4 text-center">
|
||||
<span class="text-success h2"><i class="fa fa-solid fa-circle-check"></i></span>
|
||||
<span class="text-success h5">Super!</span><br>
|
||||
<span class="small">Keine offenen Ampeln.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Ampeln Offcanvas -->
|
||||
<BaseOffcanvas id="allAmpelOffcanvas" :closeFunc="closeOffcanvas">
|
||||
<template #title><header><b>Alle meine Ampeln</b></header></template>
|
||||
<template #body>
|
||||
<div class="d-flex justify-content-evenly">
|
||||
<div class="form-check form-check-inline form-control-sm">
|
||||
<input class="form-check-input" type="radio" v-model="source" id="offen" value="offen" @change="changeDisplay" checked>
|
||||
<label class="form-check-label" for="offen">Offene Ampeln</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline form-control-sm">
|
||||
<input class="form-check-input" type="radio" v-model="source" id="alle" value="alle" @change="changeDisplay">
|
||||
<label class="form-check-label" for="alle">Alle Ampeln</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col"><button class="btn btn-light w-100" @click="filter = ''"><small>Alle: <b>{{ count.alle }}</b></small></button></div>
|
||||
<div class="row row-cols-2 g-2 mt-1">
|
||||
<div class="col"><button class="btn btn-danger w-100" @click="filter = 'ueberfaellig'"><i class="fa fa-solid fa-bolt me-2"></i><small>Überfällig: <b>{{ count.ueberfaellig }}</b></small></button></div>
|
||||
<div class="col"><button class="btn btn-warning w-100" @click="filter = 'verpflichtend'"><i class="fa fa-solid fa-triangle-exclamation me-2"></i><small>Pflicht: <b>{{ count.verpflichtend }}</b></small></button></div>
|
||||
</div>
|
||||
<div v-for="ampel in offcanvasAmpeln" :key="ampel.ampel_id" class="mt-2">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item small">
|
||||
<div class="position-relative"><!-- prevents streched-link from stretching outside this parent element -->
|
||||
<div class="d-flex">
|
||||
<span class="small text-muted me-auto"><small>Deadline: {{ getDate(ampel.deadline) }}</small></span>
|
||||
<div v-if="(new Date() > new Date(ampel.deadline)) && !ampel.bestaetigt"><span class="badge bg-danger"><i class="fa fa-solid fa-bolt"></span></div>
|
||||
<div v-if="ampel.verpflichtend"><span class="badge bg-warning ms-1"><i class="fa fa-solid fa-triangle-exclamation"></span></div>
|
||||
<div v-if="ampel.bestaetigt"><span class="badge bg-success ms-1"><i class="fa fa-solid fa-circle-check"></i></span></div>
|
||||
</div>
|
||||
<a :href="'#ampelCollapse_' + ampel.ampel_id" data-bs-toggle="collapse" class="stretched-link">{{ ampel.kurzbz }}</a><br>
|
||||
</div>
|
||||
<div class="collapse my-3" :id="'ampelCollapse_' + ampel.ampel_id" :ref="'ampelCollapse_' + ampel.ampel_id">
|
||||
{{ ampel.beschreibung[0] }}
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button class="btn btn-sm btn-primary" :class="{disabled: ampel.bestaetigt}" @click="confirm(ampel.ampel_id)">{{ validateBtnTxt(ampel.buttontext[0]) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</BaseOffcanvas>`
|
||||
}
|
||||
|
||||
const TEST_ALLE_AMPELN = [
|
||||
{
|
||||
ampel_id: 0,
|
||||
kurzbz: 'Ampeltitel 1',
|
||||
deadline: '2022-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'1-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'1-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 1,
|
||||
kurzbz: 'Ampeltitel 2 kann auch etwas länger sein',
|
||||
deadline: '2023-10-03',
|
||||
verfallszeit: 20,
|
||||
verpflichtend: true,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Gelesen', 'Read'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'2-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'2-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 2,
|
||||
kurzbz: 'Ampeltitel 3',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: null, // Dauerampel, Bis zur Bestätigung
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'3-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'3-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 3,
|
||||
kurzbz: 'Ampeltitel 4',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 4,
|
||||
kurzbz: 'Ampeltitel 5',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 5,
|
||||
kurzbz: 'Ampeltitel 6',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 6,
|
||||
kurzbz: 'Ampeltitel 7',
|
||||
deadline: '2020-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},{
|
||||
ampel_id: 7,
|
||||
kurzbz: 'Ampeltitel 8',
|
||||
deadline: '2022-09-25',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 8,
|
||||
kurzbz: 'Ampeltitel 9',
|
||||
deadline: '2022-10-01',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const TEST_OFFENE_AMPELN = [
|
||||
{
|
||||
ampel_id: 0,
|
||||
kurzbz: 'Ampeltitel 1',
|
||||
deadline: '2022-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'1-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'1-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 1,
|
||||
kurzbz: 'Ampeltitel 2 kann auch etwas länger sein',
|
||||
deadline: '2023-10-03',
|
||||
verfallszeit: 20,
|
||||
verpflichtend: true,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Gelesen', 'Read'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'2-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'2-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 7,
|
||||
kurzbz: 'Ampeltitel 8',
|
||||
deadline: '2022-09-25',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 8,
|
||||
kurzbz: 'Ampeltitel 9',
|
||||
deadline: '2022-10-01',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,115 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
import BsModal from '../Bootstrap/Modal';
|
||||
const MAX_LOADED_NEWS = 10;
|
||||
|
||||
export default {
|
||||
name: 'WidgetsNews',
|
||||
components: { BsModal },
|
||||
data: () => ({
|
||||
allNewsList: [],
|
||||
singleNews: {}
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
newsList(){
|
||||
//Return news amount depending on widget width and size
|
||||
let quantity = this.width;
|
||||
|
||||
if (this.width === 1) {
|
||||
quantity = this.height === 1 ? 4 : 10;
|
||||
}
|
||||
|
||||
return this.allNewsList.slice(0, quantity);
|
||||
}
|
||||
},
|
||||
created(){
|
||||
axios
|
||||
.get(this.apiurl + '/dashboard/Api/getNews', {params: {limit: MAX_LOADED_NEWS}})
|
||||
.then(res => { this.allNewsList = res.data })
|
||||
.catch(err => { console.error('ERROR: ', err.response.data) });
|
||||
|
||||
this.$emit('setConfig', false);
|
||||
},
|
||||
methods: {
|
||||
setSingleNews(singleNews){
|
||||
this.singleNews = singleNews;
|
||||
this.$refs.newsModal.show();
|
||||
}
|
||||
},
|
||||
template: `<div class="widgets-news w-100 h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="d-flex">
|
||||
<header><b>Top News</b></header>
|
||||
<a href="#allNewsModal" data-bs-toggle="modal" class="ms-auto mb-2">
|
||||
<i class="fa fa-arrow-up-right-from-square me-1"></i>Alle News</a>
|
||||
</div>
|
||||
<div v-if="width == 1">
|
||||
<div v-for="news in newsList" :key="news.id" class="mt-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<span class="small text-muted">{{ formatDateTime(news.insertamum) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="width > 1 && height === 1" class="h-100" :class="'row row-cols-' + width">
|
||||
<div v-for="news in newsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="card-title h5 stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<span class="small text-muted">{{ formatDateTime(news.insertamum) }} </span>
|
||||
<p class="card-text pt-3" style="overflow: hidden; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-100" :class="'row row-cols-' + width">
|
||||
<div v-for="news in newsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
|
||||
<div class="card-footer"><span class="card-subtitle small text-muted">{{ formatDateTime(news.insertamum) }}</span></div>
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="card-title h5 stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<p class="card-text pt-3">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- News Modal -->
|
||||
<BsModal ref="newsModal" id="newsModal" dialog-class="modal-lg">
|
||||
<template #title>
|
||||
<div class="row">
|
||||
<div class="col-5"><img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="img-fluid rounded-start"></div>
|
||||
<div class="col-7 d-flex align-items-end">
|
||||
<p>{{ singleNews.betreff }}<br><small class="text-muted">{{ formatDateTime(singleNews.insertamum) }}</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>{{ singleNews.text }}</template>
|
||||
</BsModal>
|
||||
|
||||
<!-- All News Modal -->
|
||||
<BsModal ref="allNewsModal" id="allNewsModal" dialog-class="modal-fullscreen">
|
||||
<template #title>Alle News</template>
|
||||
<template #default>
|
||||
<div class="row row-cols-5 g-4 h-100 px-5">
|
||||
<div v-for="news in allNewsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
|
||||
<div class="card-footer"><span class="card-subtitle small">{{ formatDateTime(news.insertamum) }}</span></div>
|
||||
<div class="card-body">
|
||||
<a href="" class="card-title h5 stretched-link" @click="setSingleNews1(news)">{{ news.betreff }}</a><br>
|
||||
<p class="card-text">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BsModal>`
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
|
||||
export default {
|
||||
name: 'WidgetsUrl',
|
||||
data: () => ({
|
||||
links: []
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
tagName() {
|
||||
return this.config.tag !== undefined && this.config.tag.length > 0 ? this.config.tag : 'Meine Urls';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addLink(){
|
||||
let linkId = this.links.length;
|
||||
|
||||
this.links.push({
|
||||
id: linkId,
|
||||
tag: this.config.tag,
|
||||
title: this.title,
|
||||
url: this.url
|
||||
})
|
||||
},
|
||||
removeLink(linkId){
|
||||
let indexToRemove = this.links.findIndex((obj => obj.id === linkId));
|
||||
this.links.splice(indexToRemove, 1);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.links = TEST_LINKS;
|
||||
// this.links = TEST_KEINE_LINKS;
|
||||
},
|
||||
template: `
|
||||
<div class="widgets-url w-100 h-100">
|
||||
<div v-if="configMode">
|
||||
<div class="mb-3">
|
||||
|
||||
<header><b>Neuer Link</b></header><br>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" placeholder="Titel" type="text" v-model="title" name="title" maxlength="50" required>
|
||||
<input class="form-control form-control-sm mt-2" type="url" placeholder="URL" v-model="url" name="url" required>
|
||||
<button class="btn btn-outline-secondary btn-sm w-100 mt-2" @click="addLink()" type="button">Link speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<header><b>{{ widgetTag }}</b></header>
|
||||
<div v-for="link in links" :key="link.id" class="d-flex mt-2">
|
||||
<a target="_blank" :href="link.url"><i class="fa fa-solid fa-arrow-up-right-from-square"></i> {{ link.title }}</a>
|
||||
<a class="ms-auto" href="#" @click.prevent="removeLink(link.id)" v-show="configMode"><i class="fa fa-regular fa-trash-can"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
const TEST_KEINE_LINKS = [];
|
||||
const TEST_LINKS = [
|
||||
{
|
||||
id: 0,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 0',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 1',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 2',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 3',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
}
|
||||
];
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -72,7 +72,6 @@ ALTER TABLE dashboard.tbl_dashboard_benutzer_override OWNER TO fhcomplete;
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
@@ -94,7 +93,6 @@ ALTER SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq OWNED B
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_dashboard_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
@@ -130,7 +128,6 @@ ALTER TABLE dashboard.tbl_dashboard_preset OWNER TO fhcomplete;
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_preset_preset_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
@@ -179,7 +176,6 @@ ALTER TABLE dashboard.tbl_widget OWNER TO fhcomplete;
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_widget_widget_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
@@ -429,3 +425,40 @@ EODASHBOARDSQL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add permission: dashboard/admin
|
||||
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'dashboard/admin';"))
|
||||
{
|
||||
if($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('dashboard/admin', 'Adminberechtigung');";
|
||||
|
||||
if(!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo 'system.tbl_berechtigung: Added permission for dashboard/admin<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add permission: dashboard/benutzer
|
||||
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'dashboard/benutzer';"))
|
||||
{
|
||||
if($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('dashboard/benutzer', 'Benutzerberechtigung');";
|
||||
|
||||
if(!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo 'system.tbl_berechtigung: Added permission for dashboard/benutzer<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6521,7 +6521,7 @@ if($result = @$db->db_query("SELECT * FROM information_schema.role_table_grants
|
||||
}
|
||||
}
|
||||
|
||||
// add Dashboard Schema and Tables
|
||||
// add Dashboard Schema, Tables and Permissions
|
||||
include __DIR__ . '/dashboard/dbupdate_dashboard.php';
|
||||
|
||||
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
|
||||
@@ -6618,6 +6618,11 @@ $tabellen=array(
|
||||
"campus.tbl_zeitsperretyp" => array("zeitsperretyp_kurzbz","beschreibung","farbe"),
|
||||
"campus.tbl_zeitwunsch" => array("stunde","mitarbeiter_uid","tag","gewicht","updateamum","updatevon","insertamum","insertvon", "zeitwunsch_id", "zeitwunsch_gueltigkeit_id"),
|
||||
"campus.tbl_zeitwunsch_gueltigkeit" => array("zeitwunsch_gueltigkeit_id","mitarbeiter_uid","von","bis","insertamum","insertvon", "updateamum","updatevon"),
|
||||
"dashboard.tbl_dashboard" => array("dashboard_id", "dashboard_kurzbz", "beschreibung"),
|
||||
"dashboard.tbl_dashboard_benutzer_override" => array("override_id", "dashboard_id", "uid", "override"),
|
||||
"dashboard.tbl_dashboard_preset" => array("preset_id", "dashboard_id", "funktion_kurzbz", "preset"),
|
||||
"dashboard.tbl_dashboard_widget" => array("dashboard_id", "widget_id"),
|
||||
"dashboard.tbl_widget" => array("widget_id", "widget_kurzbz", "beschreibung"),
|
||||
"fue.tbl_aktivitaet" => array("aktivitaet_kurzbz","beschreibung","sort"),
|
||||
"fue.tbl_aufwandstyp" => array("aufwandstyp_kurzbz","bezeichnung"),
|
||||
"fue.tbl_projekt" => array("projekt_kurzbz","nummer","titel","beschreibung","beginn","ende","oe_kurzbz","budget","farbe","aufwandstyp_kurzbz","ressource_id","anzahl_ma","aufwand_pt","projekt_id","projekttyp_kurzbz","zeitaufzeichnung"),
|
||||
|
||||
Reference in New Issue
Block a user