feat: Added notifications outbox to admin panel

This commit is contained in:
2025-05-31 16:57:58 +02:00
parent 86347e254b
commit 375bb1ceb4
21 changed files with 208 additions and 31 deletions

View File

@@ -182,6 +182,11 @@ export class AdminCommService {
}, },
getGroups: () => { getGroups: () => {
return this.http.get<Group[]>(environment.apiEndpoint+"/admin/notif/groups", {withCredentials: true}) return this.http.get<Group[]>(environment.apiEndpoint+"/admin/notif/groups", {withCredentials: true})
},
outbox: {
getSent: () => {
return this.http.get<any[]>(environment.apiEndpoint+"/admin/notif/outbox", {withCredentials: true})
}
} }
} }
//#endregion //#endregion

View File

@@ -1,17 +1,4 @@
<mat-toolbar color="accent"> <app-toolbar [drawer]="drawer"/>
<button mat-icon-button (click)="drawer.toggle()"><mat-icon>menu</mat-icon></button>
<span>{{title.getTitle()}}</span>
<span style="flex: 1 1 auto"></span>
<button mat-icon-button *ngIf="toolbar.menu" [matMenuTriggerFor]="menu"><mat-icon>more_vert</mat-icon></button>
</mat-toolbar>
<mat-menu #menu="matMenu">
@for (item of toolbar.menu; track $index) {
<button mat-menu-item *ngIf="item.check" (click)="toolbar.comp[item.fn]()">
<mat-icon *ngIf="item.icon">{{item.icon}}</mat-icon>
<span>{{item.title}}</span>
</button>
}
</mat-menu>
<mat-sidenav-container> <mat-sidenav-container>
<mat-sidenav #drawer mode="over" autoFocus="false"> <mat-sidenav #drawer mode="over" autoFocus="false">
<mat-nav-list> <mat-nav-list>

View File

@@ -1,9 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { LocalStorageService } from '../services/local-storage.service'; import { LocalStorageService } from '../services/local-storage.service';
import { Link } from '../types/link'; import { Link } from '../types/link';
import { ToolbarService } from './toolbar.service';
@Component({ @Component({
selector: 'app-admin-view', selector: 'app-admin-view',
@@ -26,7 +24,7 @@ export class AdminViewComponent {
public get LINKS(): Link[] { public get LINKS(): Link[] {
return this._LINKS.filter(v => v.enabled); 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() { goNormal() {
this.router.navigateByUrl('app') this.router.navigateByUrl('app')
} }

View File

@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ToolbarService } from '../../toolbar.service'; import { ToolbarService } from '../../toolbar/toolbar.service';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { AdminCommService } from '../../admin-comm.service'; import { AdminCommService } from '../../admin-comm.service';

View File

@@ -4,7 +4,7 @@ import * as moment from 'moment';
import { FormArray, FormBuilder } from '@angular/forms'; import { FormArray, FormBuilder } from '@angular/forms';
import { weekendFilter } from 'src/app/fd.da'; import { weekendFilter } from 'src/app/fd.da';
import { MatSnackBar } from '@angular/material/snack-bar'; 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 { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AttendenceComponent } from './attendence/attendence.component'; import { AttendenceComponent } from './attendence/attendence.component';

View File

@@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; 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 { ActivatedRoute, Router } from '@angular/router';
import { AdminCommService } from '../../admin-comm.service'; import { AdminCommService } from '../../admin-comm.service';
import * as moment from 'moment'; import * as moment from 'moment';

View File

@@ -1,4 +1,3 @@
<!-- TODO: Remake the notifications module -->
<form [formGroup]="form" (ngSubmit)="submit()"> <form [formGroup]="form" (ngSubmit)="submit()">
<div formGroupName="recp"> <div formGroupName="recp">
<mat-radio-group formControlName="type"> <mat-radio-group formControlName="type">

View File

@@ -1,20 +1,31 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { AdminCommService } from '../admin-comm.service'; import { AdminCommService } from '../admin-comm.service';
import { Notification } from 'src/app/types/notification'; import { Notification } from 'src/app/types/notification';
import { Group } from 'src/app/types/group'; import { Group } from 'src/app/types/group';
import { LocalStorageService } from 'src/app/services/local-storage.service'; import { LocalStorageService } from 'src/app/services/local-storage.service';
import { ToolbarService } from '../toolbar/toolbar.service';
import { ActivatedRoute, Router } from '@angular/router';
@Component({ @Component({
selector: 'app-notifications', selector: 'app-notifications',
templateUrl: './notifications.component.html', templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.scss'] styleUrls: ['./notifications.component.scss']
}) })
export class NotificationsComponent implements OnInit { export class NotificationsComponent implements OnInit, OnDestroy {
groups!: Group[] 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 { ngOnInit(): void {
this.acs.notif.getGroups().subscribe((v) => { 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; }; success?: { sent: number; possible: number; };
form = new FormGroup<NotificationForm>({ form = new FormGroup<NotificationForm>({

View File

@@ -0,0 +1,28 @@
<p>Wysłane wiadomości:</p>
<div class="cardContainer">
@for (item of messages; track $index) {
<mat-card>
<mat-card-header>
<mat-card-title-group>
<mat-card-title>
{{item.message.title}}
</mat-card-title>
<mat-card-subtitle>{{item.sentDate.format('[Wysłano] dddd DD MMMM YYYYr. o HH:mm')}}</mat-card-subtitle>
</mat-card-title-group>
</mat-card-header>
<mat-card-content>
<p>
{{item.message.body}}
</p>
<hr>
<ul>
@for (user of item.rcpt; track $index) {
<li>
<span *ngIf="user.room">{{user.room}}: </span>{{user.fname}} {{user.surname}} <span style="color: gray" >({{user.uname}})</span>
</li>
}
</ul>
</mat-card-content>
</mat-card>
}
</div>

View File

@@ -0,0 +1,10 @@
.cardContainer {
display: flex;
flex-wrap: wrap;
gap: 1ch;
margin: 1ch;
}
mat-card-title {
font-size: 24pt;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OutboxComponent } from './outbox.component';
describe('OutboxComponent', () => {
let component: OutboxComponent;
let fixture: ComponentFixture<OutboxComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OutboxComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OutboxComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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)
}
})
})
}
}

View File

@@ -0,0 +1,14 @@
<mat-toolbar color="accent">
<button mat-icon-button (click)="drawer.toggle()"><mat-icon>menu</mat-icon></button>
<span>{{title.getTitle()}}</span>
<span style="flex: 1 1 auto"></span>
<button mat-icon-button *ngIf="toolbar.menu" [matMenuTriggerFor]="menu" (click)="openMenu()"><mat-icon>more_vert</mat-icon></button>
</mat-toolbar>
<mat-menu #menu="matMenu">
@for (item of _menu; track $index) {
<button mat-menu-item *ngIf="item.check ?? true" (click)="toolbar.comp[item.fn]()">
<mat-icon *ngIf="item.icon">{{item.icon}}</mat-icon>
<span>{{item.title}}</span>
</button>
}
</mat-menu>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToolbarComponent } from './toolbar.component';
describe('ToolbarComponent', () => {
let component: ToolbarComponent;
let fixture: ComponentFixture<ToolbarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ToolbarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ToolbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -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()
}
}

View File

@@ -6,9 +6,6 @@ import { Injectable } from '@angular/core';
export class ToolbarService { export class ToolbarService {
public comp?: any; public comp?: any;
public menu?: {title: string, check: boolean, icon?: string, fn: string}[] public menu?: {title: string, check?: boolean, icon?: string, fn: string}[]
constructor() { }
} }

View File

@@ -19,6 +19,7 @@ import { SummaryComponent } from './admin-view/grades/summary/summary.component'
import { SettingsComponent } from './admin-view/settings/settings.component'; import { SettingsComponent } from './admin-view/settings/settings.component';
import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component'; import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component';
import { NotificationsComponent } from './admin-view/notifications/notifications.component'; import { NotificationsComponent } from './admin-view/notifications/notifications.component';
import { OutboxComponent } from './admin-view/notifications/outbox/outbox.component';
const routes: Routes = [ const routes: Routes = [
{path: "", redirectTo: "login", pathMatch: "full"}, {path: "", redirectTo: "login", pathMatch: "full"},
@@ -33,7 +34,10 @@ const routes: Routes = [
{path: "news", title: "Edytowanie wiadomości", component: NewsEditComponent}, {path: "news", title: "Edytowanie wiadomości", component: NewsEditComponent},
{path: "menu", title: "Edytowanie jadłospisu", component: MenuNewComponent}, {path: "menu", title: "Edytowanie jadłospisu", component: MenuNewComponent},
{path: "accounts", title: "Użytkownicy", component: AccountMgmtComponent}, {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: "groups", title: "Grupy", component: GroupsComponent},
{path: "keys", title: "Klucze", component: AdminKeyComponent}, {path: "keys", title: "Klucze", component: AdminKeyComponent},
{path: "grades", children: [ {path: "grades", children: [

View File

@@ -1,7 +1,8 @@
<h1>Domyślna strona po logowaniu</h1> <h1 mat-dialog-title>Domyślna strona po logowaniu</h1>
<mat-dialog-content> <mat-dialog-content>
<p>Wpisz link względem /ipwa/ w poniższym polu.</p> <p>Wpisz link względem /ipwa/ w poniższym polu.</p>
<p>Przykład: /app/menu</p> <p>Przykład: /app/menu</p>
<p style="color: red">Jeśli nie wiesz co tu wpisać, najlepiej nie zmieniaj tego ustawienia</p>
<mat-form-field> <mat-form-field>
<input matInput type="text" [(ngModel)]="redirect"> <input matInput type="text" [(ngModel)]="redirect">
<mat-label>Link</mat-label> <mat-label>Link</mat-label>

View File

@@ -81,6 +81,8 @@ import { AboutComponent } from './app-view/personal/about/about.component';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { ExtraComponent } from './app-view/personal/extra/extra.component'; import { ExtraComponent } from './app-view/personal/extra/extra.component';
import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.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({ @NgModule({
declarations: [ declarations: [
@@ -126,6 +128,8 @@ import { RedirectComponent } from './app-view/personal/extra/redirect/redirect.c
AboutComponent, AboutComponent,
ExtraComponent, ExtraComponent,
RedirectComponent, RedirectComponent,
OutboxComponent,
ToolbarComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,