BenevoleManager.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <template>
  2. <navigation v-model="show" :options="navOption">
  3. <div class="page" v-show="show == 'benevole'">
  4. <data-table
  5. class="data-table"
  6. title="Liste des bénévoles"
  7. :data="data"
  8. :clickable="true"
  9. :loadingAnimation="loading"
  10. :exportable="false"
  11. locale="fr"
  12. @row-click="onRowClick"
  13. :customButtons="customButton"
  14. ></data-table>
  15. <editeur-benevole
  16. :benevole="currentBenevole"
  17. @create="createBenevole"
  18. @delete="deleteBenevole"
  19. />
  20. <input type="file" ref="loadcsv" @change="importBenevoleTemplate" style="display: none" />
  21. </div>
  22. <editeur-questionnaire
  23. v-if="show == 'questionnaire'"
  24. :competences="competenceList"
  25. :uuid="uuid"
  26. />
  27. <manage-registration v-if="show == 'inscriptions'" />
  28. <filter-panel
  29. ref="filter"
  30. :data="benevoleForFilter"
  31. title="Filtrer les bénévoles"
  32. @filterChange="updateList($event)"
  33. :filters="filters"
  34. />
  35. </navigation>
  36. </template>
  37. <script lang="ts">
  38. import Competence from "@/models/Competence";
  39. import EditeurBenevole from "@/components/EditeurBenevole.vue";
  40. import FilterPanel, { FilterParam } from "@/components/Utils/FilteringPanel.vue";
  41. import EditeurQuestionnaire from "@/components/EditeurQuestionnaire.vue";
  42. import ManageRegistration from "@/components/Utils/ManageRegistration.vue";
  43. import Navigation from "@/components/Utils/NavigationPanel.vue";
  44. import DataTable, {
  45. CustomButton,
  46. DataTableColumn,
  47. DataTableData,
  48. } from "@/components/Utils/DataTable.vue";
  49. import { defineComponent } from "vue";
  50. import { MutationTypes } from "@/store/Mutations";
  51. import Benevole from "@/models/Benevole";
  52. import csv from "@/utils/csv";
  53. import dayjs from "dayjs";
  54. import Toast from "@/utils/Toast";
  55. const csvDefaultcolumn = "id,prenom,nom,telephone,email,commentaire";
  56. const navOption = [
  57. { id: "benevole", title: "Bénévoles" },
  58. { id: "questionnaire", title: "Gestion du questionnaire" },
  59. { id: "inscriptions", title: "Validations des inscriptions" },
  60. ];
  61. const filters: Array<FilterParam> = [
  62. {
  63. name: "Préference & compétences",
  64. type: "category",
  65. key: "competenceIdList",
  66. placeholder: "Selectionner une compétence",
  67. },
  68. ];
  69. type filterObject = {
  70. id: number;
  71. competenceIdList: Array<string>;
  72. };
  73. type BenevoleTableObject = {
  74. id: number;
  75. name: string;
  76. surname: string;
  77. fanfare: string;
  78. creneauLength: number;
  79. };
  80. const columns: Array<DataTableColumn<BenevoleTableObject>> = [
  81. {
  82. label: "Prénom",
  83. field: "name",
  84. numeric: false,
  85. html: false,
  86. class: "overflow-cell",
  87. },
  88. {
  89. label: "Nom",
  90. field: "surname",
  91. numeric: false,
  92. html: false,
  93. class: "overflow-cell",
  94. },
  95. {
  96. label: "Fanfare",
  97. field: "fanfare",
  98. numeric: false,
  99. html: false,
  100. class: "fitwidth",
  101. export: "fanfare",
  102. },
  103. {
  104. label: "Duree créneau(h)",
  105. field: "creneauLength",
  106. numeric: true,
  107. html: false,
  108. class: "fitwidth",
  109. export: "creneauLength",
  110. },
  111. ];
  112. export default defineComponent({
  113. name: "EditeurCreneau",
  114. components: {
  115. DataTable,
  116. EditeurBenevole,
  117. ManageRegistration,
  118. EditeurQuestionnaire,
  119. Navigation,
  120. FilterPanel,
  121. },
  122. data: () => ({
  123. navOption,
  124. show: "benevole",
  125. columns,
  126. currentBenevole: undefined as Benevole | undefined,
  127. loading: true,
  128. customButton: [] as Array<CustomButton>,
  129. benevoleList: [] as Array<Benevole>,
  130. filters,
  131. }),
  132. computed: {
  133. uuid(): string {
  134. return this.$store.state.evenement.uuid;
  135. },
  136. csvInputElt(): HTMLInputElement {
  137. return this.$refs.loadcsv as HTMLInputElement;
  138. },
  139. competenceList(): Array<Competence> {
  140. return this.$store.state.competenceList;
  141. },
  142. fanfareList(): Array<Competence> {
  143. return this.competenceList.filter((o) => o.name.startsWith("Fanfare"));
  144. },
  145. rawBenevoleList(): Array<Benevole> {
  146. return this.$store.state.benevoleList;
  147. },
  148. benevoleForFilter(): Array<filterObject> {
  149. return this.rawBenevoleList.map((b: Benevole) => {
  150. return {
  151. id: b.id,
  152. competenceIdList: b.competenceIdList.map(
  153. (id) => this.competenceList.find((o) => o.id == id)?.name ?? ""
  154. ),
  155. };
  156. });
  157. },
  158. data(): DataTableData<BenevoleTableObject> {
  159. return {
  160. items: this.benevoleList.map((b) => {
  161. return {
  162. id: b.id,
  163. name: b.name,
  164. surname: b.surname,
  165. fanfare: this.getFanfareForBenevole(b),
  166. creneauLength: this.getBenevoleTotalLength(b),
  167. };
  168. }),
  169. columns: this.columns,
  170. };
  171. },
  172. },
  173. watch: {
  174. rawBenevoleList() {
  175. this.benevoleList = this.rawBenevoleList;
  176. (this.$refs.filter as typeof FilterPanel).reset();
  177. },
  178. },
  179. methods: {
  180. openFilter() {
  181. (this.$refs.filter as typeof FilterPanel).open();
  182. },
  183. updateList(l: Array<filterObject>) {
  184. const ids = l.map((o) => o.id);
  185. this.benevoleList = this.rawBenevoleList.filter((b) => ids.includes(b.id));
  186. },
  187. getFanfareForBenevole(benevole: Benevole): string {
  188. if (benevole.competenceIdList.length == 0) {
  189. return "Exte";
  190. } else {
  191. const fanfare = this.fanfareList
  192. .filter((o) => benevole.competenceIdList.indexOf(o.id) > -1)
  193. .map((o) => o.name.substring(8))
  194. .join(", ");
  195. return fanfare ? fanfare : "Exte";
  196. }
  197. },
  198. getBenevoleTotalLength(benevole: Benevole): number {
  199. return this.$store.state.creneauList
  200. .filter((c) => c.benevoleIdList.indexOf(benevole.id) > -1)
  201. .reduce((acc, creneau) => acc + creneau.durationH, 0);
  202. },
  203. createBenevole() {
  204. const temp = Benevole.fromObject({ name: "Benevole i", surname: "" });
  205. this.$store.commit(MutationTypes.addBenevole, temp);
  206. this.currentBenevole = temp;
  207. },
  208. deleteBenevole(payload: Benevole) {
  209. this.$store.commit(MutationTypes.removeBenevole, payload.id);
  210. this.currentBenevole = undefined;
  211. },
  212. onRowClick(row: { id: number }) {
  213. this.currentBenevole = this.$store.getters.getBenevoleById(row.id);
  214. },
  215. getImportTemplate() {
  216. const competences = this.$store.state.competenceList;
  217. let csvContent = `${csvDefaultcolumn},${competences.map((c) => c.name).join(",")}\r\n`;
  218. this.benevoleList.forEach(
  219. (b) =>
  220. (csvContent +=
  221. [
  222. b.id,
  223. b.name,
  224. b.surname,
  225. b.phone,
  226. b.email,
  227. b.comment,
  228. ...competences.map((c) => (b.competenceIdList.includes(c.id) ? "X" : "")),
  229. ].join(",") + "\r\n")
  230. );
  231. const dummy = document.createElement("a");
  232. dummy.href = "data:text/csv;charset=utf-8, " + encodeURI(csvContent);
  233. dummy.download = "Benevole-import" + dayjs().format("-YYYY-MM-DD-hh-mm-ss[.csv]");
  234. document.body.appendChild(dummy);
  235. dummy.click();
  236. },
  237. importBenevoleTemplate() {
  238. const fs = this.csvInputElt.files;
  239. if (fs && fs.length > 0) {
  240. let reader = new FileReader();
  241. reader.readAsText(fs[0]);
  242. reader.onload = () => {
  243. let csvtxt = reader.result as string;
  244. const arr = csv.toArray(csvtxt);
  245. const defaultKey = csvDefaultcolumn.split(",");
  246. const competencesMap: { [k: string]: Competence } = {};
  247. for (let key of arr[0]) {
  248. if (!defaultKey.includes(key)) {
  249. key = key.trim();
  250. // check if a competence exist with this name
  251. let c = this.competenceList.find((c) => c.name == key);
  252. if (c == undefined) {
  253. Toast({ html: "Compétence inconnue: " + key, classes: "warning" });
  254. c = Competence.fromObject({ name: key, description: "" });
  255. this.$store.commit(MutationTypes.addConstraint, c);
  256. }
  257. if (c) {
  258. competencesMap[key] = c;
  259. }
  260. }
  261. }
  262. const objectList = csv.toObject(csvtxt);
  263. for (let item of objectList) {
  264. const id = parseInt(item.id);
  265. let b: Benevole | undefined = undefined;
  266. if (id) b = this.$store.getters.getBenevoleById(id);
  267. if (b == undefined) {
  268. b = Benevole.fromObject({
  269. id: id ?? undefined,
  270. name: item.prenom,
  271. surname: item.nom,
  272. phone: item.telephone,
  273. email: item.email,
  274. comment: item.commentaire,
  275. });
  276. this.$store.commit(MutationTypes.addBenevole, b);
  277. } else {
  278. let map: Array<{ k: string; v: keyof Benevole }> = [
  279. { k: "prenom", v: "name" },
  280. { k: "nom", v: "surname" },
  281. { k: "telephone", v: "phone" },
  282. { k: "commentaire", v: "comment" },
  283. { k: "email", v: "email" },
  284. ];
  285. map.forEach((o) =>
  286. this.$store.commit(MutationTypes.editBenevole, {
  287. id: (b as Benevole).id,
  288. field: o.v,
  289. value: item[o.k],
  290. })
  291. );
  292. }
  293. const competenceIdList = [];
  294. for (let key in competencesMap) {
  295. if (item[key].trim() == "X") {
  296. competenceIdList.push(competencesMap[key].id);
  297. }
  298. }
  299. this.$store.commit(MutationTypes.editBenevole, {
  300. id: b.id,
  301. field: "competenceIdList",
  302. value: competenceIdList,
  303. });
  304. }
  305. };
  306. }
  307. },
  308. },
  309. mounted() {
  310. this.loading = false;
  311. this.customButton.push({ icon: "filter_alt", hide: false, onclick: this.openFilter });
  312. this.customButton.push({ icon: "file_download", hide: false, onclick: this.getImportTemplate });
  313. this.customButton.push({
  314. icon: "file_upload",
  315. hide: false,
  316. onclick: () => {
  317. this.csvInputElt.click();
  318. },
  319. });
  320. this.benevoleList = this.rawBenevoleList;
  321. },
  322. });
  323. </script>
  324. <style scoped>
  325. .page {
  326. display: flex;
  327. flex-direction: column;
  328. width: 100%;
  329. max-width: 1100px;
  330. gap: 16px;
  331. }
  332. .data-table {
  333. width: 100%;
  334. height: min-content;
  335. }
  336. @media (min-width: 600px) {
  337. .page {
  338. flex-direction: row;
  339. }
  340. .data-table {
  341. width: calc(100% - 432px);
  342. }
  343. }
  344. @media (min-width: 1200px) {
  345. .page {
  346. margin-top: 8px;
  347. }
  348. }
  349. </style>