ModuleInfo.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <table class="mt-5 w-full text-left main-table">
  3. <tbody>
  4. <tr>
  5. <td class="font-bold">
  6. <span>ECTS</span>
  7. </td>
  8. <td>
  9. <span>{{ module?.ects ?? "-" }}</span>
  10. </td>
  11. </tr>
  12. <tr>
  13. <td class="font-bold">
  14. <span>Benotung</span>
  15. </td>
  16. <td>
  17. <span>{{ module ? module.marksClean : "?" }}</span>
  18. </td>
  19. </tr>
  20. <tr v-if="(module.for_degrees?.length ?? 0) > 1">
  21. <td class="font-bold">
  22. <span>Studiengänge</span>
  23. </td>
  24. <td>
  25. <ul class="list-disc ml-4">
  26. <li
  27. v-for="degree in module.for_degrees"
  28. :key="degree"
  29. >
  30. {{ degree }}
  31. </li>
  32. </ul>
  33. </td>
  34. </tr>
  35. <tr v-else>
  36. <td class="font-bold">
  37. <span>Studiengang</span>
  38. </td>
  39. <td>
  40. <span>{{ module.for_degrees ? module.for_degrees[0] : "-" }}</span>
  41. </td>
  42. </tr>
  43. <tr>
  44. <td class="font-bold">
  45. <span>Detailbeschreibung</span>
  46. </td>
  47. <td>
  48. <a
  49. :href="`${URLS.ADDITIONAL_MODULE_INFORMATION}${module?.module_id}`"
  50. target="_blank"
  51. class="external-link"
  52. :title="
  53. module?.module_id === null
  54. ? 'Keine Modul-ID vorhanden'
  55. : `${SCHOOL_NAME} Modulbeschreibung`
  56. "
  57. >
  58. <span class="mr-2">{{ SCHOOL_NAME }}</span>
  59. <font-awesome-icon icon="fa-solid fa-arrow-up-right-from-square" />
  60. </a>
  61. </td>
  62. </tr>
  63. <!------------------------->
  64. <tr>
  65. <td class="font-bold">
  66. <span>Abgeschlossen</span>
  67. </td>
  68. <td>
  69. <span>{{ completionStateString }}</span>
  70. </td>
  71. </tr>
  72. <tr>
  73. <td class="font-bold">
  74. <span>Bisherige Versuche</span>
  75. </td>
  76. <td>
  77. <span>{{
  78. module.attemptCount > 0 ? module.attemptCount : "Keine"
  79. }}</span>
  80. </td>
  81. </tr>
  82. <tr>
  83. <td
  84. :colspan="moduleClasses.length == 0 ? 1 : 2"
  85. class="font-bold"
  86. >
  87. <span>Nächste Durchführungen</span>
  88. </td>
  89. <td>
  90. <span v-if="moduleClasses.length == 0">Keine</span>
  91. </td>
  92. </tr>
  93. <tr v-if="moduleClasses.length > 0">
  94. <td
  95. colspan="2"
  96. class="pb-5"
  97. >
  98. <CurrentModuleExecutions :module-classes="moduleClasses" />
  99. </td>
  100. </tr>
  101. <tr>
  102. <td
  103. class="font-bold"
  104. :colspan="previousClasses.length == 0 ? 1 : 2"
  105. >
  106. <button
  107. v-if="previousClasses.length > 0"
  108. class="action-button mr-2"
  109. :title="
  110. 'Vergangene Durchführungen ' +
  111. (showingPastClasses ? 'ausblenden' : 'einblenden')
  112. "
  113. @click="() => (showingPastClasses = !showingPastClasses)"
  114. >
  115. <font-awesome-icon
  116. v-if="showingPastClasses"
  117. icon="fa-solid fa-chevron-down"
  118. />
  119. <font-awesome-icon
  120. v-else
  121. icon="fa-solid fa-chevron-right"
  122. />
  123. </button>
  124. <span>Vergangene Durchführungen</span>
  125. <span
  126. v-if="previousClasses.length > 0"
  127. class="font-bold"
  128. >
  129. ({{ previousClasses.length }})</span>
  130. </td>
  131. <td>
  132. <span v-if="previousClasses.length == 0">Keine</span>
  133. </td>
  134. </tr>
  135. <tr v-if="previousClasses.length > 0 && showingPastClasses">
  136. <td
  137. colspan="2"
  138. class="pb-5"
  139. >
  140. <PreviousModuleExecutions :previous-classes="previousClasses" />
  141. </td>
  142. </tr>
  143. <tr>
  144. <td class="font-bold">
  145. <span>Abhängigkeiten</span>
  146. <div
  147. title="Abhängigkeiten sind teilweise sehr unklar von der Schule definiert und variieren basierend auf verschiedenen Dokumenten. Teilweise ist es nicht möglich, die Abhängigkeiten automatisch zu detektieren (keine Modulkürzel in der Abhängigkeitsbeschreibung)."
  148. class="mx-2 w-5 text-xs aspect-square rounded-full bg-gray-200 dark:bg-gray-400 inline-flex items-center justify-center cursor-help"
  149. >
  150. <font-awesome-icon icon="fa-solid fa-info" />
  151. </div>
  152. </td>
  153. <td>
  154. <span>{{ module ? (hasModuleDeps ? "" : "Keine") : "-" }}</span>
  155. </td>
  156. </tr>
  157. </tbody>
  158. </table>
  159. </template>
  160. <script lang="ts">
  161. import { PropType } from "vue";
  162. import {
  163. classesPDFLink,
  164. dayMap,
  165. toTime,
  166. MAX_ATTEMPT_COUNT,
  167. } from "../../helpers";
  168. import { useClassesStore } from "../../stores/classes";
  169. import { useLecturersStore } from "../../stores/lecturers";
  170. import { useModulesStore } from "../../stores/modules";
  171. import { usePlanningStore } from "../../stores/planning";
  172. import { useStateStore } from "../../stores/state";
  173. import { useStudenthubStore } from "../../stores/studenthub";
  174. import { HistoricClassEntry, Module, TaughtClass } from "../../types";
  175. import CurrentModuleExecutions from "./CurrentModuleExecutions.vue";
  176. import PreviousModuleExecutions from "./PreviousModuleExecutions.vue";
  177. import { URLS, SCHOOL_NAME } from "../../globals";
  178. export default {
  179. name: "ModuleInfo",
  180. components: { CurrentModuleExecutions, PreviousModuleExecutions },
  181. props: {
  182. module: {
  183. type: Object as PropType<Module>,
  184. required: true,
  185. },
  186. },
  187. setup() {
  188. const modulesStore = useModulesStore();
  189. const lecturersStore = useLecturersStore();
  190. const planningStore = usePlanningStore();
  191. const stateStore = useStateStore();
  192. const studenthubStore = useStudenthubStore();
  193. const classesStore = useClassesStore();
  194. return {
  195. modulesStore,
  196. lecturersStore,
  197. planningStore,
  198. stateStore,
  199. studenthubStore,
  200. classesStore,
  201. dayMap,
  202. toTime,
  203. classesPDFLink,
  204. URLS,
  205. SCHOOL_NAME,
  206. };
  207. },
  208. data() {
  209. return {
  210. showingPastClasses: false,
  211. };
  212. },
  213. computed: {
  214. hasCompletedModule(): boolean {
  215. return this.studenthubStore.hasCompletedModule(
  216. this.module?.module_id ?? null
  217. );
  218. },
  219. hasModuleDeps(): boolean {
  220. if (this.module === null) return false;
  221. return (
  222. Object.values(this.module.dependencies).reduce(
  223. (sum, dep) => sum + dep.length,
  224. 0
  225. ) > 0
  226. );
  227. },
  228. maxAttemptsReached(): boolean {
  229. return this.module.attemptCount >= MAX_ATTEMPT_COUNT;
  230. },
  231. moduleClasses(): TaughtClass[] {
  232. return this.classesStore.classesForModule(this.module.short);
  233. },
  234. previousClasses(): HistoricClassEntry[] {
  235. return this.modulesStore.history[this.module.short] ?? [];
  236. },
  237. completionStateString(): string {
  238. if (this.module.hasCompleted) return "Ja";
  239. if (this.module.isActive) return "In Bearbeitung";
  240. if (this.module.hasFailed) return "Fehlgeschlagen";
  241. return "Nein";
  242. },
  243. },
  244. };
  245. </script>
  246. <style scoped>
  247. @reference "../../style.css";
  248. .action-button {
  249. @apply text-center
  250. bg-gray-200
  251. hover:bg-gray-300
  252. active:bg-gray-400
  253. dark:bg-gray-600
  254. dark:hover:bg-gray-700
  255. dark:active:bg-gray-900
  256. rounded-sm
  257. cursor-pointer
  258. w-6
  259. p-0
  260. transition-all
  261. duration-200
  262. disabled:dark:bg-gray-600
  263. disabled:text-gray-500
  264. disabled:bg-gray-100
  265. disabled:pointer-events-none;
  266. }
  267. .main-table td {
  268. @apply py-1 align-top;
  269. }
  270. </style>