|
|
@@ -30,17 +30,28 @@
|
|
|
<div class="spinner"><span class="material-icons"> sync </span></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div v-if="showExplanation" class="modal" @click="showExplanation = false">
|
|
|
+ <div class="modal-content" v-html="explanation" @click="(e) => e.stopPropagation()"></div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<script lang="ts">
|
|
|
import { defineComponent } from "vue";
|
|
|
-import toast from "./utils/Toast";
|
|
|
+import toast, { Toast } from "./utils/Toast";
|
|
|
import "@/assets/css/tabs.css";
|
|
|
+import ConstraintTranslation from "@/assets/ConstraintTranslation";
|
|
|
import Evenement from "./models/Evenement";
|
|
|
import Benevole from "./models/Benevole";
|
|
|
import Creneau from "./models/Creneau";
|
|
|
import Competence from "./models/Competence";
|
|
|
-import { SolverInput, SolverOutput } from "./models/SolverInput";
|
|
|
+import {
|
|
|
+ AssignementPair,
|
|
|
+ SolverInput,
|
|
|
+ SolverOutput,
|
|
|
+ JustificationObject,
|
|
|
+ ScoreExplanation,
|
|
|
+ HardConstraint,
|
|
|
+} from "./models/SolverInput";
|
|
|
import Ressource, { IRessource, RessourceJSON } from "jc-timeline/lib/Ressource";
|
|
|
import { MutationTypes } from "./store/Mutations";
|
|
|
import dayjs from "dayjs";
|
|
|
@@ -48,10 +59,16 @@ import { StateJSON } from "./store/State";
|
|
|
const keyofEvent: Array<keyof Evenement> = ["name", "uuid", "start", "end"];
|
|
|
export default defineComponent({
|
|
|
data() {
|
|
|
- return { optimisationInProgress: false };
|
|
|
+ return {
|
|
|
+ optimisationInProgress: false,
|
|
|
+ explanation: "",
|
|
|
+ showExplanation: false,
|
|
|
+ lastToast: null as null | Toast,
|
|
|
+ };
|
|
|
},
|
|
|
mounted() {
|
|
|
const previousState = window.localStorage.getItem("activeState");
|
|
|
+ toast({ html: "coucou" });
|
|
|
if (previousState) {
|
|
|
this.importJsonState(JSON.parse(previousState), false);
|
|
|
}
|
|
|
@@ -63,6 +80,12 @@ export default defineComponent({
|
|
|
localSave() {
|
|
|
window.localStorage.setItem("activeState", JSON.stringify(this.$store.getters.getJSONState));
|
|
|
},
|
|
|
+ clearToast() {
|
|
|
+ if (this.lastToast) {
|
|
|
+ this.lastToast.dismiss();
|
|
|
+ this.lastToast = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
exportStateToJson(): void {
|
|
|
const obj: StateJSON = this.$store.getters.getJSONState;
|
|
|
const mimeType = "data:text/json;charset=utf-8";
|
|
|
@@ -83,7 +106,9 @@ export default defineComponent({
|
|
|
const constraints = this.$store.state.competenceList.map((c) => c.toSkill());
|
|
|
const getAssignementPair = (c: Creneau) =>
|
|
|
c.benevoleIdList.map((id) => {
|
|
|
- return { volonteerId: id, slotId: c.id };
|
|
|
+ const obj: AssignementPair = { volonteerId: id, slotId: c.id };
|
|
|
+ if (c.fixedAttendee) obj.isFixed = true;
|
|
|
+ return obj;
|
|
|
});
|
|
|
const assignements = slots.flatMap(getAssignementPair);
|
|
|
const mealAssignements = meals.flatMap(getAssignementPair);
|
|
|
@@ -110,7 +135,7 @@ export default defineComponent({
|
|
|
this.optimisationInProgress = false;
|
|
|
});
|
|
|
},
|
|
|
- updatePlanningWithNewPairing(data: SolverOutput) {
|
|
|
+ updatePlanningWithNewPairing(data: SolverOutput): void {
|
|
|
this.optimisationInProgress = false;
|
|
|
// Display message from the solver
|
|
|
toast({
|
|
|
@@ -118,10 +143,9 @@ export default defineComponent({
|
|
|
classes: "success",
|
|
|
displayLength: 10000,
|
|
|
});
|
|
|
- data.message
|
|
|
- .split("\n")
|
|
|
- .filter((s) => s != "")
|
|
|
- .forEach((s) => toast({ html: s, classes: "warning", displayLength: 10000 }));
|
|
|
+ if (data.message) toast({ html: data.message, classes: "warning", displayLength: 10000 });
|
|
|
+ this.displayExplanation(data.explanation);
|
|
|
+
|
|
|
// Remove previous timeslot assignement
|
|
|
this.$store.commit(MutationTypes.clearBenevole2Creneau, undefined);
|
|
|
// Add new timeslot assignement
|
|
|
@@ -132,6 +156,49 @@ export default defineComponent({
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
+ displayExplanation(explanation: string) {
|
|
|
+ // Display score explanation if any
|
|
|
+ const scoreExplanation: ScoreExplanation = JSON.parse(explanation);
|
|
|
+ let scoreExplanationHTML = "";
|
|
|
+ let k: HardConstraint;
|
|
|
+ for (k in scoreExplanation) {
|
|
|
+ // Title of the section
|
|
|
+ scoreExplanationHTML += `<h4>${ConstraintTranslation[k]}</h4>`;
|
|
|
+ // Content of the section splitted by group
|
|
|
+ scoreExplanationHTML += scoreExplanation[k].map((list) => {
|
|
|
+ const pairs = list
|
|
|
+ .map((o) => {
|
|
|
+ if (typeof o === "string" || o instanceof String) {
|
|
|
+ return o;
|
|
|
+ } else {
|
|
|
+ const parseObj = o as JustificationObject;
|
|
|
+ const slot = this.$store.getters.getCreneauById(parseObj.slotId);
|
|
|
+ const benevole = this.$store.getters.getBenevoleById(parseObj.volonteerId);
|
|
|
+ return `Bénevole :\t${benevole?.shortame}<br>Créneau:\t${slot?.title} ${slot?.horaire}`;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .join("<br>Et<br>");
|
|
|
+ if (pairs != "") {
|
|
|
+ return `<p>${pairs}</p>`;
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ });
|
|
|
+ // End of the section
|
|
|
+ scoreExplanationHTML += "<hr>";
|
|
|
+ }
|
|
|
+ this.explanation = scoreExplanationHTML;
|
|
|
+ this.clearToast();
|
|
|
+ this.lastToast = toast({
|
|
|
+ html: `<span>Ouvrir l'explication du score</span><i class="material-icons">close<i>`,
|
|
|
+ displayLength: Infinity,
|
|
|
+ classes: "action",
|
|
|
+ });
|
|
|
+ this.lastToast.element.firstElementChild?.addEventListener(
|
|
|
+ "click",
|
|
|
+ () => (this.showExplanation = true)
|
|
|
+ );
|
|
|
+ this.lastToast.element.lastElementChild?.addEventListener("click", this.clearToast);
|
|
|
+ },
|
|
|
importJsonState(obj: StateJSON, preserve = false) {
|
|
|
// Remove previous content and load the main event title
|
|
|
if (preserve == false) {
|
|
|
@@ -196,4 +263,15 @@ export default defineComponent({
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
-<style></style>
|
|
|
+<style>
|
|
|
+.toast.action > span {
|
|
|
+ cursor: pointer;
|
|
|
+ color: var(--color-primary-600);
|
|
|
+}
|
|
|
+.toast.action > i.material-icons {
|
|
|
+ cursor: pointer;
|
|
|
+ margin-left: 16px;
|
|
|
+ margin-right: -8px;
|
|
|
+ font-size: 18px;
|
|
|
+}
|
|
|
+</style>
|