Browse Source

implement contextMenu & update timeline to 0.2.9

tripeur 4 years ago
parent
commit
e0568f2055

+ 1 - 1
package-lock.json

@@ -6913,7 +6913,7 @@
       "dev": true
     },
     "jc-timeline": {
-      "version": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git#97e0fe591514b2f4be6ffd8091098fd120c320d0",
+      "version": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git#f957903ec989ec620726571e18b74f84ebbe727f",
       "from": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git",
       "requires": {
         "dayjs": "^1.10.4",

+ 30 - 20
src/PlannerApp.vue

@@ -14,14 +14,18 @@
       @newEvenement="newEvenement"
     />
     <div v-if="optimisationInProgress" class="modal">
-      <div class="modal-content">
+      <div class="modal-content explanation">
         <h3>Optimisation en cours</h3>
         <p>L'ordinateur calcule un meilleur planning</p>
         <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
+        class="modal-content explanation"
+        v-html="explanation"
+        @click="(e) => e.stopPropagation()"
+      ></div>
     </div>
   </div>
   <c-footer v-if="showFooter" />
@@ -265,24 +269,26 @@ export default defineComponent({
         // 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 "";
-        });
+        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>${slot?.toString()}`;
+                }
+              })
+              .join("<br>Et<br>");
+            if (pairs != "") {
+              return `<p>${pairs}</p>`;
+            }
+            return "";
+          })
+          .join("");
         // End of the section
         scoreExplanationHTML += "<hr>";
       }
@@ -316,4 +322,8 @@ export default defineComponent({
   margin-right: -8px;
   font-size: 18px;
 }
+.explanation {
+  overflow-y: auto;
+  max-height: calc(100vh - 116px);
+}
 </style>

+ 11 - 11
src/mixins/ImportJsonState.ts

@@ -32,26 +32,26 @@ export default defineComponent({
         this.$store.commit(MutationTypes.addBenevole, Benevole.fromJSON(b));
       });
       // Import creneau group
-      const dict: { [k: string]: RessourceJSON } = {};
-      obj.creneauGroups.forEach((element) => {
-        dict[element.id] = element;
+      const dict: { [k: string]: Ressource } = {};
+      const creneauGroups = obj.creneauGroups.map((element) => {
+        const ressource = new Ressource(element);
+        dict[element.id] = ressource;
+        return ressource;
       });
       // map parent to children
-      const creneauGroups = obj.creneauGroups.map((ressource) => {
-        const iRessource: IRessource = { ...ressource };
+      obj.creneauGroups.forEach((iRessource) => {
         if (iRessource.parentId) {
           if (iRessource.parentId in dict) {
-            iRessource.parent = dict[iRessource.parentId];
+            dict[iRessource.id].parent = dict[iRessource.parentId];
           } else {
-            throw new Error("Missing parent of creneau group : " + ressource.id);
+            throw new Error("Missing parent of creneau group : " + iRessource.id);
           }
         }
         return iRessource;
       });
-      // Push the items to this application
-      creneauGroups.forEach((r) => {
-        this.$store.commit(MutationTypes.addCreneauGroup, new Ressource(r));
-      });
+      // Push the items to the store
+      this.$store.commit(MutationTypes.reorderCreneauGroup, creneauGroups);
+
       // Import Creneau
       for (const c of obj.creneaux) {
         const creneau = Creneau.fromJSON(c);

+ 52 - 20
src/models/Creneau.ts

@@ -57,8 +57,14 @@ class Creneau implements ICreneau {
     this._isMeal = value;
     this.event.bgColor = value ? "gray" : undefined;
   }
-  fixedAttendee: boolean;
-
+  _fixedAttendee = false;
+  public get fixedAttendee(): boolean {
+    return this._fixedAttendee;
+  }
+  public set fixedAttendee(value: boolean) {
+    this._fixedAttendee = value;
+    this.updateEventContent();
+  }
   constructor(obj: ICreneau) {
     if (!(typeof obj.event.id == "string") || obj.event.id == "") {
       throw new TypeError(
@@ -102,15 +108,30 @@ class Creneau implements ICreneau {
   set end(value: Date) {
     this.event.end = value;
   }
+  /** Duration of the slot in millisecond */
   get durationMs(): number {
     return this.event.end.getTime() - this.event.start.getTime();
   }
+  /** Duration of the slot in minutes */
   get durationMin(): number {
     return this.durationMs / 1000 / 60;
   }
+  /** Duration of the slot in hours */
   get durationH(): number {
     return this.durationMin / 60;
   }
+  /** Human readable format of the start & end, date & time */
+  get fromTo(): string {
+    return (
+      "le " +
+      dayjs(this.event.start).format("YYYY/MM/DD") +
+      " de " +
+      dayjs(this.event.start).format("HH:mm") +
+      " à " +
+      dayjs(this.event.end).format("HH:mm")
+    );
+  }
+  /** Human readable format of the start & end time */
   get horaire(): string {
     return (
       "De " +
@@ -120,32 +141,23 @@ class Creneau implements ICreneau {
     );
   }
   toString(): string {
-    return `Créneau[titre:${this.title},\tHoraire:${this.horaire}]`;
+    return `Créneau[titre:${this.title},\tDate:${this.fromTo}]`;
   }
+
+  /** update the content of the event object based on the Creneau state */
   updateEventContent(): void {
     const missingbenevole = Math.max(this.minAttendee - this.benevoleIdList.length, 0);
     const spanClass =
-      this.benevoleIdList.length == 0 ? "red" : missingbenevole != 0 ? "orange" : "";
-    this.event.content = `<div class="bubble ${spanClass}">${this.benevoleIdList.length} / ${this.minAttendee}</div>`;
+      (this._fixedAttendee ? " anchor" : "") +
+      (this.benevoleIdList.length == 0 ? " red" : missingbenevole != 0 ? " orange" : "");
+    this.event.content = `<div class="bubble${spanClass}">${this.benevoleIdList.length} / ${this.minAttendee}</div>`;
   }
+
+  /** Check if the two creneaux share common time */
   collide(other: Creneau): boolean {
     return this.event.start < other.event.end && other.event.start < this.event.end;
   }
-  toJSON(): CreneauJSON {
-    const e = this.event.toJSON();
-    delete e.content;
-    return {
-      event: e,
-      penibility: this.penibility,
-      minAttendee: this.minAttendee,
-      maxAttendee: this.maxAttendee,
-      benevoleIdList: this.benevoleIdList,
-      competencesIdList: this.competencesIdList,
-      description: this.description,
-      isMeal: this.isMeal,
-      fixedAttendee: this.fixedAttendee,
-    };
-  }
+  /** Return the Mealslot to feed the optimizer*/
   toMealSlot(): MealSlot {
     return {
       id: this.event.id,
@@ -154,6 +166,8 @@ class Creneau implements ICreneau {
       maxAttendee: this.maxAttendee,
     };
   }
+
+  /** Return the timeslot to feed the optimizer*/
   toTimeslotRaw(): TimeslotRaw {
     return {
       id: this.event.id,
@@ -164,6 +178,24 @@ class Creneau implements ICreneau {
       maxAttendee: this.maxAttendee,
     };
   }
+
+  /** Get the plain javscript object that can be serialized */
+  toJSON(): CreneauJSON {
+    const e = this.event.toJSON();
+    delete e.content;
+    return {
+      event: e,
+      penibility: this.penibility,
+      minAttendee: this.minAttendee,
+      maxAttendee: this.maxAttendee,
+      benevoleIdList: this.benevoleIdList,
+      competencesIdList: this.competencesIdList,
+      description: this.description,
+      isMeal: this.isMeal,
+      fixedAttendee: this.fixedAttendee,
+    };
+  }
+  /** Parse the plain javscript object that can be serialized */
   static fromJSON(input: string | CreneauJSON): Creneau {
     let obj: CreneauJSON;
     if (typeof input == "string") {

+ 23 - 1
src/views/Evenement.vue

@@ -56,12 +56,17 @@
       </div>
       <div class="actions">
         <button class="btn primary small" @click="copyLink">
-          <i class="material-icons">content_copy</i>Lien pour les bénévoles
+          <i class="material-icons">link</i>Lien pour les bénévoles
         </button>
         <a v-if="inscription" class="btn primary small" :href="inscription">
           <i class="material-icons">launch</i>Page pour l'inscription
         </a>
       </div>
+      <div class="actions">
+        <button class="btn primary small" @click="duplicateEvent">
+          <i class="material-icons">content_copy</i>Dupliquer l'événement
+        </button>
+      </div>
     </div>
     <div v-if="showModal" class="modal" @click="toggleModal">
       <div class="modal-content" @click="(e) => e.stopPropagation()">
@@ -81,6 +86,7 @@
         </div>
       </div>
     </div>
+
     <div>
       <h2>
         Version antérieurs
@@ -102,6 +108,7 @@
 
 <script lang="ts">
 import { Dayjs } from "dayjs";
+import { v4 as uuidv4 } from "uuid";
 import { defineComponent } from "vue";
 import { MutationTypes } from "@/store/Mutations";
 import Evenement from "@/models/Evenement";
@@ -177,6 +184,21 @@ export default defineComponent({
     toggleModal() {
       this.showModal = !this.showModal;
     },
+    duplicateEvent() {
+      if (
+        confirm(
+          "Êtes vous sûr de vouloir copié toutes les données relatives à l'événement " +
+            this.evenement.name
+        )
+      ) {
+        this.$store.commit(MutationTypes.editEvenement, { field: "uuid", value: uuidv4() });
+        this.$store.commit(MutationTypes.editEvenement, {
+          field: "name",
+          value: "Copie de " + this.evenement.name,
+        });
+        this.$emit("save");
+      }
+    },
     initDate() {
       if (this.evenement) {
         this.start = this.evenement.startingDate;

+ 4 - 4
src/views/EvenementManager.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="width: 95%; max-width: 900px">
     <data-table
-      title="Utilisateurs"
+      title="Evenements"
       :data="data"
       :searchable="true"
       :customButtons="customButtons"
@@ -14,8 +14,8 @@
           <button class="btn icon small error" @click="deleteEvt(props.row.uuid)">
             <i class="material-icons">delete_forever</i>
           </button>
-        </td></template
-      >
+        </td>
+      </template>
     </data-table>
   </div>
 </template>
@@ -82,7 +82,7 @@ export default defineComponent({
       if (
         evt &&
         confirm(
-          "Êtes vous sûr de vouloir supprimer toutes les donénes relatives à l'événement " +
+          "Êtes vous sûr de vouloir supprimer toutes les données relatives à l'événement " +
             evt.name
         )
       )

+ 14 - 3
src/views/Planning.vue

@@ -163,10 +163,14 @@ export default defineComponent({
       this.registerRessource(ressource);
     },
     registerRessource(ressource: Ressource, pos = 0) {
-      this.$store.commit(MutationTypes.addCreneauGroupAt, { pos, r: ressource });
       this.timeline.clearSelectedItems();
-      this.timeline.addRessource(ressource, pos).selected = true;
-      this.currentCreneauGroup = ressource;
+      this.currentCreneauGroup = this.timeline.addRessource(ressource, pos);
+      this.currentCreneauGroup.selected = true;
+
+      this.$store.commit(MutationTypes.addCreneauGroupAt, {
+        pos: this.timeline.getRessources().indexOf(this.currentCreneauGroup),
+        r: this.currentCreneauGroup,
+      });
     },
     duplicateCreneau(payload: Creneau) {
       const newCreneau = new Creneau({
@@ -401,6 +405,7 @@ export default defineComponent({
     this.timeline.setAttribute("start", this.start.toISOString());
     this.timeline.setAttribute("end", this.end.toISOString());
     this.timeline.addRessources(this.creneauGroupList);
+    this.$store.commit(MutationTypes.reorderCreneauGroup, this.timeline.getRessources());
     this.timeline.addEvents(this.eventList);
     this.timeline.setLegendUnitFormat("d", "dddd D MMMM");
     this.timeline.customStyle = `.bubble{
@@ -419,6 +424,12 @@ export default defineComponent({
     transform: translateX(50%);
     white-space: nowrap;
 }
+.anchor:after{
+  content: "anchor";
+  font-family: "Material Icons";
+  font-weight: normal;
+  margin-left: 2px;
+}
 .bubble.red{
     background: #e4002b;
 }