ClassInfo.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <template>
  2. <div class="flex items-center justify-center w-full mt-5 gap-2">
  3. <button
  4. :title="chooseTitle"
  5. class="action-button"
  6. :disabled="hasCompletedModule || maxAttemptsReached"
  7. @click="toggleClassState"
  8. >
  9. <font-awesome-icon
  10. v-if="addable"
  11. class="py-2 text-xl"
  12. icon="fa-solid fa-plus"
  13. />
  14. <font-awesome-icon
  15. v-else
  16. class="py-2 text-xl"
  17. icon="fa-solid fa-minus"
  18. />
  19. </button>
  20. <button
  21. title="Modulinformationen"
  22. class="action-button"
  23. :disabled="!module"
  24. @click="stateStore.inspectingModule = module"
  25. >
  26. <font-awesome-icon
  27. class="py-2 text-xl"
  28. icon="fa-solid fa-puzzle-piece"
  29. />
  30. </button>
  31. <a
  32. :href="
  33. !module
  34. ? ''
  35. : `${URLS.ADDITIONAL_MODULE_INFORMATION}${module.module_id}`
  36. "
  37. target="_blank"
  38. :title="
  39. module?.module_id === null
  40. ? 'Keine Modul-ID vorhanden'
  41. : `${SCHOOL_NAME} Modulbeschreibung`
  42. "
  43. class="action-button"
  44. :class="[!module ? 'action-button-disabled' : '']"
  45. >
  46. <font-awesome-icon
  47. class="py-2 text-xl"
  48. icon="fa-solid fa-info"
  49. />
  50. </a>
  51. <a
  52. title="Klassen PDF in einem neuem Tab öffnen"
  53. :href="classesPDFLink(cls)"
  54. target="_blank"
  55. class="action-button"
  56. >
  57. <font-awesome-icon
  58. class="py-2 text-xl"
  59. icon="fa-solid fa-file-pdf"
  60. />
  61. </a>
  62. </div>
  63. <table class="mt-5 w-full text-left">
  64. <tbody>
  65. <tr>
  66. <td class="font-bold">
  67. <span>Dozent</span>
  68. </td>
  69. <td>
  70. <ul
  71. class="list-inside"
  72. :class="[lecturers.length > 1 ? 'list-disc' : '']"
  73. >
  74. <li
  75. v-for="lecturer in lecturers"
  76. :key="lecturer.short"
  77. >
  78. <span><b>{{ lecturer.short }}</b>: {{ lecturer.firstname }} {{ lecturer.surname }}</span>
  79. </li>
  80. </ul>
  81. </td>
  82. </tr>
  83. <tr>
  84. <td class="font-bold">
  85. <span>Klasse</span>
  86. </td>
  87. <td>
  88. <span>{{ cls.class }}</span>
  89. </td>
  90. </tr>
  91. <tr>
  92. <td class="font-bold">
  93. <span>Raum</span>
  94. </td>
  95. <td>
  96. <ul
  97. class="list-inside"
  98. :class="[cls.rooms.length > 1 ? 'list-disc' : '']"
  99. >
  100. <li
  101. v-for="room in cls.rooms"
  102. :key="room"
  103. >
  104. <span>{{ room }}</span>
  105. </li>
  106. </ul>
  107. </td>
  108. </tr>
  109. <tr>
  110. <td class="font-bold">
  111. <span>Art</span>
  112. </td>
  113. <td>
  114. <TeachingTypeIcon :teaching-type="cls.teaching_type" />
  115. </td>
  116. </tr>
  117. <tr>
  118. <td class="font-bold">
  119. <span>Durchführung</span>
  120. </td>
  121. <td>
  122. <span v-if="cls.weekday !== null">{{ dayMap[cls.weekday] }}, {{ toTime(cls.from) }} -
  123. {{ toTime(cls.to) }}</span>
  124. <span v-else>Blockmodul</span>
  125. </td>
  126. </tr>
  127. <tr>
  128. <td class="font-bold">
  129. <span>ECTS</span>
  130. </td>
  131. <td>
  132. <span>{{ module?.ects ?? "-" }}</span>
  133. </td>
  134. </tr>
  135. <tr>
  136. <td class="font-bold">
  137. <span>Benotung</span>
  138. </td>
  139. <td>
  140. <span>{{ module ? module.marksClean : "?" }}</span>
  141. </td>
  142. </tr>
  143. <tr>
  144. <td
  145. :colspan="moduleClasses.length == 0 ? 1 : 2"
  146. class="font-bold"
  147. >
  148. <span>Weitere Durchführungen</span>
  149. </td>
  150. <td>
  151. <span v-if="moduleClasses.length == 0">Keine</span>
  152. </td>
  153. </tr>
  154. <tr v-if="moduleClasses.length > 0">
  155. <td
  156. colspan="2"
  157. class="pb-5"
  158. >
  159. <CurrentModuleExecutions
  160. :module-classes="moduleClasses"
  161. :highlight-class="cls"
  162. />
  163. </td>
  164. </tr>
  165. <tr>
  166. <td class="font-bold">
  167. <span>Abhängigkeiten</span>
  168. <div
  169. 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)."
  170. 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"
  171. >
  172. <font-awesome-icon icon="fa-solid fa-info" />
  173. </div>
  174. </td>
  175. <td>
  176. <span>{{ module ? (hasModuleDeps ? "" : "Keine") : "-" }}</span>
  177. </td>
  178. </tr>
  179. </tbody>
  180. </table>
  181. </template>
  182. <script lang="ts">
  183. import { PropType } from "vue";
  184. import {
  185. classesPDFLink,
  186. dayMap,
  187. toTime,
  188. MAX_ATTEMPT_COUNT,
  189. } from "../../helpers";
  190. import { useClassesStore } from "../../stores/classes";
  191. import { useLecturersStore } from "../../stores/lecturers";
  192. import { useModulesStore } from "../../stores/modules";
  193. import { usePlanningStore } from "../../stores/planning";
  194. import { useStateStore } from "../../stores/state";
  195. import { useStudenthubStore } from "../../stores/studenthub";
  196. import { Module, TaughtClass, Lecturer, TeachingType } from "../../types";
  197. import CurrentModuleExecutions from "./CurrentModuleExecutions.vue";
  198. import TeachingTypeIcon from "./TeachingTypeIcon.vue";
  199. import { URLS, SCHOOL_NAME } from "../../globals";
  200. export default {
  201. name: "ClassInfo",
  202. components: { CurrentModuleExecutions, TeachingTypeIcon },
  203. props: {
  204. cls: {
  205. type: Object as PropType<TaughtClass>,
  206. required: true,
  207. },
  208. },
  209. setup() {
  210. const modulesStore = useModulesStore();
  211. const lecturersStore = useLecturersStore();
  212. const planningStore = usePlanningStore();
  213. const stateStore = useStateStore();
  214. const studenthubStore = useStudenthubStore();
  215. const classesStore = useClassesStore();
  216. return {
  217. modulesStore,
  218. lecturersStore,
  219. planningStore,
  220. stateStore,
  221. studenthubStore,
  222. classesStore,
  223. dayMap,
  224. toTime,
  225. classesPDFLink,
  226. TeachingType,
  227. URLS,
  228. SCHOOL_NAME,
  229. };
  230. },
  231. computed: {
  232. lecturers(): Lecturer[] {
  233. const r = this.lecturersStore.fromShort(this.cls.teachers);
  234. return r;
  235. },
  236. hasCompletedModule(): boolean {
  237. return this.studenthubStore.hasCompletedModule(
  238. this.module?.module_id ?? null
  239. );
  240. },
  241. module(): Module | null {
  242. return this.cls.module;
  243. },
  244. hasModuleDeps(): boolean {
  245. if (this.module === null) return false;
  246. return (
  247. Object.values(this.module.dependencies).reduce(
  248. (sum, dep) => sum + dep.length,
  249. 0
  250. ) > 0
  251. );
  252. },
  253. maxAttemptsReached(): boolean {
  254. return (this.module?.attemptCount ?? 0) >= MAX_ATTEMPT_COUNT;
  255. },
  256. chooseTitle(): string {
  257. if (this.hasCompletedModule) return "Modul bereits bestanden";
  258. if (this.maxAttemptsReached)
  259. return `Du hast dieses Modul bereits ${this.module?.attemptCount} Mal versucht!`;
  260. if (this.classChosen) return "Von der Planung entfernen";
  261. return "Zur Planung hinzufügen";
  262. },
  263. classChosen(): boolean {
  264. return this.planningStore.isModuleChosen(this.cls);
  265. },
  266. addable(): boolean {
  267. return !this.classChosen || this.hasCompletedModule;
  268. },
  269. moduleClasses(): TaughtClass[] {
  270. if (this.module === null) return [];
  271. return this.classesStore.classesForModule(this.module.short);
  272. },
  273. },
  274. methods: {
  275. toggleClassState() {
  276. if (this.classChosen) this.planningStore.remove(this.cls);
  277. else this.planningStore.add(this.cls);
  278. },
  279. },
  280. };
  281. </script>
  282. <style scoped>
  283. .action-button {
  284. @apply text-center
  285. bg-gray-200
  286. hover:bg-gray-300
  287. active:bg-gray-400
  288. dark:bg-gray-600
  289. dark:hover:bg-gray-700
  290. dark:active:bg-gray-900
  291. rounded
  292. cursor-pointer
  293. w-full
  294. p-0
  295. transition-all
  296. duration-200
  297. disabled:dark:bg-gray-600
  298. disabled:text-gray-500
  299. disabled:bg-gray-100
  300. disabled:pointer-events-none;
  301. }
  302. .action-button-disabled {
  303. @apply pointer-events-none
  304. dark:bg-gray-600
  305. text-gray-500
  306. bg-gray-100;
  307. }
  308. </style>