menu.html 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <main class="container">
  2. <dialog :open="option.modal" v-if="option.modal">
  3. <article>
  4. <header>
  5. <a href="#close" aria-label="Close" class="close" @click="closeModal"></a>
  6. <h2>{{ option.modal }}</h2>
  7. </header>
  8. <form>
  9. <div v-for="item in currentOptions">
  10. <label :for="item.name">{{ item.label }}</label>
  11. <input :type="item.type" :name="item.name" :value="item.value" :list="item.name"
  12. v-model="item.value">
  13. <datalist v-if="item.datalist.length" :id="item.name">
  14. <option v-for="option in item.datalist" :value="option.id">{{ option.nombre }}</option>
  15. </datalist>
  16. </div>
  17. <div class="grid">
  18. <button type="reset" class="danger secondary">
  19. <i class="fas fa-eraser"></i>
  20. Limpiar
  21. </button>
  22. <button type="submit" @click="submitModal" :disabled="currentOptions?.some(item => !item.value)">
  23. <i class="fas fa-check"></i>
  24. Exportar</button>
  25. </div>
  26. </form>
  27. </article>
  28. </dialog>
  29. <button type="button" class="secondary" v-for="item in menu" @click="item.click">
  30. <i :class="item.icon"></i>
  31. {{ item.name }}
  32. </button>
  33. </main>
  34. <script>
  35. const option = PetiteVue.reactive({ modal: null });
  36. function createDownloadLink({ url, filename, postData }) {
  37. return async () => {
  38. store.loading = true;
  39. const response = await fetch(url, { method: 'POST', body: JSON.stringify(postData) });
  40. const blob = await response.blob();
  41. const downloadUrl = window.URL.createObjectURL(blob);
  42. const anchor = document.createElement('a');
  43. anchor.href = downloadUrl;
  44. anchor.download = filename;
  45. anchor.charset = "windows-1252"; // Set the charset to ANSI for compatibility
  46. anchor.click();
  47. anchor.remove();
  48. store.loading = false;
  49. };
  50. }
  51. async function fetchOptions(url, optionName) {
  52. const response = await fetch(url);
  53. const data = await response.json();
  54. return data.map(item => ({ id: item[optionName.id], nombre: item[optionName.nombre] }));
  55. }
  56. PetiteVue.createApp({
  57. option,
  58. modal: false,
  59. menu: [
  60. {
  61. name: 'Construcción de Calificación',
  62. icon: 'fas fa-plus',
  63. url: '/export/excel.php',
  64. filename: 'calificacion.csv',
  65. click: createDownloadLink({ url: '/export/excel.php', filename: 'calificacion.csv', postData: { query: 'calificaciones' } })
  66. },
  67. {
  68. name: 'Calificaciones Brutas',
  69. icon: 'fas fa-plus',
  70. url: '/export/excel.php',
  71. filename: 'calificacion.csv',
  72. click: createDownloadLink({ url: '/export/excel.php', filename: 'calificaciones.csv', postData: { query: 'calificaciones_brutas' } })
  73. },
  74. {
  75. name: 'Usuarios Registrados',
  76. icon: 'fas fa-plus',
  77. url: '/export/excel.php',
  78. filename: 'usuarios.csv',
  79. opciones: [
  80. // radio button
  81. { type: 'radio', name: 'query', label: 'Alumno', value: 'alumnos' },
  82. { type: 'radio', name: 'query', label: 'Usuarios temporales', value: 'usuarios_temporales' },
  83. { type: 'radio', name: 'query', label: 'Todos los usuarios', value: 'usuarios' },
  84. ],
  85. click: async function () {
  86. option.modal = this.name;
  87. try {
  88. await new Promise((resolve, reject) => {
  89. option.closeModal = () => {
  90. option.modal = false;
  91. reject(new Error("Modal closed by user"));
  92. };
  93. option.submitModal = resolve;
  94. });
  95. store.loading = true;
  96. option.modal = false;
  97. let formData = { query: 'usuarios' };
  98. document.querySelectorAll('input[name="query"]').forEach(input => {
  99. if (input.checked) formData.query = input.value;
  100. });
  101. await createDownloadLink({ url: this.url, filename: this.filename, postData: formData })();
  102. } catch (error) {
  103. console.error(error);
  104. } finally {
  105. store.loading = false;
  106. }
  107. }
  108. },
  109. {
  110. name: 'Reporte Syllabus Plan de Cátedra',
  111. icon: 'fas fa-plus',
  112. url: '/export/gema.php',
  113. filename: 'syllabus_plan_catedra.csv',
  114. opciones: [
  115. { type: 'list', name: 'periodo_id', label: 'Periodo de GEMA', value: null, datalist: [] },
  116. ],
  117. mounted: async function () {
  118. this.opciones.find(item => item.name === 'periodo_id').datalist = await fetchOptions('/fetch/periodos.php', { nombre: 'Periodo_desc', id: 'Periodo_id' });
  119. },
  120. click: async function () {
  121. option.modal = this.name;
  122. await this.mounted();
  123. try {
  124. await new Promise((resolve, reject) => {
  125. option.closeModal = () => {
  126. option.modal = false;
  127. reject(new Error("Modal closed by user"));
  128. };
  129. option.submitModal = resolve;
  130. });
  131. store.loading = true;
  132. option.modal = false;
  133. let formData = { query: 'cursos' };
  134. this.opciones.forEach(opt => formData[opt.name] = opt.value);
  135. await createDownloadLink({ url: this.url, filename: this.filename, postData: formData })();
  136. } catch (error) {
  137. console.error(error);
  138. } finally {
  139. store.loading = false;
  140. }
  141. }
  142. },
  143. ],
  144. get currentOptions() {
  145. const currentItem = this.menu.find(item => item.name === this.option.modal);
  146. return currentItem ? currentItem.opciones : [];
  147. },
  148. closeModal: function () {
  149. if (typeof option.closeModal === 'function') option.closeModal();
  150. },
  151. submitModal: function () {
  152. if (typeof option.submitModal === 'function') option.submitModal();
  153. },
  154. }).mount();
  155. </script>