Evenement.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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> Sauvegarder
  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/DatePicker.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. import commit from "@/mixins/commit";
  120. const API_URL = process.env.VUE_APP_API_URL;
  121. export default defineComponent({
  122. mixins: [updatePlanningVersions, fetchPlanningVersion, commit],
  123. components: { styledInput, datepicker, EvenementDataTable },
  124. data() {
  125. return {
  126. start: null as Dayjs | null,
  127. end: null as Dayjs | null,
  128. showModal: false,
  129. };
  130. },
  131. watch: {
  132. start(val: Dayjs, oldval) {
  133. if (oldval && !val.isSame(this.evenement.startingDate)) {
  134. this.commit(MutationTypes.editEvenement, {
  135. field: "start",
  136. value: val.toDate(),
  137. });
  138. }
  139. },
  140. end(val: Dayjs, oldval) {
  141. if (oldval && !val.isSame(this.evenement.endingDate, "minutes")) {
  142. this.commit(MutationTypes.editEvenement, {
  143. field: "end",
  144. value: val.subtract(1, "h").endOf("h").toDate(),
  145. });
  146. }
  147. },
  148. "evenement.start": function () {
  149. this.initDate();
  150. },
  151. "evenement.end": function () {
  152. this.initDate();
  153. },
  154. },
  155. computed: {
  156. evenement(): Evenement {
  157. return this.$store.state.evenement;
  158. },
  159. planningVersions(): Array<EvenementVersion> {
  160. return this.$store.state.history;
  161. },
  162. inscription(): string {
  163. return this.planningVersions.length > 0 ? `/inscription/${this.evenement.uuid}` : "";
  164. },
  165. },
  166. methods: {
  167. copyLink() {
  168. const link = `${API_URL}planning/display/${this.evenement.uuid}`;
  169. navigator.clipboard.writeText(link);
  170. Toast({
  171. html: `Lien copié <a href="http://${link}" target="_blank"><i class="material-icons" style="vertical-align: middle;margin-left: 8px;">launch</i></a>`,
  172. displayLength: 5000,
  173. });
  174. },
  175. loadVersion(version: EvenementVersion) {
  176. this.fetchPlanningVersions(
  177. `${API_URL}api/evenements/history/${version.uuid}/content/${version.id}`
  178. ).then(() => {
  179. Toast({ html: "Version du " + version.lastModified + " chargée" });
  180. });
  181. },
  182. toggleModal() {
  183. this.showModal = !this.showModal;
  184. },
  185. duplicateEvent() {
  186. if (
  187. confirm(
  188. "Êtes vous sûr de vouloir copié toutes les données relatives à l'événement " +
  189. this.evenement.name
  190. )
  191. ) {
  192. this.commit(MutationTypes.editEvenement, { field: "uuid", value: uuidv4() });
  193. this.commit(MutationTypes.editEvenement, {
  194. field: "name",
  195. value: "Copie de " + this.evenement.name,
  196. });
  197. this.$emit("save");
  198. }
  199. },
  200. initDate() {
  201. if (this.evenement) {
  202. this.start = this.evenement.startingDate;
  203. this.end = this.evenement.endingDate;
  204. }
  205. },
  206. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  207. inputListener(event: any, field: keyof Evenement) {
  208. this.commit(MutationTypes.editEvenement, {
  209. field: field,
  210. value: event.target.value,
  211. });
  212. },
  213. exportStateToJson() {
  214. this.$emit("export");
  215. },
  216. clickInput() {
  217. (this.$refs["input"] as HTMLElement).click();
  218. },
  219. importJsonState(event: InputEvent) {
  220. const files = (event.target as HTMLInputElement).files;
  221. if (files) {
  222. const file = files[0];
  223. if (file) {
  224. var reader = new FileReader();
  225. reader.onload = () => {
  226. var obj = JSON.parse(reader.result as string);
  227. this.$emit("import", obj);
  228. };
  229. reader.readAsText(file);
  230. }
  231. }
  232. },
  233. clearCreneau() {
  234. this.showModal = false;
  235. const max = this.$store.state.evenement.end;
  236. const min = this.$store.state.evenement.start;
  237. const toBeRemoved = this.$store.state.creneauList.filter((c) => max < c.start || c.end < min);
  238. for (let elt of toBeRemoved) {
  239. this.commit(MutationTypes.removeCreneau, elt.id);
  240. }
  241. },
  242. },
  243. mounted() {
  244. this.initDate();
  245. },
  246. });
  247. </script>
  248. <style scoped>
  249. .centered-box {
  250. display: flex;
  251. justify-content: center;
  252. align-items: center;
  253. margin: 20px;
  254. flex-direction: column;
  255. }
  256. .centered-box > div {
  257. box-shadow: 0 0 2px var(--color-neutral-400);
  258. padding: 16px;
  259. margin-bottom: 32px;
  260. }
  261. .actions {
  262. margin-top: 8px;
  263. text-align: center;
  264. }
  265. h2 {
  266. margin-top: 0px;
  267. margin-left: 8px;
  268. }
  269. table {
  270. margin: 8px;
  271. max-height: 800px;
  272. overflow: auto;
  273. }
  274. </style>