changes how to display the hr table with all the user update requests and the update request table in the profil view, also adds some new columns to the cis_profil_update table to store the status, status date and status message

This commit is contained in:
SimonGschnell
2024-01-22 14:45:32 +01:00
parent 0d5e8e89a6
commit fb9b65b138
14 changed files with 372 additions and 92 deletions
+2 -2
View File
@@ -94,7 +94,7 @@ class Profil extends Auth_Controller
$payload = $json->payload;
$type = isset($json->payload->kontakt_id)? "kontakt_id" : "adresse_id";
$data = ["topic"=>$json->topic,"uid" => $this->uid, "requested_change" => json_encode($payload), "change_timestamp" => "NOW()" ];
$data = ["topic"=>$json->topic,"uid" => $this->uid, "requested_change" => json_encode($payload), "change_timestamp" => "NOW()","status"=>"pending" ];
//? loops over all updateRequests from a user to validate if the new request is valid
$res = $this->ProfilChangeModel->loadWhere(["uid"=>$this->uid]);
@@ -148,7 +148,7 @@ class Profil extends Auth_Controller
$json = json_decode($this->input->raw_input_stream);
$data = ["topic"=>$json->topic,"uid" => $this->uid, "requested_change" => json_encode($json->payload), "change_timestamp" => "NOW()" ];
$data = ["topic"=>$json->topic,"uid" => $this->uid, "requested_change" => json_encode($json->payload), "change_timestamp" => "NOW()","status"=>"pending" ];
//? gets all the requested changes from a user
+35 -16
View File
@@ -13,6 +13,8 @@ class ProfilUpdate extends Auth_Controller
parent::__construct([
'index' => ['student/anrechnung_beantragen:r', 'user:r'], // TODO(chris): permissions?
'getAllRequests' => ['student/anrechnung_beantragen:r', 'user:r'],
'acceptProfilRequest'=>['user:r'],
'denyProfilRequest'=>['user:r'],
]);
//? put the uid and pid inside the controller to reuse in controller
@@ -28,22 +30,39 @@ class ProfilUpdate extends Auth_Controller
}
public function getAllRequests(){
$res = $this->ProfilChangeModel->load();
if(isError($res)){
// catch exception
echo $res->retval->data;
return;
}else{
if(hasData($res)){
$res = getData($res);
foreach($res as $element){
$element->change_timestamp = date_create($element->change_timestamp)->format('d/m/Y H:i');
}
}else{
$res = null;
}
}
$res = $this->ProfilChangeModel->getProfilUpdate();
$res = hasData($res)? getData($res) : null;
echo json_encode($res);
}
public function acceptProfilRequest(){
$_POST = json_decode($this->input->raw_input_stream,true);
$id = $this->input->post('requestID',true);
if(isset($id)){
$res =$this->ProfilChangeModel->update([$id], ["status"=>"accepted","status_timestamp"=>"NOW()"]);
echo json_encode($res);
}
}
public function denyProfilRequest(){
$_POST = json_decode($this->input->raw_input_stream,true);
$id = $this->input->post('requestID',true);
if(isset($id)){
var_dump($id);
//! instead of deleting the rejected profil update, the status of the db entry is set to rejected
//$res = $this->ProfilChangeModel->delete([$id]);
$res = $this->ProfilChangeModel->update([$id],["status"=>"rejected","status_timestamp"=>"NOW()"]);
echo json_encode($res);
}
}
}
@@ -30,8 +30,13 @@ class Profil_change_model extends DB_Model
* returns a profil update with id
* returns all profil updates if id is set to null
*/
public function getProfilUpdate($uid,$id=null){
$whereClause = ["uid"=>$uid];
public function getProfilUpdate($uid=null,$id=null){
$whereClause=[];
if(!is_null($uid)){
$whereClause['uid']=$uid;
}
//?
if(!is_null($id)){
$whereClause['profil_update_id']=$id;
}
+1 -1
View File
@@ -3,7 +3,7 @@ $includesArray = array(
'title' => 'Stundenplan',
'customJSModules' => ['public/js/apps/Cis/Profil.js'],
'tabulator5' => true,
'customCSSs' => ['public/css/components/calendar.css', 'public/css/components/FilterComponent.css','public/css/components/Profil.css'],
'customCSSs' => ['public/css/components/calendar.css', 'public/css/components/FilterComponent.css','public/css/components/Profil.css','public/css/components/FormUnderline.css'],
);
+1 -1
View File
@@ -2,7 +2,7 @@
$includesArray = ['title'=> 'Profil Änderungen',
'customJSModules'=> ['public/js/apps/Cis/ProfilUpdateRequests.js'],
'tabulator5'=> true,
'customCSSs'=>['public/css/components/FilterComponent.css'],
'customCSSs'=>['public/css/components/FilterComponent.css','public/css/components/FormUnderline.css'],
];
$this->load->view('templates/CISHTML-Header',$includesArray);
+35
View File
@@ -0,0 +1,35 @@
/* FORM UNDERLINE
*/
.form-underline{
display: flex;
flex-direction: column;
justify-content: center;
align-content: space-between;
}
.form-underline .form-underline-content{
border-width: 1px;
border-color: transparent transparent #dee2e6 transparent;
border-style: solid;
padding-bottom: 0.25rem;
padding-left: 0.5rem;
/*optional not wrapping text that is horizontally scrollable and does not display a scrollbar */
white-space: nowrap;
overflow: auto;
scrollbar-width: none;
}
.form-underline .form-underline-content::-webkit-scrollbar {
display: none;
}
.form-underline .form-underline-titel{
opacity: 0.65;
font-size: .85rem;
padding-left: 0.5rem;
}
-34
View File
@@ -41,41 +41,7 @@
}
/* FORM UNDERLINE
*/
.form-underline{
display: flex;
flex-direction: column;
justify-content: center;
align-content: space-between;
}
.form-underline .form-underline-content{
border-width: 1px;
border-color: transparent transparent #dee2e6 transparent;
border-style: solid;
padding-bottom: 0.25rem;
padding-left: 0.5rem;
/*optional not wrapping text that is horizontally scrollable and does not display a scrollbar */
white-space: nowrap;
overflow: auto;
scrollbar-width: none;
}
.form-underline .form-underline-content::-webkit-scrollbar {
display: none;
}
.form-underline .form-underline-titel{
opacity: 0.65;
font-size: .85rem;
padding-left: 0.5rem;
}
.btn-circle {
width: 30px;
+71 -21
View File
@@ -1,53 +1,100 @@
import fhcapifactory from "../api/fhcapifactory.js";
import {CoreFilterCmpt} from '../../components/filter/Filter.js'
import {CoreFilterCmpt} from "../../components/filter/Filter.js";
import AcceptDenyUpdate from "../../components/Cis/ProfilUpdate/AcceptDenyUpdate.js";
Vue.$fhcapi = fhcapifactory;
/*
data: [
{
uid: "",
profil_changes: "",
change_timestamp: "",
},
], */
let printIcon= function(cell, formatterParams, onRendered){ //plain text value
return "<i class='fa fa-eye'></i>";
};
const app = Vue.createApp({
components:{
CoreFilterCmpt,
},
data(){
return{
profil_updates_table_options:{
ajaxURL:FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/Cis/ProfilUpdate/getAllRequests',
height:300,
layout:'fitColumns',
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters:false,
responsiveLayoutCollapseFormatter:this.collapseFormatter,
columns: [
{
title: "Uid",
field: "uid",
title: "Topic",
field: "topic",
resizable: true,
minWidth: 200,
responsive:0,
//responsive:0,
},
{
title: "Update",
field: "profil_changes",
minWidth: 10000,
responsive:3,
title: "UID",
field: "uid",
minWidth: 200,
//responsive:0,
},
{
title: "Date",
field: "change_timestamp",
resizable: true,
minWidth: 200,
responsive:0,
//responsive:0,
},
{
title: "Status",
field: "status",
hozAlign:'center',
formatter: function(cell,para){
let res =Object.getPrototypeOf(cell);
//console.log(res);
switch(cell.getValue()){
case "pending": return "<i class='fa fa-circle-info text-info fa-lg'></i> pending";
case "accepted": return "<i class='fa fa-circle-check text-success fa-lg'></i> accepted";
case "rejected": return "<i class='fa-solid fa-circle-xmark text-danger fa-lg '></i> rejected";
default: return "<p>default</p>";
}
},
resizable: true,
minWidth: 200,
//responsive:0,
},
{
title: "View",
formatter:printIcon,
resizable: true,
minWidth: 200,
hozAlign: 'center',
cellClick:(e, cell)=>{
//! function that is called when clicking on a row in the table
let cellData = cell.getRow().getData();
AcceptDenyUpdate.popup({value:cellData}).then(res=>{
console.log("res of the modal: ",res);
//? refetches the data, if any request was denied or accepted
//* setData will call the ajaxURL again to refresh the data
this.$refs.UpdatesTable.tabulator.setData();
}).catch(e=>{
//? catches the rejected Promise if the result of the modal was falsy
console.log("catch of the modal: ",e);
});
},
//responsive:0,
},
],
},
}
},
computed:{
},
methods:{
sideMenuFunction: function(){
@@ -82,7 +129,9 @@ const app = Vue.createApp({
},
created(){
Vue.$fhcapi.ProfilUpdate.getProfilUpdateRequest().then((res)=>{
console.log(res.data);
})
},
@@ -93,6 +142,7 @@ const app = Vue.createApp({
template:`
<div>
<core-filter-cmpt title="Update Requests" ref="UpdatesTable" :tabulator-options="profil_updates_table_options" tableOnly :sideMenu="false" />
</div>`,
+3 -1
View File
@@ -1,7 +1,9 @@
import Search from "./search.js";
import UserData from "./userdata.js";
import ProfilUpdate from "./profilUpdate.js"
export default {
"Search": Search,
"UserData": UserData
"UserData": UserData,
"ProfilUpdate": ProfilUpdate,
};
+20
View File
@@ -0,0 +1,20 @@
export default {
//! API calls for profil update requests
getProfilUpdateRequest: function(){
const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/Cis/ProfilUpdate/getAllRequests';
return axios.get(url);
},
acceptProfilRequest: function(requestID){
const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/Cis/ProfilUpdate/acceptProfilRequest';
return axios.post(url,{requestID: requestID});
},
denyProfilRequest: function(requestID){
const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/Cis/ProfilUpdate/denyProfilRequest';
return axios.post(url,{requestID: requestID});
}
}
-6
View File
@@ -56,12 +56,6 @@ export default {
},
//! API Calls for Profil Update View
getProfilUpdateRequest: function(){
const url = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/Cis/ProfilUpdate/getAllRequests';
return axios.get(url);
}
@@ -33,12 +33,13 @@ export default {
case "Private Adressen" : return "EditAdresse"; break;
case "Add Adressen" : return "EditAdresse"; break;
case "Add Kontakte" : return "EditKontakt"; break;
case "Delete Adressen" : return "EditAdresse"; break;
case "Delete Kontakte" : return "EditKontakt"; break;
case "Delete Adressen" : return "Adresse"; break;
case "Delete Kontakte" : return "Kontakt"; break;
default: return "text_input"; break;
}
},
openModal(updateRequest) {
console.log(JSON.stringify(updateRequest));
let view = this.getView(updateRequest.topic);
let content =null;
@@ -104,18 +105,42 @@ export default {
<thead>
<tr >
<th scope="col">Topic</th>
<th scope="col">Status</th>
<th scope="col">Date of Request</th>
<th scope="col">Bearbeiten</th>
<th style="white-space:normal" scope="col">Löschen</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr v-for="item in data">
<!-- :class="{'bg-success':item.status === 'accepted', 'bg-danger':item.status === 'rejected', 'text-white':item.status =='rejected' || item.status=='accepted'}" -->
<tr v-for="item in data" :style="item.status=='accepted'?'background-color:lightgreen':item.status==='rejected'?'background-color:lightcoral':''">
<td class="align-middle">{{item.topic}}</td>
<td class="align-middle text-center" >{{item.status}}</td>
<td class="align-middle">{{item.change_timestamp}}</td>
<td v-if="item.topic.toLowerCase().includes('delete')" class="align-middle text-center" >{{item.requested_change.adr_typ?item.requested_change.adr_typ:item.requested_change.kontakt}}</td>
<td v-else class="align-middle text-center" ><i style="color:#00639c" @click="openModal(item)" role="button" class="fa fa-edit"></i></td>
<td class="align-middle text-center"><i style="color:red" role="button" @click="deleteRequest(item)" class="fa fa-trash"></i></td>
<template v-if="item.status === 'pending'">
<td>
<template v-if="item.topic.toLowerCase().includes('delete')">
<!-- old edit view for delete requests <div class="align-middle text-center" >{{item.requested_change.adr_typ?item.requested_change.adr_typ:item.requested_change.kontakt}}</div>-->
<div class="align-middle text-center"><i style="color:gray" role="button" @click="openModal(item)" class="fa fa-eye"></i></div>
</template>
<template v-else >
<div class="align-middle text-center" ><i style="color:#00639c" @click="openModal(item)" role="button" class="fa fa-edit"></i></div>
</template>
<div class="align-middle text-center"><i style="color:red" role="button" @click="deleteRequest(item)" class="fa fa-trash"></i></div>
</td>
</template>
</tr>
</tbody>
</table>
@@ -0,0 +1,160 @@
import BsModal from "../../Bootstrap/Modal.js";
import Alert from "../../Bootstrap/Alert.js";
import Kontakt from "../Profil/ProfilComponents/Kontakt.js";
import Adresse from "../Profil/ProfilComponents/Adresse.js";
export default {
components: {
BsModal,
Alert,
Kontakt,
Adresse,
},
mixins: [BsModal],
props: {
title:{
type:String,
default:"Profil Update Request"
},
value: {
type:Object,
},
/*
* NOTE(chris):
* Hack to expose in "emits" declared events to $props which we use
* in the v-bind directive to forward all events.
* @see: https://github.com/vuejs/core/issues/3432
*/
onHideBsModal: Function,
onHiddenBsModal: Function,
onHidePreventedBsModal: Function,
onShowBsModal: Function,
onShownBsModal: Function,
},
data() {
return {
data: this.value,
//? result is returned from the Promise when the modal is closed
result: false,
info: null,
}
},
methods: {
acceptRequest: function(){
console.log(this.data.profil_update_id);
Vue.$fhcapi.ProfilUpdate.acceptProfilRequest(this.data.profil_update_id).then(res =>{
console.log("res",res);
console.log("res.data",res.data);
this.result = true;
})
this.hide();
},
denyRequest: function(){
console.log(this.data.profil_update_id);
Vue.$fhcapi.ProfilUpdate.denyProfilRequest(this.data.profil_update_id).then(res =>{
console.log("res",res);
console.log("res.data",res.data);
this.result = true;
})
this.hide();
},
submitProfilChange(){
//TODO: check if the updated value is different from the original value before submitting the request
if(false){
//? inserts new row in public.tbl_cis_profil_update
//* calls the update api call if an update field is present in the data that was passed to the module
Vue.$fhcapi.UserData[this.editData.update?'updateProfilRequest':'insertProfilRequest'](this.topic,this.profilUpdate).then((res)=>{
if(res.data.error == 0){
this.result= true;
this.hide();
Alert.popup("Ihre Anfrage wurde erfolgreich gesendet. Bitte warten Sie, während sich das Team um Ihre Anfrage kümmert.");
}else{
this.result= false;
this.hide();
Alert.popup("Ein Fehler ist aufgetreten: "+ JSON.stringify(res.data.retval));
}
});
}
},
},
computed: {
getComponentView: function(){
if(this.data.topic.toLowerCase().includes("kontakt")){
return "kontakt";
}else if (this.data.topic.toLowerCase().includes("adresse")){
return "adresse";
}else{
return "text_input";
}
},
},
created() {
console.log("data passed as prop",this.data);
},
mounted() {
this.modal = this.$refs.modalContainer.modal;
},
popup(options) {
return BsModal.popup.bind(this)(null, options);
},
template: `
<bs-modal ref="modalContainer" v-bind="$props" body-class="" dialog-class="modal-lg" class="bootstrap-alert" backdrop="false" >
<template v-slot:title>
{{title}}
</template>
<template v-slot:default>
<pre>{{JSON.stringify(data.profil_update_id,null,2)}}</pre>
<!-- debugging prints
<pre>view {{getComponentView}}</pre>
<pre>topic {{JSON.stringify(data.topic,null,2)}}</pre>
<pre>data {{JSON.stringify(data.requested_change,null,2)}}</pre>
-->
<div class="form-underline mb-2">
<div class="form-underline-titel">UserID: </div>
<span class="form-underline-content" >{{data.uid}}</span>
</div>
<div class="form-underline mb-2">
<div class="form-underline-titel">Topic of Request: </div>
<span class="form-underline-content" >{{data.topic}}</span>
</div>
<div class="form-underline mb-2">
<div class="form-underline-titel">Date of Request:</div>
<span class="form-underline-content" >{{data.change_timestamp}}</span>
</div>
<div v-if="getComponentView==='text_input'" class="form-underline mb-2">
<div class="form-underline-titel">{{data.topic}}</div>
<span class="form-underline-content" >{{data.requested_change}}</span>
</div>
<component v-else :is="getComponentView" :data="data.requested_change"></component>
</template>
<template v-slot:footer>
<button @click="acceptRequest" class="btn btn-primary">Accept <i class="fa fa-check"></i></button>
<button @click="denyRequest" class="btn btn-danger">Deny <i class="fa fa-xmark"></i></button>
</template>
</bs-modal>`,
};
+4
View File
@@ -8,6 +8,9 @@
uid VARCHAR(32) NOT NULL,
requested_change jsonb NOT NULL,
change_timestamp TIMESTAMP NOT NULL,
status VARCHAR(32) NOT NULL,
status_timestamp TIMESTAMP NULL,
status_message TEXT NULL,
CONSTRAINT tbl_cis_profil_update_pk PRIMARY KEY(profil_update_id),
CONSTRAINT tbl_cis_profil_update_fk FOREIGN KEY(uid) REFERENCES public.tbl_benutzer(uid)
);
@@ -19,6 +22,7 @@
CACHE 1;
ALTER TABLE public.tbl_cis_profil_update ALTER COLUMN profil_update_id SET DEFAULT nextval('public.tbl_cis_profil_update_id_seq');
ALTER TABLE public.tbl_cis_profil_update ADD CONSTRAINT tbl_cis_profil_update_restricted_status CHECK (status IN ('pending','accepted','rejected'));
--ALTER TABLE public.tbl_cis_profil_update ADD CONSTRAINT cis_profil_udpate_topic_uid_unique UNIQUE (uid,topic);
GRANT SELECT, INSERT, UPDATE, DELETE ON public.tbl_cis_profil_update TO vilesci;