2022-04-26 12:15:31 +02:00
|
|
|
|
<template>
|
|
|
|
|
|
<!-- TODO: order by name, status -->
|
|
|
|
|
|
<div ref="macroschedule" class="macroschedule">
|
|
|
|
|
|
<div class="row mb-3">
|
|
|
|
|
|
<div class="col-1">
|
|
|
|
|
|
<button class="btn btn-primary" @click.prevent="goPreviousWeek()">←</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col">
|
|
|
|
|
|
<h1 class="text-secondary">Plan de charge</h1>
|
|
|
|
|
|
<div class="input-group">
|
|
|
|
|
|
<label for="scheduleDateSelect" class="input-group-text">Voir date : </label>
|
|
|
|
|
|
<input id="scheduleDateSelect" class="form-control" type="date" :value="startMoment.format('YYYY-MM-DD')" @input="showMacroScheduleDate($event.target.value)">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col">
|
|
|
|
|
|
<fieldset>
|
|
|
|
|
|
<legend>Légende</legend>
|
|
|
|
|
|
<template v-if="isAdmin">
|
|
|
|
|
|
<span v-for="activityEnum of activitiesEnum" :key="activityEnum.value" :class="'p-1 m-2 border badge badge-activity-' + activityEnum.value">{{ activityEnum.label }}</span>
|
|
|
|
|
|
<span class="p-1 m-2 border badge badge-marker">Marqueur</span>
|
|
|
|
|
|
<span class="p-1 m-2 border badge badge-today">Aujourd'hui</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<span class="p-1 m-2 border badge badge-activity-light">Libre</span>
|
|
|
|
|
|
<span class="p-1 m-2 border badge badge-activity-dark">Occupé</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</fieldset>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-1">
|
|
|
|
|
|
<button class="btn btn-primary" @click.prevent="goNextWeek()">→</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row justify-content-center">
|
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
|
<table class="table table-bordered">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th scope="col" rowspan="3" class="align-middle">{{ isAdmin ? 'Projets' : '' }}</th>
|
|
|
|
|
|
<th scope="col" rowspan="3" class="align-middle">{{ isAdmin ? 'Statut' : '' }}</th>
|
|
|
|
|
|
<th v-if="daysThisMonth.length" :colspan="daysThisMonth.length" class="text-center">
|
|
|
|
|
|
{{ daysThisMonth[0].format('MMMM') | ucfirst }}
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th v-if="daysNextMonth.length" :colspan="daysNextMonth.length" class="text-center">
|
|
|
|
|
|
{{ daysNextMonth[0].format('MMMM') | ucfirst }}
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th v-for="weekNumber in macroscheduleConfig.numberOfWeeksToShow" :key="weekNumber" :colspan="7 - 2" class="text-center">
|
|
|
|
|
|
SEMAINE {{ currentWeek + weekNumber - 1}}
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<template v-for="(currentDay, index) of daysToShow">
|
|
|
|
|
|
<th scope="col" v-if="dayShouldbeenSeen(currentDay)" :key="index" :class="'dayCell p-0 text-center ' + (currentDay.isSame(todayMoment, 'day') ? 'today' : '')">
|
|
|
|
|
|
{{ currentDay.format('dd') }} {{ String(currentDay.date()).padStart(2, '0') }}
|
|
|
|
|
|
</th>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<template v-if="isAdmin">
|
|
|
|
|
|
<tr v-for="project of projects" :key="project.id" class="projectRow">
|
|
|
|
|
|
<th scope="col">
|
2022-05-22 13:23:50 +02:00
|
|
|
|
<a :href="route('projects.show', {PROJECT: project.id })" :class="'rounded-1 p-1 project text-nowrap text-truncate overflow-hidden bg-enum-'+project.status">{{ project.name | str_limit(45) }}</a>
|
2022-04-26 12:15:31 +02:00
|
|
|
|
</th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<span class="fw-bold text-nowrap text-truncate overflow-hidden">{{ projectStatusEnum[project.status].label }}</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<template v-for="(currentDay, index) of daysToShow">
|
|
|
|
|
|
<td :class="'p-0 border border-dark activities ' + (currentDay.isSame(todayMoment, 'day') ? 'today' : '')" v-if="dayShouldbeenSeen(currentDay)" :key="index">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="(activity, index) in [getActivity(project, getMorningMoment(currentDay)), getActivity(project, getAfterNoonMoment(currentDay))]"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
:class="getActivityClass(activity)+ ' activity ' + (hoverActivityId === activity.id ? 'hover' : '')"
|
|
|
|
|
|
@click="showModale(project, activity, currentDay, index === 0)"
|
|
|
|
|
|
@mouseover="setActivityHover(activity)"
|
|
|
|
|
|
@mouseleave="setActivityHover(activity, false)">
|
|
|
|
|
|
<div :title="getActivityTooltip(activity)" data-bs-toggle="tooltip">
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template v-if="projectHasMarkersForTheDay(currentDay, project)">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="marker"
|
|
|
|
|
|
data-bs-toggle="tooltip"
|
|
|
|
|
|
data-bs-html="true"
|
|
|
|
|
|
:title="getMarkersTooltip(currentDay, project)"></div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<tr class="projectRow">
|
|
|
|
|
|
<th scope="col" class="border-end-0 d-flex justify-content-between"><span class="fw-bold h5">Disponibilité</span></th>
|
|
|
|
|
|
<td class="border-start-0"></td>
|
|
|
|
|
|
<template v-for="(currentDay, index) of daysToShow">
|
|
|
|
|
|
<td :class="'p-0 border border-dark activities ' + (currentDay.isSame(todayMoment, 'day') ? 'today' : '')" v-if="dayShouldbeenSeen(currentDay)" :key="index">
|
|
|
|
|
|
<template v-if="isAdmin">
|
|
|
|
|
|
<div class="activity text-center" :style="{ 'background-color' : 'rgba(0,0,0,'+ getLoadPercent(getMorningMoment(currentDay)) }">
|
|
|
|
|
|
{{ getLoad(getMorningMoment(currentDay)) > 1 ? '🔥' : ' ' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="activity text-center" :style="{ 'background-color' : 'rgba(0,0,0,'+ getLoadPercent(getAfterNoonMoment(currentDay)) }">
|
|
|
|
|
|
{{ getLoad(getAfterNoonMoment(currentDay)) > 1 ? '🔥' : ' ' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div :class="'activity text-center ' +(getLoad(getMorningMoment(currentDay)) > macroscheduleConfig.loadDevThreshold ? 'bg-dark' : 'bg-light')"> </div>
|
|
|
|
|
|
<div :class="'activity text-center ' +(getLoad(getAfterNoonMoment(currentDay)) > macroscheduleConfig.loadDevThreshold ? 'bg-dark' : 'bg-light')"> </div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!--
|
|
|
|
|
|
- une Date début, et un choix matin/après-midi
|
|
|
|
|
|
- une Date Fin, et un choix matin/après-midi
|
|
|
|
|
|
- une activité (cf. liste annexe ci-dessous)
|
|
|
|
|
|
- une charge (en nombre d’hommes occupés)
|
|
|
|
|
|
- un commentaire
|
|
|
|
|
|
-->
|
|
|
|
|
|
<div ref="editModal" class="modal fade" 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">Activité pour {{ modaleProps.name }}</h5>
|
|
|
|
|
|
</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">Activité</label>
|
|
|
|
|
|
<select id="categorie" ref="type" class="form-select" aria-label="Sélectionnez une catégorie" :disabled="modalLoading" v-model="modaleProps.activity" required>
|
|
|
|
|
|
<option :value="undefined" disabled>Sélectionnez une activité</option>
|
|
|
|
|
|
<option v-for="activityEnum in activitiesEnum" :key="activityEnum.value" :value="activityEnum.value" :class="activityEnum.value">{{ activityEnum.label }}</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group mb-3">
|
|
|
|
|
|
<label for="start" class="col-form-label text-md-right ms-5">Date début</label>
|
|
|
|
|
|
<div class="input-group mb-3">
|
|
|
|
|
|
<input class="form-control" id="start" type="date" name="start" :value="moment(modaleProps.start).format('YYYY-MM-DD')" @input="updateDateField($event, 'start')" :disabled="modalLoading">
|
|
|
|
|
|
<select class="form-select" :disabled="modalLoading" v-model="modaleProps.startMorning" @input="updateHourField($event, 'start')">
|
|
|
|
|
|
<option value="1" :selected="moment(modaleProps.start).format('HH') === '00'">Matin</option>
|
|
|
|
|
|
<option value="0" :selected="moment(modaleProps.start).format('HH') === '12'">Midi</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group mb-3">
|
|
|
|
|
|
<label for="end" class="col-form-label text-md-right ms-5">Date fin</label>
|
|
|
|
|
|
<div class="input-group mb-3">
|
|
|
|
|
|
<input class="form-control" id="end" type="date" name="end" :value="moment(modaleProps.end).format('YYYY-MM-DD')" @input="updateDateField($event, 'end')" :disabled="modalLoading">
|
|
|
|
|
|
<select class="form-select" :disabled="modalLoading" v-model="modaleProps.endMorning" @input="updateHourField($event, 'end')">
|
|
|
|
|
|
<option value="1" :selected="moment(modaleProps.end).format('HH') === '11'">Midi</option>
|
|
|
|
|
|
<option value="0" :selected="moment(modaleProps.end).format('HH') === '23'">Soir</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group mb-3">
|
|
|
|
|
|
<label for="comment" class="col-form-label text-md-right ms-5">Commentaire</label>
|
|
|
|
|
|
<textarea ref="comment" class="form-control" name="comment" id="comment" rows="2" placeholder="" @keydown="limitLines($event)" v-model="modaleProps.comment" :disabled="modalLoading"></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group mb-3">
|
|
|
|
|
|
<label for="load" class="col-form-label text-md-right ms-5">Charge <i>(nombre de personnes consommées sur la période)</i></label>
|
|
|
|
|
|
<input type="number" class="form-control" name="load" id="load" v-model="modaleProps.load" :disabled="modalLoading" min="1"/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row justify-content-center">
|
|
|
|
|
|
<div class="col-8 text-center">
|
|
|
|
|
|
<button class="btn btn-primary me-3" type="button" :disabled="modalLoading" @click="saveActivity()">
|
|
|
|
|
|
<span v-if="modalLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
|
|
|
|
{{ modalLoading ? 'Chargement...' : 'Valider' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn btn-danger" @click="modale.hide();$nextTick(refreshTooltips)" :disabled="modalLoading">Fermer</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import Toastify from 'toastify-js'
|
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
let self = this
|
|
|
|
|
|
window.Toastify = Toastify
|
2022-05-23 17:41:14 +02:00
|
|
|
|
|
|
|
|
|
|
let data = JSON.parse(this.$parent.$options.json)
|
|
|
|
|
|
this.projects = data.projects
|
|
|
|
|
|
this.loadCollection = data.loadCollection
|
|
|
|
|
|
this.macroscheduleConfig = data.macroscheduleConfig
|
|
|
|
|
|
this.activitiesEnum = data.activitiesEnum
|
|
|
|
|
|
this.projectStatusEnum = data.projectStatusEnum
|
|
|
|
|
|
this.today = data.today
|
|
|
|
|
|
this.startDay = data.startDay
|
|
|
|
|
|
this.isAdmin = data.isAdmin
|
2022-04-26 12:15:31 +02:00
|
|
|
|
this.$nextTick(this.refreshTooltips)
|
2022-05-23 17:41:14 +02:00
|
|
|
|
this.modale = new this.bootstrap.Modal(this.$refs.editModal)
|
2022-04-26 12:15:31 +02:00
|
|
|
|
this.$refs.editModal.addEventListener('hide.bs.modal', function (e) {
|
|
|
|
|
|
if(self.modalLoading) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2022-05-23 17:41:14 +02:00
|
|
|
|
isAdmin: false,
|
|
|
|
|
|
today: '',
|
|
|
|
|
|
startDay: '',
|
2022-04-26 12:15:31 +02:00
|
|
|
|
textLimit: 254,
|
|
|
|
|
|
hoverActivityId: null,
|
|
|
|
|
|
error: '',
|
|
|
|
|
|
modaleProps: {
|
|
|
|
|
|
activity: null,
|
|
|
|
|
|
start: '',
|
|
|
|
|
|
startMorning: 1,
|
|
|
|
|
|
end: '',
|
|
|
|
|
|
endMorning: 1,
|
|
|
|
|
|
load: 0,
|
|
|
|
|
|
comment: '',
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
project: null
|
|
|
|
|
|
},
|
|
|
|
|
|
currentActivity: null,
|
|
|
|
|
|
projects: [],
|
|
|
|
|
|
activitiesEnum: [],
|
|
|
|
|
|
projectStatusEnum: [],
|
|
|
|
|
|
macroscheduleConfig: [],
|
|
|
|
|
|
loadCollection: {},
|
|
|
|
|
|
modale: null,
|
|
|
|
|
|
modalLoading: false,
|
|
|
|
|
|
tooltipTriggerList: []
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
todayMoment() {
|
|
|
|
|
|
return this.moment(this.today)
|
|
|
|
|
|
},
|
|
|
|
|
|
startMoment() {
|
|
|
|
|
|
return this.moment(this.startDay)
|
|
|
|
|
|
},
|
|
|
|
|
|
selectedMoment() {
|
|
|
|
|
|
return this.moment(this.startMoment.week(), 'w').hours(0).minutes(0).seconds(0).milliseconds(0).add(1, 'day').startOf('week')
|
|
|
|
|
|
},
|
|
|
|
|
|
currentMonthName() {
|
|
|
|
|
|
return this.selectedMoment.format('MMMM')
|
|
|
|
|
|
},
|
|
|
|
|
|
currentWeek() {
|
|
|
|
|
|
return this.selectedMoment.week()
|
|
|
|
|
|
},
|
|
|
|
|
|
daysToShow() {
|
|
|
|
|
|
let days = [], startMoment = this.selectedMoment.clone()
|
|
|
|
|
|
for (let i = this.macroscheduleConfig.numberOfWeeksToShow; i > 0; i--) {
|
|
|
|
|
|
for(let dayNumber = 0; dayNumber < 7; dayNumber++) {
|
|
|
|
|
|
days.push(startMoment)
|
|
|
|
|
|
startMoment = startMoment.clone().add(1, 'day')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return days
|
|
|
|
|
|
},
|
|
|
|
|
|
daysThisMonth() {
|
|
|
|
|
|
let days = this.daysToShow, currentMonth = days[0]?.month(),
|
|
|
|
|
|
out = []
|
|
|
|
|
|
for (let day of days) {
|
|
|
|
|
|
if(currentMonth === day.month() && this.dayShouldbeenSeen(day)) {
|
|
|
|
|
|
out.push(day)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
|
|
|
|
|
},
|
|
|
|
|
|
daysNextMonth() {
|
|
|
|
|
|
let days = this.daysToShow, currentMonth = days[0]?.month(),
|
|
|
|
|
|
out = []
|
|
|
|
|
|
for (let day of days) {
|
|
|
|
|
|
if(currentMonth !== day.month() && this.dayShouldbeenSeen(day)) {
|
|
|
|
|
|
out.push(day)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
modaleProps: {
|
|
|
|
|
|
handler(object) {
|
|
|
|
|
|
// * Validate Date fields
|
|
|
|
|
|
this.error = ''
|
|
|
|
|
|
this.validateDateField(this.moment(this.modaleProps.start), 'start')
|
|
|
|
|
|
this.validateDateField(this.moment(this.modaleProps.end), 'end')
|
|
|
|
|
|
},
|
|
|
|
|
|
deep: true
|
|
|
|
|
|
},
|
|
|
|
|
|
currentActivity: {
|
|
|
|
|
|
handler(activityObject) {
|
|
|
|
|
|
if (activityObject.project_id) {
|
|
|
|
|
|
let project = this.getProject(activityObject.project_id)
|
|
|
|
|
|
// * Insert (or update) activity
|
|
|
|
|
|
this.$set(project.activities, activityObject.id, Object.assign({}, activityObject))
|
|
|
|
|
|
this.$nextTick(this.refreshTooltips)
|
|
|
|
|
|
this.$forceUpdate()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
deep: true
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
validateDateField(moment, fieldName) {
|
|
|
|
|
|
if(!this.dayShouldbeenSeen(moment)) {
|
|
|
|
|
|
this.error = 'La date de ' + (fieldName === 'start' ? 'début' : 'fin') + ' ne peut être un jour de weekend.'
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
},
|
|
|
|
|
|
updateDateField(e, fieldName) {
|
|
|
|
|
|
let date = this.moment(this.modaleProps[fieldName]),
|
|
|
|
|
|
time = Number.parseInt(this.modaleProps[fieldName + 'Morning']) === 1 ?
|
|
|
|
|
|
this.getMorningMoment(date, fieldName === 'start') :
|
|
|
|
|
|
this.getAfterNoonMoment(date, fieldName === 'start'),
|
|
|
|
|
|
input = this.moment(e.target.value)
|
|
|
|
|
|
if(this.validateDateField(input, fieldName)) {
|
|
|
|
|
|
this.error = ''
|
|
|
|
|
|
this.$set(this.modaleProps, fieldName, input.format('YYYY-MM-DD') + 'T' + time.format('HH:mm:ss'))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
updateHourField(e, fieldName) {
|
|
|
|
|
|
let date = this.moment(this.modaleProps[fieldName])
|
|
|
|
|
|
if(Number.parseInt(e.target.value) === 1) {
|
|
|
|
|
|
this.$set(this.modaleProps, fieldName, this.getMorningMoment(date, fieldName === 'start').format('YYYY-MM-DD\THH:mm:ss'))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.$set(this.modaleProps, fieldName, this.getAfterNoonMoment(date, fieldName === 'start').format('YYYY-MM-DD\THH:mm:ss'))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
saveActivity() {
|
|
|
|
|
|
let self = this,
|
|
|
|
|
|
activity = Object.assign({}, this.currentActivity)
|
|
|
|
|
|
|
|
|
|
|
|
if(this.error.length) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.modalLoading = true
|
|
|
|
|
|
|
|
|
|
|
|
activity.activity = self.modaleProps.activity
|
|
|
|
|
|
activity.start = self.modaleProps.start
|
|
|
|
|
|
activity.end = self.modaleProps.end
|
|
|
|
|
|
activity.load = self.modaleProps.load
|
|
|
|
|
|
activity.comment = self.modaleProps.comment
|
|
|
|
|
|
activity.project_id = self.modaleProps.project.id
|
|
|
|
|
|
|
|
|
|
|
|
axios.post(self.route('macroplanning.activity'), activity)
|
|
|
|
|
|
.then(response => {
|
|
|
|
|
|
self.error = ''
|
|
|
|
|
|
self.sendToast('L\'activité à été mis à jour')
|
|
|
|
|
|
self.$nextTick(() => {
|
|
|
|
|
|
self.modalLoading = false
|
|
|
|
|
|
self.modale.hide()
|
|
|
|
|
|
activity.id = response.data.id
|
|
|
|
|
|
self.$set(self, 'currentActivity', activity)
|
|
|
|
|
|
})
|
|
|
|
|
|
if(response.data.hasOverlap) {
|
|
|
|
|
|
window.location.reload()
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(error => {
|
|
|
|
|
|
if(error.response.status === 429) {
|
|
|
|
|
|
location.reload()
|
|
|
|
|
|
}
|
|
|
|
|
|
if(error.response.status === 422) {
|
|
|
|
|
|
if(error.response.data.errors) {
|
|
|
|
|
|
self.error = ''
|
|
|
|
|
|
for(let id in error.response.data.errors) {
|
|
|
|
|
|
let field = error.response.data.errors[id]
|
|
|
|
|
|
self.error += field.join('\n') + '\n'
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
} else if(error.response.data.message) {
|
|
|
|
|
|
self.error = error.response.data.message
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
self.error = error
|
|
|
|
|
|
}).then(() => {
|
|
|
|
|
|
self.modalLoading = false
|
|
|
|
|
|
self.$nextTick(self.refreshTooltips)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
showModale(project, activity, selectedMoment, isMorning = true) {
|
|
|
|
|
|
|
|
|
|
|
|
selectedMoment = selectedMoment.clone()
|
|
|
|
|
|
|
|
|
|
|
|
let self = this,
|
|
|
|
|
|
startMoment = (activity.start ? this.moment(activity.start) :
|
|
|
|
|
|
(isMorning ? this.getMorningMoment(selectedMoment, true) : this.getAfterNoonMoment(selectedMoment, true))),
|
|
|
|
|
|
endMoment = (activity.end ? this.moment(activity.end) :
|
|
|
|
|
|
(isMorning ? this.getMorningMoment(selectedMoment, false) : this.getAfterNoonMoment(selectedMoment, false)))
|
|
|
|
|
|
|
|
|
|
|
|
this.modaleProps = {
|
|
|
|
|
|
activity: activity.activity,
|
|
|
|
|
|
start: activity.start ?? startMoment.format('YYYY-MM-DD\THH:mm:ss'),
|
|
|
|
|
|
startMorning: startMoment.format('HH') === '00' ? 1 : 0,
|
|
|
|
|
|
end: activity.end ?? endMoment.format('YYYY-MM-DD\THH:mm:ss'),
|
|
|
|
|
|
endMorning: endMoment.format('HH') === '11' ? 1 : 0,
|
|
|
|
|
|
load: activity.load,
|
|
|
|
|
|
comment: activity.comment,
|
|
|
|
|
|
name: project.name,
|
|
|
|
|
|
project: project
|
|
|
|
|
|
}
|
|
|
|
|
|
this.currentActivity = activity
|
|
|
|
|
|
this.modale.show()
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
self.$nextTick(self.refreshTooltips)
|
|
|
|
|
|
}, 2000)
|
|
|
|
|
|
},
|
|
|
|
|
|
dayShouldbeenSeen(moment) {
|
|
|
|
|
|
return moment.day() !== 6 && moment.day() !== 0
|
|
|
|
|
|
},
|
|
|
|
|
|
projectHasMarkersForTheDay(moment, project) {
|
|
|
|
|
|
for(let marker of project.markers) {
|
|
|
|
|
|
if (moment.isSame(marker.marker, 'day')) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
setActivityHover(activity, activate = true) {
|
|
|
|
|
|
if(!activate) {
|
|
|
|
|
|
this.hoverActivityId = null
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.hoverActivityId = activity.activity !== undefined ? activity.id : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// * GETTERS
|
|
|
|
|
|
getLoadPercent(moment) {
|
|
|
|
|
|
let load = this.getLoad(moment)
|
|
|
|
|
|
return load > 1 ? 1 : load
|
|
|
|
|
|
},
|
|
|
|
|
|
getLoad(moment) {
|
|
|
|
|
|
return this.loadCollection[moment.format('YYYY-MM-DD 00:00:00')] ?? null
|
|
|
|
|
|
},
|
|
|
|
|
|
getActivityTooltip(activity) {
|
|
|
|
|
|
let activityEnumLabel = activity.activity !== undefined ? (this.activitiesEnum[activity.activity] ? this.activitiesEnum[activity.activity].label : '') : ''
|
|
|
|
|
|
return activity.activity !== undefined ? `${activityEnumLabel}, Charge ${activity.load} personnes, ${activity.comment}` : ''
|
|
|
|
|
|
},
|
|
|
|
|
|
getActivityClass(activity) {
|
|
|
|
|
|
return activity ? `bg-enum-${activity.activity}` : 'bg-white'
|
|
|
|
|
|
},
|
|
|
|
|
|
getProject(project_id) {
|
|
|
|
|
|
return this.projects[project_id]
|
|
|
|
|
|
},
|
|
|
|
|
|
getActivity(project, moment) {
|
|
|
|
|
|
for(let activity of Object.values(project.activities)) {
|
|
|
|
|
|
if(moment.isBetween(this.moment(activity.start, this.moment.DATETIME_LOCAL_SECONDS), this.moment(activity.end, this.moment.DATETIME_LOCAL_SECONDS), 'hour', '[]')) {
|
|
|
|
|
|
return activity
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return {}
|
|
|
|
|
|
},
|
|
|
|
|
|
getMorningMoment(moment, start = true) {
|
|
|
|
|
|
moment = moment.clone()
|
|
|
|
|
|
return start ? moment.hours(0).minutes(0).seconds(0) : moment.hours(11).minutes(59).seconds(0)
|
|
|
|
|
|
},
|
|
|
|
|
|
getAfterNoonMoment(moment, start = true) {
|
|
|
|
|
|
moment = moment.clone()
|
|
|
|
|
|
return start ? moment.hours(12).minutes(0).seconds(0) : moment.hours(23).minutes(59).seconds(0)
|
|
|
|
|
|
},
|
|
|
|
|
|
getMarkersTooltip(moment, project) {
|
|
|
|
|
|
let tooltip = ''
|
|
|
|
|
|
for(let marker of project.markers) {
|
|
|
|
|
|
if (moment.isSame(marker.marker, 'day')) {
|
|
|
|
|
|
tooltip += `${this.moment(marker.marker).format('DD/MM/YYYY')} ${marker.label}<br>`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return tooltip
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// * NAVIGATION
|
|
|
|
|
|
goPreviousWeek() {
|
|
|
|
|
|
let moment = this.startMoment ?? this.todayMoment
|
|
|
|
|
|
moment = this.moment(moment.week(), 'w')
|
|
|
|
|
|
this.showMacroScheduleDate('', moment.subtract(1, 'week'))
|
|
|
|
|
|
},
|
|
|
|
|
|
goNextWeek() {
|
|
|
|
|
|
let moment = this.startMoment ?? this.todayMoment
|
|
|
|
|
|
moment = this.moment(moment.week(), 'w')
|
|
|
|
|
|
this.showMacroScheduleDate('', moment.add(1, 'week'))
|
|
|
|
|
|
},
|
|
|
|
|
|
showMacroScheduleDate(dateString, moment = null) {
|
|
|
|
|
|
let url = new URL(window.location)
|
|
|
|
|
|
moment = moment ?? this.moment(dateString)
|
|
|
|
|
|
location = url.origin+ url.pathname + '?date='+moment.format('DD/MM/YYYY')
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
sendToast(message) {
|
|
|
|
|
|
Toastify({
|
|
|
|
|
|
text: message,
|
|
|
|
|
|
duration: 3000,
|
|
|
|
|
|
gravity: "top",
|
|
|
|
|
|
position: "right",
|
|
|
|
|
|
offset: {
|
|
|
|
|
|
x: '2rem',
|
|
|
|
|
|
y: '3rem'
|
|
|
|
|
|
},
|
|
|
|
|
|
style: {
|
|
|
|
|
|
background: "linear-gradient(to right, #00317d, #0058ca)",
|
|
|
|
|
|
}
|
|
|
|
|
|
}).showToast();
|
|
|
|
|
|
},
|
|
|
|
|
|
refreshTooltips() {
|
|
|
|
|
|
let exisitingToolips = document.querySelectorAll('.tooltip[role="tooltip"]')
|
|
|
|
|
|
for(let tooltip of exisitingToolips) {
|
|
|
|
|
|
tooltip.remove()
|
|
|
|
|
|
}
|
|
|
|
|
|
this.tooltipTriggerList = [].slice.call(this.$refs.macroschedule.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
|
|
|
|
|
this.tooltipTriggerList.map((tooltipTriggerEl) => {
|
2022-05-23 17:41:14 +02:00
|
|
|
|
return new this.bootstrap.Tooltip(tooltipTriggerEl, { delay: 500 })
|
2022-04-26 12:15:31 +02:00
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
limitLines(e) {
|
|
|
|
|
|
let keynum, lines = this.$refs.comment.value.split('\n').length;
|
|
|
|
|
|
// IE
|
|
|
|
|
|
if(window.event) {
|
|
|
|
|
|
keynum = e.keyCode;
|
|
|
|
|
|
// Netscape/Firefox/Opera
|
|
|
|
|
|
} else if(e.which) {
|
|
|
|
|
|
keynum = e.which;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(keynum != 8 && this.$refs.comment.value.length >= this.textLimit) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(keynum == 13 && lines == this.$refs.comment.rows) {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
@import './../../sass/variables';
|
|
|
|
|
|
@import './../../sass/complement';
|
|
|
|
|
|
@import "toastify-js/src/toastify.css";
|
|
|
|
|
|
|
|
|
|
|
|
.macroschedule {
|
|
|
|
|
|
@each $index, $color in $enum-activities {
|
|
|
|
|
|
.badge-activity-#{$index} {
|
|
|
|
|
|
background-color: $color;
|
|
|
|
|
|
color: a11y-color(#353535 ,$color);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-marker {
|
|
|
|
|
|
background-color: rgba(255, 2, 204, 0.6);
|
|
|
|
|
|
color: a11y-color(#353535 ,rgba(255, 2, 204, 0.6));
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-today {
|
|
|
|
|
|
background-color: rgba(0, 255, 0, 0.6);
|
|
|
|
|
|
color: a11y-color(#353535 ,rgba(0, 255, 0, 0.6));
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-activity-light {
|
|
|
|
|
|
background-color: $light;
|
|
|
|
|
|
color: a11y-color(#353535 ,$light);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-activity-dark {
|
|
|
|
|
|
background-color: $dark;
|
|
|
|
|
|
color: a11y-color(#FFF ,$dark);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.project {
|
|
|
|
|
|
@each $index, $color in $enum-project-status {
|
|
|
|
|
|
&.bg-enum-#{$index} {
|
|
|
|
|
|
background-color: $color;
|
|
|
|
|
|
color: a11y-color(#f6f6f6 ,$color);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th.today,td.today > div {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
&:before {
|
|
|
|
|
|
content: ' ';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 0.2rem;
|
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
|
height: 110%;
|
|
|
|
|
|
width: 0.3rem;
|
|
|
|
|
|
background-color: rgba(0, 255, 0, 0.6);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tr:last-child > td.today {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
&:after {
|
|
|
|
|
|
content: '↑ Aujourd\'hui';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: -2.5rem;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
background-color: rgb(0, 255, 0);
|
|
|
|
|
|
padding: 0.2rem;
|
|
|
|
|
|
border: 1px solid rgba(0, 0, 0, 0.4);
|
|
|
|
|
|
border-radius: 0.2rem;
|
|
|
|
|
|
width: 7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dayCell {
|
|
|
|
|
|
width: 3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.projectRow {
|
|
|
|
|
|
&:not(:first-child) {
|
|
|
|
|
|
border-top: 2px solid black;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.activities .activity {
|
|
|
|
|
|
&:not(:first-child) {
|
|
|
|
|
|
border-top: 1px solid black;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$buttonBgColor: #4d4d4d;
|
|
|
|
|
|
|
|
|
|
|
|
.activity {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.bg-white {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@each $index, $color in $enum-activities {
|
|
|
|
|
|
&.bg-enum-#{$index} {
|
|
|
|
|
|
background-color: $color;
|
|
|
|
|
|
color: a11y-color(#353535 ,$color);
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0.1rem;
|
|
|
|
|
|
left: 0.1rem;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
width: 1.8rem;
|
|
|
|
|
|
height: 1.8rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
background-color: $buttonBgColor;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover,
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
background-color: darken($buttonBgColor, 10%);
|
|
|
|
|
|
}
|
|
|
|
|
|
&.btn-clear {
|
|
|
|
|
|
color: darken($color: #fff, $amount: 5);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.infos {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.hover > div {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
& > div.marker {
|
|
|
|
|
|
z-index: 200;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
right: 0.2rem;
|
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
|
height: 110%;
|
|
|
|
|
|
width: 0.7rem;
|
|
|
|
|
|
background-color: rgba(255, 2, 204, 0.6);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|