From 227af36c05f166d05e2ad323893d66a0be2586e0 Mon Sep 17 00:00:00 2001 From: "leon.hoppe" Date: Sat, 29 Apr 2023 14:01:27 +0200 Subject: [PATCH] finished schedule page + added substitution functions --- .../Controllers/IServController.cs | 2 +- .../src/app/api/iserv.service.ts | 55 +++++- BetterIServ.Mobile/src/app/app.component.html | 2 +- BetterIServ.Mobile/src/app/app.component.scss | 4 + BetterIServ.Mobile/src/app/app.component.ts | 3 +- BetterIServ.Mobile/src/app/app.routes.ts | 8 + BetterIServ.Mobile/src/app/entities/course.ts | 16 ++ .../src/app/pages/login/login.page.scss | 1 + .../src/app/pages/schedule/schedule.page.html | 185 ++++++++++++++++++ .../src/app/pages/schedule/schedule.page.scss | 64 ++++++ .../src/app/pages/schedule/schedule.page.ts | 129 ++++++++++++ .../pages/substitution/substitution.page.scss | 2 +- .../pages/substitution/substitution.page.ts | 30 ++- .../src/app/pages/tasks/tasks.page.html | 38 ++++ .../src/app/pages/tasks/tasks.page.scss | 0 .../src/app/pages/tasks/tasks.page.ts | 20 ++ BetterIServ.Mobile/src/app/pipes/week.pipe.ts | 35 ++++ BetterIServ.Mobile/src/assets/shapes.svg | 1 - 18 files changed, 584 insertions(+), 11 deletions(-) create mode 100644 BetterIServ.Mobile/src/app/entities/course.ts create mode 100644 BetterIServ.Mobile/src/app/pages/schedule/schedule.page.html create mode 100644 BetterIServ.Mobile/src/app/pages/schedule/schedule.page.scss create mode 100644 BetterIServ.Mobile/src/app/pages/schedule/schedule.page.ts create mode 100644 BetterIServ.Mobile/src/app/pages/tasks/tasks.page.html create mode 100644 BetterIServ.Mobile/src/app/pages/tasks/tasks.page.scss create mode 100644 BetterIServ.Mobile/src/app/pages/tasks/tasks.page.ts create mode 100644 BetterIServ.Mobile/src/app/pipes/week.pipe.ts delete mode 100644 BetterIServ.Mobile/src/assets/shapes.svg diff --git a/BetterIServ.Backend/Controllers/IServController.cs b/BetterIServ.Backend/Controllers/IServController.cs index 4ad0694..0f12623 100644 --- a/BetterIServ.Backend/Controllers/IServController.cs +++ b/BetterIServ.Backend/Controllers/IServController.cs @@ -8,7 +8,7 @@ namespace BetterIServ.Backend.Controllers; [ApiController] [Route("iserv")] -public class AuthController : ControllerBase { +public class IServController : ControllerBase { [HttpPost("login")] public async Task> GetAuthKeysV2([FromBody] Credentials credentials) { diff --git a/BetterIServ.Mobile/src/app/api/iserv.service.ts b/BetterIServ.Mobile/src/app/api/iserv.service.ts index 07f05f4..39d1622 100644 --- a/BetterIServ.Mobile/src/app/api/iserv.service.ts +++ b/BetterIServ.Mobile/src/app/api/iserv.service.ts @@ -12,6 +12,27 @@ export class IServService { public keys?: AuthKeys; public backend: string = "http://localhost:5273"; + public courseNames: {[id: string]: string} = { + ["Bi"]: "Biologie", + ["Ch"]: "Chemie", + ["Ma"]: "Mathe", + ["Ph"]: "Physik", + ["De"]: "Deutsch", + ["Ek"]: "Erdkunde", + ["En"]: "Englisch", + ["PW"]: "Politik", + ["Sn"]: "Spanisch", + ["If"]: "Informatik", + ["Sp"]: "Sport", + ["WN"]: "Werte und Normen", + ["La"]: "Latein", + ["Re"]: "Religion", + ["Ge"]: "Geschichte", + ["Ku"]: "Kunst", + ["Sf"]: "Seminarfach", + ["DS"]: "Darstellendes Spiel", + }; + constructor(private client: HttpClient) { const data = localStorage.getItem("userdata"); if (data != null) { @@ -42,6 +63,11 @@ export class IServService { } } + public logout() { + delete this.userdata; + delete this.keys; + } + public async getKeys(): Promise { const keys = await firstValueFrom(this.client.post(this.backend + "/iserv/login", this.userdata)); localStorage.setItem("keys", JSON.stringify(keys)); @@ -52,9 +78,34 @@ export class IServService { try { return (await firstValueFrom(this.client.post<{value: string[]}>(this.backend + "/iserv/groups?domain=" + this.userdata.domain, this.keys))).value; } catch { - await this.getKeys(); - return (await firstValueFrom(this.client.post<{value: string[]}>(this.backend + "/iserv/groups?domain=" + this.userdata.domain, this.keys))).value; + const keys = await this.getKeys(); + return (await firstValueFrom(this.client.post<{value: string[]}>(this.backend + "/iserv/groups?domain=" + this.userdata.domain, keys))).value; } } + public async getCoursesAndClass(groups?: string[]): Promise<{class: string, courses: string[]}> { + if (groups == undefined) { + groups = await this.getGroups(); + } + + const result: {class: string, courses: string[]} = {class: undefined, courses: []}; + + const classNames = groups.filter(group => group.startsWith("Klasse ") && !group.includes(".")); + if (classNames.length != 0) { + result.class = classNames[0].replace("Klasse ", ""); + }else { + const grades = groups.filter(group => group.startsWith("Jahrgang ") && !group.includes(".")); + if (grades.length != 0) { + result.class = grades[0].replace("Jahrgang ", "").toUpperCase(); + } + } + + for (let group of groups) { + if (!group.includes(".") || !group.toLowerCase().startsWith("q")) continue; + result.courses.push(group.split(".")[1]); + } + + return result; + } + } diff --git a/BetterIServ.Mobile/src/app/app.component.html b/BetterIServ.Mobile/src/app/app.component.html index 1138eb0..d3c91dc 100644 --- a/BetterIServ.Mobile/src/app/app.component.html +++ b/BetterIServ.Mobile/src/app/app.component.html @@ -1,6 +1,6 @@ - + BetterIServ diff --git a/BetterIServ.Mobile/src/app/app.component.scss b/BetterIServ.Mobile/src/app/app.component.scss index 4015eb8..8d3756c 100644 --- a/BetterIServ.Mobile/src/app/app.component.scss +++ b/BetterIServ.Mobile/src/app/app.component.scss @@ -107,3 +107,7 @@ ion-note { ion-item.selected { --color: var(--ion-color-primary); } + +.hide { + display: none; +} diff --git a/BetterIServ.Mobile/src/app/app.component.ts b/BetterIServ.Mobile/src/app/app.component.ts index 1223413..032887d 100644 --- a/BetterIServ.Mobile/src/app/app.component.ts +++ b/BetterIServ.Mobile/src/app/app.component.ts @@ -28,7 +28,8 @@ export class AppComponent { } public logout() { - localStorage.removeItem("userdata"); + localStorage.clear(); + this.iserv.logout(); this.router.navigate(["login"]); } diff --git a/BetterIServ.Mobile/src/app/app.routes.ts b/BetterIServ.Mobile/src/app/app.routes.ts index 83cbf65..a47c425 100644 --- a/BetterIServ.Mobile/src/app/app.routes.ts +++ b/BetterIServ.Mobile/src/app/app.routes.ts @@ -26,4 +26,12 @@ export const routes: Routes = [ path: 'substitution', loadComponent: () => import('./pages/substitution/substitution.page').then( m => m.SubstitutionPage) }, + { + path: 'tasks', + loadComponent: () => import('./pages/tasks/tasks.page').then( m => m.TasksPage) + }, + { + path: 'schedule', + loadComponent: () => import('./pages/schedule/schedule.page').then( m => m.SchedulePage) + }, ]; diff --git a/BetterIServ.Mobile/src/app/entities/course.ts b/BetterIServ.Mobile/src/app/entities/course.ts new file mode 100644 index 0000000..69c4155 --- /dev/null +++ b/BetterIServ.Mobile/src/app/entities/course.ts @@ -0,0 +1,16 @@ +export interface Course { + id: string; + name: string; + short: string; + color: string; +} + +export type Lesson = {course: string, room: string, week?: 'a' | 'b' | 'all'}; + +export interface Timetable { + mon: Lesson[]; + tue: Lesson[]; + wed: Lesson[]; + thu: Lesson[]; + fri: Lesson[]; +} diff --git a/BetterIServ.Mobile/src/app/pages/login/login.page.scss b/BetterIServ.Mobile/src/app/pages/login/login.page.scss index 30b6972..c2c439d 100644 --- a/BetterIServ.Mobile/src/app/pages/login/login.page.scss +++ b/BetterIServ.Mobile/src/app/pages/login/login.page.scss @@ -6,6 +6,7 @@ ion-card { width: 90%; + max-width: 700px; ion-card-content { display: flex; diff --git a/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.html b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.html new file mode 100644 index 0000000..77aad98 --- /dev/null +++ b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.html @@ -0,0 +1,185 @@ + + + + + + Stundenplan + + + + + + + + + + Abbrechen + + {{currentCourse?.name || "Neuer Kurs"}} + + Fertig + + + + + + Farbe + + + {{color.name}} + + + + + Name + + + + Kürzel + + + + Identifikator + + + + Kurs löschen + + + + + + + + + Abbrechen + + {{currentLesson?.lesson.course || "Neue Stunde"}} + + Fertig + + + + + + Kurs + + + {{course.name}} + + + + + Wochentyp + + Immer + Woche A + Woche B + + + + Raum + + + + + Wochentag + + + Montag + Dinstag + Mittwoch + Donnerstag + Freitag + + + + + Stunde + + + {{lessonName + 1}} + + + + + Stunde löschen + + + + + + + + + + + Stundenplan + + + +
+ Meine Kurse +
+ + + + {{course.short}} + + + + {{course.name}}
+ {{course.id}} +
+
+
+
+ +
+
+ Mo + Di + Mi + Do + Fr + + + + + {{findCourse(lesson?.course)?.short || "⠀ ⠀"}} + + + + {{lesson?.room || "⠀ ⠀"}} + + +
+
+
+ + + + + + + + + Stundenplan + + + + + + Kurse + + + + + + diff --git a/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.scss b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.scss new file mode 100644 index 0000000..d697a25 --- /dev/null +++ b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.scss @@ -0,0 +1,64 @@ +.course-content ion-item { + margin-bottom: 16px; +} + + +.icon { + margin-inline: auto; + aspect-ratio: 1; + width: max-content; + padding: 10px; + border-radius: 50%; + line-height: 20px; + + background-color: var(--background); + color: var(--foreground); +} + +.courses { + ion-item { + --background: transparent; + --border-color: transparent; + } + + .course-container { + display: grid; + gap: 16px; + margin: 16px; + grid-template-columns: repeat(3, 1fr); + + ion-card { + margin: 0; + } + + ion-item { + justify-content: center; + } + } +} + +.timetable { + display: flex; + justify-content: space-evenly; + gap: 5px; + padding-inline: 16px; + + div { + flex-grow: 1; + flex-basis: 0; + padding-block: 16px; + } + + ion-card { + margin: 10px 0 0; + width: 100%; + + ion-card-header, ion-card-content { + padding: 7px; + } + } +} + +.hide { + opacity: 0; +} diff --git a/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.ts b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.ts new file mode 100644 index 0000000..d8e3b53 --- /dev/null +++ b/BetterIServ.Mobile/src/app/pages/schedule/schedule.page.ts @@ -0,0 +1,129 @@ +import {Component, ElementRef, NgZone, OnInit, ViewChild} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import {IonicModule, IonModal} from '@ionic/angular'; +import {IServService} from "../../api/iserv.service"; +import {Course, Lesson, Timetable} from "../../entities/course"; +import {WeekPipe} from "../../pipes/week.pipe"; + +@Component({ + selector: 'app-schedule', + templateUrl: './schedule.page.html', + styleUrls: ['./schedule.page.scss'], + standalone: true, + imports: [IonicModule, CommonModule, FormsModule, WeekPipe] +}) +export class SchedulePage implements OnInit { + + public showCourses: boolean = false; + public courses: Course[] = []; + public currentCourse: Course; + public timetable: Timetable = {mon: [], tue: [], wed: [], thu: [], fri: []}; + public currentLesson: {lesson: Lesson, day: string, time: number}; + public rerender: boolean = false; + public colors: {name: string; val: string}[] = [ + {name: "Blau", val: "primary"}, + {name: "Hellblau", val: "secondary"}, + {name: "Lila", val: "tertiary"}, + {name: "Grün", val: "success"}, + {name: "Gelb", val: "warning"}, + {name: "Rot", val: "danger"} + ]; + + @ViewChild('courseModal') courseModal: IonModal; + @ViewChild('tableModal') tableModal: IonModal; + + constructor(private iserv: IServService) { } + + async ngOnInit() { + if (localStorage.getItem("courses") == undefined) { + const data = await this.iserv.getCoursesAndClass(); + + if (data.class.startsWith("Q")) { + for (let course of data.courses) { + const short = course.substring(1, 3); + const name = this.iserv.courseNames[short]; + if (name == undefined) continue; + this.courses.push({ + id: course, + short: short.toUpperCase(), + name: name, + color: this.colors[Math.floor(Math.random() * this.colors.length)].val + }); + } + this.courses.sort((a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + + localStorage.setItem("courses", JSON.stringify(this.courses)); + } + }else { + this.courses = JSON.parse(localStorage.getItem("courses")); + } + + if (localStorage.getItem("timetable") == undefined) { + for (let day of ['mon', 'tue', 'wed', 'thu', 'fri']) { + for (let i = 0; i < 10; i++) { + this.timetable[day].push(undefined); + } + } + localStorage.setItem("timetable", JSON.stringify(this.timetable)); + }else { + this.timetable = JSON.parse(localStorage.getItem("timetable")); + } + } + + public async onEditOrAdd(course?: Course, lesson?: {lesson: Lesson, day: string, time: number}) { + this.currentCourse = course; + this.currentLesson = lesson; + if (this.showCourses) await this.courseModal.present(); + else await this.tableModal.present(); + } + + public async updateOrCreateCourse(event: any) { + if (event.detail.role == "delete") { + this.courses.splice(this.courses.indexOf(this.currentCourse), 1); + } + + if (event.detail.role == "confirm") { + const data = event.detail.data as Course; + + if (this.currentCourse != undefined) { + this.courses[this.courses.indexOf(this.currentCourse)] = data; + delete this.currentCourse; + }else { + this.courses.push(data); + } + } + + this.courses.sort((a, b) => { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + return 0; + }); + localStorage.setItem("courses", JSON.stringify(this.courses)); + } + + public async updateOrCreateLesson(event: any) { + if (event.detail.role == "delete") { + delete this.timetable[this.currentLesson.day][this.currentLesson.time]; + } + + if (event.detail.role == "confirm") { + const data = event.detail.data as {lesson: Lesson, day: string, time: number}; + this.timetable[data.day][data.time] = data.lesson; + } + + localStorage.setItem("timetable", JSON.stringify(this.timetable)); + location.reload(); + } + + public findCourse(id: string): Course { + for (let course of this.courses) + if (course.id == id) return course; + return undefined; + } + +} diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss index 22c8c07..b9345dc 100644 --- a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss +++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.scss @@ -14,7 +14,7 @@ margin-left: auto; display: flex; flex-direction: column; - align-items: end; + align-items: flex-end; .type { font-size: 18px; diff --git a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts index df978e0..d6667e4 100644 --- a/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts +++ b/BetterIServ.Mobile/src/app/pages/substitution/substitution.page.ts @@ -5,6 +5,7 @@ import {AlertController, IonicModule} from '@ionic/angular'; import {UnitsService} from "../../api/units.service"; import {Substitution, UnitsData} from "../../entities/substitution"; import {IServService} from "../../api/iserv.service"; +import {Course} from "../../entities/course"; @Component({ selector: 'app-substitution', @@ -40,10 +41,31 @@ export class SubstitutionPage implements OnInit { this.data = await this.units.getSubstitutionPlan("today"); - const groups = await this.iserv.getGroups(); - for (let group of groups) { - if (!group.includes(".")) continue; - this.courses.push(group.split(".")[1]); + const data = await this.iserv.getCoursesAndClass(); + if (localStorage.getItem("class") == null) { + if (!data.class.startsWith("Q")) { + this.changeClass(data.class); + }else { + this.changeClass(data.class); + this.showOnlyCourses(true); + this.filterByClasses = true; + } + } + + if (data.class.startsWith("Q")) { + if (localStorage.getItem("courses") != undefined) { + const courses = JSON.parse(localStorage.getItem("courses")) as Course[]; + for (let course of courses) { + this.courses.push(course.id); + } + }else { + const alert = await this.alerts.create({ + header: "Achtung", + message: "Füge deine Kurse im Stundenplan hinzu um sie hier zu filtern!", + buttons: ["Ok"] + }); + await alert.present(); + } } } diff --git a/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.html b/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.html new file mode 100644 index 0000000..de2908d --- /dev/null +++ b/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.html @@ -0,0 +1,38 @@ + + + + + + Aufgaben + + + + + + + Aufgaben + + + Coming soon! + + + + + + + + + + Aktuelle Aufgaben + + + + + + Vergangene Aufgaben + + + + + + diff --git a/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.scss b/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.ts b/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.ts new file mode 100644 index 0000000..f4b60e2 --- /dev/null +++ b/BetterIServ.Mobile/src/app/pages/tasks/tasks.page.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +@Component({ + selector: 'app-tasks', + templateUrl: './tasks.page.html', + styleUrls: ['./tasks.page.scss'], + standalone: true, + imports: [IonicModule, CommonModule, FormsModule] +}) +export class TasksPage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/BetterIServ.Mobile/src/app/pipes/week.pipe.ts b/BetterIServ.Mobile/src/app/pipes/week.pipe.ts new file mode 100644 index 0000000..a779783 --- /dev/null +++ b/BetterIServ.Mobile/src/app/pipes/week.pipe.ts @@ -0,0 +1,35 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import {Lesson} from "../entities/course"; + +@Pipe({ + name: 'week', + standalone: true +}) +export class WeekPipe implements PipeTransform { + + transform(objects: Lesson[]): Lesson[] { + const week = this.getWeek(new Date()) % 2; + const label = week == 0 ? "a" : "b"; + const result = []; + for (let lesson of objects) { + if (lesson != undefined && (lesson.week == "all" || lesson.week == label)) + result.push(lesson); + else result.push(undefined); + } + + return result; + } + + private getWeek(orig: Date): number { + const date = new Date(orig.getTime()); + date.setHours(0, 0, 0, 0); + // Thursday in current week decides the year. + date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); + // January 4 is always in week 1. + const week1 = new Date(date.getFullYear(), 0, 4); + // Adjust to Thursday in week 1 and count number of weeks from date to week1. + return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 + - 3 + (week1.getDay() + 6) % 7) / 7); + } + +} diff --git a/BetterIServ.Mobile/src/assets/shapes.svg b/BetterIServ.Mobile/src/assets/shapes.svg deleted file mode 100644 index d370b4d..0000000 --- a/BetterIServ.Mobile/src/assets/shapes.svg +++ /dev/null @@ -1 +0,0 @@ -