| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- <template>
- <div class="header">
- <div class="logo"></div>
- <h1 class="appname">BDLG planner</h1>
- <nav class="tabs floating">
- <router-link class="tab" active-class="selected" to="/"> Accueil </router-link>
- <router-link class="tab" active-class="selected" to="/evenement">Planning</router-link>
- <router-link class="tab" active-class="selected" to="/competences">
- Gestion des compétences
- </router-link>
- <router-link class="tab" active-class="selected" to="/benevoles">
- Gestion des bénévoles
- </router-link>
- <router-link class="tab" active-class="selected" to="/planningIndividuel">
- Planning Individuel
- </router-link>
- </nav>
- </div>
- <div class="container">
- <router-view
- @export="exportStateToJson"
- @import="(e) => importJsonState(e, false)"
- @localSave="localSave"
- @solve="solve"
- />
- <div v-if="optimisationInProgress" class="modal">
- <div class="modal-content">
- <h3>Optimisation en cours</h3>
- <p>L'ordinateur calcul un meilleur planning</p>
- <div class="spinner"><span class="material-icons"> sync </span></div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts">
- import { defineComponent } from "vue";
- import toast from "./utils/Toast";
- import "@/assets/css/tabs.css";
- 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 Ressource, { IRessource, RessourceJSON } from "jc-timeline/lib/Ressource";
- import { MutationTypes } from "./store/Mutations";
- import dayjs from "dayjs";
- import { StateJSON } from "./store/State";
- const keyofEvent: Array<keyof Evenement> = ["name", "uuid", "start", "end"];
- export default defineComponent({
- data() {
- return { optimisationInProgress: false };
- },
- mounted() {
- const previousState = window.localStorage.getItem("activeState");
- if (previousState) {
- this.importJsonState(JSON.parse(previousState), false);
- }
- window.onbeforeunload = () => {
- this.localSave();
- };
- },
- methods: {
- localSave() {
- window.localStorage.setItem("activeState", JSON.stringify(this.$store.getters.getJSONState));
- },
- exportStateToJson(): void {
- const obj: StateJSON = this.$store.getters.getJSONState;
- const mimeType = "data:text/json;charset=utf-8";
- const dummy = document.createElement("a");
- dummy.href = mimeType + ", " + encodeURI(JSON.stringify(obj));
- dummy.download =
- "Planning-" + this.$store.state.evenement.name + dayjs().format("-YYYY-MM-DD[.json]");
- document.body.appendChild(dummy);
- dummy.click();
- setTimeout(() => document.body.removeChild(dummy), 10000);
- },
- solve() {
- const meals = this.$store.state.creneauList.filter((o) => o.isMeal);
- const slots = this.$store.state.creneauList.filter((o) => !o.isMeal);
- const timeslots = slots.map((o) => o.toTimeslotRaw());
- const mealSlots = meals.map((o) => o.toMealSlot());
- const volonteerList = this.$store.state.benevoleList.map((b) => b.toVolonteeRaw());
- 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 assignements = slots.flatMap(getAssignementPair);
- const mealAssignements = meals.flatMap(getAssignementPair);
- const body: SolverInput = {
- constraints,
- timeslots,
- mealSlots,
- volonteerList,
- assignements,
- mealAssignements,
- };
- const options = {
- method: "POST",
- body: JSON.stringify(body),
- headers: { "Content-type": "application/json; charset=UTF-8" },
- };
- this.optimisationInProgress = true;
- fetch(`http://localhost:8080/planning/solve`, options)
- .then((response) => response.json())
- .then(this.updatePlanningWithNewPairing)
- .catch((error) => {
- toast({ html: "Pas de réponse du serveur<br>" + error.toString(), classes: "error" });
- this.optimisationInProgress = false;
- });
- },
- updatePlanningWithNewPairing(data: SolverOutput) {
- this.optimisationInProgress = false;
- // Display message from the solver
- toast({
- html: "Le planning a été mis à jour <br>Score : " + data.score,
- classes: "success",
- displayLength: 10000,
- });
- data.message
- .split("\n")
- .filter((s) => s != "")
- .forEach((s) => toast({ html: s, classes: "warning", displayLength: 10000 }));
- // Remove previous timeslot assignement
- this.$store.commit(MutationTypes.clearBenevole2Creneau, undefined);
- // Add new timeslot assignement
- for (const pair of data.assignements) {
- this.$store.commit(MutationTypes.addBenevole2Creneau, {
- creneauId: pair.slotId,
- benevoleId: pair.volonteerId,
- });
- }
- },
- importJsonState(obj: StateJSON, preserve = false) {
- console.log(obj);
- // Remove previous content and load the main event title
- if (preserve == false) {
- this.$store.commit(MutationTypes.resetState, undefined);
- const e = Evenement.fromJSON(obj.evenement);
- for (const k of keyofEvent) {
- this.$store.commit(MutationTypes.editEvenement, {
- field: k,
- value: e[k],
- });
- }
- }
- // Import constraint
- obj.competences.forEach((c) => {
- this.$store.commit(MutationTypes.addConstraint, Competence.fromJSON(c));
- });
- // Import Benevoles
- obj.benevoles.forEach((b) => {
- this.$store.commit(MutationTypes.addBenevole, Benevole.fromJSON(b));
- });
- // Import creneau group
- const dict: { [k: string]: RessourceJSON } = {};
- obj.creneauGroups.forEach((element) => {
- dict[element.id] = element;
- });
- // map parent to children
- const creneauGroups = obj.creneauGroups.map((ressource) => {
- const iRessource: IRessource = { ...ressource };
- if (iRessource.parentId) {
- if (iRessource.parentId in dict) {
- iRessource.parent = dict[iRessource.parentId];
- } else {
- throw new Error("Missing parent of creneau group : " + ressource.id);
- }
- }
- return iRessource;
- });
- // Push the items to this application
- creneauGroups.forEach((r) => {
- this.$store.commit(MutationTypes.addCreneauGroup, new Ressource(r));
- });
- // Import Creneau
- for (let c of obj.creneaux) {
- const creneau = Creneau.fromJSON(c);
- this.$store.commit(MutationTypes.addCreneau, creneau);
- // add the creneau to the corresponding benevole
- for (const id of creneau.benevoleIdList) {
- const b = this.$store.getters.getBenevoleById(id);
- if (b) {
- this.$store.commit(MutationTypes.editBenevole, {
- id: id,
- field: "creneauIdList",
- value: Array.from(new Set([...b.creneauIdList, creneau.id])),
- });
- } else {
- throw new Error(`The benevole ${id} was not find `);
- }
- }
- }
- },
- },
- });
- </script>
- <style>
- .modal {
- display: flex;
- justify-content: center;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 100;
- }
- .modal-content {
- background: white;
- padding: 16px 32px;
- margin-top: 100px;
- height: min-content;
- border-radius: 2px;
- }
- .spinner {
- color: var(--color-accent-400);
- text-align: center;
- }
- .spinner > .material-icons {
- font-size: 3rem;
- animation: spin 2s linear infinite;
- }
- @keyframes spin {
- 0% {
- transform: rotateZ(360deg);
- }
- 100% {
- transform: rotateZ(0deg);
- }
- }
- </style>
|