Evenement.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <template>
  2. <div class="centered-box">
  3. <div>
  4. <h3>Planning Actif</h3>
  5. <styled-input label="Identifiant" :modelValue="evenement.uuid" disabled />
  6. <styled-input
  7. label="Nom de l'événement"
  8. :modelValue="evenement.name"
  9. @input="inputListener($event, 'name')"
  10. />
  11. <datepicker label="Début" target="hour" v-model="start" />
  12. <datepicker label="Fin" target="hour" v-model="end" />
  13. <div class="actions" style="text-align: center">
  14. <button class="btn success" @click="$emit('solve')">
  15. <i class="material-icons">play_arrow</i> Résoudre la plannification
  16. </button>
  17. <div
  18. class="tooltiped tooltiped--medium"
  19. style="
  20. cursor: pointer;
  21. display: inline-block;
  22. vertical-align: middle;
  23. margin-left: 8px;
  24. color: var(--color-accent-400);
  25. "
  26. aria-tooltip="Comment ça marche"
  27. >
  28. <i class="material-icons" @click="$router.push('docs')">info</i>
  29. </div>
  30. </div>
  31. <div class="actions" style="margin-top: 8px">
  32. <button class="btn primary small" @click="$emit('save')">
  33. <i class="material-icons">save</i> Sauvergarder
  34. </button>
  35. <button class="btn primary small" @click="exportStateToJson">
  36. <i class="material-icons">download</i> Télécharger les données
  37. </button>
  38. <button class="btn primary small" @click="clickInput">
  39. <i class="material-icons">upload</i>Import des données
  40. </button>
  41. <button
  42. class="btn small icon error tooltiped tooltiped--medium"
  43. @click="toggleModal"
  44. aria-tooltip="Nettoyer le planning"
  45. >
  46. <i class="material-icons">event_busy</i>
  47. </button>
  48. <input
  49. ref="input"
  50. style="display: none"
  51. type="file"
  52. accept=".json,application/json"
  53. @change="importJsonState"
  54. />
  55. </div>
  56. <div class="actions">
  57. <button class="btn primary small" @click="copyLink">
  58. <i class="material-icons">link</i>Lien pour les bénévoles
  59. </button>
  60. <a v-if="inscription" class="btn primary small" :href="inscription">
  61. <i class="material-icons">launch</i>Page pour l'inscription
  62. </a>
  63. </div>
  64. <div class="actions">
  65. <button class="btn primary small" @click="duplicateEvent">
  66. <i class="material-icons">content_copy</i>Dupliquer l'événement
  67. </button>
  68. </div>
  69. </div>
  70. <div v-if="showModal" class="modal" @click="toggleModal">
  71. <div class="modal-content" @click="(e) => e.stopPropagation()">
  72. <h3>Confirmer la suppression des créneaux</h3>
  73. <p>
  74. Êtes vous sûr de vouloir supprimer tout les créneaux qui ne sont pas entre le<br />
  75. {{ start.format("dddd DD MMMM YYYY, HH:mm") }}<br />
  76. et le <br />{{ end.format("dddd DD MMMM YYYY, HH:mm") }}
  77. </p>
  78. <div class="actions" style="margin-top: 8px; text-align: right">
  79. <button class="btn error small" @click="clearCreneau">
  80. <i class="material-icons">delete_forever</i>Confirmer
  81. </button>
  82. <button class="btn primary small" @click="toggleModal">
  83. <i class="material-icons">close</i>Annuler
  84. </button>
  85. </div>
  86. </div>
  87. </div>
  88. <div>
  89. <h2>
  90. Version antérieurs
  91. <button
  92. class="btn icon small secondary tooltiped tooltiped--medium"
  93. aria-tooltip="Rafraichir le liste des versions"
  94. @click="updatePlanningVersions"
  95. >
  96. <i class="material-icons">refresh</i>
  97. </button>
  98. </h2>
  99. <evenement-data-table
  100. :versions="planningVersions"
  101. @loadVersion="loadVersion"
  102. ></evenement-data-table>
  103. </div>
  104. </div>
  105. </template>
  106. <script lang="ts">
  107. import { Dayjs } from "dayjs";
  108. import { v4 as uuidv4 } from "uuid";
  109. import { defineComponent } from "vue";
  110. import { MutationTypes } from "@/store/Mutations";
  111. import Evenement from "@/models/Evenement";
  112. import EvenementVersion from "@/models/EvenementVersion";
  113. import styledInput from "@/components/Form/Input.vue";
  114. import datepicker from "@/components/Form/date-picker.vue";
  115. import EvenementDataTable from "@/components/EvenementDataTable.vue";
  116. import updatePlanningVersions from "@/mixins/updatePlanningVersions";
  117. import fetchPlanningVersion from "@/mixins/fetchPlanningVersion";
  118. import Toast from "@/utils/Toast";
  119. const API_URL = process.env.VUE_APP_API_URL;
  120. export default defineComponent({
  121. mixins: [updatePlanningVersions, fetchPlanningVersion],
  122. components: { styledInput, datepicker, EvenementDataTable },
  123. data() {
  124. return {
  125. start: null as Dayjs | null,
  126. end: null as Dayjs | null,
  127. showModal: false,
  128. };
  129. },
  130. watch: {
  131. start(val: Dayjs, oldval) {
  132. if (oldval && !val.isSame(this.evenement.startingDate)) {
  133. this.$store.commit(MutationTypes.editEvenement, {
  134. field: "start",
  135. value: val.toDate(),
  136. });
  137. }
  138. },
  139. end(val: Dayjs, oldval) {
  140. if (oldval && !val.isSame(this.evenement.endingDate, "minutes")) {
  141. this.$store.commit(MutationTypes.editEvenement, {
  142. field: "end",
  143. value: val.subtract(1, "h").endOf("h").toDate(),
  144. });
  145. }
  146. },
  147. "evenement.start": function () {
  148. this.initDate();
  149. },
  150. "evenement.end": function () {
  151. this.initDate();
  152. },
  153. },
  154. computed: {
  155. evenement(): Evenement {
  156. return this.$store.state.evenement;
  157. },
  158. planningVersions(): Array<EvenementVersion> {
  159. return this.$store.state.history;
  160. },
  161. inscription(): string {
  162. return this.planningVersions.length > 0 ? `/inscription/${this.evenement.uuid}` : "";
  163. },
  164. },
  165. methods: {
  166. copyLink() {
  167. const link = `${API_URL}planning/display/${this.evenement.uuid}`;
  168. navigator.clipboard.writeText(link);
  169. Toast({
  170. html: `Lien copié <a href="http://${link}" target="_blank"><i class="material-icons" style="vertical-align: middle;margin-left: 8px;">launch</i></a>`,
  171. displayLength: 5000,
  172. });
  173. },
  174. loadVersion(version: EvenementVersion) {
  175. this.fetchPlanningVersions(
  176. `${API_URL}api/evenements/history/${version.uuid}/content/${version.id}`
  177. ).then(() => {
  178. Toast({ html: "Version du " + version.lastModified + " chargée" });
  179. });
  180. },
  181. toggleModal() {
  182. this.showModal = !this.showModal;
  183. },
  184. duplicateEvent() {
  185. if (
  186. confirm(
  187. "Êtes vous sûr de vouloir copié toutes les données relatives à l'événement " +
  188. this.evenement.name
  189. )
  190. ) {
  191. this.$store.commit(MutationTypes.editEvenement, { field: "uuid", value: uuidv4() });
  192. this.$store.commit(MutationTypes.editEvenement, {
  193. field: "name",
  194. value: "Copie de " + this.evenement.name,
  195. });
  196. this.$emit("save");
  197. }
  198. },
  199. initDate() {
  200. if (this.evenement) {
  201. this.start = this.evenement.startingDate;
  202. this.end = this.evenement.endingDate;
  203. }
  204. },
  205. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  206. inputListener(event: any, field: keyof Evenement) {
  207. this.$store.commit(MutationTypes.editEvenement, {
  208. field: field,
  209. value: event.target.value,
  210. });
  211. },
  212. exportStateToJson() {
  213. this.$emit("export");
  214. },
  215. clickInput() {
  216. (this.$refs["input"] as HTMLElement).click();
  217. },
  218. importJsonState(event: InputEvent) {
  219. const files = (event.target as HTMLInputElement).files;
  220. if (files) {
  221. const file = files[0];
  222. if (file) {
  223. var reader = new FileReader();
  224. reader.onload = () => {
  225. var obj = JSON.parse(reader.result as string);
  226. this.$emit("import", obj);
  227. };
  228. reader.readAsText(file);
  229. }
  230. }
  231. },
  232. clearCreneau() {
  233. this.showModal = false;
  234. const max = this.$store.state.evenement.end;
  235. const min = this.$store.state.evenement.start;
  236. const toBeRemoved = this.$store.state.creneauList.filter((c) => max < c.start || c.end < min);
  237. for (let elt of toBeRemoved) {
  238. this.$store.commit(MutationTypes.removeCreneau, elt);
  239. }
  240. },
  241. },
  242. mounted() {
  243. this.initDate();
  244. },
  245. });
  246. </script>
  247. <style scoped>
  248. .centered-box {
  249. display: flex;
  250. justify-content: center;
  251. align-items: center;
  252. margin: 20px;
  253. flex-direction: column;
  254. }
  255. .centered-box > div {
  256. box-shadow: 0 0 2px var(--color-neutral-400);
  257. padding: 16px;
  258. margin-bottom: 32px;
  259. }
  260. .actions {
  261. margin-top: 8px;
  262. text-align: center;
  263. }
  264. h2 {
  265. margin-top: 0px;
  266. margin-left: 8px;
  267. }
  268. table {
  269. margin: 8px;
  270. max-height: 800px;
  271. overflow: auto;
  272. }
  273. </style>