ldc-supportweb/resources/js/components/ScheduleComponent.vue
2021-12-18 18:20:57 +01:00

599 lines
21 KiB
Vue

<template>
<div class="plannings">
<div class="row justify-content-center">
<div class="col-1 text-center">
<a class="sticky-top btn btn-primary mt-5" @click="goToPlanning(false)">←</a>
</div>
<div class="col-10">
<table class="table table-bordered align-middle text-center">
<thead>
<tr>
<th rowspan="2" colspan="2">
</th>
<th :colspan="showWeekends ? 7:5" v-for="item in weeksCells" class="Mon Fri" :key="item.num">
S {{item.num}} - {{item.month}} {{item.year}}
</th>
</tr>
<tr>
<th v-for="day in days" :class="'day '+day.eday" :key="day.num">
{{day.day}} {{day.num}}
</th>
</tr>
</thead>
<tbody>
<template v-for="(user,index) in schedules">
<tr v-for="(slot,key) in user" :key="index + '' + key">
<th rowspan="4" colspan="2" v-if="key == 1">
{{users[index].name}}
</th>
<td v-for="day in days" :class="'dynCell day '+user[key][day.key].type.value+ ' ' +day.eday" :key="day.key">
<b v-if="user[key][day.key]['model_lie']['value']">
<a target="_blank" class="text-light bg-dark"
:href="getCellObjectInfo(user[key][day.key]).url">
{{user[key][day.key]['id_lie']['label']}}
</a>
</b><br/>
{{user[key][day.key]['text']}}
<button class="btn btn-edit" type="button" data-bs-toggle="modal"
data-bs-target="#editModal" @click="setModalData(index,key,day.key,user[key][day.key]['id'])">✎</button>
<button class="btn btn-clear" @click="eraseCell(user[key][day.key]['id'])">⌫</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="col-1 text-center">
<a class="sticky-top btn btn-primary mt-5" @click="goToPlanning(true)">→</a>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="modalLabel" aria-hidden="true" >
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel" ref="modalLabel">{{modale.title}}</h5>
<button ref="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" ></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row justify-content-center">
<div v-if="error" class="alert alert-danger" role="alert">
{{ error }}
</div>
<div class="col-8">
<div class="form-group mb-3">
<label for="categorie" class="col-form-label text-md-right ms-5">Categorie</label>
<select id="categorie" ref="type" class="form-select" aria-label="Sélectionnez une catégorie">
<option value="" >Sélectionnez une catégorie</option>
<option v-for="activity in activities" :key="activity.value" :value="activity.value"
:selected="modale.activity.value == activity.value"
:class="activity.value">{{ activity.label }}</option>
</select>
</div>
<div class="form-group mb-3">
<label for="morph" class="col-form-label text-md-right ms-5">Type d'élément lié</label>
<select id="morph" ref="morph" class="form-select" @change="selectChange" aria-label="Sélectionnez un élément à lier">
<option value="">Sélectionnez un type d'objet à lier</option>
<option v-for="model in models" :key="model.value" :value="model.value"
:selected="modale.model_lie.value == model.value"
>{{ model.label }}</option>
</select>
</div>
<div class="form-group mb-3" v-if="modale.dDisplay">
<label for="select_id_lie" class="col-form-label text-md-right ms-5">Élément lié</label>
<v-select
id="select_id_lie"
ref="select"
:loading="loading"
v-model="value"
:clearable="false"
label="label"
:options='data'
@search="search"
:disabled="dReadonly||dDisabled"
:filterable="true"
:searchable="true"
@input="setSelected">
<li slot="list-footer" class="pagination">
<button
@click="$event.preventDefault(); page -= 1"
:disabled="!hasPrevPage"
class="btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2">Précédent</button>
<button
@click="$event.preventDefault(); page += 1"
:disabled="!hasNextPage"
class="btn btn-sm btn-primary mt-2 mb-2">Suivant</button>
</li>
</v-select>
</div>
<div class="form-group mb-3">
<label for="text" class="col-form-label text-md-right ms-5">Description courte</label>
<textarea ref="text" class="form-control" name="text"
id="text" rows="2" placeholder="" @keydown="limitLines($event)" v-model="modale.text"></textarea>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-8 text-center">
<button class="btn btn-primary me-3" @click="saveCell()">Valider</button>
<button class="btn btn-danger" @click="$refs.close.click()">Fermer</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
dbSchedules: {
type: String,
},
dbActivities: {
type: String,
},
dbModels: {
type: String,
},
dbUsers: {
type: String,
},
weeks: {
type: String,
},
browseDate: {
type: String,
default: function() {
return new Date();
}
},
showWeekends: {
type: Boolean,
default: false,
}
},
mounted() {
this.currentDate = JSON.parse(this.browseDate);
this.weeksCells = JSON.parse(this.weeks);
this.numweeks = this.weeksCells.length;
this.days = this.prepareDays();
this.users = JSON.parse(this.dbUsers);
this.schedules = this.prepareSched(JSON.parse(this.dbSchedules));
this.activities = JSON.parse(this.dbActivities);
this.models = JSON.parse(this.dbModels);
},
data() {
return {
currentDate: null,
weeksCells: [],
days: [],
users:[],
schedules: [],
activities: [],
models: [],
error: '',
modale:{
userid: 0,
slot: 0,
day: '01/01/1900',
title: 0,
dDisplay: false,
text: '',
activity: {
label: '',
value: '',
},
model_lie: {
label: '',
value: '',
},
id_lie: {
label: '',
value: '',
},
},
//v-select de ses morts
dDisabled : false,
dReadonly : false,
loading: false,
value: {
label: null,
value: null
},
searchText: '',
data: [],
page: 1,
perPage: 15,
hasNextPage: true,
hasPrevPage: false
};
},
watch: {
page() {
if(this.page < 1) {
this.page = 1
}
if(this.page > 1) {
this.hasPrevPage = true
} else {
this.hasPrevPage = false
}
this.search()
},
data() {
if(this.data.length == this.perPage) {
this.hasNextPage = true
} else {
this.hasNextPage = false
}
}
},
methods: {
goToPlanning(forward) {
// console.log(forward);
let date = this.moment(this.currentDate).startOf('week').subtract(7, 'day');
if(forward) {
date = this.moment(this.currentDate).add(7, 'day')
}
let url = new URL(this.route('schedule.index'))
location = url.origin+ url.pathname + '?p='+date.format('DD/MM/YYYY')
},
prepareDays() {
let mydays = [];
for(let i=0; i < this.numweeks * 7; i++) {
let date = this.moment(this.currentDate).add(i, 'day');
let obj = {
day: date.format('ddd'),
num: date.format('DD'),
eday: date.locale("en").format('ddd'),
key: date.format('DD/MM/YYYY'),
}
mydays.push(obj);
if(!this.showWeekends) {
if(date.day() == 5) {
i+=2;
}
}
}
return mydays;
},
prepareSched(dbscheds) {
var templateObj = {
"type": {
"label":"",
"value":""
},
"text":"",
"id_lie": {
"label":"",
"value":""
},
"model_lie": {
"label":"",
"value":""
},
"id":null}
//we need to make missing users
Object.keys(this.users).forEach(id => {
if(dbscheds[id] === undefined) {
dbscheds[id] = {};
}
});
Object.keys(dbscheds).forEach(userid => {
//what if we missing a slot for the whole period ?
//we need to make missing slots too
for(let i=1; i < 5; i++) {
if(dbscheds[userid][i] === undefined) {
dbscheds[userid][i] = {};
}
}
Object.keys(dbscheds[userid]).forEach(slotkey => {
this.days.forEach(day => {
if(dbscheds[userid][slotkey][day.key] === undefined) {
//console.log('need ' + day.key + ' for slot ' + slotkey + ' for user ' + userid);
dbscheds[userid][slotkey][day.key] = templateObj;
}
});
});
});
return dbscheds;
},
getCellObjectInfo(cellData) {
for(let model of this.models) {
if(cellData['model_lie'].value === model.value && model.value == 'tickets') {
return {
'url': this.route('ticket.show', { TICKET: cellData['id_lie'].value }),
'text': cellData['model_lie'].label
}
}
if(cellData['model_lie'].value === model.value && model.value == 'sites') {
return {
'url': this.route('sites.show', { SITE: cellData['id_lie'].value}),
'text': cellData['model_lie'].label
}
}
if(cellData['model_lie'].value === model.value && model.value == 'projects') {
return {
'url': this.route('projects.show', { PROJECT: cellData['id_lie'].value}),
'text': cellData['model_lie'].label
}
}
}
return null
},
resetModalFields() {
this.$set(this, 'currentType' , null)
this.$set(this, 'value' , {
label: null,
value: null
})
this.$refs.type.value = ''
this.$refs.morph.value = ''
},
setModalData(userid,slotkey,day,id) {
let text = this.schedules[userid][slotkey][day]['text'];
let activity = this.schedules[userid][slotkey][day]['type'];
let model = this.schedules[userid][slotkey][day]['model_lie'];
let enumobject = {
label: '',
value: '',
};
let currentModale = {
userid: userid,
slot: slotkey,
day: day,
title: id,
dDisplay: false,
text: text,
activity: activity,
model_lie: model ?? enumobject,
id_lie: this.schedules[userid][slotkey][day]['id_lie'],
}
this.modale = currentModale;
this.$nextTick(() => {
let route = null
switch(this.modale.model_lie.value) {
case 'tickets':
this.dDisplay = true
route = this.route('ticket.show', {
TICKET: this.modale.id_lie
})
break
case 'projects':
this.dDisplay = true
route = this.route('projects.show', {
PROJECT: this.modale.id_lie
})
break
case 'sites':
this.dDisplay = true
route = this.route('sites.show', {
SITE: this.modale.id_lie
})
break
default: this.dDisplay = false
}
this.selectChange();
this.value = this.schedules[userid][slotkey][day]['id_lie'];
})
},
setSelected(value) {
this.modale.id_lie = value;
},
search (search = null) {
this.$nextTick(() => {
this.error = null
let route;
switch(this.$refs.morph.value) {
case 'tickets':
route = this.route('ticket.index')
break
case 'projects':
route = this.route('projects.index')
break
case 'sites':
route = this.route('sites.index')
break
default: return
}
if(search !== false && search !== null) {
this.searchText = search
}
let init = search === false ? true : false
route += '?page='+this.page
route += '&s='+this.searchText
this.loading = true
axios.get(route)
.then(response => {
this.data = response.data
})
.catch(error => {
if(error.response.status === 429) {
location.reload()
}
this.error = error
}).then(() => {
this.loading = false
})
});
},
selectChange() {
this.value = {
label: null,
value: null
}
switch(this.$refs.morph.value) {
case 'tickets':
case 'projects':
case 'sites':
this.modale.dDisplay = true
this.search(false)
break
default: this.modale.dDisplay = false
}
},
eraseCell(id) {
}
}
};
</script>
<style lang="scss">
@import "vue-select/src/scss/vue-select.scss";
.plannings {
.sticky-top {
top: 20rem !important;
}
}
.grid-1week {
display: grid;
grid-template-columns: 2fr repeat(5, 1fr);
grid-auto-rows: minmax(50px, auto);
}
.grid-2week {
display: grid;
grid-template-columns: 2fr repeat(10, 1fr);
grid-auto-rows: minmax(50px, auto);
align-items: center;
}
.grid-week div {
border-width:0 1px 1px 0;
border-style: solid;
border-color: var(--bs-cyan);
display: flex;
justify-content: center;
align-self: stretch;
align-items: center;
}
.empty {
grid-column: auto;
grid-row: auto /span 2;
}
.week {
grid-column: auto /span 5;
grid-row: auto;
}
.name {
grid-column: auto;
grid-row: auto /span 4;
}
table {
.Mon {
border-left: 4px solid;
}
// .Fri {
// border-right: 4px solid;
// }
}
.dynCell.day {
position: relative;
&:hover .btn {
opacity: 1;
}
$buttonBgColor: #4d4d4d;
// Reset button Style to nice one
button {
display: inline;
border: none;
padding: 0.6rem 3rem;
margin: 0;
text-decoration: none;
background: $buttonBgColor;
font-size: 1rem;
cursor: pointer;
text-align: center;
transition: background 250ms ease-in-out, transform 150ms ease;
-webkit-appearance: none;
-moz-appearance: none;
}
button:disabled,
button[disabled] {
color: #aaa;
}
button:hover,
button:focus {
background: darken($buttonBgColor, 10%);
}
button:focus {
outline: 1px solid #fff;
outline-offset: -4px;
}
button:active {
transform: scale(0.99);
}
.btn {
position: absolute;
top: 0.1rem;
right: 0.1rem;
padding: 0;
width: 1.8rem;
height: 1.8rem;
margin: 0;
background-color: $buttonBgColor;
opacity: 0;
transition: 200ms opacity;
&.hide {
opacity: 0 !important;
}
&:hover,
&:focus {
background-color: darken($buttonBgColor, 10%);
}
&.btn-clear {
color: darken($color: #fff, $amount: 5);
}
&:nth-last-child(2) {
right: inherit;
left: 0.1rem;
}
&:nth-last-child(3) {
right: inherit;
top: 2.1rem;
left: 0.1rem;
}
&:nth-last-child(1) {
top: 2.1rem;
right: 0.1rem;
}
}
}
@import "toastify-js/src/toastify.css";
@import "vue-select/src/scss/vue-select.scss";
.planning {
overflow: auto;
td {
height: 50px;
}
#text {
height: 5.5rem;
padding: 1rem 2rem;
resize: none;
width: 37rem;
margin: 0 auto;
}
}
</style>