Planning.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <template>
  2. <div class="timeline">
  3. <div class="timelime-control">
  4. <h1>Planning global de l'événement</h1>
  5. <div class="actions">
  6. <button
  7. class="btn small secondary"
  8. @click="createCreneau"
  9. :disabled="creneauGroupList.length == 0"
  10. >
  11. <i class="material-icons">create</i>Creneau
  12. </button>
  13. <button class="btn small secondary" @click="createRessource">
  14. <i class="material-icons">create</i>Ligne
  15. </button>
  16. <button class="btn icon small primary tooltiped" aria-tooltip="Zoomer" @click="zoom(1)">
  17. <i class="material-icons">zoom_in</i>
  18. </button>
  19. <button class="btn icon small primary tooltiped" aria-tooltip="Dezoomer" @click="zoom(-1)">
  20. <i class="material-icons">zoom_out</i>
  21. </button>
  22. </div>
  23. </div>
  24. <jc-timeline
  25. ref="timeline"
  26. :slotduration="slotduration"
  27. :legendspan="legendspan"
  28. :slotwidth="slotwidth"
  29. :start="start.toDate().toISOString()"
  30. :end="end.toDate().toISOString()"
  31. @event-change="eventChangeHandler"
  32. @item-selected="selectionChangeHandler"
  33. @reorder-ressource="ressourceChangeHandler"
  34. ></jc-timeline>
  35. </div>
  36. <editeur-creneau
  37. class="planning"
  38. v-if="currentCreneau"
  39. :creneau="currentCreneau"
  40. @create="createCreneau"
  41. @delete="deleteCreneau"
  42. @duplicate="duplicateCreneau"
  43. @edit="updateCreneau"
  44. ></editeur-creneau>
  45. <editeur-ligne
  46. class="planning"
  47. v-else
  48. :creneauGroupId="currentCreneauGroupId"
  49. @create="createRessource"
  50. @delete="deleteRessource"
  51. @edit="updateCreneauGroup"
  52. ></editeur-ligne>
  53. </template>
  54. <script lang="ts">
  55. import { v4 as uuidv4 } from "uuid";
  56. import { defineComponent } from "vue";
  57. import Timeline from "jc-timeline/lib/Timeline";
  58. import { Event as jcEvent } from "jc-timeline/lib/Event";
  59. import EditeurCreneau from "@/components/EditeurCreneau.vue";
  60. import EditeurLigne from "@/components/EditeurCreneauGroup.vue";
  61. import { Ressource } from "jc-timeline";
  62. import Creneau from "@/models/Creneau";
  63. import { MutationTypes } from "@/store/Mutations";
  64. import Selectable from "node_modules/jc-timeline/lib/utils/selectable";
  65. import toast from "@/utils/Toast";
  66. import * as dayjs from "dayjs";
  67. import "dayjs/locale/fr";
  68. dayjs.locale("fr");
  69. type changePayload = {
  70. items: Array<jcEvent>;
  71. };
  72. export default defineComponent({
  73. name: "Planning",
  74. components: {
  75. EditeurCreneau,
  76. EditeurLigne,
  77. },
  78. data() {
  79. return {
  80. currentCreneau: undefined as Creneau | undefined,
  81. currentCreneauGroup: undefined as Ressource | undefined,
  82. zoomLevel: 14,
  83. slotduration: 30,
  84. legendspan: 2,
  85. slotwidth: 30,
  86. };
  87. },
  88. methods: {
  89. createCreneau(): void {
  90. // take the current crenau as reference if any
  91. const groupId = this.currentCreneau
  92. ? this.currentCreneau.ressourceId
  93. : // else the current row as reference if any
  94. this.currentCreneauGroup
  95. ? this.currentCreneauGroup.id
  96. : // else the first row as reference if any
  97. this.creneauList.length > 0
  98. ? this.creneauList[0].id
  99. : false;
  100. if (groupId) {
  101. const start = new Date(this.timeline.start);
  102. const e = this.timeline.addEvent(
  103. new jcEvent({
  104. id: uuidv4(),
  105. start: start,
  106. end: new Date(start.getTime() + 1000 * 60 * 60),
  107. ressourceId: groupId,
  108. title: "Nouveau creneau",
  109. })
  110. );
  111. if (e) {
  112. this.timeline.clearSelectedItems();
  113. e.selected = true;
  114. const creneau = new Creneau({
  115. event: e,
  116. penibility: 12,
  117. minAttendee: 1,
  118. maxAttendee: 1,
  119. benevoleIdList: [],
  120. competencesIdList: [],
  121. description: "",
  122. isMeal: false,
  123. fixedAttendee: false,
  124. });
  125. this.$store.commit(MutationTypes.addCreneau, creneau);
  126. this.currentCreneau = this.$store.getters.getCreneauById(creneau.id);
  127. this.currentCreneauGroup = undefined;
  128. }
  129. } else {
  130. toast({
  131. html:
  132. "Erreur: Pas de ligne auquel associer un nouveau creneau.<br>" +
  133. " Veuillez selectioné un créneau existant ou une ligne.",
  134. });
  135. }
  136. },
  137. createRessource(): void {
  138. const ressource = new Ressource({
  139. id: uuidv4(),
  140. title: "Nouvelle ligne",
  141. });
  142. this.$store.commit(MutationTypes.addCreneauGroupAt, { pos: 0, r: ressource });
  143. this.timeline.clearSelectedItems();
  144. this.timeline.addRessource(ressource).selected = true;
  145. this.currentCreneauGroup = ressource;
  146. },
  147. duplicateCreneau(payload: Creneau) {
  148. const newCreneau = new Creneau({
  149. ...payload.toJSON(),
  150. event: new jcEvent({ ...payload.event, id: uuidv4() }),
  151. });
  152. newCreneau.title = "Copy de " + newCreneau.title;
  153. this.timeline.addEvent(newCreneau.event);
  154. this.$store.commit(MutationTypes.addCreneau, newCreneau);
  155. this.currentCreneau = newCreneau;
  156. },
  157. deleteRessource(): void {
  158. if (this.currentCreneauGroup) {
  159. const contentRemoved = this.timeline.removeRessourceById(this.currentCreneauGroup.id);
  160. contentRemoved.ressources.map((r) =>
  161. this.$store.commit(MutationTypes.removeCreneauGroup, r)
  162. );
  163. contentRemoved.items.map((e) => {
  164. const crenenau = this.$store.getters.getCreneauById(e.id);
  165. if (crenenau) this.$store.commit(MutationTypes.removeCreneau, crenenau);
  166. });
  167. }
  168. },
  169. deleteCreneau(): void {
  170. if (this.currentCreneau) {
  171. this.timeline.removeEventById(this.currentCreneau.id);
  172. this.$store.commit(MutationTypes.removeCreneau, this.currentCreneau);
  173. }
  174. },
  175. selectionChangeHandler(ev: CustomEvent) {
  176. const elts = ev.detail.items as Array<Selectable>;
  177. if (elts.length == 1) {
  178. const item = elts[0];
  179. if (item instanceof Ressource) {
  180. this.currentCreneauGroup = item;
  181. this.currentCreneau = undefined;
  182. }
  183. if (item instanceof jcEvent) {
  184. this.currentCreneau = this.$store.getters.getCreneauById(item.id);
  185. this.currentCreneauGroup = undefined;
  186. }
  187. }
  188. if (elts.length == 0) {
  189. this.currentCreneau = undefined;
  190. this.currentCreneauGroup = undefined;
  191. }
  192. },
  193. eventChangeHandler(ev: CustomEvent<changePayload>) {
  194. this.currentCreneauGroup = undefined;
  195. const jcEvents = ev.detail.items;
  196. for (let index = 0; index < jcEvents.length; index++) {
  197. const element = jcEvents[index];
  198. this.$store.commit(MutationTypes.editCreneau, {
  199. id: element.id,
  200. field: "start",
  201. value: element.start,
  202. });
  203. this.$store.commit(MutationTypes.editCreneau, {
  204. id: element.id,
  205. field: "end",
  206. value: element.end,
  207. });
  208. this.$store.commit(MutationTypes.editCreneau, {
  209. id: element.id,
  210. field: "ressourceId",
  211. value: element.ressourceId,
  212. });
  213. this.currentCreneau = this.$store.getters.getCreneauById(element.id);
  214. }
  215. },
  216. ressourceChangeHandler(ev: CustomEvent<{ ressources: Array<Ressource> }>) {
  217. this.$store.commit(MutationTypes.reorderCreneauGroup, ev.detail.ressources);
  218. },
  219. updateCreneauGroup<K extends keyof Ressource>(payload: {
  220. id: string;
  221. field: K;
  222. value: Ressource[K];
  223. }) {
  224. const prevState = this.$store.getters.getCreneauGroupById(payload.id);
  225. if (prevState && prevState[payload.field] !== payload.value) {
  226. const r = this.timeline.updateRessource(payload.id, payload.field, payload.value);
  227. if (r) {
  228. payload.value = r[payload.field];
  229. }
  230. this.$store.commit(MutationTypes.editCreneauGroup, payload);
  231. }
  232. },
  233. updateCreneau<K extends keyof Creneau>(payload: { id: string; field: K; value: Creneau[K] }) {
  234. this.$store.commit(MutationTypes.editCreneau, payload);
  235. const creneau = this.$store.getters.getCreneauById(payload.id);
  236. if (creneau) {
  237. this.timeline.removeEventById(payload.id);
  238. this.timeline.addEvent(creneau.event);
  239. }
  240. },
  241. zoom(lvl: number) {
  242. const durations = [
  243. 10080,
  244. 10080,
  245. 10080,
  246. 10080,
  247. 10080,
  248. 1440,
  249. 1440,
  250. 720,
  251. 720,
  252. 360,
  253. 120,
  254. 60,
  255. 60,
  256. 60,
  257. 30,
  258. 30,
  259. 15,
  260. 5,
  261. ];
  262. const slotWidths = [40, 60, 80, 120, 160, 30, 40, 30, 50, 40, 25, 20, 30, 40, 30, 40, 35, 20];
  263. const legendSpans = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 4];
  264. this.zoomLevel += lvl;
  265. this.zoomLevel = Math.min(Math.max(0, this.zoomLevel), durations.length - 1);
  266. this.slotduration = durations[this.zoomLevel];
  267. this.slotwidth = slotWidths[this.zoomLevel];
  268. this.legendspan = legendSpans[this.zoomLevel];
  269. },
  270. },
  271. computed: {
  272. timeline(): Timeline {
  273. const output = this.$refs["timeline"];
  274. return output as Timeline;
  275. },
  276. currentCreneauGroupId(): string {
  277. return this.currentCreneauGroup ? this.currentCreneauGroup.id : "";
  278. },
  279. creneauList(): Array<Creneau> {
  280. return this.$store.state.creneauList;
  281. },
  282. eventList(): Array<jcEvent> {
  283. return this.creneauList.map((o) => o.event);
  284. },
  285. creneauGroupList(): Array<Ressource> {
  286. return this.$store.state.creneauGroupList;
  287. },
  288. start(): dayjs.Dayjs {
  289. return this.$store.state.evenement.startingDate;
  290. },
  291. end(): dayjs.Dayjs {
  292. return this.$store.state.evenement.endingDate;
  293. },
  294. },
  295. watch: {},
  296. mounted() {
  297. this.timeline.setAttribute("start", this.start.toISOString());
  298. this.timeline.setAttribute("end", this.end.toISOString());
  299. this.timeline.addRessources(this.creneauGroupList);
  300. this.timeline.addEvents(this.eventList);
  301. this.timeline.setLegendUnitFormat("d", "dddd D MMMM");
  302. this.timeline.customStyle = `.bubble{
  303. padding: 2px 4px;
  304. display: flex;
  305. justify-content: center;
  306. align-items: center;
  307. position: absolute;
  308. right: -5px;
  309. bottom: -5px;
  310. border-radius: 4px;
  311. font-size: 12px;
  312. font-weight: bold;
  313. z-index:1;
  314. background: #08875b;
  315. transform: translateX(50%);
  316. white-space: nowrap;
  317. }
  318. .bubble.red{
  319. background: #e4002b;
  320. }
  321. .bubble.orange{
  322. background: #fbca32;
  323. }`;
  324. this.timeline.clearSelectedItems();
  325. },
  326. });
  327. </script>
  328. <style lang="scss" scoped>
  329. .planning-container {
  330. display: flex;
  331. justify-content: center;
  332. }
  333. .timeline {
  334. margin: 0px 16px;
  335. width: 100%;
  336. }
  337. jc-timeline {
  338. margin-top: 8px;
  339. display: flex;
  340. flex-direction: column;
  341. height: calc(100vh - 13rem);
  342. }
  343. .actions {
  344. display: inline-flex;
  345. justify-content: left;
  346. margin-bottom: 12px;
  347. }
  348. .actions > .btn {
  349. margin-right: 4px;
  350. }
  351. </style>