From 90d5b5da1ce0d1ece8bc5088eff77ce078492707 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Fri, 16 May 2025 00:38:59 +0200 Subject: [PATCH 01/15] feat: Made login errors download from server --- src/app/login/login.component.ts | 14 +------------- src/app/types/status.ts | 3 ++- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index c5315ce..257917e 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -26,22 +26,10 @@ export class LoginComponent implements OnInit { } } - errorParser(err: any) { - switch (err.status) { - case 401: - this.error = "Zła nazwa użytkownika lub hasło" - break; - - default: - this.error = "Nieznany błąd" - break; - } - } - submit() { const val = this.form.value this.ac.login(val.uname, val.pass).pipe(catchError((err,caught)=>{ - this.errorParser(err) + this.error = err.error.message return throwError(() => new Error(err.message)) })).subscribe((data) => { this.ls.loggedIn = true diff --git a/src/app/types/status.ts b/src/app/types/status.ts index dd47250..faf2ff4 100644 --- a/src/app/types/status.ts +++ b/src/app/types/status.ts @@ -1,3 +1,4 @@ export interface Status { - status: number + status: number, + message?: string } From 7d98cc2c496e84f504a8352f171ea8da8406d280 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 20 May 2025 20:58:52 +0200 Subject: [PATCH 02/15] fix: Added serviceWorker env and fixed #19 --- angular.json | 24 ++++++++++++++++++++++++ ngsw-config.json | 14 +++++++------- src/app/app.module.ts | 3 ++- src/app/services/app-update.service.ts | 16 +++++++++++++++- src/environments/environment.swdev.ts | 5 +++++ 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 src/environments/environment.swdev.ts diff --git a/angular.json b/angular.json index 2ceac64..718930c 100644 --- a/angular.json +++ b/angular.json @@ -70,6 +70,30 @@ "with": "src/environments/environment.development.ts" } ] + }, + "swDevelopment": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true, + "outputHashing": "all", + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.swdev.ts" + } + ], + "assets": [ + { + "glob": "ngsw-worker.js", + "input": "node_modules/@angular/servce-worker", + "output": "." + }, + "src/ngsw.json", + "src/manifest.webmanifest" + ] } }, "defaultConfiguration": "production" diff --git a/ngsw-config.json b/ngsw-config.json index 07fb61d..f25973d 100644 --- a/ngsw-config.json +++ b/ngsw-config.json @@ -1,16 +1,16 @@ { "$schema": "./node_modules/@angular/service-worker/config/schema.json", - "index": "./index.html", + "index": "/ipwa/index.html", "assetGroups": [ { "name": "app", "installMode": "prefetch", "resources": { "files": [ - "/favicon.ico", - "/manifest.webmanifest", - "/*.css", - "/*.js" + "/ipwa/favicon.ico", + "/ipwa/manifest.webmanifest", + "/ipwa/*.css", + "/ipwa/*.js" ] } }, @@ -20,8 +20,8 @@ "updateMode": "prefetch", "resources": { "files": [ - "./assets/**", - "/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" + "/ipwa/assets/**", + "/ipwa/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" ] } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6bb5523..9cd3082 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -78,6 +78,7 @@ import { AttendenceComponent } from './admin-view/grades/attendence/attendence.c import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component'; import { HourDisplayComponent } from './admin-view/grades/attendence-summary/hour-display/hour-display.component'; import { AboutComponent } from './app-view/personal/about/about.component'; +import { environment } from 'src/environments/environment'; @NgModule({ declarations: [ @@ -160,7 +161,7 @@ import { AboutComponent } from './app-view/personal/about/about.component'; A11yModule, MatAutocompleteModule, ServiceWorkerModule.register('ngsw-worker.js', { - enabled: !isDevMode(), + enabled: environment.production, // Register the ServiceWorker as soon as the application is stable // or after 30 seconds (whichever comes first). registrationStrategy: 'registerWhenStable:30000' diff --git a/src/app/services/app-update.service.ts b/src/app/services/app-update.service.ts index 07d4763..7384f00 100644 --- a/src/app/services/app-update.service.ts +++ b/src/app/services/app-update.service.ts @@ -8,7 +8,21 @@ import { catchError, concat, first, from, interval, tap, throwError } from 'rxjs }) export class AppUpdateService implements OnInit { - constructor(readonly appRef: ApplicationRef, readonly update: SwUpdate, readonly sb: MatSnackBar) { } + constructor(readonly appRef: ApplicationRef, readonly update: SwUpdate, readonly sb: MatSnackBar) { + this.update.versionUpdates.subscribe((evt) => { + switch (evt.type) { + case 'VERSION_DETECTED': + console.log(`Downloading ${evt.version.hash}`); + break; + case 'VERSION_READY': + console.log(`Current: ${evt.currentVersion.hash}, new: ${evt.latestVersion.hash}`); + break; + case 'VERSION_INSTALLATION_FAILED': + console.error(`Failed to install ${evt.version.hash}: ${evt.error}`); + break; + } + }) + } ngOnInit(): void { const appIsStable = this.appRef.isStable.pipe(first(isStable => isStable === true)) diff --git a/src/environments/environment.swdev.ts b/src/environments/environment.swdev.ts new file mode 100644 index 0000000..35fd6cc --- /dev/null +++ b/src/environments/environment.swdev.ts @@ -0,0 +1,5 @@ +export const environment = { + apiEndpoint: `http://localhost:12230`, + version: "testing (swDev)", + production: true +}; From 26dac21e7eece7d0280e42db38a802b5015728a8 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 20 May 2025 21:10:21 +0200 Subject: [PATCH 03/15] fix: Added missing news message. Resolves #18. --- src/app/admin-view/news-edit/news-edit.component.html | 5 +++++ src/app/admin-view/news-edit/news-edit.component.scss | 4 ++++ src/app/app-view/news/news.component.html | 5 +++++ src/app/app-view/news/news.component.scss | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/src/app/admin-view/news-edit/news-edit.component.html b/src/app/admin-view/news-edit/news-edit.component.html index 6ad5dfc..b2525c3 100644 --- a/src/app/admin-view/news-edit/news-edit.component.html +++ b/src/app/admin-view/news-edit/news-edit.component.html @@ -22,4 +22,9 @@

{{item.date | date:'d-LL-yyyy HH:mm'}}

+ + +

+ Brak wiadomości. +

\ No newline at end of file diff --git a/src/app/admin-view/news-edit/news-edit.component.scss b/src/app/admin-view/news-edit/news-edit.component.scss index 616c118..efcf568 100644 --- a/src/app/admin-view/news-edit/news-edit.component.scss +++ b/src/app/admin-view/news-edit/news-edit.component.scss @@ -22,6 +22,10 @@ mat-card-content p { white-space: pre-line; } +mat-card p { + margin: 15px; +} + button { margin-right: 4pt; } diff --git a/src/app/app-view/news/news.component.html b/src/app/app-view/news/news.component.html index 85fa33f..1caebb4 100644 --- a/src/app/app-view/news/news.component.html +++ b/src/app/app-view/news/news.component.html @@ -9,4 +9,9 @@

{{item.date | date:'d-LL-yyyy HH:mm'}}

+ + +

+ Brak wiadomości. +

\ No newline at end of file diff --git a/src/app/app-view/news/news.component.scss b/src/app/app-view/news/news.component.scss index 6a81a9f..df4b130 100644 --- a/src/app/app-view/news/news.component.scss +++ b/src/app/app-view/news/news.component.scss @@ -29,3 +29,7 @@ mat-card-footer p { mat-card-content p { white-space: pre-line; } + +mat-card p { + margin: 15px; +} \ No newline at end of file From 92768ceda6a6a9c8f9a6b0655e41c34e5fc9193b Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 20 May 2025 21:22:49 +0200 Subject: [PATCH 04/15] fix: The date picker now outputs start of day --- src/app/admin-view/menu-new/menu-add/menu-add.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/admin-view/menu-new/menu-add/menu-add.component.ts b/src/app/admin-view/menu-new/menu-add/menu-add.component.ts index d320ab8..ff9c0fc 100644 --- a/src/app/admin-view/menu-new/menu-add/menu-add.component.ts +++ b/src/app/admin-view/menu-new/menu-add/menu-add.component.ts @@ -31,7 +31,7 @@ export class MenuAddComponent { submit() { switch (this.type) { case "day": - this.dialogRef.close({type: "day", value: this.day.utc()}) + this.dialogRef.close({type: "day", value: this.day.utc().startOf('day')}) break; case "week": this.dialogRef.close({type: "week", value: {start: this.range.value.start?.utc().hours(24), count: 5}}) From 45fb44712ecba57a8c20c4c46cd4281d9d92b962 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 20 May 2025 22:02:51 +0200 Subject: [PATCH 05/15] fix: Made menu empty if no items. Not too elegant of a solution, but works. Going to do the same in print display of backend. Probably not gonna be elegant aswell. --- .../admin-view/menu-new/menu-new.component.ts | 4 ++-- src/app/app-view/menu/menu.component.html | 6 +++--- src/app/app-view/menu/menu.component.ts | 21 +++++++++++++++++-- src/app/types/menu.ts | 4 ++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/app/admin-view/menu-new/menu-new.component.ts b/src/app/admin-view/menu-new/menu-new.component.ts index 41361c0..41a2e49 100644 --- a/src/app/admin-view/menu-new/menu-new.component.ts +++ b/src/app/admin-view/menu-new/menu-new.component.ts @@ -101,11 +101,11 @@ export class MenuNewComponent { } editSn(id: string) { - this.ac.menu.editSn(id, this.dataSource.data.find(v => v._id == id)?.sn).subscribe(s => this.refreshIfGood(s)) + this.ac.menu.editSn(id, this.dataSource.data.find(v => v._id == id)!.sn).subscribe(s => this.refreshIfGood(s)) } editOb(id: string) { - this.ac.menu.editOb(id, this.dataSource.data.find(v => v._id == id)?.ob).subscribe(s => this.refreshIfGood(s)) + this.ac.menu.editOb(id, this.dataSource.data.find(v => v._id == id)!.ob).subscribe(s => this.refreshIfGood(s)) } editKol(id: string) { diff --git a/src/app/app-view/menu/menu.component.html b/src/app/app-view/menu/menu.component.html index 269bcda..8810f42 100644 --- a/src/app/app-view/menu/menu.component.html +++ b/src/app/app-view/menu/menu.component.html @@ -13,8 +13,8 @@
  • {{i}}
  • -
  • {{i.charAt(0).toUpperCase()+i.substring(1)}}
  • -
  • {{getsn.second.charAt(0).toUpperCase()+getsn.second.substring(1)}}
  • +
  • {{capitalize(i)}}
  • +
  • {{capitalize(getsn.second)}}
@@ -51,7 +51,7 @@ - + Brak danych, wybierz inny dzień. diff --git a/src/app/app-view/menu/menu.component.ts b/src/app/app-view/menu/menu.component.ts index d7fa30c..ca0eb09 100644 --- a/src/app/app-view/menu/menu.component.ts +++ b/src/app/app-view/menu/menu.component.ts @@ -32,17 +32,34 @@ export class MenuComponent { } menu?: Menu; - get getsn() {return (this.menu && this.menu.sn) ? this.menu.sn : null} - get getob() {return (this.menu && this.menu.ob) ? this.menu.ob : null} + get getsn() {return (this.menu && this.checkIfAnyProperty(this.menu.sn)) ? this.menu.sn : null} + get getob() {return (this.menu && this.checkIfAnyProperty(this.menu.ob)) ? this.menu.ob : null} get getkol() {return (this.menu && this.menu.kol) ? this.menu.kol : null} get gettitle() {return (this.menu && this.menu.dayTitle && this.menu.dayTitle != "") ? this.menu.dayTitle : null} + private checkIfAnyProperty(obj: { [x: string]: string | string[];}) { + for (let i in obj) { + if (Array.isArray(obj[i])) { + if (obj[i].length > 0) return true + } else { + if (!!obj[i]) return true + } + } + return false + } + + capitalize(str: string) { + return str.charAt(0).toUpperCase()+str.substring(1) + } + updateMenu(silent?: boolean) { this.loading = !silent if (!silent) this.menu = undefined this.uc.getMenu(this.day).subscribe(m => { this.loading = false this.menu = m + console.log(m); + }) } diff --git a/src/app/types/menu.ts b/src/app/types/menu.ts index 4b79dd3..9cc5c00 100644 --- a/src/app/types/menu.ts +++ b/src/app/types/menu.ts @@ -3,11 +3,11 @@ import { Moment } from "moment"; export interface Menu { _id: string; day: Moment; - sn?: { + sn: { fancy: string[]; second: string; }; - ob?: { + ob: { soup: string; vege: string; meal: string; From cf2fa0b6079c54f9e6c533253ebba92479a55b57 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Wed, 21 May 2025 19:56:25 +0200 Subject: [PATCH 06/15] fix: Redesigned user cards --- .../account-mgmt/account-mgmt.component.html | 14 +- .../account-mgmt/account-mgmt.component.ts | 75 +------- .../user-edit/user-edit.component.html | 104 +++++++---- .../user-edit/user-edit.component.scss | 24 ++- .../user-edit/user-edit.component.ts | 173 ++++++++++++++---- src/app/admin-view/admin-comm.service.ts | 10 +- src/app/types/user.ts | 3 + 7 files changed, 247 insertions(+), 156 deletions(-) diff --git a/src/app/admin-view/account-mgmt/account-mgmt.component.html b/src/app/admin-view/account-mgmt/account-mgmt.component.html index cba85bc..27dad79 100644 --- a/src/app/admin-view/account-mgmt/account-mgmt.component.html +++ b/src/app/admin-view/account-mgmt/account-mgmt.component.html @@ -3,7 +3,7 @@ Wyszukaj - + @@ -24,17 +24,9 @@
-
+ diff --git a/src/app/admin-view/account-mgmt/account-mgmt.component.ts b/src/app/admin-view/account-mgmt/account-mgmt.component.ts index 8d6be8c..45c43b8 100644 --- a/src/app/admin-view/account-mgmt/account-mgmt.component.ts +++ b/src/app/admin-view/account-mgmt/account-mgmt.component.ts @@ -4,10 +4,7 @@ import { MatDialog } from '@angular/material/dialog'; import { MatTableDataSource } from '@angular/material/table'; import { MatPaginator } from '@angular/material/paginator'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { UserDeleteComponent } from './user-delete/user-delete.component'; import { UserEditComponent } from './user-edit/user-edit.component'; -import { catchError, throwError } from 'rxjs'; -import { UserResetComponent } from './user-reset/user-reset.component'; import { LocalStorageService } from 'src/app/services/local-storage.service'; import { Group } from 'src/app/types/group'; import User from 'src/app/types/user'; @@ -57,75 +54,9 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit { this.users.filter = value.toLowerCase().trim() } - edit(item: any) { - this.dialog.open(UserEditComponent, {data: {user: item, groups: this.groups}}).afterClosed().subscribe(reply => { - if (reply) { - this.ac.accs.putAcc(item._id, reply).pipe(catchError((err)=>{ - this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") - return throwError(()=> new Error(err.message)) - })).subscribe((data)=> { - if (data.status == 200) { - this.sb.open("Użytkownik został zmodyfikowany.", undefined, {duration: 2500}) - this.ngOnInit() - } else { - this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") - } - }) - } - }) - } - - new() { - this.dialog.open(UserEditComponent, {data: {groups: this.groups}}).afterClosed().subscribe(reply => { - if (reply) { - this.ac.accs.postAcc(reply).pipe(catchError((err)=>{ - this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") - return throwError(()=> new Error(err.message)) - })).subscribe((data)=> { - if (data.status == 201) { - this.sb.open("Użytkownik został utworzony.", undefined, {duration: 2500}) - this.ngOnInit() - } else { - this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") - } - }) - } - }) - } - - delete(id: string) { - this.dialog.open(UserDeleteComponent).afterClosed().subscribe(reply => { - if (reply) { - this.ac.accs.deleteAcc(id).subscribe((res) => { - if (res.status == 200) { - this.sb.open("Użytkownik został usunięty.", undefined, {duration: 2500}) - this.ngOnInit() - } else { - this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") - console.error(res); - } - }) - } - }) - } - - resetPass(id: string) { - this.dialog.open(UserResetComponent).afterClosed().subscribe((res) => { - if (res == true) { - this.ac.accs.resetPass(id).subscribe((patch)=>{ - if (patch.status == 200) { - this.sb.open("Hasło zostało zresetowane", undefined, {duration: 2500}) - } - }) - } - }) - } - - toggleLock(item: any) { - this.ac.accs.putAcc(item._id, {locked: !item.locked}).subscribe((res) => { - if (res.status == 200) { - item.locked = !item.locked - } + openUserCard(id?: string) { + this.dialog.open(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => { + if (r) this.ngOnInit() }) } diff --git a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.html b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.html index 0d278b4..baad3aa 100644 --- a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.html +++ b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.html @@ -1,39 +1,67 @@ - - - Imię - - - - Nazwisko - - - - Pokój - - - - Nazwa użytkownika - - - - Grupy - - @for (item of groups; track $index) { - {{item.name}} +

Karta użytkownika

+ + +
+ + Imię + + + + Nazwisko + + + + Pokój + + + + Grupy + + @for (item of groups; track $index) { + {{item.name}} + } + + + Data rejestracji:
{{regDate?.format('DD.MM.YYYY')}}
+
+
+ + Nazwa użytkownika + + + @if (data.type == "edit") { + + @if (locked) { + + } @else { + + } + @if (lockout) { + + } @else { + + } + + Uprawnienia + + Wiadomości + Jadłospis + Powiadomienia + Grupy + Konta + Klucze + Czystość + + } - - - - Uprawnienia - - Wiadomości - Jadłospis - Powiadomienia - Grupy - Konta - Klucze - Czystość - - - - \ No newline at end of file +
+ +
+ + @if (data.type == "edit") { + + } + + + + \ No newline at end of file diff --git a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.scss b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.scss index e269236..9290470 100644 --- a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.scss +++ b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.scss @@ -4,7 +4,29 @@ } form { + margin-top: 1ch !important; display: flex; - flex-direction: column; + grid-auto-flow: column; + flex-direction: row; + flex-wrap: wrap; align-items: center; + justify-content: center; + column-gap: 3ch; + div { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(5, 1fr); + align-items: center; + button { + align-self: stretch; + justify-self: stretch; + height: auto; + margin-bottom: 1lh; + } + } +} + +mat-dialog-actions { + display: flex; + justify-content: flex-end; } \ No newline at end of file diff --git a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts index 76bdc20..09d8454 100644 --- a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts +++ b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts @@ -1,8 +1,15 @@ import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { FormControl, FormGroup } from '@angular/forms'; import { LocalStorageService } from 'src/app/services/local-storage.service'; import { Group } from 'src/app/types/group'; +import { AdminCommService } from '../../admin-comm.service'; +import { UserDeleteComponent } from '../user-delete/user-delete.component'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { UserResetComponent } from '../user-reset/user-reset.component'; +import { catchError, throwError } from 'rxjs'; +import { Moment } from 'moment'; +import * as moment from 'moment'; @Component({ selector: 'app-user-edit', @@ -10,43 +17,105 @@ import { Group } from 'src/app/types/group'; styleUrls: ['./user-edit.component.scss'] }) export class UserEditComponent { - form: FormGroup + lockout = false; + locked = false; + loading = false; + form: FormGroup = new FormGroup({ + fname: new FormControl(""), + surname: new FormControl(""), + room: new FormControl(""), + uname: new FormControl(""), + groups: new FormControl>([]), + flags: new FormControl>([]), + }) groups: Group[] - constructor (public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, readonly ls: LocalStorageService) { - if (data.user == null) { - data.user = { - fname: "", - surname: "", - room: "", - uname: "", - groups: [], - admin: 0 + id?: string + regDate?: Moment; + constructor ( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ({type: "edit", id: string} | {type: "new"}) & {groups: Group[]}, + readonly ls: LocalStorageService, + readonly acu: AdminCommService, + private dialog: MatDialog, + private sb: MatSnackBar + ) { + this.groups = data.groups + if (data.type == "edit") { + this.id = data.id + this.acu.accs.getUser(data.id).subscribe((r) => { + this.regDate = moment(r.regDate) + var flags: Array = [] + if (r.admin) { + if ((r.admin & 1) == 1) flags.push(1) + if ((r.admin & 2) == 2) flags.push(2) + if ((r.admin & 4) == 4) flags.push(4) + if ((r.admin & 8) == 8) flags.push(8) + if ((r.admin & 16) == 16) flags.push(16) + if ((r.admin & 32) == 32) flags.push(32) + if ((r.admin & 64) == 64) flags.push(64) + if ((r.admin & 128) == 128) flags.push(128) + } + this.locked = r.locked ? true : false + this.lockout = r.lockout + this.form.get("fname")?.setValue(r.fname) + this.form.get("surname")?.setValue(r.surname) + this.form.get("room")?.setValue(r.room) + this.form.get("uname")?.setValue(r.uname) + this.form.get("groups")?.setValue(r.groups) + this.form.get("flags")?.setValue(flags) + }) + } + } + + protected submit() { + this.loading = true + if (this.data.type == "edit") { + this.acu.accs.putAcc(this.id!, this.getForm()).pipe(catchError((err)=>{ + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + return throwError(()=> new Error(err.message)) + })).subscribe((data)=> { + if (data.status == 200) { + this.sb.open("Użytkownik został zmodyfikowany.", undefined, {duration: 2500}) + this.dialogRef.close(true) + } else { + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + this.loading = false + } + }) + } else { + this.acu.accs.postAcc(this.getForm()).pipe(catchError((err)=>{ + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + return throwError(()=> new Error(err.message)) + })).subscribe((data)=> { + if (data.status == 201) { + this.sb.open("Użytkownik został utworzony.", undefined, {duration: 2500}) + this.dialogRef.close(true) + } else { + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + this.loading = false + } + }) + } + } + + protected disableLockout() { + this.loading = true + this.acu.accs.clearLockout(this.id!).pipe(catchError((err)=>{ + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + return throwError(()=> new Error(err.message)) + })).subscribe((s) => { + if (s.status == 200) { + this.loading = false + this.lockout = false + } else { + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + this.loading = false } - } - this.groups = data.groups ? data.groups : [] - var flags: Array = [] - if (data.user.admin) { - if ((data.user.admin & 1) == 1) flags.push(1) - if ((data.user.admin & 2) == 2) flags.push(2) - if ((data.user.admin & 4) == 4) flags.push(4) - if ((data.user.admin & 8) == 8) flags.push(8) - if ((data.user.admin & 16) == 16) flags.push(16) - if ((data.user.admin & 32) == 32) flags.push(32) - if ((data.user.admin & 64) == 64) flags.push(64) - if ((data.user.admin & 128) == 128) flags.push(128) - } - this.form = new FormGroup({ - fname: new FormControl(data.user.fname), - surname: new FormControl(data.user.surname), - room: new FormControl(data.user.room), - uname: new FormControl(data.user.uname), - groups: new FormControl>(data.user.groups), - flags: new FormControl>(flags), }) } - protected editUser() { - this.dialogRef.close({ + protected getForm() { + return { fname: this.form.get('fname')?.value, surname: this.form.get('surname')?.value, room: this.form.get('room')?.value, @@ -60,6 +129,44 @@ export class UserEditComponent { return undefined } })() + } + } + + protected delete() { + this.dialog.open(UserDeleteComponent).afterClosed().subscribe(reply => { + if (reply) { + this.acu.accs.deleteAcc(this.id!).subscribe((res) => { + if (res.status == 200) { + this.sb.open("Użytkownik został usunięty.", undefined, {duration: 2500}) + this.dialogRef.close() + } else { + this.sb.open("Wystąpił błąd. Skontaktuj się z obsługą programu.") + console.error(res); + } + }) + } + }) + } + + protected resetPass() { + this.loading = true + this.dialog.open(UserResetComponent).afterClosed().subscribe((res) => { + if (res == true) { + this.acu.accs.resetPass(this.id!).subscribe((patch)=>{ + if (patch.status == 200) { + this.sb.open("Hasło zostało zresetowane", undefined, {duration: 2500}) + this.loading = false + } + }) + } + }) + } + + protected toggleLock(state: boolean) { + this.acu.accs.putAcc(this.id!, {locked: state}).subscribe((res) => { + if (res.status == 200) { + this.locked = state + } }) } } diff --git a/src/app/admin-view/admin-comm.service.ts b/src/app/admin-view/admin-comm.service.ts index f81e9c2..bee8779 100644 --- a/src/app/admin-view/admin-comm.service.ts +++ b/src/app/admin-view/admin-comm.service.ts @@ -131,7 +131,7 @@ export class AdminCommService { return this.http.post(environment.apiEndpoint+`/admin/accs`, item, {withCredentials: true}) }, - putAcc: (id: string, update: object) => { + putAcc: (id: string, update: Partial) => { return this.http.put(environment.apiEndpoint+`/admin/accs/${id}`, update, {withCredentials: true}) }, @@ -141,6 +141,14 @@ export class AdminCommService { deleteAcc: (id: string) => { return this.http.delete(environment.apiEndpoint+`/admin/accs/${id}`, {withCredentials: true}) + }, + + getUser: (id: string) => { + return this.http.get & {lockout: boolean}>(environment.apiEndpoint+`/admin/accs/${id}`, {withCredentials: true}) + }, + + clearLockout: (id: string) => { + return this.http.delete(environment.apiEndpoint+`/admin/accs/${id}/lockout`, {withCredentials: true}) } } //#endregion diff --git a/src/app/types/user.ts b/src/app/types/user.ts index 6d10c36..00b5aa2 100644 --- a/src/app/types/user.ts +++ b/src/app/types/user.ts @@ -1,3 +1,5 @@ +import { Moment } from "moment"; + export default interface User { _id: string; uname: string; @@ -8,4 +10,5 @@ export default interface User { fname?: string; surname?: string; groups: string[]; + regDate: Moment; } \ No newline at end of file From 86347e254b7a4ee65b5573bb1c1b47fd191950a0 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sat, 24 May 2025 11:26:42 +0200 Subject: [PATCH 07/15] feat: Added redirect after login for users. Closes #17 --- .../account-mgmt/account-mgmt.component.ts | 2 +- .../user-edit/user-edit.component.ts | 11 ++++-- src/app/admin.guard.ts | 2 +- .../personal/extra/extra.component.html | 14 +++++++ .../personal/extra/extra.component.scss | 0 .../personal/extra/extra.component.spec.ts | 23 +++++++++++ .../personal/extra/extra.component.ts | 29 ++++++++++++++ .../extra/redirect/redirect.component.html | 13 +++++++ .../extra/redirect/redirect.component.scss | 0 .../extra/redirect/redirect.component.spec.ts | 23 +++++++++++ .../extra/redirect/redirect.component.ts | 20 ++++++++++ .../app-view/personal/personal.component.html | 4 ++ .../app-view/personal/personal.component.ts | 5 +++ src/app/app.module.ts | 4 ++ src/app/login/login.component.ts | 4 +- src/app/services/auth.client.ts | 38 ++++++++++++++++--- src/app/services/local-storage.service.ts | 18 ++++----- src/app/types/user.ts | 1 + 18 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 src/app/app-view/personal/extra/extra.component.html create mode 100644 src/app/app-view/personal/extra/extra.component.scss create mode 100644 src/app/app-view/personal/extra/extra.component.spec.ts create mode 100644 src/app/app-view/personal/extra/extra.component.ts create mode 100644 src/app/app-view/personal/extra/redirect/redirect.component.html create mode 100644 src/app/app-view/personal/extra/redirect/redirect.component.scss create mode 100644 src/app/app-view/personal/extra/redirect/redirect.component.spec.ts create mode 100644 src/app/app-view/personal/extra/redirect/redirect.component.ts diff --git a/src/app/admin-view/account-mgmt/account-mgmt.component.ts b/src/app/admin-view/account-mgmt/account-mgmt.component.ts index 45c43b8..274e87a 100644 --- a/src/app/admin-view/account-mgmt/account-mgmt.component.ts +++ b/src/app/admin-view/account-mgmt/account-mgmt.component.ts @@ -55,7 +55,7 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit { } openUserCard(id?: string) { - this.dialog.open(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => { + this.dialog.open(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => { if (r) this.ngOnInit() }) } diff --git a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts index 09d8454..64d05a3 100644 --- a/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts +++ b/src/app/admin-view/account-mgmt/user-edit/user-edit.component.ts @@ -11,12 +11,17 @@ import { catchError, throwError } from 'rxjs'; import { Moment } from 'moment'; import * as moment from 'moment'; +export namespace UserEditComponent { + export type InputData = {type: "new" | "edit", id?: string, groups: Group[]} + export type ReturnData = true | undefined +} + @Component({ selector: 'app-user-edit', templateUrl: './user-edit.component.html', styleUrls: ['./user-edit.component.scss'] }) -export class UserEditComponent { +export class UserEditComponent { lockout = false; locked = false; loading = false; @@ -33,7 +38,7 @@ export class UserEditComponent { regDate?: Moment; constructor ( public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: ({type: "edit", id: string} | {type: "new"}) & {groups: Group[]}, + @Inject(MAT_DIALOG_DATA) public data: UserEditComponent.InputData, readonly ls: LocalStorageService, readonly acu: AdminCommService, private dialog: MatDialog, @@ -42,7 +47,7 @@ export class UserEditComponent { this.groups = data.groups if (data.type == "edit") { this.id = data.id - this.acu.accs.getUser(data.id).subscribe((r) => { + this.acu.accs.getUser(data.id!).subscribe((r) => { this.regDate = moment(r.regDate) var flags: Array = [] if (r.admin) { diff --git a/src/app/admin.guard.ts b/src/app/admin.guard.ts index 1c35f29..047b1e1 100644 --- a/src/app/admin.guard.ts +++ b/src/app/admin.guard.ts @@ -4,6 +4,6 @@ import { LocalStorageService } from './services/local-storage.service'; export const adminGuard: CanActivateChildFn = (childRoute, state) => { const router = inject(Router) - if (inject(LocalStorageService).admin == false) return router.parseUrl('/') + if (inject(LocalStorageService).admin == undefined) return router.parseUrl('/') return true }; diff --git a/src/app/app-view/personal/extra/extra.component.html b/src/app/app-view/personal/extra/extra.component.html new file mode 100644 index 0000000..252f579 --- /dev/null +++ b/src/app/app-view/personal/extra/extra.component.html @@ -0,0 +1,14 @@ +

Dodatkowe ustawienia

+ + + @for (link of LINKS; track link) { + + } + + + + + \ No newline at end of file diff --git a/src/app/app-view/personal/extra/extra.component.scss b/src/app/app-view/personal/extra/extra.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app-view/personal/extra/extra.component.spec.ts b/src/app/app-view/personal/extra/extra.component.spec.ts new file mode 100644 index 0000000..f89f871 --- /dev/null +++ b/src/app/app-view/personal/extra/extra.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExtraComponent } from './extra.component'; + +describe('ExtraComponent', () => { + let component: ExtraComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExtraComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExtraComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/app-view/personal/extra/extra.component.ts b/src/app/app-view/personal/extra/extra.component.ts new file mode 100644 index 0000000..e36a40a --- /dev/null +++ b/src/app/app-view/personal/extra/extra.component.ts @@ -0,0 +1,29 @@ +import { ComponentType } from '@angular/cdk/portal'; +import { Component } from '@angular/core'; +import { Link } from 'src/app/types/link'; +import { RedirectComponent } from './redirect/redirect.component'; +import { MatDialog } from '@angular/material/dialog'; + +@Component({ + selector: 'app-extra', + templateUrl: './extra.component.html', + styleUrl: './extra.component.scss' +}) +export class ExtraComponent { + + constructor (private dialog: MatDialog) {} + + private readonly _LINKS: (Omit & {component: ComponentType})[] = [ + { title: "Domyślna strona po logowaniu", component: RedirectComponent, enabled: true, icon: "home" } + ] + + public get LINKS() { + return this._LINKS.filter((v) => { + return v.enabled + }); + } + + open(component: ComponentType) { + this.dialog.open(component) + } +} diff --git a/src/app/app-view/personal/extra/redirect/redirect.component.html b/src/app/app-view/personal/extra/redirect/redirect.component.html new file mode 100644 index 0000000..1dc4d9a --- /dev/null +++ b/src/app/app-view/personal/extra/redirect/redirect.component.html @@ -0,0 +1,13 @@ +

Domyślna strona po logowaniu

+ +

Wpisz link względem /ipwa/ w poniższym polu.

+

Przykład: /app/menu

+ + + Link + +
+ + + + \ No newline at end of file diff --git a/src/app/app-view/personal/extra/redirect/redirect.component.scss b/src/app/app-view/personal/extra/redirect/redirect.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app-view/personal/extra/redirect/redirect.component.spec.ts b/src/app/app-view/personal/extra/redirect/redirect.component.spec.ts new file mode 100644 index 0000000..af6541a --- /dev/null +++ b/src/app/app-view/personal/extra/redirect/redirect.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RedirectComponent } from './redirect.component'; + +describe('RedirectComponent', () => { + let component: RedirectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RedirectComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RedirectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/app-view/personal/extra/redirect/redirect.component.ts b/src/app/app-view/personal/extra/redirect/redirect.component.ts new file mode 100644 index 0000000..81d0eb4 --- /dev/null +++ b/src/app/app-view/personal/extra/redirect/redirect.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { AuthClient } from 'src/app/services/auth.client'; + +@Component({ + selector: 'app-redirect', + templateUrl: './redirect.component.html', + styleUrl: './redirect.component.scss' +}) +export class RedirectComponent { + protected redirect = "" + constructor (public dialogRef: MatDialogRef, private ac: AuthClient) { + this.redirect = ac.redirect + } + + protected save() { + this.ac.redirect = this.redirect + this.dialogRef.close() + } +} diff --git a/src/app/app-view/personal/personal.component.html b/src/app/app-view/personal/personal.component.html index c4ab40d..b3d43b1 100644 --- a/src/app/app-view/personal/personal.component.html +++ b/src/app/app-view/personal/personal.component.html @@ -30,6 +30,10 @@
Panel administracyjny
Poprzednio Tryb edycji
+ - {{title.getTitle()}} - - - - - @for (item of toolbar.menu; track $index) { - - } - + diff --git a/src/app/admin-view/admin-view.component.ts b/src/app/admin-view/admin-view.component.ts index 38fe929..717b90d 100644 --- a/src/app/admin-view/admin-view.component.ts +++ b/src/app/admin-view/admin-view.component.ts @@ -1,9 +1,7 @@ import { Component } from '@angular/core'; -import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { LocalStorageService } from '../services/local-storage.service'; import { Link } from '../types/link'; -import { ToolbarService } from './toolbar.service'; @Component({ selector: 'app-admin-view', @@ -26,7 +24,7 @@ export class AdminViewComponent { public get LINKS(): Link[] { return this._LINKS.filter(v => v.enabled); } - constructor(readonly title: Title, readonly router: Router, readonly ls: LocalStorageService, protected toolbar: ToolbarService) { } + constructor(readonly router: Router, readonly ls: LocalStorageService) { } goNormal() { this.router.navigateByUrl('app') } diff --git a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts index 900ab29..ccb30a4 100644 --- a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts +++ b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { ToolbarService } from '../../toolbar.service'; +import { ToolbarService } from '../../toolbar/toolbar.service'; import { Router, ActivatedRoute } from '@angular/router'; import { MatTableDataSource } from '@angular/material/table'; import { AdminCommService } from '../../admin-comm.service'; diff --git a/src/app/admin-view/grades/grades.component.ts b/src/app/admin-view/grades/grades.component.ts index 5c75ce1..de2a2d2 100644 --- a/src/app/admin-view/grades/grades.component.ts +++ b/src/app/admin-view/grades/grades.component.ts @@ -4,7 +4,7 @@ import * as moment from 'moment'; import { FormArray, FormBuilder } from '@angular/forms'; import { weekendFilter } from 'src/app/fd.da'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { ToolbarService } from '../toolbar.service'; +import { ToolbarService } from '../toolbar/toolbar.service'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { AttendenceComponent } from './attendence/attendence.component'; diff --git a/src/app/admin-view/grades/summary/summary.component.ts b/src/app/admin-view/grades/summary/summary.component.ts index 5f8b10d..1a0859d 100644 --- a/src/app/admin-view/grades/summary/summary.component.ts +++ b/src/app/admin-view/grades/summary/summary.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { ToolbarService } from '../../toolbar.service'; +import { ToolbarService } from '../../toolbar/toolbar.service'; import { ActivatedRoute, Router } from '@angular/router'; import { AdminCommService } from '../../admin-comm.service'; import * as moment from 'moment'; diff --git a/src/app/admin-view/notifications/notifications.component.html b/src/app/admin-view/notifications/notifications.component.html index 0be7e21..f4720b7 100644 --- a/src/app/admin-view/notifications/notifications.component.html +++ b/src/app/admin-view/notifications/notifications.component.html @@ -1,4 +1,3 @@ -
diff --git a/src/app/admin-view/notifications/notifications.component.ts b/src/app/admin-view/notifications/notifications.component.ts index 9b5de2e..1335e9b 100644 --- a/src/app/admin-view/notifications/notifications.component.ts +++ b/src/app/admin-view/notifications/notifications.component.ts @@ -1,20 +1,31 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { AdminCommService } from '../admin-comm.service'; import { Notification } from 'src/app/types/notification'; import { Group } from 'src/app/types/group'; import { LocalStorageService } from 'src/app/services/local-storage.service'; +import { ToolbarService } from '../toolbar/toolbar.service'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-notifications', templateUrl: './notifications.component.html', styleUrls: ['./notifications.component.scss'] }) -export class NotificationsComponent implements OnInit { +export class NotificationsComponent implements OnInit, OnDestroy { groups!: Group[] - constructor (private readonly acs: AdminCommService, readonly ls: LocalStorageService) { } + constructor (private readonly acs: AdminCommService, readonly ls: LocalStorageService, private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute ) { + this.toolbar.comp = this + this.toolbar.menu = [ + { title: "Wysłane", fn: "outbox", icon: "outbox" } + ] + } + + outbox() { + this.router.navigate(["outbox"], { relativeTo: this.route }) + } ngOnInit(): void { this.acs.notif.getGroups().subscribe((v) => { @@ -22,6 +33,15 @@ export class NotificationsComponent implements OnInit { }) } + ngOnDestroy(): void { + this.toolbar.comp = undefined + this.toolbar.menu = undefined + } + + public inbox() { + + } + success?: { sent: number; possible: number; }; form = new FormGroup({ diff --git a/src/app/admin-view/notifications/outbox/outbox.component.html b/src/app/admin-view/notifications/outbox/outbox.component.html new file mode 100644 index 0000000..cec270c --- /dev/null +++ b/src/app/admin-view/notifications/outbox/outbox.component.html @@ -0,0 +1,28 @@ +

Wysłane wiadomości:

+
+ @for (item of messages; track $index) { + + + + + {{item.message.title}} + + {{item.sentDate.format('[Wysłano] dddd DD MMMM YYYYr. o HH:mm')}} + + + +

+ {{item.message.body}} +

+
+
    + @for (user of item.rcpt; track $index) { +
  • + {{user.room}}: {{user.fname}} {{user.surname}} ({{user.uname}}) +
  • + } +
+
+
+ } +
\ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/outbox.component.scss b/src/app/admin-view/notifications/outbox/outbox.component.scss new file mode 100644 index 0000000..3879d78 --- /dev/null +++ b/src/app/admin-view/notifications/outbox/outbox.component.scss @@ -0,0 +1,10 @@ +.cardContainer { + display: flex; + flex-wrap: wrap; + gap: 1ch; + margin: 1ch; +} + +mat-card-title { + font-size: 24pt; +} \ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/outbox.component.spec.ts b/src/app/admin-view/notifications/outbox/outbox.component.spec.ts new file mode 100644 index 0000000..8802dca --- /dev/null +++ b/src/app/admin-view/notifications/outbox/outbox.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OutboxComponent } from './outbox.component'; + +describe('OutboxComponent', () => { + let component: OutboxComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OutboxComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OutboxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin-view/notifications/outbox/outbox.component.ts b/src/app/admin-view/notifications/outbox/outbox.component.ts new file mode 100644 index 0000000..8d87892 --- /dev/null +++ b/src/app/admin-view/notifications/outbox/outbox.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { AdminCommService } from '../../admin-comm.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ToolbarService } from '../../toolbar/toolbar.service'; +import * as moment from 'moment'; + +@Component({ + selector: 'app-outbox', + templateUrl: './outbox.component.html', + styleUrl: './outbox.component.scss' +}) +export class OutboxComponent implements OnInit { + + messages!: any[] + + constructor (private readonly acs: AdminCommService, private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute ) { + this.toolbar.comp = this + this.toolbar.menu = [ + { title: "Powiadomienia", fn: "goBack", icon: "arrow_back" } + ] + } + + goBack() { + this.router.navigate(['../'], {relativeTo: this.route}) + } + + ngOnInit(): void { + this.acs.notif.outbox.getSent().subscribe((v) => { + this.messages = v.map(i => { + return { + ...i, + sentDate: moment(i.sentDate) + } + }) + }) + } + +} diff --git a/src/app/admin-view/toolbar/toolbar.component.html b/src/app/admin-view/toolbar/toolbar.component.html new file mode 100644 index 0000000..24706ab --- /dev/null +++ b/src/app/admin-view/toolbar/toolbar.component.html @@ -0,0 +1,14 @@ + + + {{title.getTitle()}} + + + + + @for (item of _menu; track $index) { + + } + \ No newline at end of file diff --git a/src/app/admin-view/toolbar/toolbar.component.scss b/src/app/admin-view/toolbar/toolbar.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/admin-view/toolbar/toolbar.component.spec.ts b/src/app/admin-view/toolbar/toolbar.component.spec.ts new file mode 100644 index 0000000..a699881 --- /dev/null +++ b/src/app/admin-view/toolbar/toolbar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToolbarComponent } from './toolbar.component'; + +describe('ToolbarComponent', () => { + let component: ToolbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ToolbarComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ToolbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin-view/toolbar/toolbar.component.ts b/src/app/admin-view/toolbar/toolbar.component.ts new file mode 100644 index 0000000..137863c --- /dev/null +++ b/src/app/admin-view/toolbar/toolbar.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { MatDrawer } from '@angular/material/sidenav'; +import { Title } from '@angular/platform-browser'; +import { ToolbarService } from './toolbar.service'; +import { MatMenuTrigger } from '@angular/material/menu'; + +@Component({ + selector: 'app-toolbar', + templateUrl: './toolbar.component.html', + styleUrl: './toolbar.component.scss' +}) +export class ToolbarComponent { + @Input() drawer!: MatDrawer; + @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; + + protected _menu?: typeof this.toolbar.menu + + constructor(readonly title: Title, protected toolbar: ToolbarService) { + + } + + openMenu () { + this._menu = this.toolbar.menu + this.trigger.openMenu() + } +} diff --git a/src/app/admin-view/toolbar.service.spec.ts b/src/app/admin-view/toolbar/toolbar.service.spec.ts similarity index 100% rename from src/app/admin-view/toolbar.service.spec.ts rename to src/app/admin-view/toolbar/toolbar.service.spec.ts diff --git a/src/app/admin-view/toolbar.service.ts b/src/app/admin-view/toolbar/toolbar.service.ts similarity index 57% rename from src/app/admin-view/toolbar.service.ts rename to src/app/admin-view/toolbar/toolbar.service.ts index 80c69ff..6bd02da 100644 --- a/src/app/admin-view/toolbar.service.ts +++ b/src/app/admin-view/toolbar/toolbar.service.ts @@ -6,9 +6,6 @@ import { Injectable } from '@angular/core'; export class ToolbarService { public comp?: any; - public menu?: {title: string, check: boolean, icon?: string, fn: string}[] - - constructor() { } - - + public menu?: {title: string, check?: boolean, icon?: string, fn: string}[] + } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c27adbb..4504679 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -19,6 +19,7 @@ import { SummaryComponent } from './admin-view/grades/summary/summary.component' import { SettingsComponent } from './admin-view/settings/settings.component'; import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component'; import { NotificationsComponent } from './admin-view/notifications/notifications.component'; +import { OutboxComponent } from './admin-view/notifications/outbox/outbox.component'; const routes: Routes = [ {path: "", redirectTo: "login", pathMatch: "full"}, @@ -33,7 +34,10 @@ const routes: Routes = [ {path: "news", title: "Edytowanie wiadomości", component: NewsEditComponent}, {path: "menu", title: "Edytowanie jadłospisu", component: MenuNewComponent}, {path: "accounts", title: "Użytkownicy", component: AccountMgmtComponent}, - {path: "notifications", title: "Powiadomienia", component: NotificationsComponent}, + {path: "notifications", children: [ + {path: "", pathMatch: "full", title: "Powiadomienia", component: NotificationsComponent}, + {path: "outbox", title: "Wysłane", component: OutboxComponent} + ]}, {path: "groups", title: "Grupy", component: GroupsComponent}, {path: "keys", title: "Klucze", component: AdminKeyComponent}, {path: "grades", children: [ diff --git a/src/app/app-view/personal/extra/redirect/redirect.component.html b/src/app/app-view/personal/extra/redirect/redirect.component.html index 1dc4d9a..caed671 100644 --- a/src/app/app-view/personal/extra/redirect/redirect.component.html +++ b/src/app/app-view/personal/extra/redirect/redirect.component.html @@ -1,7 +1,8 @@ -

Domyślna strona po logowaniu

+

Domyślna strona po logowaniu

Wpisz link względem /ipwa/ w poniższym polu.

Przykład: /app/menu

+

Jeśli nie wiesz co tu wpisać, najlepiej nie zmieniaj tego ustawienia

Link diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 229946c..94a5065 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -81,6 +81,8 @@ import { AboutComponent } from './app-view/personal/about/about.component'; import { environment } from 'src/environments/environment'; import { ExtraComponent } from './app-view/personal/extra/extra.component'; import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.component'; +import { OutboxComponent } from './admin-view/notifications/outbox/outbox.component'; +import { ToolbarComponent } from './admin-view/toolbar/toolbar.component'; @NgModule({ declarations: [ @@ -126,6 +128,8 @@ import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.c AboutComponent, ExtraComponent, RedirectComponent, + OutboxComponent, + ToolbarComponent, ], imports: [ BrowserModule, From efd76e16a19332a70467446d800ef657b75bae34 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sat, 31 May 2025 19:56:31 +0200 Subject: [PATCH 09/15] feat: Added notification dialog on frontend --- src/app/admin-view/admin-comm.service.ts | 8 ++++- .../outbox/message/message.component.html | 31 +++++++++++++++++++ .../outbox/message/message.component.scss | 3 ++ .../outbox/message/message.component.spec.ts | 23 ++++++++++++++ .../outbox/message/message.component.ts | 31 +++++++++++++++++++ .../outbox/outbox.component.html | 24 +------------- .../outbox/outbox.component.scss | 4 --- .../notifications/outbox/outbox.component.ts | 6 +++- src/app/app-view/app-view.component.ts | 18 ++++++++++- .../notif-dialog/notif-dialog.component.html | 10 ++++++ .../notif-dialog/notif-dialog.component.scss | 0 .../notif-dialog.component.spec.ts | 23 ++++++++++++++ .../notif-dialog/notif-dialog.component.ts | 27 ++++++++++++++++ src/app/app.module.ts | 4 +++ src/app/services/updates.service.ts | 9 ++++++ 15 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 src/app/admin-view/notifications/outbox/message/message.component.html create mode 100644 src/app/admin-view/notifications/outbox/message/message.component.scss create mode 100644 src/app/admin-view/notifications/outbox/message/message.component.spec.ts create mode 100644 src/app/admin-view/notifications/outbox/message/message.component.ts create mode 100644 src/app/app-view/notif-dialog/notif-dialog.component.html create mode 100644 src/app/app-view/notif-dialog/notif-dialog.component.scss create mode 100644 src/app/app-view/notif-dialog/notif-dialog.component.spec.ts create mode 100644 src/app/app-view/notif-dialog/notif-dialog.component.ts diff --git a/src/app/admin-view/admin-comm.service.ts b/src/app/admin-view/admin-comm.service.ts index 73ac336..f26804b 100644 --- a/src/app/admin-view/admin-comm.service.ts +++ b/src/app/admin-view/admin-comm.service.ts @@ -185,7 +185,13 @@ export class AdminCommService { }, outbox: { getSent: () => { - return this.http.get(environment.apiEndpoint+"/admin/notif/outbox", {withCredentials: true}) + return this.http.get<{_id: string, sentDate: moment.Moment, title: string}[]>(environment.apiEndpoint+"/admin/notif/outbox", {withCredentials: true}) + }, + getBody: (id: string) => { + return this.http.get(environment.apiEndpoint+`/admin/notif/outbox/${id}/message`, {withCredentials: true, responseType: "text"}) + }, + getRcpts: (id: string) => { + return this.http.get<{_id: string, uname: string, room?: string, fname?: string, surname?: string}[]>(environment.apiEndpoint+`/admin/notif/outbox/${id}/rcpts`, {withCredentials: true}) } } } diff --git a/src/app/admin-view/notifications/outbox/message/message.component.html b/src/app/admin-view/notifications/outbox/message/message.component.html new file mode 100644 index 0000000..02eec4f --- /dev/null +++ b/src/app/admin-view/notifications/outbox/message/message.component.html @@ -0,0 +1,31 @@ + + + + + {{item.title}} + + {{item.sentDate.format('[Wysłano] dddd DD MMMM YYYYr. o HH:mm')}} + + + +

+ {{body}} +

+
+
    + @for (user of rcpts; track $index) { +
  • + {{user.room}}: {{user.fname}} {{user.surname}} ({{user.uname}}) +
  • + } +
+
+ + + + + + + +
\ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/message/message.component.scss b/src/app/admin-view/notifications/outbox/message/message.component.scss new file mode 100644 index 0000000..68af8c3 --- /dev/null +++ b/src/app/admin-view/notifications/outbox/message/message.component.scss @@ -0,0 +1,3 @@ +mat-card-title { + font-size: 24pt; +} \ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/message/message.component.spec.ts b/src/app/admin-view/notifications/outbox/message/message.component.spec.ts new file mode 100644 index 0000000..d4bdb50 --- /dev/null +++ b/src/app/admin-view/notifications/outbox/message/message.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MessageComponent } from './message.component'; + +describe('MessageComponent', () => { + let component: MessageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MessageComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MessageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin-view/notifications/outbox/message/message.component.ts b/src/app/admin-view/notifications/outbox/message/message.component.ts new file mode 100644 index 0000000..f84cd56 --- /dev/null +++ b/src/app/admin-view/notifications/outbox/message/message.component.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; +import { AdminCommService } from 'src/app/admin-view/admin-comm.service'; + +@Component({ + selector: 'app-message', + templateUrl: './message.component.html', + styleUrl: './message.component.scss' +}) +export class MessageComponent { + @Input() item!: {_id: string, sentDate: moment.Moment, title: string} + body?: string + rcpts?: {_id: string, uname: string, room?: string, fname?: string, surname?: string}[] + loading: boolean = false + constructor (readonly acu: AdminCommService) {} + + getMessage() { + this.loading = true + this.acu.notif.outbox.getBody(this.item._id).subscribe(v => { + this.body = v + this.loading = false + }) + } + + getRcpts() { + this.loading = true + this.acu.notif.outbox.getRcpts(this.item._id).subscribe(v => { + this.rcpts = v + this.loading = false + }) + } +} diff --git a/src/app/admin-view/notifications/outbox/outbox.component.html b/src/app/admin-view/notifications/outbox/outbox.component.html index cec270c..9b36659 100644 --- a/src/app/admin-view/notifications/outbox/outbox.component.html +++ b/src/app/admin-view/notifications/outbox/outbox.component.html @@ -1,28 +1,6 @@

Wysłane wiadomości:

@for (item of messages; track $index) { - - - - - {{item.message.title}} - - {{item.sentDate.format('[Wysłano] dddd DD MMMM YYYYr. o HH:mm')}} - - - -

- {{item.message.body}} -

-
-
    - @for (user of item.rcpt; track $index) { -
  • - {{user.room}}: {{user.fname}} {{user.surname}} ({{user.uname}}) -
  • - } -
-
-
+ }
\ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/outbox.component.scss b/src/app/admin-view/notifications/outbox/outbox.component.scss index 3879d78..0702771 100644 --- a/src/app/admin-view/notifications/outbox/outbox.component.scss +++ b/src/app/admin-view/notifications/outbox/outbox.component.scss @@ -3,8 +3,4 @@ flex-wrap: wrap; gap: 1ch; margin: 1ch; -} - -mat-card-title { - font-size: 24pt; } \ No newline at end of file diff --git a/src/app/admin-view/notifications/outbox/outbox.component.ts b/src/app/admin-view/notifications/outbox/outbox.component.ts index 8d87892..b1e2fc2 100644 --- a/src/app/admin-view/notifications/outbox/outbox.component.ts +++ b/src/app/admin-view/notifications/outbox/outbox.component.ts @@ -11,7 +11,11 @@ import * as moment from 'moment'; }) export class OutboxComponent implements OnInit { - messages!: any[] + messages!: { + _id: string; + sentDate: moment.Moment; + title: string; + }[] constructor (private readonly acs: AdminCommService, private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute ) { this.toolbar.comp = this diff --git a/src/app/app-view/app-view.component.ts b/src/app/app-view/app-view.component.ts index a2bbe43..509833c 100644 --- a/src/app/app-view/app-view.component.ts +++ b/src/app/app-view/app-view.component.ts @@ -6,6 +6,8 @@ import { Link } from '../types/link'; import { LocalStorageService } from '../services/local-storage.service'; import { interval } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatDialog } from '@angular/material/dialog'; +import { NotifDialogComponent } from './notif-dialog/notif-dialog.component'; @Component({ selector: 'app-app-view', @@ -25,7 +27,14 @@ export class AppViewComponent implements OnInit { }); } - constructor (private ac: AuthClient, readonly swPush: SwPush, private us: UpdatesService, private ls: LocalStorageService, private sb: MatSnackBar) {} + constructor ( + private ac: AuthClient, + readonly swPush: SwPush, + private us: UpdatesService, + private ls: LocalStorageService, + private sb: MatSnackBar, + private dialog: MatDialog + ) {} subscribeToNotif() { if (this.swPush.isEnabled && this.ls.capCheck(4)) { @@ -45,6 +54,13 @@ export class AppViewComponent implements OnInit { } newsCheck() { + if (this.ls.capCheck(4)) { + this.us.getNotifCheck().subscribe((s) => { + s.forEach(v => { + this.dialog.open(NotifDialogComponent, {data: v}) + }) + }) + } if (this.ls.newsflag) return; this.us.newsCheck().subscribe((s) => { if (s.hash != this.ls.newsCheck.hash) { diff --git a/src/app/app-view/notif-dialog/notif-dialog.component.html b/src/app/app-view/notif-dialog/notif-dialog.component.html new file mode 100644 index 0000000..9dc65ef --- /dev/null +++ b/src/app/app-view/notif-dialog/notif-dialog.component.html @@ -0,0 +1,10 @@ +

{{data.message.title}}

+ +

+ {{data.message.body}} +

+
{{data.sentDate.format("[Wysłano] dddd DD MMMM YYYYr. o HH:mm")}}
+
+ + + \ No newline at end of file diff --git a/src/app/app-view/notif-dialog/notif-dialog.component.scss b/src/app/app-view/notif-dialog/notif-dialog.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app-view/notif-dialog/notif-dialog.component.spec.ts b/src/app/app-view/notif-dialog/notif-dialog.component.spec.ts new file mode 100644 index 0000000..168d201 --- /dev/null +++ b/src/app/app-view/notif-dialog/notif-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotifDialogComponent } from './notif-dialog.component'; + +describe('NotifDialogComponent', () => { + let component: NotifDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [NotifDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotifDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/app-view/notif-dialog/notif-dialog.component.ts b/src/app/app-view/notif-dialog/notif-dialog.component.ts new file mode 100644 index 0000000..2b86f52 --- /dev/null +++ b/src/app/app-view/notif-dialog/notif-dialog.component.ts @@ -0,0 +1,27 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import * as moment from 'moment'; +import { UpdatesService } from 'src/app/services/updates.service'; + +@Component({ + selector: 'app-notif-dialog', + templateUrl: './notif-dialog.component.html', + styleUrl: './notif-dialog.component.scss' +}) +export class NotifDialogComponent { + + constructor ( + @Inject(MAT_DIALOG_DATA) public data: {_id: string, message: {title: string, body: string}, sentDate: moment.Moment}, + public dialogRef: MatDialogRef, + private uc: UpdatesService + ) { + data.sentDate = moment(data.sentDate) + } + + ack () { + this.uc.postInfoAck(this.data._id).subscribe((v) => { + this.dialogRef.close() + }) + } + +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 94a5065..255925e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -83,6 +83,8 @@ import { ExtraComponent } from './app-view/personal/extra/extra.component'; import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.component'; import { OutboxComponent } from './admin-view/notifications/outbox/outbox.component'; import { ToolbarComponent } from './admin-view/toolbar/toolbar.component'; +import { MessageComponent } from './admin-view/notifications/outbox/message/message.component'; +import { NotifDialogComponent } from './app-view/notif-dialog/notif-dialog.component'; @NgModule({ declarations: [ @@ -130,6 +132,8 @@ import { ToolbarComponent } from './admin-view/toolbar/toolbar.component'; RedirectComponent, OutboxComponent, ToolbarComponent, + MessageComponent, + NotifDialogComponent, ], imports: [ BrowserModule, diff --git a/src/app/services/updates.service.ts b/src/app/services/updates.service.ts index 0165772..db543a8 100644 --- a/src/app/services/updates.service.ts +++ b/src/app/services/updates.service.ts @@ -7,6 +7,7 @@ import * as moment from 'moment'; import { map } from 'rxjs'; import { UKey } from '../types/key'; import { CleanNote } from '../types/clean-note'; +import { Status } from '../types/status'; @Injectable({ providedIn: 'root' @@ -55,4 +56,12 @@ export class UpdatesService { getClean(date: moment.Moment) { return this.http.get<{grade: number, notes: CleanNote[], tips: string}>(environment.apiEndpoint+`/app/clean/${date.toISOString()}`, {withCredentials: true}) } + + getNotifCheck() { + return this.http.get<{_id: string, message: {title: string, body: string}, sentDate: moment.Moment}[]>(environment.apiEndpoint+`/app/notif/check`, {withCredentials: true}) + } + + postInfoAck(id: string) { + return this.http.post(environment.apiEndpoint+`/app/notif/${id}/ack`, undefined, {withCredentials: true}) + } } From 3b56d40d5aea116c9f952457b1d35ab5244954bb Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sun, 1 Jun 2025 10:25:05 +0200 Subject: [PATCH 10/15] feat: Added admin start page --- src/app/admin-view/start/start.component.html | 1 + src/app/admin-view/start/start.component.scss | 13 +++++++++++ .../admin-view/start/start.component.spec.ts | 23 +++++++++++++++++++ src/app/admin-view/start/start.component.ts | 10 ++++++++ src/app/app-routing.module.ts | 2 ++ src/app/app.module.ts | 2 ++ 6 files changed, 51 insertions(+) create mode 100644 src/app/admin-view/start/start.component.html create mode 100644 src/app/admin-view/start/start.component.scss create mode 100644 src/app/admin-view/start/start.component.spec.ts create mode 100644 src/app/admin-view/start/start.component.ts diff --git a/src/app/admin-view/start/start.component.html b/src/app/admin-view/start/start.component.html new file mode 100644 index 0000000..8c6c0fe --- /dev/null +++ b/src/app/admin-view/start/start.component.html @@ -0,0 +1 @@ +arrow_upwardWybierz zakładkę w menu \ No newline at end of file diff --git a/src/app/admin-view/start/start.component.scss b/src/app/admin-view/start/start.component.scss new file mode 100644 index 0000000..8e24cb3 --- /dev/null +++ b/src/app/admin-view/start/start.component.scss @@ -0,0 +1,13 @@ +.main { + margin-top: 8px; + margin-left: 16px; + display: flex; + align-items: center; + gap: 1ch; +} + +.icon { + width: fit-content; + height: fit-content; + font-size: 32pt; +} \ No newline at end of file diff --git a/src/app/admin-view/start/start.component.spec.ts b/src/app/admin-view/start/start.component.spec.ts new file mode 100644 index 0000000..32701e4 --- /dev/null +++ b/src/app/admin-view/start/start.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StartComponent } from './start.component'; + +describe('StartComponent', () => { + let component: StartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [StartComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin-view/start/start.component.ts b/src/app/admin-view/start/start.component.ts new file mode 100644 index 0000000..49e9e98 --- /dev/null +++ b/src/app/admin-view/start/start.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-start', + templateUrl: './start.component.html', + styleUrl: './start.component.scss' +}) +export class StartAdminComponent { + +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 4504679..664ec13 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -20,6 +20,7 @@ import { SettingsComponent } from './admin-view/settings/settings.component'; import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component'; import { NotificationsComponent } from './admin-view/notifications/notifications.component'; import { OutboxComponent } from './admin-view/notifications/outbox/outbox.component'; +import { StartAdminComponent } from './admin-view/start/start.component'; const routes: Routes = [ {path: "", redirectTo: "login", pathMatch: "full"}, @@ -31,6 +32,7 @@ const routes: Routes = [ {path: "grades", component: PersonalComponent, title: "Konto"} ]}, {path: "admin", component: AdminViewComponent, title: "Panel administracyjny", canActivateChild: [authGuard, adminGuard], children: [ + {path: "", pathMatch: "full", component: StartAdminComponent}, {path: "news", title: "Edytowanie wiadomości", component: NewsEditComponent}, {path: "menu", title: "Edytowanie jadłospisu", component: MenuNewComponent}, {path: "accounts", title: "Użytkownicy", component: AccountMgmtComponent}, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 255925e..98bd3cd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -85,6 +85,7 @@ import { OutboxComponent } from './admin-view/notifications/outbox/outbox.compon import { ToolbarComponent } from './admin-view/toolbar/toolbar.component'; import { MessageComponent } from './admin-view/notifications/outbox/message/message.component'; import { NotifDialogComponent } from './app-view/notif-dialog/notif-dialog.component'; +import { StartAdminComponent } from './admin-view/start/start.component'; @NgModule({ declarations: [ @@ -134,6 +135,7 @@ import { NotifDialogComponent } from './app-view/notif-dialog/notif-dialog.compo ToolbarComponent, MessageComponent, NotifDialogComponent, + StartAdminComponent, ], imports: [ BrowserModule, From 94702834b4274f37da890b10417304ebc97e4193 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sun, 1 Jun 2025 13:54:47 +0200 Subject: [PATCH 11/15] feat: Added user search component. Resolves #15 --- src/app/app.module.ts | 2 + .../user-search/user-search.component.html | 16 ++++ .../user-search/user-search.component.scss | 9 +++ .../user-search/user-search.component.spec.ts | 23 ++++++ .../user-search/user-search.component.ts | 76 +++++++++++++++++++ 5 files changed, 126 insertions(+) create mode 100644 src/app/commonComponents/user-search/user-search.component.html create mode 100644 src/app/commonComponents/user-search/user-search.component.scss create mode 100644 src/app/commonComponents/user-search/user-search.component.spec.ts create mode 100644 src/app/commonComponents/user-search/user-search.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 98bd3cd..92b1d1d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -85,6 +85,7 @@ import { OutboxComponent } from './admin-view/notifications/outbox/outbox.compon import { ToolbarComponent } from './admin-view/toolbar/toolbar.component'; import { MessageComponent } from './admin-view/notifications/outbox/message/message.component'; import { NotifDialogComponent } from './app-view/notif-dialog/notif-dialog.component'; +import { UserSearchComponent } from './commonComponents/user-search/user-search.component'; import { StartAdminComponent } from './admin-view/start/start.component'; @NgModule({ @@ -135,6 +136,7 @@ import { StartAdminComponent } from './admin-view/start/start.component'; ToolbarComponent, MessageComponent, NotifDialogComponent, + UserSearchComponent, StartAdminComponent, ], imports: [ diff --git a/src/app/commonComponents/user-search/user-search.component.html b/src/app/commonComponents/user-search/user-search.component.html new file mode 100644 index 0000000..5a738d6 --- /dev/null +++ b/src/app/commonComponents/user-search/user-search.component.html @@ -0,0 +1,16 @@ + + {{label}} + + + + + @for (item of list; track $index) { + + @if (item.fname) { + {{item.fname}} {{item.surname}} ({{item.room}}) + } @else { + {{item.uname}} + } + + } + \ No newline at end of file diff --git a/src/app/commonComponents/user-search/user-search.component.scss b/src/app/commonComponents/user-search/user-search.component.scss new file mode 100644 index 0000000..f06ba3a --- /dev/null +++ b/src/app/commonComponents/user-search/user-search.component.scss @@ -0,0 +1,9 @@ +:host { + display: flex; + align-items: center; + gap: 1ch; +} + +.room { + color: gray; +} \ No newline at end of file diff --git a/src/app/commonComponents/user-search/user-search.component.spec.ts b/src/app/commonComponents/user-search/user-search.component.spec.ts new file mode 100644 index 0000000..6cb2c16 --- /dev/null +++ b/src/app/commonComponents/user-search/user-search.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UserSearchComponent } from './user-search.component'; + +describe('UserSearchComponent', () => { + let component: UserSearchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UserSearchComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(UserSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/commonComponents/user-search/user-search.component.ts b/src/app/commonComponents/user-search/user-search.component.ts new file mode 100644 index 0000000..eaa9dcc --- /dev/null +++ b/src/app/commonComponents/user-search/user-search.component.ts @@ -0,0 +1,76 @@ +import { Component, EventEmitter, forwardRef, Input, Output, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatInput } from '@angular/material/input'; +import { AdminCommService } from 'src/app/admin-view/admin-comm.service'; + +interface UserSearchResult { + _id: string; + fname: string; + surname: string; + uname: string; + room: string; +} + +@Component({ + selector: 'app-user-search', + templateUrl: './user-search.component.html', + styleUrl: './user-search.component.scss', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => UserSearchComponent), + multi: true + } + ], + host: { + '(blur)': '_onTouched()' + } +}) +export class UserSearchComponent implements ControlValueAccessor { + protected loading: boolean = false + @Input() label?: boolean + control: FormControl = new FormControl(); + protected list: UserSearchResult[] = [] + private timeout?: NodeJS.Timeout + private _onChange!: (_: UserSearchResult) => void + private _onTouched!: any + + constructor(readonly acu: AdminCommService) { + this.control.valueChanges.subscribe(() => { + this.loading = true + if (this.timeout) clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.acu.userFilter(this.control.value).subscribe(v => { + this.list = v + this.loading = false + }) + }, 500) + }) + } + + writeValue(obj: string): void { + this.control.setValue(obj) + } + + registerOnChange(fn: (_: UserSearchResult) => void): void { + this._onChange = fn + } + + registerOnTouched(fn: any): void { + this._onTouched = fn + } + + setDisabledState?(isDisabled: boolean): void { + isDisabled ? this.control.disable() : this.control.enable() + } + + protected displayFn(u: UserSearchResult): string { + if (!u) return '' + return u.fname ? `${u.fname} ${u.surname}` : u.uname + } + + protected saveValue(e: MatAutocompleteSelectedEvent) { + this._onChange(this.control.value) + } +} From ca6037d405e746f4f256cf43de6283f56e26f9f5 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sun, 1 Jun 2025 17:44:48 +0200 Subject: [PATCH 12/15] feat: added user search to various components --- src/app/admin-view/key/key.component.html | 2 +- .../key/new-key/new-key.component.html | 14 +- .../key/new-key/new-key.component.ts | 24 +-- .../notifications.component.html | 6 +- .../notifications/notifications.component.ts | 18 +-- .../user-search/user-search.component.html | 31 ++-- .../user-search/user-search.component.scss | 9 ++ .../user-search/user-search.component.ts | 145 ++++++++++++++++-- src/app/types/notification.ts | 2 +- 9 files changed, 171 insertions(+), 80 deletions(-) diff --git a/src/app/admin-view/key/key.component.html b/src/app/admin-view/key/key.component.html index 85fb7aa..200618d 100644 --- a/src/app/admin-view/key/key.component.html +++ b/src/app/admin-view/key/key.component.html @@ -8,7 +8,7 @@
- +
{{element.uname}} AkcjeKarta użytkownika - - - - +
diff --git a/src/app/admin-view/key/new-key/new-key.component.html b/src/app/admin-view/key/new-key/new-key.component.html index 56d3310..c1ef8ad 100644 --- a/src/app/admin-view/key/new-key/new-key.component.html +++ b/src/app/admin-view/key/new-key/new-key.component.html @@ -1,6 +1,6 @@ - + Sala @for (item of rooms; track $index) { @@ -9,17 +9,9 @@ Wymagane - + Wypożyczający - - - - Zła nazwa użytkownika + Wymagane diff --git a/src/app/admin-view/key/new-key/new-key.component.ts b/src/app/admin-view/key/new-key/new-key.component.ts index 4d02e89..f9969fb 100644 --- a/src/app/admin-view/key/new-key/new-key.component.ts +++ b/src/app/admin-view/key/new-key/new-key.component.ts @@ -1,8 +1,8 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { AdminCommService } from '../../admin-comm.service'; import { MatDialogRef } from '@angular/material/dialog'; import { FormControl, FormGroup } from '@angular/forms'; -import { startWith } from 'rxjs'; +import { UserSearchResult } from 'src/app/commonComponents/user-search/user-search.component'; @Component({ selector: 'app-new-key', @@ -10,11 +10,10 @@ import { startWith } from 'rxjs'; styleUrl: './new-key.component.scss' }) export class NewKeyComponent implements OnInit { - // @ViewChild('input') input!: ElementRef rooms: string[] = [] form = new FormGroup({ room: new FormControl(""), - user: new FormControl("") + user: new FormControl(null) }) unames: any[] = [] constructor ( private ac: AdminCommService, public dialogRef: MatDialogRef ) {} @@ -24,26 +23,11 @@ export class NewKeyComponent implements OnInit { this.rooms = v }) } - - // filter() { - // const v = this.input.nativeElement.value - // console.log(v); - - // if (v) { - // this.ac.userFilter(v.toLowerCase()).subscribe((v) => { - // this.unames = v - // }) - // } else { - // this.unames = [] - // } - // } send() { if (this.form.valid) { this.dialogRef.close(this.form.value) - } else { - this.form.controls['user'].setErrors({unf: true}) - } + } } } diff --git a/src/app/admin-view/notifications/notifications.component.html b/src/app/admin-view/notifications/notifications.component.html index f4720b7..c5fec6a 100644 --- a/src/app/admin-view/notifications/notifications.component.html +++ b/src/app/admin-view/notifications/notifications.component.html @@ -1,10 +1,10 @@
- + - Nazwa użytkownika - + Użytkownik + diff --git a/src/app/admin-view/notifications/notifications.component.ts b/src/app/admin-view/notifications/notifications.component.ts index 1335e9b..9bc99f9 100644 --- a/src/app/admin-view/notifications/notifications.component.ts +++ b/src/app/admin-view/notifications/notifications.component.ts @@ -6,6 +6,7 @@ import { Group } from 'src/app/types/group'; import { LocalStorageService } from 'src/app/services/local-storage.service'; import { ToolbarService } from '../toolbar/toolbar.service'; import { ActivatedRoute, Router } from '@angular/router'; +import { UserSearchResult } from 'src/app/commonComponents/user-search/user-search.component'; @Component({ selector: 'app-notifications', @@ -44,9 +45,9 @@ export class NotificationsComponent implements OnInit, OnDestroy { success?: { sent: number; possible: number; }; - form = new FormGroup({ + form = new FormGroup({ recp: new FormGroup({ - uname: new FormControl(''), + uid: new FormControl(null), room: new FormControl(null), group: new FormControl(''), type: new FormControl<"room" | "uname" | "group">('uname', {nonNullable: true}) @@ -56,19 +57,8 @@ export class NotificationsComponent implements OnInit, OnDestroy { }) submit() { - this.acs.notif.send(this.form.value as Notification).subscribe((data) => { + this.acs.notif.send({...this.form.value, recp: {...this.form.get("recp")?.value, uid: this.form.controls['recp'].controls['uid'].value?._id}} as Notification).subscribe((data) => { this.success = data }) } } - -interface NotificationForm { - body: FormControl; - title: FormControl; - recp: FormGroup<{ - uname: FormControl; - room: FormControl; - group: FormControl; - type: FormControl<"room" | "uname" | "group">; - }> -} \ No newline at end of file diff --git a/src/app/commonComponents/user-search/user-search.component.html b/src/app/commonComponents/user-search/user-search.component.html index 5a738d6..298bdbf 100644 --- a/src/app/commonComponents/user-search/user-search.component.html +++ b/src/app/commonComponents/user-search/user-search.component.html @@ -1,16 +1,15 @@ - - {{label}} - - - - - @for (item of list; track $index) { - - @if (item.fname) { - {{item.fname}} {{item.surname}} ({{item.room}}) - } @else { - {{item.uname}} - } - - } - \ No newline at end of file +
+ + + + @for (item of list; track $index) { + + @if (item.fname) { + {{item.fname}} {{item.surname}} ({{item.room}}) + } @else { + {{item.uname}} + } + + } + +
\ No newline at end of file diff --git a/src/app/commonComponents/user-search/user-search.component.scss b/src/app/commonComponents/user-search/user-search.component.scss index f06ba3a..0313682 100644 --- a/src/app/commonComponents/user-search/user-search.component.scss +++ b/src/app/commonComponents/user-search/user-search.component.scss @@ -6,4 +6,13 @@ .room { color: gray; +} + +input { + border: none; + background: none; + padding: 0; + outline: 0; + font: inherit; + color: currentColor; } \ No newline at end of file diff --git a/src/app/commonComponents/user-search/user-search.component.ts b/src/app/commonComponents/user-search/user-search.component.ts index eaa9dcc..3be722f 100644 --- a/src/app/commonComponents/user-search/user-search.component.ts +++ b/src/app/commonComponents/user-search/user-search.component.ts @@ -1,10 +1,12 @@ -import { Component, EventEmitter, forwardRef, Input, Output, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core'; +import { ControlValueAccessor, FormControl, FormGroupDirective, NgControl, NgForm } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; -import { MatInput } from '@angular/material/input'; +import { MatFormFieldControl } from '@angular/material/form-field'; +import { Subject } from 'rxjs'; import { AdminCommService } from 'src/app/admin-view/admin-comm.service'; -interface UserSearchResult { +export interface UserSearchResult { _id: string; fname: string; surname: string; @@ -18,26 +20,120 @@ interface UserSearchResult { styleUrl: './user-search.component.scss', providers: [ { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => UserSearchComponent), - multi: true + provide: MatFormFieldControl, + useExisting: UserSearchComponent } ], host: { '(blur)': '_onTouched()' } }) -export class UserSearchComponent implements ControlValueAccessor { +export class UserSearchComponent implements ControlValueAccessor, MatFormFieldControl, OnDestroy, DoCheck { protected loading: boolean = false - @Input() label?: boolean control: FormControl = new FormControl(); protected list: UserSearchResult[] = [] private timeout?: NodeJS.Timeout private _onChange!: (_: UserSearchResult) => void private _onTouched!: any - constructor(readonly acu: AdminCommService) { + static nextId = 0; + + @Input() + public get value(): UserSearchResult | null { + return this.control.value; + } + + public set value(value: UserSearchResult | null) { + this.control.setValue(value) + this.stateChanges.next() + } + + touched = false + + stateChanges = new Subject(); + + @HostBinding() id: string = `app-user-search-${UserSearchComponent.nextId++}`; + + private _placeholder: string = ""; + @Input() + public get placeholder(): string { + return this._placeholder; + } + public set placeholder(value: string) { + this._placeholder = value; + this.stateChanges.next() + } + + focused: boolean = false; + onFocusIn(event: FocusEvent) { + if (!this.focused) { + this.focused = true; + this.stateChanges.next(); + } + } + + onFocusOut(event: FocusEvent) { + if (!this._elementRef.nativeElement.contains(event.relatedTarget as Element)) { + this.touched = true + this.focused = false; + this._onTouched(); + this.stateChanges.next(); + } + } + get empty(): boolean { + return !this.control.value + } + @HostBinding('class.floating') + get shouldLabelFloat(): boolean { + return this.focused || !this.empty + } + private _required: boolean = false; + @Input() + public get required(): boolean { + return this._required; + } + + public set required(value: BooleanInput) { + this._required = coerceBooleanProperty(value); + this.stateChanges.next() + } + + private _disabled: boolean = false; + @Input() + public get disabled(): boolean { + return this._disabled; + } + public set disabled(value: BooleanInput) { + this._disabled = coerceBooleanProperty(value); + this._disabled ? this.control.disable() : this.control.enable() + this.stateChanges.next() + } + errorState: boolean = false + controlType?: string | undefined = "app-user-search"; + autofilled?: boolean | undefined; + @Input('aria-describedby') userAriaDescribedBy?: string; + setDescribedByIds(ids: string[]): void { + const controlElement = this._elementRef.nativeElement.querySelector('.app-user-search-container')!; + controlElement.setAttribute('aria-describedby', ids.join(' ')) + } + onContainerClick(event: MouseEvent): void { + if ((event.target as Element).tagName.toLowerCase() != 'input') { + this._elementRef.nativeElement.querySelector('input').focus() + } + } + + constructor( + readonly acu: AdminCommService, + @Optional() @Self() public ngControl: NgControl, + @Optional() private _parentForm: NgForm, + @Optional() private _parentFormGroup: FormGroupDirective, + private _elementRef: ElementRef + ) { + if (this.ngControl != null) { + (this.ngControl as NgControl).valueAccessor = this + } this.control.valueChanges.subscribe(() => { + if (typeof this.control.value == "object") return; this.loading = true if (this.timeout) clearTimeout(this.timeout) this.timeout = setTimeout(() => { @@ -48,9 +144,28 @@ export class UserSearchComponent implements ControlValueAccessor { }, 500) }) } + ngDoCheck(): void { + if (this.ngControl) { + this.updateErrorState() + } + } + private updateErrorState() { + const parent = this._parentFormGroup || this._parentForm - writeValue(obj: string): void { - this.control.setValue(obj) + const oldState = this.errorState; + const newState = (this.ngControl?.invalid || this.control.invalid) && (this.touched || parent.submitted); + + if (oldState !== newState) { + this.errorState = newState + this.stateChanges.next() + } + } + ngOnDestroy(): void { + this.stateChanges.complete() + } + + writeValue(obj: UserSearchResult): void { + this.value = obj } registerOnChange(fn: (_: UserSearchResult) => void): void { @@ -62,7 +177,7 @@ export class UserSearchComponent implements ControlValueAccessor { } setDisabledState?(isDisabled: boolean): void { - isDisabled ? this.control.disable() : this.control.enable() + this.disabled = isDisabled } protected displayFn(u: UserSearchResult): string { @@ -71,6 +186,8 @@ export class UserSearchComponent implements ControlValueAccessor { } protected saveValue(e: MatAutocompleteSelectedEvent) { - this._onChange(this.control.value) + this.autofilled = true + this.value = e.option.value + this._onChange(this.value!) } } diff --git a/src/app/types/notification.ts b/src/app/types/notification.ts index b654ee0..62c8524 100644 --- a/src/app/types/notification.ts +++ b/src/app/types/notification.ts @@ -2,7 +2,7 @@ export interface Notification { body: string; title: string; recp: { - uname: string | null; + uid: string | null; room: string | null; type: "all" | "room" | "uname" } From 0c60f39152a55eca87d65aaecec591392a01a76e Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Sun, 1 Jun 2025 21:48:56 +0200 Subject: [PATCH 13/15] feat: Added menu items and account security to settings --- .../settings/settings.component.html | 68 +++++++++++++++++++ .../admin-view/settings/settings.component.ts | 57 +++++++++++++++- .../admin-view/start/start.component.spec.ts | 10 +-- 3 files changed, 127 insertions(+), 8 deletions(-) diff --git a/src/app/admin-view/settings/settings.component.html b/src/app/admin-view/settings/settings.component.html index 65b3708..9c4f03e 100644 --- a/src/app/admin-view/settings/settings.component.html +++ b/src/app/admin-view/settings/settings.component.html @@ -1,4 +1,5 @@ + @@ -8,6 +9,8 @@

Kliknij listę aby edytować

+ + Powody nieczystości @@ -16,6 +19,8 @@

Kliknij listę aby edytować

+ + Sale z kluczami @@ -23,6 +28,68 @@ + + + + + Domyślne wpisy jadłospisu + + +
Sala
+ + + + + + + + + + + + + +
Domyślne wpisy w jadłospisie dla danych pozycji
ŚniadanieKolacja
+ + + +
+ + + + + + Bezpieczeństwo + + + +

+ Domyślne hasło użytkownika po wygenerowaniu konto to pierwszelogowanie
+ Reset hasła powoduje zmianę na reset +

+ +

Ograniczenia logowania

+ + Dozwolone próby logowania + +
+ + Okres liczenia prób + + Podaj w minutach +
+ + Czas blokady konta + + Podaj w minutach +
+ + +
+
+
+ + Sterowanie programem @@ -41,4 +108,5 @@ Wyloguj wszystkich użytkowników --> + \ No newline at end of file diff --git a/src/app/admin-view/settings/settings.component.ts b/src/app/admin-view/settings/settings.component.ts index c16c921..11b2d36 100644 --- a/src/app/admin-view/settings/settings.component.ts +++ b/src/app/admin-view/settings/settings.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { AdminCommService } from '../admin-comm.service'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { FormBuilder } from '@angular/forms'; @Component({ selector: 'app-settings', @@ -11,10 +12,18 @@ export class SettingsComponent implements OnInit { usettings!: IUSettings reloadTimeout: boolean = false; - constructor (private readonly acu: AdminCommService, private readonly sb: MatSnackBar) { } + constructor (private readonly acu: AdminCommService, private readonly sb: MatSnackBar, private readonly fb: FormBuilder) { } + + accSec = this.fb.nonNullable.group({ + attempts: this.fb.nonNullable.control(1), + time: this.fb.nonNullable.control(1), + lockout: this.fb.nonNullable.control(1), + }) + ngOnInit(): void { this.acu.settings.getAll().subscribe((r) => { this.usettings = r + this.accSecTimeouts = r.security.loginTimeout }) } @@ -31,10 +40,39 @@ export class SettingsComponent implements OnInit { this.send() } + saveSn(event: string[]) { + this.usettings.menu.defaultItems.sn = event + this.send() + } + saveKol(event: string[]) { + this.usettings.menu.defaultItems.kol = event + this.send() + } + + saveAccSecTimeouts() { + this.usettings.security.loginTimeout = this.accSecTimeouts + this.send() + } + + set accSecTimeouts(value: IUSettings['security']['loginTimeout']) { + this.accSec.setValue({ + attempts: value.attempts, + lockout: value.lockout / 60, + time: value.time / 60 + }) + } + get accSecTimeouts(): IUSettings['security']['loginTimeout'] { + return { + attempts: this.accSec.controls['attempts'].value, + lockout: this.accSec.controls['lockout'].value * 60, + time: this.accSec.controls['time'].value * 60 + } + } + send() { this.acu.settings.post(this.usettings).subscribe((s) => { if (s.status == 200) { - this.sb.open("Zapisano!", undefined, {duration: 1000}) + this.sb.open("Zapisano!", undefined, { duration: 1000 }) } else { console.error(s); } @@ -51,7 +89,7 @@ export class SettingsComponent implements OnInit { }, 5000); this.acu.settings.reload().subscribe((s) => { if (s.status == 200) { - this.sb.open("Przeładowano ustawienia!", undefined, {duration: 3000}) + this.sb.open("Przeładowano ustawienia!", undefined, { duration: 3000 }) } else { console.error(s); } @@ -63,4 +101,17 @@ export interface IUSettings { keyrooms: string[]; rooms: string[]; cleanThings: string[]; + menu: { + defaultItems: { + sn: string[]; + kol: string[]; + } + }; + security: { + loginTimeout: { + attempts: number; + time: number; + lockout: number; + } + } } \ No newline at end of file diff --git a/src/app/admin-view/start/start.component.spec.ts b/src/app/admin-view/start/start.component.spec.ts index 32701e4..2865fd0 100644 --- a/src/app/admin-view/start/start.component.spec.ts +++ b/src/app/admin-view/start/start.component.spec.ts @@ -1,18 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { StartComponent } from './start.component'; +import { StartAdminComponent } from './start.component'; describe('StartComponent', () => { - let component: StartComponent; - let fixture: ComponentFixture; + let component: StartAdminComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [StartComponent] + declarations: [StartAdminComponent] }) .compileComponents(); - fixture = TestBed.createComponent(StartComponent); + fixture = TestBed.createComponent(StartAdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); From d4c7084820356913ac0ad0dc204c507195d62d34 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 3 Jun 2025 13:23:58 +0200 Subject: [PATCH 14/15] feat: Added unchecked room highlighting. Resolves #11 --- src/app/admin-view/admin-comm.service.ts | 2 +- .../grades/attendence-summary/attendence-summary.component.html | 2 +- .../grades/attendence-summary/attendence-summary.component.ts | 2 +- src/app/admin-view/grades/grades.component.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/admin-view/admin-comm.service.ts b/src/app/admin-view/admin-comm.service.ts index f26804b..775dddf 100644 --- a/src/app/admin-view/admin-comm.service.ts +++ b/src/app/admin-view/admin-comm.service.ts @@ -252,7 +252,7 @@ export class AdminCommService { return this.http.post(environment.apiEndpoint+`/admin/clean/attendence/${room}`, attendence, {withCredentials: true}) }, getSummary: () => { - return this.http.get<{room: string, hours: string[], notes: string}[]>(environment.apiEndpoint+`/admin/clean/attendenceSummary`, {withCredentials: true}) + return this.http.get<{room: string, hours: string[], notes: string, auto: boolean}[]>(environment.apiEndpoint+`/admin/clean/attendenceSummary`, {withCredentials: true}) }, deleteRoom: (room: string) => { return this.http.delete(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true}) diff --git a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.html b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.html index 760c99b..7d08dbf 100644 --- a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.html +++ b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.html @@ -18,7 +18,7 @@
Usuń - +
diff --git a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts index ccb30a4..c2d5e25 100644 --- a/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts +++ b/src/app/admin-view/grades/attendence-summary/attendence-summary.component.ts @@ -11,7 +11,7 @@ import { AdminCommService } from '../../admin-comm.service'; }) export class AttendenceSummaryComponent implements OnInit { - data: MatTableDataSource<{room: string, hours: string[], notes: string}> = new MatTableDataSource<{room: string, hours: string[], notes: string}>(); + data: MatTableDataSource<{room: string, hours: string[], notes: string, auto: boolean}> = new MatTableDataSource<{room: string, hours: string[], notes: string, auto: boolean}>(); collumns = ['room', 'hours', 'actions'] constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) { diff --git a/src/app/admin-view/grades/grades.component.ts b/src/app/admin-view/grades/grades.component.ts index de2a2d2..24078f9 100644 --- a/src/app/admin-view/grades/grades.component.ts +++ b/src/app/admin-view/grades/grades.component.ts @@ -49,8 +49,8 @@ export class GradesComponent implements OnInit, OnDestroy { if (!this.filter(this.date)) this.date.isoWeekday(8); this.toolbar.comp = this this.toolbar.menu = [ + { title: "Pokoje do sprawdzenia", check: true, fn: "attendenceSummary", icon: "overview"}, { title: "Podsumowanie", check: true, fn: "summary", icon: "analytics" }, - { title: "Obecność", check: true, fn: "attendenceSummary", icon: "overview"} ] this.form.valueChanges.subscribe((v) => { this.calculate() From 00daf7c972a36364d022f54302e7a552dd2d3d67 Mon Sep 17 00:00:00 2001 From: Jan Szumotalski Date: Tue, 3 Jun 2025 13:26:28 +0200 Subject: [PATCH 15/15] chore: Bumped version numbers --- package-lock.json | 2 +- package.json | 2 +- src/environments/environment.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe0dadb..d59fc2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ipwa", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 47c1f11..04086e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwa", - "version": "1.0.1", + "version": "1.1.0", "license": "GPL-3.0-or-later", "scripts": { "ng": "ng", diff --git a/src/environments/environment.ts b/src/environments/environment.ts index d85f039..b005d05 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,5 +1,5 @@ export const environment = { apiEndpoint: `${window.location.origin}/api`, - version: "v1.0.1", + version: "v1.1.0", production: true };