1252 lines
38 KiB
Vue
1252 lines
38 KiB
Vue
<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 ? ($event.target as HTMLInputElement).value : ''
|
||
)
|
||
"
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="col">
|
||
<fieldset>
|
||
<legend>Légende</legend>
|
||
<template v-if="isAdmin">
|
||
<span
|
||
v-for="(activityEnum, index) of activitiesEnum"
|
||
:key="index"
|
||
:class="`p-1 m-2 border badge badge-activity-${String(
|
||
activityEnum.value
|
||
)}`"
|
||
>
|
||
{{ String(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"
|
||
>
|
||
{{ ucfirst(daysThisMonth[0].format("MMMM")) }}
|
||
</th>
|
||
<th
|
||
v-if="daysNextMonth.length"
|
||
:colspan="daysNextMonth.length"
|
||
class="text-center"
|
||
>
|
||
{{ ucfirst(daysNextMonth[0].format("MMMM")) }}
|
||
</th>
|
||
</tr>
|
||
<tr v-if="macroscheduleConfig !== null">
|
||
<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, index) of projects"
|
||
:key="index"
|
||
class="projectRow"
|
||
>
|
||
<th scope="col">
|
||
<a
|
||
:href="getProjectRoute(project)"
|
||
:class="
|
||
'rounded-1 p-1 project text-nowrap text-truncate overflow-hidden bg-enum-' +
|
||
project.status
|
||
"
|
||
>
|
||
{{ str_limit(String(project.name), 45) }}
|
||
</a>
|
||
</th>
|
||
<td>
|
||
<span
|
||
class="fw-bold text-nowrap text-truncate overflow-hidden"
|
||
>
|
||
{{
|
||
String(projectStatusEnum[project.status as number].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(getMorningMoment(currentDay), project),
|
||
getActivity(getAfterNoonMoment(currentDay), project),
|
||
]"
|
||
: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)"
|
||
/>
|
||
</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" />
|
||
<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
|
||
v-if="macroscheduleConfig !== null"
|
||
:class="
|
||
'activity text-center ' +
|
||
(getLoad(getMorningMoment(currentDay)) >
|
||
macroscheduleConfig.loadDevThreshold
|
||
? 'bg-dark'
|
||
: 'bg-light')
|
||
"
|
||
>
|
||
|
||
</div>
|
||
<div
|
||
v-if="macroscheduleConfig !== null"
|
||
: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, index) in activitiesEnum"
|
||
:key="index"
|
||
: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="
|
||
momentJs(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="
|
||
momentJs(modaleProps.start).format('HH') === '00'
|
||
"
|
||
>
|
||
Matin
|
||
</option>
|
||
<option
|
||
value="0"
|
||
:selected="
|
||
momentJs(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="momentJs(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="
|
||
momentJs(modaleProps.end).format('HH') === '11'
|
||
"
|
||
>
|
||
Midi
|
||
</option>
|
||
<option
|
||
value="0"
|
||
:selected="
|
||
momentJs(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"
|
||
/>
|
||
</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"
|
||
/>
|
||
{{ modalLoading ? "Chargement..." : "Valider" }}
|
||
</button>
|
||
<button
|
||
class="btn btn-danger"
|
||
@click.prevent="
|
||
modale ? modale.hide() : null;
|
||
$nextTick(refreshTooltips);
|
||
"
|
||
:disabled="modalLoading"
|
||
>
|
||
Fermer
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { defineComponent } from "vue";
|
||
import * as Toastify from "toastify-js";
|
||
import axios from "axios";
|
||
import trans from "../modules/trans";
|
||
import route from "../modules/route";
|
||
import { Moment } from "moment";
|
||
import * as momentJs from "moment";
|
||
import type { Modal } from "bootstrap";
|
||
|
||
export default defineComponent({
|
||
mixins: [trans, route, { methods: { momentJs } }],
|
||
mounted() {
|
||
let self = this;
|
||
window.Toastify = Toastify;
|
||
|
||
const json = String(this.$attrs.json ?? "{}"),
|
||
data = JSON.parse(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;
|
||
this.$nextTick(this.refreshTooltips);
|
||
this.modale = new window.bootstrap.Modal(
|
||
this.$refs.editModal as HTMLElement
|
||
);
|
||
(this.$refs.editModal as HTMLElement).addEventListener(
|
||
"hide.bs.modal",
|
||
function (e: Event) {
|
||
if (self.modalLoading) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
}
|
||
);
|
||
this.initTooltips();
|
||
},
|
||
data(): {
|
||
isAdmin: boolean;
|
||
today: string;
|
||
startDay: string;
|
||
textLimit: number;
|
||
hoverActivityId: string | null;
|
||
error: string | undefined;
|
||
modaleProps: {
|
||
[key: string]: LaravelModel | number | string | null | 1 | 0;
|
||
activity: number | null;
|
||
start: string;
|
||
startMorning: 1 | 0;
|
||
end: string;
|
||
endMorning: 1 | 0;
|
||
load: number;
|
||
comment: string;
|
||
name: string;
|
||
project: LaravelModel | null;
|
||
};
|
||
currentActivity: LaravelModel | null;
|
||
projects: LaravelModelList;
|
||
activitiesEnum: LaravelModelList;
|
||
projectStatusEnum: LaravelModelList;
|
||
macroscheduleConfig: {
|
||
loadDevThreshold: number;
|
||
numberOfWeeksToShow: number;
|
||
} | null;
|
||
loadCollection: Record<string, number> | null;
|
||
modale: Modal | null;
|
||
modalLoading: boolean;
|
||
} {
|
||
return {
|
||
isAdmin: false,
|
||
today: "",
|
||
startDay: "",
|
||
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: null,
|
||
loadCollection: {},
|
||
modale: null,
|
||
modalLoading: false,
|
||
};
|
||
},
|
||
computed: {
|
||
todayMoment(): Moment {
|
||
return this.momentJs(this.today);
|
||
},
|
||
startMoment(): Moment {
|
||
return this.momentJs(this.startDay);
|
||
},
|
||
selectedMoment(): Moment {
|
||
return this.momentJs(this.startMoment.week(), "w")
|
||
.hours(0)
|
||
.minutes(0)
|
||
.seconds(0)
|
||
.milliseconds(0)
|
||
.add(1, "day")
|
||
.startOf("week");
|
||
},
|
||
currentMonthName(): string {
|
||
return this.selectedMoment.format("MMMM");
|
||
},
|
||
currentWeek(): number {
|
||
return this.selectedMoment.week();
|
||
},
|
||
daysToShow(): Moment[] {
|
||
let days = [],
|
||
startMoment = this.selectedMoment.clone();
|
||
if (!this.macroscheduleConfig) {
|
||
return [];
|
||
}
|
||
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(): Moment[] {
|
||
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(): Moment[] {
|
||
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() {
|
||
// * Validate Date fields
|
||
this.error = "";
|
||
this.validateDateField(this.momentJs(this.modaleProps.start), "start");
|
||
this.validateDateField(this.momentJs(this.modaleProps.end), "end");
|
||
},
|
||
deep: true,
|
||
},
|
||
currentActivity: {
|
||
handler(activityObject: LaravelModel | null) {
|
||
if (activityObject && activityObject.project_id) {
|
||
const project = this.getProject(activityObject.project_id as number);
|
||
// * Insert (or update) activity
|
||
(project.activities as LaravelModelList)[
|
||
activityObject.id as number
|
||
] = Object.assign({}, activityObject);
|
||
this.$nextTick(this.refreshTooltips);
|
||
this.$forceUpdate();
|
||
}
|
||
},
|
||
deep: true,
|
||
},
|
||
},
|
||
methods: {
|
||
validateDateField(moment: Moment, fieldName: string): boolean {
|
||
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: Event, fieldName: string): void {
|
||
const date = this.momentJs(this.modaleProps[fieldName]),
|
||
target = e.target,
|
||
time =
|
||
Number.parseInt(String(this.modaleProps[`${fieldName}Morning`])) === 1
|
||
? this.getMorningMoment(date, fieldName === "start")
|
||
: this.getAfterNoonMoment(date, fieldName === "start"),
|
||
input = target
|
||
? this.momentJs((target as HTMLInputElement).value)
|
||
: null;
|
||
|
||
if (!input) {
|
||
throw new Error("updateDateField target input is undefined");
|
||
}
|
||
|
||
if (this.validateDateField(input, fieldName)) {
|
||
this.error = "";
|
||
this.modaleProps[fieldName] =
|
||
input.format("YYYY-MM-DD") + "T" + time.format("HH:mm:ss");
|
||
}
|
||
},
|
||
updateHourField(e: Event, fieldName: string): void {
|
||
const date = this.momentJs(this.modaleProps[fieldName]),
|
||
target = e.target;
|
||
if (target && Number.parseInt((target as HTMLInputElement).value) === 1) {
|
||
this.modaleProps[fieldName] = this.getMorningMoment(
|
||
date,
|
||
fieldName === "start"
|
||
).format("YYYY-MM-DDTHH:mm:ss");
|
||
} else {
|
||
this.modaleProps[fieldName] = this.getAfterNoonMoment(
|
||
date,
|
||
fieldName === "start"
|
||
).format("YYYY-MM-DDTHH:mm:ss");
|
||
}
|
||
},
|
||
saveActivity(): void {
|
||
const self = this,
|
||
activity = Object.assign({}, this.currentActivity) as LaravelModel,
|
||
routeUri = self.route("macroplanning.activity");
|
||
|
||
if (self.error?.length) {
|
||
return;
|
||
}
|
||
|
||
if (!routeUri) {
|
||
throw new Error("macroplanning.activity is undefined");
|
||
}
|
||
|
||
self.modalLoading = true;
|
||
|
||
if (self.modaleProps.activity === null) {
|
||
throw new Error("modaleProps.activity is null");
|
||
}
|
||
if (self.modaleProps.project === null) {
|
||
throw new Error("modaleProps.project is null");
|
||
}
|
||
|
||
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(routeUri, activity)
|
||
.then((response) => {
|
||
self.error = "";
|
||
self.sendToast("L'activité à été mis à jour");
|
||
self.$nextTick(() => {
|
||
if (!self.modale) {
|
||
throw new Error("modale is undefined");
|
||
}
|
||
self.modalLoading = false;
|
||
self.modale.hide();
|
||
activity.id = response.data.id;
|
||
self.currentActivity = activity;
|
||
});
|
||
if (response.data.hasOverlap) {
|
||
window.location.reload();
|
||
}
|
||
})
|
||
.catch((error: any) => {
|
||
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: LaravelModel,
|
||
activity: LaravelModel,
|
||
selectedMoment: Moment,
|
||
isMorning = true
|
||
) {
|
||
selectedMoment = selectedMoment.clone();
|
||
|
||
if (!this.modale) {
|
||
throw new Error("modale is undefined");
|
||
}
|
||
|
||
const self = this,
|
||
startMoment = activity.start
|
||
? this.momentJs(String(activity.start))
|
||
: isMorning
|
||
? this.getMorningMoment(selectedMoment, true)
|
||
: this.getAfterNoonMoment(selectedMoment, true),
|
||
endMoment = activity.end
|
||
? this.momentJs(String(activity.end))
|
||
: isMorning
|
||
? this.getMorningMoment(selectedMoment, false)
|
||
: this.getAfterNoonMoment(selectedMoment, false);
|
||
|
||
this.modaleProps = {
|
||
activity: activity.activity as number | null,
|
||
start: String(
|
||
activity.start ?? startMoment.format("YYYY-MM-DDTHH:mm:ss")
|
||
),
|
||
startMorning: startMoment.format("HH") === "00" ? 1 : 0,
|
||
end: String(activity.end ?? endMoment.format("YYYY-MM-DDTHH:mm:ss")),
|
||
endMorning: endMoment.format("HH") === "11" ? 1 : 0,
|
||
load: Number.parseInt(String(activity.load)),
|
||
comment: String(activity.comment ?? ""),
|
||
name: String(project.name ?? ""),
|
||
project: project,
|
||
};
|
||
this.currentActivity = activity;
|
||
this.modale.show();
|
||
|
||
setTimeout(() => {
|
||
self.$nextTick(self.refreshTooltips);
|
||
}, 2000);
|
||
},
|
||
dayShouldbeenSeen(moment: Moment) {
|
||
return moment.day() !== 6 && moment.day() !== 0;
|
||
},
|
||
projectHasMarkersForTheDay(moment: Moment, project: LaravelModel) {
|
||
for (let marker of project.markers as LaravelModelList) {
|
||
if (moment.isSame(String(marker.marker), "day")) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
|
||
setActivityHover(activity: LaravelModel, activate = true): void {
|
||
if (!activate) {
|
||
this.hoverActivityId = null;
|
||
} else {
|
||
this.hoverActivityId =
|
||
activity.id !== undefined ? String(activity.id) : "";
|
||
}
|
||
},
|
||
|
||
// * GETTERS
|
||
getLoadPercent(moment: Moment): number {
|
||
const load = this.getLoad(moment);
|
||
if (load === null) {
|
||
return 0;
|
||
}
|
||
return load > 1 ? 1 : load;
|
||
},
|
||
getLoad(moment: Moment): number {
|
||
if (!this.loadCollection) {
|
||
throw new Error("loadCollection is not defined");
|
||
}
|
||
return this.loadCollection[moment.format("YYYY-MM-DD 00:00:00")] ?? 0;
|
||
},
|
||
getActivityTooltip(activity: LaravelModel): string {
|
||
let activityEnumLabel =
|
||
activity.activity !== undefined
|
||
? this.activitiesEnum[activity.activity as number]
|
||
? this.activitiesEnum[activity.activity as number].label
|
||
: ""
|
||
: "";
|
||
return activity.activity !== undefined
|
||
? `${activityEnumLabel}, Charge ${activity.load} personnes, ${activity.comment}`
|
||
: "";
|
||
},
|
||
getActivityClass(activity: LaravelModel): string {
|
||
return activity ? `bg-enum-${activity.activity}` : "bg-white";
|
||
},
|
||
getProject(project_id: number): LaravelModel {
|
||
return this.projects[project_id];
|
||
},
|
||
getActivity(moment: Moment, project: LaravelModel): LaravelModel {
|
||
for (let activity of Object.values(
|
||
project.activities
|
||
) as LaravelModelList) {
|
||
if (
|
||
moment.isBetween(
|
||
this.momentJs(
|
||
String(activity.start),
|
||
momentJs.HTML5_FMT.DATETIME_LOCAL_SECONDS
|
||
),
|
||
this.momentJs(
|
||
String(activity.end),
|
||
momentJs.HTML5_FMT.DATETIME_LOCAL_SECONDS
|
||
),
|
||
"hour",
|
||
"[]"
|
||
)
|
||
) {
|
||
return activity;
|
||
}
|
||
}
|
||
return {};
|
||
},
|
||
getMorningMoment(moment: Moment, start = true): Moment {
|
||
moment = moment.clone();
|
||
return start
|
||
? moment.hours(0).minutes(0).seconds(0)
|
||
: moment.hours(11).minutes(59).seconds(0);
|
||
},
|
||
getAfterNoonMoment(moment: Moment, start = true): Moment {
|
||
moment = moment.clone();
|
||
return start
|
||
? moment.hours(12).minutes(0).seconds(0)
|
||
: moment.hours(23).minutes(59).seconds(0);
|
||
},
|
||
getMarkersTooltip(moment: Moment, project: LaravelModel): string {
|
||
let tooltip = "";
|
||
for (let marker of project.markers as LaravelModelList) {
|
||
if (moment.isSame(String(marker.marker), "day")) {
|
||
tooltip += `${this.momentJs(String(marker.marker)).format(
|
||
"DD/MM/YYYY"
|
||
)} ${marker.label}<br>`;
|
||
}
|
||
}
|
||
return tooltip;
|
||
},
|
||
getProjectRoute(project: LaravelModel): string {
|
||
const route = this.route("projects.show", {
|
||
PROJECT: project.id as number,
|
||
});
|
||
if (!route) {
|
||
throw new Error("undefined route projects.show");
|
||
}
|
||
return route;
|
||
},
|
||
|
||
// * FILTERS
|
||
str_limit(value: string, size = 25) {
|
||
if (!value) return "";
|
||
value = value.toString();
|
||
if (value.length <= size) {
|
||
return value;
|
||
}
|
||
return value.substr(0, size) + "...";
|
||
},
|
||
ucfirst(value: string) {
|
||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||
},
|
||
|
||
// * NAVIGATION
|
||
goPreviousWeek(): void {
|
||
let moment = this.startMoment ?? this.todayMoment;
|
||
moment = this.momentJs(moment.week(), "w");
|
||
this.showMacroScheduleDate("", moment.subtract(1, "week"));
|
||
},
|
||
goNextWeek(): void {
|
||
let moment = this.startMoment ?? this.todayMoment;
|
||
moment = this.momentJs(moment.week(), "w");
|
||
this.showMacroScheduleDate("", moment.add(1, "week"));
|
||
},
|
||
showMacroScheduleDate(
|
||
dateString: string,
|
||
moment: Moment | null = null
|
||
): void {
|
||
const url = new URL(String(window.location));
|
||
moment = moment ?? this.momentJs(dateString);
|
||
(window as Window).location =
|
||
url.origin + url.pathname + "?date=" + moment.format("DD/MM/YYYY");
|
||
},
|
||
sendToast(message: string): void {
|
||
Toastify({
|
||
text: message,
|
||
duration: 3000,
|
||
gravity: "top",
|
||
position: "right",
|
||
offset: {
|
||
x: "2rem",
|
||
y: "3rem",
|
||
},
|
||
style: {
|
||
background: "linear-gradient(to right, #00317d, #0058ca)",
|
||
},
|
||
}).showToast();
|
||
},
|
||
refreshTooltips(): void {
|
||
let exisitingToolips = document.querySelectorAll(
|
||
".tooltip[role=\"tooltip\"]"
|
||
);
|
||
for (let tooltip of exisitingToolips) {
|
||
tooltip.remove();
|
||
}
|
||
this.initTooltips();
|
||
},
|
||
limitLines(e: KeyboardEvent) {
|
||
const comment = this.$refs.comment as HTMLTextAreaElement;
|
||
let keynum,
|
||
lines = comment.value.split("\n").length;
|
||
if (e.key !== undefined) {
|
||
keynum = e.key;
|
||
} else if (e.keyCode !== undefined) {
|
||
// IE
|
||
if (window.event) {
|
||
keynum = e.keyCode;
|
||
// Netscape/Firefox/Opera
|
||
} else if (e.which) {
|
||
keynum = e.which;
|
||
}
|
||
}
|
||
|
||
if (keynum === undefined) {
|
||
throw new Error("undefined pressed key");
|
||
}
|
||
keynum = Number.parseInt(String(keynum));
|
||
|
||
if (keynum !== 8 && comment.value.length >= this.textLimit) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
if (keynum === 13 && lines == comment.rows) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
},
|
||
initTooltips(): void {
|
||
// * INIT Bootstrap tooltips
|
||
let tooltipTriggerList = [].slice.call(
|
||
document.querySelectorAll(
|
||
".color-picker-component [data-bs-toggle=\"tooltip\"]"
|
||
)
|
||
);
|
||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||
return new window.bootstrap.Tooltip(tooltipTriggerEl, {
|
||
delay: { show: 1000, hide: 300 },
|
||
});
|
||
});
|
||
},
|
||
},
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import "./../../sass/variables";
|
||
@import "./../../sass/vendor/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>
|