Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
fa126e02de
|
|||
|
a65baa06fd
|
|||
|
da8c21eb0d
|
|||
|
2e42f65d88
|
|||
|
33eef9d305
|
|||
|
6a145b224c
|
|||
|
913ccd0bf1
|
|||
|
271ba89704
|
|||
| b7f2f35acb | |||
|
86fdb8366e
|
|||
| 46f351ec5b | |||
|
06c88cd920
|
|||
|
00e79e5f4d
|
|||
|
c525dfe1c1
|
|||
| a14d860022 | |||
| 9f97e584bd | |||
|
00daf7c972
|
|||
|
d4c7084820
|
|||
|
0c60f39152
|
|||
|
ca6037d405
|
|||
|
94702834b4
|
|||
|
3b56d40d5a
|
|||
|
efd76e16a1
|
|||
|
375bb1ceb4
|
|||
|
86347e254b
|
|||
|
cf2fa0b607
|
|||
|
45fb44712e
|
|||
|
92768ceda6
|
|||
|
26dac21e7e
|
|||
|
7d98cc2c49
|
|||
|
90d5b5da1c
|
42
.gitea/workflows/test.yaml
Normal file
42
.gitea/workflows/test.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: IPWA Tests
|
||||||
|
run-name: ${{ gitea.actor }} is testing out IPWA
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Run Angular tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [22]
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{matrix.node-version}}
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: package-lock.json
|
||||||
|
- name: Setup Chrome
|
||||||
|
uses: browser-actions/setup-chrome@v1.7.3
|
||||||
|
id: setup-chrome
|
||||||
|
with:
|
||||||
|
install-dependencies: true
|
||||||
|
- name: Install node dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Echo chrome path
|
||||||
|
run: |
|
||||||
|
echo "$CHROME_BIN"
|
||||||
|
env:
|
||||||
|
CHROME_BIN: ${{steps.setup-chrome.outputs.chrome-path}}
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run ci
|
||||||
|
env:
|
||||||
|
CHROME_BIN: ${{steps.setup-chrome.outputs.chrome-path}}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
# Ipwa
|
# Ipwa
|
||||||
|
|
||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.4.
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.4.
|
||||||
This project depends on the [Backend server](https://github.com/Slasherss1/ipwa-backend2)
|
This project depends on the [Backend server](https://github.com/Slasherss1/ipwa-backend)
|
||||||
|
|
||||||
## Things to change
|
|
||||||
Change following files:
|
|
||||||
- (Optional) `src/assets/icons/*` - You can change the icons to your own
|
|
||||||
|
|||||||
25
angular.json
25
angular.json
@@ -70,6 +70,30 @@
|
|||||||
"with": "src/environments/environment.development.ts"
|
"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"
|
"defaultConfiguration": "production"
|
||||||
@@ -104,6 +128,7 @@
|
|||||||
"zone.js/testing"
|
"zone.js/testing"
|
||||||
],
|
],
|
||||||
"tsConfig": "tsconfig.spec.json",
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"karmaConfig": "karma.conf.js",
|
||||||
"inlineStyleLanguage": "scss",
|
"inlineStyleLanguage": "scss",
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/favicon.ico",
|
"src/favicon.ico",
|
||||||
|
|||||||
45
karma.conf.js
Normal file
45
karma.conf.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, './coverage/ipwa'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
restartOnFileChange: true,
|
||||||
|
customLaunchers: {
|
||||||
|
ChromeHeadlessCustom: {
|
||||||
|
base: "ChromeHeadless",
|
||||||
|
flags: ["--no-sandbox", "--disable-gpu"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||||
"index": "./index.html",
|
"index": "/ipwa/index.html",
|
||||||
"assetGroups": [
|
"assetGroups": [
|
||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"installMode": "prefetch",
|
"installMode": "prefetch",
|
||||||
"resources": {
|
"resources": {
|
||||||
"files": [
|
"files": [
|
||||||
"/favicon.ico",
|
"/ipwa/favicon.ico",
|
||||||
"/manifest.webmanifest",
|
"/ipwa/manifest.webmanifest",
|
||||||
"/*.css",
|
"/ipwa/*.css",
|
||||||
"/*.js"
|
"/ipwa/*.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
"updateMode": "prefetch",
|
"updateMode": "prefetch",
|
||||||
"resources": {
|
"resources": {
|
||||||
"files": [
|
"files": [
|
||||||
"./assets/**",
|
"/ipwa/assets/**",
|
||||||
"/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
"/ipwa/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwa",
|
"name": "ipwa",
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwa",
|
"name": "ipwa",
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"build": "ng build -c production",
|
"build": "ng build -c production",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"ci": "ng test --no-watch --no-progress --browsers=ChromiumHeadless"
|
"ci": "ng test --no-watch --no-progress --browsers=ChromeHeadlessCustom"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<mat-label>Wyszukaj</mat-label>
|
<mat-label>Wyszukaj</mat-label>
|
||||||
<input matInput (keyup)="filter($event)">
|
<input matInput (keyup)="filter($event)">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-icon-button (click)="new()"><mat-icon>add</mat-icon></button>
|
<button mat-icon-button (click)="openUserCard()"><mat-icon>add</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
<mat-spinner *ngIf="loading"></mat-spinner>
|
||||||
<table mat-table [dataSource]="users">
|
<table mat-table [dataSource]="users">
|
||||||
@@ -24,17 +24,9 @@
|
|||||||
<td mat-cell *matCellDef="let element">{{element.uname}}</td>
|
<td mat-cell *matCellDef="let element">{{element.uname}}</td>
|
||||||
</div>
|
</div>
|
||||||
<div matColumnDef="actions">
|
<div matColumnDef="actions">
|
||||||
<th mat-header-cell *matHeaderCellDef>Akcje</th>
|
<th mat-header-cell *matHeaderCellDef>Karta użytkownika</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<button mat-mini-fab (click)="resetPass(element._id)"><mat-icon>lock_reset</mat-icon></button>
|
<button mat-mini-fab (click)="openUserCard(element._id)"><mat-icon>manage_accounts</mat-icon></button>
|
||||||
<button mat-mini-fab (click)="edit(element)"><mat-icon>edit</mat-icon></button>
|
|
||||||
<button mat-mini-fab (click)="toggleLock(element)">
|
|
||||||
<div [ngSwitch]="element.locked">
|
|
||||||
<mat-icon *ngSwitchCase="true">lock</mat-icon>
|
|
||||||
<mat-icon *ngSwitchDefault>lock_open</mat-icon>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button mat-mini-fab (click)="delete(element._id)"><mat-icon>delete_forever</mat-icon></button>
|
|
||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
||||||
|
|||||||
@@ -11,21 +11,35 @@ import { of } from 'rxjs';
|
|||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
describe('AccountMgmtComponent', () => {
|
describe('AccountMgmtComponent', () => {
|
||||||
let component: AccountMgmtComponent;
|
let component: AccountMgmtComponent;
|
||||||
let fixture: ComponentFixture<AccountMgmtComponent>;
|
let fixture: ComponentFixture<AccountMgmtComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const acMock = jasmine.createSpyObj("AdminCommService", {
|
acMock = {
|
||||||
getAccs: of()
|
accs: {
|
||||||
})
|
getAccs: jasmine.createSpy("getAccs").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [AccountMgmtComponent],
|
declarations: [AccountMgmtComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AdminCommService, useValue: acMock}
|
{provide: AdminCommService, useValue: acMock}
|
||||||
],
|
],
|
||||||
imports: [MatDialogModule, MatSnackBarModule, MatFormFieldModule, MatIconModule, MatPaginatorModule, MatTableModule, MatInputModule, BrowserAnimationsModule]
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatSnackBarModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatPaginatorModule,
|
||||||
|
MatTableModule,
|
||||||
|
MatInputModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
fixture = TestBed.createComponent(AccountMgmtComponent);
|
fixture = TestBed.createComponent(AccountMgmtComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { UserDeleteComponent } from './user-delete/user-delete.component';
|
|
||||||
import { UserEditComponent } from './user-edit/user-edit.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 { LocalStorageService } from 'src/app/services/local-storage.service';
|
||||||
import { Group } from 'src/app/types/group';
|
import { Group } from 'src/app/types/group';
|
||||||
import User from 'src/app/types/user';
|
import User from 'src/app/types/user';
|
||||||
@@ -57,75 +54,9 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit {
|
|||||||
this.users.filter = value.toLowerCase().trim()
|
this.users.filter = value.toLowerCase().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(item: any) {
|
openUserCard(id?: string) {
|
||||||
this.dialog.open(UserEditComponent, {data: {user: item, groups: this.groups}}).afterClosed().subscribe(reply => {
|
this.dialog.open<UserEditComponent, UserEditComponent.InputData, UserEditComponent.ReturnData>(UserEditComponent, {data: {id: id, type: id ? "edit" : "new", groups: this.groups}}).afterClosed().subscribe(r => {
|
||||||
if (reply) {
|
if (r) this.ngOnInit()
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
<form [formGroup]="form" (ngSubmit)="editUser()">
|
<h1 mat-dialog-title>Karta użytkownika</h1>
|
||||||
<mat-form-field appearance="outline">
|
<mat-dialog-content>
|
||||||
|
<form [formGroup]="form">
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" color="accent">
|
||||||
<mat-label>Imię</mat-label>
|
<mat-label>Imię</mat-label>
|
||||||
<input type="text" matInput formControlName="fname">
|
<input type="text" matInput formControlName="fname">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" color="accent">
|
||||||
<mat-label>Nazwisko</mat-label>
|
<mat-label>Nazwisko</mat-label>
|
||||||
<input type="text" matInput formControlName="surname">
|
<input type="text" matInput formControlName="surname">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" color="accent">
|
||||||
<mat-label>Pokój</mat-label>
|
<mat-label>Pokój</mat-label>
|
||||||
<input type="text" matInput formControlName="room">
|
<input type="text" matInput formControlName="room">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline" color="accent">
|
||||||
<mat-label>Nazwa użytkownika</mat-label>
|
|
||||||
<input type="text" matInput required formControlName="uname">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Grupy</mat-label>
|
<mat-label>Grupy</mat-label>
|
||||||
<mat-select multiple formControlName="groups">
|
<mat-select multiple formControlName="groups">
|
||||||
@for (item of groups; track $index) {
|
@for (item of groups; track $index) {
|
||||||
@@ -23,7 +22,26 @@
|
|||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field *ngIf="this.ls.permChecker(32)">
|
<span *ngIf="data.type == 'edit'">Data rejestracji:<br>{{regDate?.format('DD.MM.YYYY')}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" color="accent">
|
||||||
|
<mat-label>Nazwa użytkownika</mat-label>
|
||||||
|
<input type="text" matInput required formControlName="uname">
|
||||||
|
</mat-form-field>
|
||||||
|
@if (data.type == "edit") {
|
||||||
|
<button mat-stroked-button color="accent" (click)="resetPass()">Resetuj hasło</button>
|
||||||
|
@if (locked) {
|
||||||
|
<button mat-stroked-button color="warn" (click)="toggleLock(false)"><mat-icon>lock</mat-icon>Blokada ręczna</button>
|
||||||
|
} @else {
|
||||||
|
<button mat-stroked-button color="accent" (click)="toggleLock(true)">Zablokuj konto</button>
|
||||||
|
}
|
||||||
|
@if (lockout) {
|
||||||
|
<button mat-stroked-button color="warn" (click)="disableLockout()"><mat-icon>lock_clock</mat-icon>Auto-Blokada</button>
|
||||||
|
} @else {
|
||||||
|
<button mat-stroked-button disabled>Auto-Blokada nieczynna</button>
|
||||||
|
}
|
||||||
|
<mat-form-field *ngIf="ls.permChecker(32)" color="accent">
|
||||||
<mat-label>Uprawnienia</mat-label>
|
<mat-label>Uprawnienia</mat-label>
|
||||||
<mat-select multiple formControlName="flags">
|
<mat-select multiple formControlName="flags">
|
||||||
<mat-option [value]="1" *ngIf="ls.capCheck(1)">Wiadomości</mat-option>
|
<mat-option [value]="1" *ngIf="ls.capCheck(1)">Wiadomości</mat-option>
|
||||||
@@ -35,5 +53,15 @@
|
|||||||
<mat-option [value]="128" *ngIf="ls.capCheck(16)">Czystość</mat-option>
|
<mat-option [value]="128" *ngIf="ls.capCheck(16)">Czystość</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-stroked-button>Wyślij</button>
|
}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
@if (data.type == "edit") {
|
||||||
|
<button mat-stroked-button color="warn" style="margin-right: auto;" (click)="delete()">Usuń konto</button>
|
||||||
|
}
|
||||||
|
<button mat-stroked-button mat-dialog-close>Zamknij</button>
|
||||||
|
<button mat-flat-button color="accent" (click)="submit()">Zapisz</button>
|
||||||
|
<mat-spinner diameter="32" color="accent" *ngIf="loading"></mat-spinner>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -4,7 +4,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
|
margin-top: 1ch !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
grid-auto-flow: column;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
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;
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,36 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { UserEditComponent } from './user-edit.component';
|
import { UserEditComponent } from './user-edit.component';
|
||||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { forwardRef } from '@angular/core';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
|
||||||
describe('UserEditComponent', () => {
|
describe('UserEditComponent', () => {
|
||||||
let component: UserEditComponent;
|
let component: UserEditComponent;
|
||||||
let fixture: ComponentFixture<UserEditComponent>;
|
let fixture: ComponentFixture<UserEditComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [UserEditComponent],
|
declarations: [UserEditComponent],
|
||||||
imports: [MatDialogModule, MatFormFieldModule, ReactiveFormsModule, MatInputModule, BrowserAnimationsModule],
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
MatSelectModule
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: MatDialogRef, useValue: {} },
|
{ provide: MatDialogRef, useValue: {} },
|
||||||
{provide: MAT_DIALOG_DATA, useValue: {}}
|
{ provide: MAT_DIALOG_DATA, useValue: { groups: [] } },
|
||||||
|
{ provide: AdminCommService, useValue: acMock },
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
fixture = TestBed.createComponent(UserEditComponent);
|
fixture = TestBed.createComponent(UserEditComponent);
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
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 { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { LocalStorageService } from 'src/app/services/local-storage.service';
|
import { LocalStorageService } from 'src/app/services/local-storage.service';
|
||||||
import { Group } from 'src/app/types/group';
|
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';
|
||||||
|
|
||||||
|
export namespace UserEditComponent {
|
||||||
|
export type InputData = {type: "new" | "edit", id?: string, groups: Group[]}
|
||||||
|
export type ReturnData = true | undefined
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-edit',
|
selector: 'app-user-edit',
|
||||||
@@ -10,43 +22,105 @@ import { Group } from 'src/app/types/group';
|
|||||||
styleUrls: ['./user-edit.component.scss']
|
styleUrls: ['./user-edit.component.scss']
|
||||||
})
|
})
|
||||||
export class UserEditComponent {
|
export class UserEditComponent {
|
||||||
form: FormGroup
|
lockout = false;
|
||||||
|
locked = false;
|
||||||
|
loading = false;
|
||||||
|
form: FormGroup = new FormGroup({
|
||||||
|
fname: new FormControl<string>(""),
|
||||||
|
surname: new FormControl<string>(""),
|
||||||
|
room: new FormControl<string>(""),
|
||||||
|
uname: new FormControl<string>(""),
|
||||||
|
groups: new FormControl<Array<string>>([]),
|
||||||
|
flags: new FormControl<Array<number>>([]),
|
||||||
|
})
|
||||||
groups: Group[]
|
groups: Group[]
|
||||||
constructor (public dialogRef: MatDialogRef<UserEditComponent>, @Inject(MAT_DIALOG_DATA) public data: any, readonly ls: LocalStorageService) {
|
id?: string
|
||||||
if (data.user == null) {
|
regDate?: Moment;
|
||||||
data.user = {
|
constructor (
|
||||||
fname: "",
|
public dialogRef: MatDialogRef<UserEditComponent>,
|
||||||
surname: "",
|
@Inject(MAT_DIALOG_DATA) public data: UserEditComponent.InputData,
|
||||||
room: "",
|
readonly ls: LocalStorageService,
|
||||||
uname: "",
|
readonly acu: AdminCommService,
|
||||||
groups: [],
|
private dialog: MatDialog,
|
||||||
admin: 0
|
private sb: MatSnackBar
|
||||||
}
|
) {
|
||||||
}
|
this.groups = data.groups
|
||||||
this.groups = data.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<number> = []
|
var flags: Array<number> = []
|
||||||
if (data.user.admin) {
|
if (r.admin) {
|
||||||
if ((data.user.admin & 1) == 1) flags.push(1)
|
if ((r.admin & 1) == 1) flags.push(1)
|
||||||
if ((data.user.admin & 2) == 2) flags.push(2)
|
if ((r.admin & 2) == 2) flags.push(2)
|
||||||
if ((data.user.admin & 4) == 4) flags.push(4)
|
if ((r.admin & 4) == 4) flags.push(4)
|
||||||
if ((data.user.admin & 8) == 8) flags.push(8)
|
if ((r.admin & 8) == 8) flags.push(8)
|
||||||
if ((data.user.admin & 16) == 16) flags.push(16)
|
if ((r.admin & 16) == 16) flags.push(16)
|
||||||
if ((data.user.admin & 32) == 32) flags.push(32)
|
if ((r.admin & 32) == 32) flags.push(32)
|
||||||
if ((data.user.admin & 64) == 64) flags.push(64)
|
if ((r.admin & 64) == 64) flags.push(64)
|
||||||
if ((data.user.admin & 128) == 128) flags.push(128)
|
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.form = new FormGroup({
|
|
||||||
fname: new FormControl(data.user.fname),
|
|
||||||
surname: new FormControl(data.user.surname),
|
|
||||||
room: new FormControl(data.user.room),
|
|
||||||
uname: new FormControl<string>(data.user.uname),
|
|
||||||
groups: new FormControl<Array<string>>(data.user.groups),
|
|
||||||
flags: new FormControl<Array<number>>(flags),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected editUser() {
|
protected getForm() {
|
||||||
this.dialogRef.close({
|
return {
|
||||||
fname: this.form.get('fname')?.value,
|
fname: this.form.get('fname')?.value,
|
||||||
surname: this.form.get('surname')?.value,
|
surname: this.form.get('surname')?.value,
|
||||||
room: this.form.get('room')?.value,
|
room: this.form.get('room')?.value,
|
||||||
@@ -60,6 +134,44 @@ export class UserEditComponent {
|
|||||||
return undefined
|
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
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { UserResetComponent } from './user-reset.component';
|
import { UserResetComponent } from './user-reset.component';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
|
||||||
describe('UserResetComponent', () => {
|
describe('UserResetComponent', () => {
|
||||||
let component: UserResetComponent;
|
let component: UserResetComponent;
|
||||||
@@ -8,7 +9,10 @@ describe('UserResetComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [UserResetComponent]
|
declarations: [UserResetComponent],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule
|
||||||
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(UserResetComponent);
|
fixture = TestBed.createComponent(UserResetComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export class AdminCommService {
|
|||||||
return this.http.post<Status>(environment.apiEndpoint+`/admin/accs`, item, {withCredentials: true})
|
return this.http.post<Status>(environment.apiEndpoint+`/admin/accs`, item, {withCredentials: true})
|
||||||
},
|
},
|
||||||
|
|
||||||
putAcc: (id: string, update: object) => {
|
putAcc: (id: string, update: Partial<User>) => {
|
||||||
return this.http.put<Status>(environment.apiEndpoint+`/admin/accs/${id}`, update, {withCredentials: true})
|
return this.http.put<Status>(environment.apiEndpoint+`/admin/accs/${id}`, update, {withCredentials: true})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -141,6 +141,14 @@ export class AdminCommService {
|
|||||||
|
|
||||||
deleteAcc: (id: string) => {
|
deleteAcc: (id: string) => {
|
||||||
return this.http.delete<Status>(environment.apiEndpoint+`/admin/accs/${id}`, {withCredentials: true})
|
return this.http.delete<Status>(environment.apiEndpoint+`/admin/accs/${id}`, {withCredentials: true})
|
||||||
|
},
|
||||||
|
|
||||||
|
getUser: (id: string) => {
|
||||||
|
return this.http.get<Omit<User, "pass"> & {lockout: boolean}>(environment.apiEndpoint+`/admin/accs/${id}`, {withCredentials: true})
|
||||||
|
},
|
||||||
|
|
||||||
|
clearLockout: (id: string) => {
|
||||||
|
return this.http.delete<Status>(environment.apiEndpoint+`/admin/accs/${id}/lockout`, {withCredentials: true})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@@ -174,6 +182,17 @@ 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<{_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})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@@ -233,7 +252,7 @@ export class AdminCommService {
|
|||||||
return this.http.post<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, attendence, {withCredentials: true})
|
return this.http.post<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, attendence, {withCredentials: true})
|
||||||
},
|
},
|
||||||
getSummary: () => {
|
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) => {
|
deleteRoom: (room: string) => {
|
||||||
return this.http.delete<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true})
|
return this.http.delete<Status>(environment.apiEndpoint+`/admin/clean/attendence/${room}`, {withCredentials: true})
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -21,6 +8,10 @@
|
|||||||
<a matListItemTitle>{{link.title}}</a>
|
<a matListItemTitle>{{link.title}}</a>
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
}
|
}
|
||||||
|
<a mat-list-item href="https://foliand.men/wiki/!ipwa/" target="_blank">
|
||||||
|
<mat-icon matListItemIcon>developer_guide</mat-icon>
|
||||||
|
<a matListItemTitle>Dokumentacja</a>
|
||||||
|
</a>
|
||||||
<mat-list-item (click)="goNormal()">
|
<mat-list-item (click)="goNormal()">
|
||||||
<mat-icon matListItemIcon>close</mat-icon>
|
<mat-icon matListItemIcon>close</mat-icon>
|
||||||
<h4 matListItemTitle>Zakończ edycję</h4>
|
<h4 matListItemTitle>Zakończ edycję</h4>
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { AdminViewComponent } from './admin-view.component';
|
import { AdminViewComponent } from './admin-view.component';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({selector: "app-toolbar", template: ''})
|
||||||
|
class ToolbarMock {
|
||||||
|
@Input() drawer!: MatDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
describe('AdminViewComponent', () => {
|
describe('AdminViewComponent', () => {
|
||||||
let component: AdminViewComponent;
|
let component: AdminViewComponent;
|
||||||
@@ -14,7 +20,7 @@ describe('AdminViewComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [AdminViewComponent],
|
declarations: [AdminViewComponent, ToolbarMock],
|
||||||
imports: [MatToolbarModule, MatIconModule, MatSidenavModule, BrowserAnimationsModule, MatListModule, RouterModule.forRoot([])]
|
imports: [MatToolbarModule, MatIconModule, MatSidenavModule, BrowserAnimationsModule, MatListModule, RouterModule.forRoot([])]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(AdminViewComponent);
|
fixture = TestBed.createComponent(AdminViewComponent);
|
||||||
|
|||||||
@@ -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',
|
||||||
@@ -21,12 +19,11 @@ export class AdminViewComponent {
|
|||||||
{ title: "Czystość", icon: "cleaning_services", href: "grades", enabled: this.ls.permChecker(128) && this.ls.capCheck(16) },
|
{ title: "Czystość", icon: "cleaning_services", href: "grades", enabled: this.ls.permChecker(128) && this.ls.capCheck(16) },
|
||||||
{ title: "Frekwencja", icon: "checklist", href: "attendence", enabled: false },
|
{ title: "Frekwencja", icon: "checklist", href: "attendence", enabled: false },
|
||||||
{ title: "Ustawienia", icon: "settings_applications", href: "settings", enabled: this.ls.permChecker(32) },
|
{ title: "Ustawienia", icon: "settings_applications", href: "settings", enabled: this.ls.permChecker(32) },
|
||||||
{ title: "Instrukcje", icon: "description", href: "guide", enabled: true }
|
|
||||||
];
|
];
|
||||||
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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div matColumnDef="actions">
|
<div matColumnDef="actions">
|
||||||
<th mat-header-cell *matHeaderCellDef>Usuń</th>
|
<th mat-header-cell *matHeaderCellDef>Usuń</th>
|
||||||
<td mat-cell *matCellDef="let item"><button mat-mini-fab color="warn" (click)="delete(item.room)"><mat-icon>delete</mat-icon></button></td>
|
<td mat-cell *matCellDef="let item"><button mat-mini-fab color="warn" (click)="delete(item.room)" *ngIf="!item.auto"><mat-icon>delete</mat-icon></button></td>
|
||||||
</div>
|
</div>
|
||||||
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
||||||
<tr mat-row *matRowDef="let rowData; columns: collumns"></tr>
|
<tr mat-row *matRowDef="let rowData; columns: collumns"></tr>
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AttendenceSummaryComponent } from './attendence-summary.component';
|
import { AttendenceSummaryComponent } from './attendence-summary.component';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
|
||||||
describe('AttendenceSummaryComponent', () => {
|
describe('AttendenceSummaryComponent', () => {
|
||||||
let component: AttendenceSummaryComponent;
|
let component: AttendenceSummaryComponent;
|
||||||
let fixture: ComponentFixture<AttendenceSummaryComponent>;
|
let fixture: ComponentFixture<AttendenceSummaryComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
clean: {
|
||||||
|
attendence: {
|
||||||
|
getSummary: jasmine.createSpy("getSummary").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [AttendenceSummaryComponent]
|
declarations: [AttendenceSummaryComponent],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
MatTableModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -11,7 +11,7 @@ import { AdminCommService } from '../../admin-comm.service';
|
|||||||
})
|
})
|
||||||
export class AttendenceSummaryComponent implements OnInit {
|
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']
|
collumns = ['room', 'hours', 'actions']
|
||||||
|
|
||||||
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) {
|
constructor (private toolbar: ToolbarService, private router: Router, private route: ActivatedRoute, private ac: AdminCommService) {
|
||||||
|
|||||||
@@ -1,14 +1,41 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AttendenceComponent } from './attendence.component';
|
import { AttendenceComponent } from './attendence.component';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
describe('AttendenceComponent', () => {
|
describe('AttendenceComponent', () => {
|
||||||
let component: AttendenceComponent;
|
let component: AttendenceComponent;
|
||||||
let fixture: ComponentFixture<AttendenceComponent>;
|
let fixture: ComponentFixture<AttendenceComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const acMock = {
|
||||||
|
clean: {
|
||||||
|
attendence: {
|
||||||
|
getUsers: jasmine.createSpy("getUsers").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [AttendenceComponent]
|
declarations: [AttendenceComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: MAT_DIALOG_DATA, useValue: {}},
|
||||||
|
{provide: MatDialogRef, useValue: {}},
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,55 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { GradesComponent } from './grades.component';
|
import { GradesComponent } from './grades.component';
|
||||||
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@Component({selector: "app-date-selector", template: ''})
|
||||||
|
class DateSelectorStub {
|
||||||
|
@Input() date: moment.Moment = moment.utc().startOf('day');
|
||||||
|
@Output() dateChange = new EventEmitter<moment.Moment>();
|
||||||
|
@Input() filter: (date: moment.Moment | null) => boolean = () => true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: "app-room-chooser", template: ''})
|
||||||
|
class RoomSelectorStub {
|
||||||
|
@Input() rooms: string[] = []
|
||||||
|
@Output() room: EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
}
|
||||||
|
|
||||||
describe('GradesComponent', () => {
|
describe('GradesComponent', () => {
|
||||||
let component: GradesComponent;
|
let component: GradesComponent;
|
||||||
let fixture: ComponentFixture<GradesComponent>;
|
let fixture: ComponentFixture<GradesComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
clean: {
|
||||||
|
getConfig: jasmine.createSpy("getConfig").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [GradesComponent]
|
declarations: [GradesComponent, DateSelectorStub, RoomSelectorStub],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
MatIconModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -49,8 +49,8 @@ export class GradesComponent implements OnInit, OnDestroy {
|
|||||||
if (!this.filter(this.date)) this.date.isoWeekday(8);
|
if (!this.filter(this.date)) this.date.isoWeekday(8);
|
||||||
this.toolbar.comp = this
|
this.toolbar.comp = this
|
||||||
this.toolbar.menu = [
|
this.toolbar.menu = [
|
||||||
|
{ title: "Pokoje do sprawdzenia", check: true, fn: "attendenceSummary", icon: "overview"},
|
||||||
{ title: "Podsumowanie", check: true, fn: "summary", icon: "analytics" },
|
{ title: "Podsumowanie", check: true, fn: "summary", icon: "analytics" },
|
||||||
{ title: "Obecność", check: true, fn: "attendenceSummary", icon: "overview"}
|
|
||||||
]
|
]
|
||||||
this.form.valueChanges.subscribe((v) => {
|
this.form.valueChanges.subscribe((v) => {
|
||||||
this.calculate()
|
this.calculate()
|
||||||
|
|||||||
@@ -1,14 +1,49 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { SummaryComponent } from './summary.component';
|
import { SummaryComponent } from './summary.component';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
|
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MAT_MOMENT_DATE_FORMATS, MomentDateAdapter, provideMomentDateAdapter } from '@angular/material-moment-adapter';
|
||||||
|
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
describe('SummaryComponent', () => {
|
describe('SummaryComponent', () => {
|
||||||
let component: SummaryComponent;
|
let component: SummaryComponent;
|
||||||
let fixture: ComponentFixture<SummaryComponent>;
|
let fixture: ComponentFixture<SummaryComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const acMock = {
|
||||||
|
clean: {
|
||||||
|
summary: {
|
||||||
|
getSummary: jasmine.createSpy("getSummary").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [SummaryComponent]
|
declarations: [SummaryComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: AdminCommService, useValue: acMock },
|
||||||
|
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_DATE_FORMATS, MAT_MOMENT_DATE_ADAPTER_OPTIONS] },
|
||||||
|
{ provide: MAT_DATE_LOCALE, useValue: "pl-PL" },
|
||||||
|
{ provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
|
||||||
|
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } },
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatDatepickerModule,
|
||||||
|
MatIconModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatTableModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { GroupsComponent } from './groups.component';
|
import { GroupsComponent } from './groups.component';
|
||||||
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
describe('GroupsComponent', () => {
|
describe('GroupsComponent', () => {
|
||||||
let component: GroupsComponent;
|
let component: GroupsComponent;
|
||||||
let fixture: ComponentFixture<GroupsComponent>;
|
let fixture: ComponentFixture<GroupsComponent>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const acMock = {
|
||||||
|
groups: {
|
||||||
|
getGroups: jasmine.createSpy("getGroups").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [GroupsComponent]
|
declarations: [GroupsComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(GroupsComponent);
|
fixture = TestBed.createComponent(GroupsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { RemoveConfirmComponent } from './remove-confirm.component';
|
import { RemoveConfirmComponent } from './remove-confirm.component';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
|
||||||
describe('RemoveConfirmComponent', () => {
|
describe('RemoveConfirmComponent', () => {
|
||||||
let component: RemoveConfirmComponent;
|
let component: RemoveConfirmComponent;
|
||||||
@@ -8,7 +9,8 @@ describe('RemoveConfirmComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [RemoveConfirmComponent]
|
declarations: [RemoveConfirmComponent],
|
||||||
|
imports: [MatDialogModule]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(RemoveConfirmComponent);
|
fixture = TestBed.createComponent(RemoveConfirmComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</mat-chip-listbox>
|
</mat-chip-listbox>
|
||||||
<button mat-icon-button (click)="new()"><mat-icon>add</mat-icon></button>
|
<button mat-icon-button (click)="new()"><mat-icon>add</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
<mat-spinner *ngIf="loading"></mat-spinner>
|
<mat-spinner *ngIf="loading" color="accent"></mat-spinner>
|
||||||
<table mat-table [dataSource]="keys">
|
<table mat-table [dataSource]="keys">
|
||||||
<div matColumnDef="room">
|
<div matColumnDef="room">
|
||||||
<th mat-header-cell *matHeaderCellDef>Sala</th>
|
<th mat-header-cell *matHeaderCellDef>Sala</th>
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AdminKeyComponent } from './key.component';
|
import { AdminKeyComponent } from './key.component';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
describe('KeyComponent', () => {
|
describe('AdminKeyComponent', () => {
|
||||||
let component: AdminKeyComponent;
|
let component: AdminKeyComponent;
|
||||||
let fixture: ComponentFixture<AdminKeyComponent>;
|
let fixture: ComponentFixture<AdminKeyComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
keys: {
|
||||||
|
getKeys: jasmine.createSpy("getKeys").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [AdminKeyComponent]
|
declarations: [AdminKeyComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [MatFormFieldModule, MatChipsModule, MatIconModule, MatPaginatorModule, FormsModule, MatProgressSpinnerModule, MatTableModule, MatInputModule, NoopAnimationsModule]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<form (ngSubmit)="send()" [formGroup]="form">
|
<form (ngSubmit)="send()" [formGroup]="form">
|
||||||
<mat-form-field>
|
<mat-form-field color="accent">
|
||||||
<mat-label>Sala</mat-label>
|
<mat-label>Sala</mat-label>
|
||||||
<mat-select formControlName="room" required>
|
<mat-select formControlName="room" required>
|
||||||
@for (item of rooms; track $index) {
|
@for (item of rooms; track $index) {
|
||||||
@@ -9,17 +9,9 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="form.controls['room'].hasError('required')">Wymagane</mat-error>
|
<mat-error *ngIf="form.controls['room'].hasError('required')">Wymagane</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field color="accent">
|
||||||
<mat-label>Wypożyczający</mat-label>
|
<mat-label>Wypożyczający</mat-label>
|
||||||
<!-- TODO: Add user selector -->
|
<app-user-search formControlName="user" required/>
|
||||||
<input matInput placeholder="Nazwa użytkownika" formControlName="user" required>
|
|
||||||
<!-- <input #input matInput placeholder="Nazwa użytkownika" formControlName="user" required [matAutocomplete]="auto" (input)="filter()">
|
|
||||||
<mat-autocomplete requireSelection #auto="matAutocomplete">
|
|
||||||
@for (item of unames; track item) {
|
|
||||||
<mat-option [value]="item">{{item}}</mat-option>
|
|
||||||
}
|
|
||||||
</mat-autocomplete> -->
|
|
||||||
<mat-error *ngIf="form.controls['user'].hasError('unf')">Zła nazwa użytkownika</mat-error>
|
|
||||||
<mat-error *ngIf="form.controls['user'].hasError('required')">Wymagane</mat-error>
|
<mat-error *ngIf="form.controls['user'].hasError('required')">Wymagane</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-button>Wyślij</button>
|
<button mat-button>Wyślij</button>
|
||||||
|
|||||||
@@ -1,14 +1,74 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { NewKeyComponent } from './new-key.component';
|
import { NewKeyComponent } from './new-key.component';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { Component, forwardRef, Optional, Self } from '@angular/core';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { AbstractControlDirective, ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-user-search", template: '', providers: [{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => UserSearchStub),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MatFormFieldControl,
|
||||||
|
useExisting: UserSearchStub
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class UserSearchStub implements ControlValueAccessor, MatFormFieldControl<never> {
|
||||||
|
value: null = null;
|
||||||
|
stateChanges: Observable<void> = of();
|
||||||
|
id: string = "";
|
||||||
|
placeholder: string = "";
|
||||||
|
ngControl: NgControl | AbstractControlDirective | null = null;
|
||||||
|
focused: boolean = false;
|
||||||
|
empty: boolean = true;
|
||||||
|
shouldLabelFloat: boolean = true;
|
||||||
|
required: boolean = false;
|
||||||
|
disabled: boolean = false;
|
||||||
|
errorState: boolean = false;
|
||||||
|
controlType?: string | undefined;
|
||||||
|
autofilled?: boolean | undefined;
|
||||||
|
userAriaDescribedBy?: string | undefined;
|
||||||
|
setDescribedByIds(ids: string[]): void {}
|
||||||
|
onContainerClick(event: MouseEvent): void {}
|
||||||
|
writeValue(obj: any): void {}
|
||||||
|
registerOnChange(fn: any): void {}
|
||||||
|
registerOnTouched(fn: any): void {}
|
||||||
|
setDisabledState?(isDisabled: boolean): void {}
|
||||||
|
}
|
||||||
|
|
||||||
describe('NewKeyComponent', () => {
|
describe('NewKeyComponent', () => {
|
||||||
let component: NewKeyComponent;
|
let component: NewKeyComponent;
|
||||||
let fixture: ComponentFixture<NewKeyComponent>;
|
let fixture: ComponentFixture<NewKeyComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
keys: {
|
||||||
|
avalKeys: jasmine.createSpy("avalKeys").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [NewKeyComponent]
|
declarations: [NewKeyComponent, UserSearchStub],
|
||||||
|
providers: [
|
||||||
|
{ provide: AdminCommService, useValue: acMock },
|
||||||
|
{ provide: MatDialogRef, useValue: {} }
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -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 { AdminCommService } from '../../admin-comm.service';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { startWith } from 'rxjs';
|
import { UserSearchResult } from 'src/app/commonComponents/user-search/user-search.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-new-key',
|
selector: 'app-new-key',
|
||||||
@@ -10,11 +10,10 @@ import { startWith } from 'rxjs';
|
|||||||
styleUrl: './new-key.component.scss'
|
styleUrl: './new-key.component.scss'
|
||||||
})
|
})
|
||||||
export class NewKeyComponent implements OnInit {
|
export class NewKeyComponent implements OnInit {
|
||||||
// @ViewChild('input') input!: ElementRef<HTMLInputElement>
|
|
||||||
rooms: string[] = []
|
rooms: string[] = []
|
||||||
form = new FormGroup({
|
form = new FormGroup({
|
||||||
room: new FormControl<string>(""),
|
room: new FormControl<string>(""),
|
||||||
user: new FormControl<string>("")
|
user: new FormControl<UserSearchResult | null>(null)
|
||||||
})
|
})
|
||||||
unames: any[] = []
|
unames: any[] = []
|
||||||
constructor ( private ac: AdminCommService, public dialogRef: MatDialogRef<NewKeyComponent> ) {}
|
constructor ( private ac: AdminCommService, public dialogRef: MatDialogRef<NewKeyComponent> ) {}
|
||||||
@@ -25,24 +24,9 @@ export class NewKeyComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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() {
|
send() {
|
||||||
if (this.form.valid) {
|
if (this.form.valid) {
|
||||||
this.dialogRef.close(this.form.value)
|
this.dialogRef.close(this.form.value)
|
||||||
} else {
|
|
||||||
this.form.controls['user'].setErrors({unf: true})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { MenuAddComponent } from './menu-add.component';
|
import { MenuAddComponent } from './menu-add.component';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
describe('MenuAddComponent', () => {
|
describe('MenuAddComponent', () => {
|
||||||
let component: MenuAddComponent;
|
let component: MenuAddComponent;
|
||||||
@@ -8,7 +11,17 @@ describe('MenuAddComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [MenuAddComponent]
|
declarations: [MenuAddComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: MAT_DIALOG_DATA, useValue: {}},
|
||||||
|
{provide: MatDialogRef, useValue: {}}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatRadioModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class MenuAddComponent {
|
|||||||
submit() {
|
submit() {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "day":
|
case "day":
|
||||||
this.dialogRef.close({type: "day", value: this.day.utc()})
|
this.dialogRef.close({type: "day", value: this.day.utc().startOf('day')})
|
||||||
break;
|
break;
|
||||||
case "week":
|
case "week":
|
||||||
this.dialogRef.close({type: "week", value: {start: this.range.value.start?.utc().hours(24), count: 5}})
|
this.dialogRef.close({type: "week", value: {start: this.range.value.start?.utc().hours(24), count: 5}})
|
||||||
|
|||||||
@@ -101,11 +101,11 @@ export class MenuNewComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editSn(id: string) {
|
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) {
|
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) {
|
editKol(id: string) {
|
||||||
|
|||||||
@@ -23,3 +23,8 @@
|
|||||||
<p>{{item.date | date:'d-LL-yyyy HH:mm'}}</p>
|
<p>{{item.date | date:'d-LL-yyyy HH:mm'}}</p>
|
||||||
</mat-card-footer>
|
</mat-card-footer>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
<mat-card *ngIf="news.length == 0">
|
||||||
|
<p>
|
||||||
|
Brak wiadomości.
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
@@ -22,6 +22,10 @@ mat-card-content p {
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mat-card p {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-right: 4pt;
|
margin-right: 4pt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,25 @@ import { AdminCommService } from '../admin-comm.service';
|
|||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
|
||||||
describe('NewsEditComponent', () => {
|
describe('NewsEditComponent', () => {
|
||||||
let component: NewsEditComponent;
|
let component: NewsEditComponent;
|
||||||
let fixture: ComponentFixture<NewsEditComponent>;
|
let fixture: ComponentFixture<NewsEditComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const acMock = jasmine.createSpyObj('AdminCommService', {
|
acMock = {
|
||||||
getNews: of()
|
news: {
|
||||||
})
|
getNews: jasmine.createSpy('getNews').and.returnValue(of([]))
|
||||||
|
}
|
||||||
|
}
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [NewsEditComponent],
|
declarations: [NewsEditComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AdminCommService, useValue: acMock}
|
{provide: AdminCommService, useValue: acMock}
|
||||||
],
|
],
|
||||||
imports: [MatDialogModule, MatSnackBarModule]
|
imports: [MatDialogModule, MatSnackBarModule, MatCardModule]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(NewsEditComponent);
|
fixture = TestBed.createComponent(NewsEditComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<!-- 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">
|
||||||
<mat-radio-button value="uname">
|
<mat-radio-button value="uid">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Nazwa użytkownika</mat-label>
|
<mat-label>Użytkownik</mat-label>
|
||||||
<input matInput type="text" formControlName="uname">
|
<app-user-search formControlName="uid" required/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-radio-button>
|
</mat-radio-button>
|
||||||
<mat-radio-button value="room">
|
<mat-radio-button value="room">
|
||||||
|
|||||||
@@ -1,14 +1,76 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { NotificationsComponent } from './notifications.component';
|
import { NotificationsComponent } from './notifications.component';
|
||||||
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
|
import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { Component, forwardRef } from '@angular/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { AbstractControlDirective, ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-user-search", template: '', providers: [{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => UserSearchStub),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MatFormFieldControl,
|
||||||
|
useExisting: UserSearchStub
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class UserSearchStub implements ControlValueAccessor, MatFormFieldControl<never> {
|
||||||
|
value: null = null;
|
||||||
|
stateChanges: Observable<void> = of();
|
||||||
|
id: string = "";
|
||||||
|
placeholder: string = "";
|
||||||
|
ngControl: NgControl | AbstractControlDirective | null = null;
|
||||||
|
focused: boolean = false;
|
||||||
|
empty: boolean = true;
|
||||||
|
shouldLabelFloat: boolean = true;
|
||||||
|
required: boolean = false;
|
||||||
|
disabled: boolean = false;
|
||||||
|
errorState: boolean = false;
|
||||||
|
controlType?: string | undefined;
|
||||||
|
autofilled?: boolean | undefined;
|
||||||
|
userAriaDescribedBy?: string | undefined;
|
||||||
|
setDescribedByIds(ids: string[]): void {}
|
||||||
|
onContainerClick(event: MouseEvent): void {}
|
||||||
|
writeValue(obj: any): void {}
|
||||||
|
registerOnChange(fn: any): void {}
|
||||||
|
registerOnTouched(fn: any): void {}
|
||||||
|
setDisabledState?(isDisabled: boolean): void {}
|
||||||
|
}
|
||||||
|
|
||||||
describe('NotificationsComponent', () => {
|
describe('NotificationsComponent', () => {
|
||||||
let component: NotificationsComponent;
|
let component: NotificationsComponent;
|
||||||
let fixture: ComponentFixture<NotificationsComponent>;
|
let fixture: ComponentFixture<NotificationsComponent>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const acMock = {
|
||||||
|
notif: {
|
||||||
|
getGroups: jasmine.createSpy("getGroups").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [NotificationsComponent]
|
declarations: [NotificationsComponent, UserSearchStub],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
MatRadioModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatIconModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
NoopAnimationsModule
|
||||||
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(NotificationsComponent);
|
fixture = TestBed.createComponent(NotificationsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormBuilder, 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';
|
||||||
|
import { UserSearchResult } from 'src/app/commonComponents/user-search/user-search.component';
|
||||||
|
|
||||||
@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[]
|
||||||
|
form = this.fb.group({
|
||||||
|
recp: this.fb.group({
|
||||||
|
uid: this.fb.control<UserSearchResult | null>(null),
|
||||||
|
room: this.fb.control<string|null>(null),
|
||||||
|
group: this.fb.control<string>(''),
|
||||||
|
type: this.fb.control<"room" | "uname" | "group">('uname', {nonNullable: true})
|
||||||
|
}),
|
||||||
|
title: this.fb.control('', {nonNullable: true}),
|
||||||
|
body: this.fb.control('', {nonNullable: true})
|
||||||
|
})
|
||||||
|
|
||||||
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, private fb: FormBuilder ) {
|
||||||
|
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,33 +44,21 @@ 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>({
|
|
||||||
recp: new FormGroup({
|
|
||||||
uname: new FormControl<string>(''),
|
|
||||||
room: new FormControl<string|null>(null),
|
|
||||||
group: new FormControl<string>(''),
|
|
||||||
type: new FormControl<"room" | "uname" | "group">('uname', {nonNullable: true})
|
|
||||||
}),
|
|
||||||
title: new FormControl('', {nonNullable: true}),
|
|
||||||
body: new FormControl('', {nonNullable: true})
|
|
||||||
})
|
|
||||||
|
|
||||||
submit() {
|
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
|
this.success = data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationForm {
|
|
||||||
body: FormControl<string>;
|
|
||||||
title: FormControl<string>;
|
|
||||||
recp: FormGroup<{
|
|
||||||
uname: FormControl<string | null>;
|
|
||||||
room: FormControl<string | null>;
|
|
||||||
group: FormControl<string | null>;
|
|
||||||
type: FormControl<"room" | "uname" | "group">;
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title-group>
|
||||||
|
<mat-card-title>
|
||||||
|
{{item.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 *ngIf="body">
|
||||||
|
{{body}}
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
@for (user of rcpts; 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-footer>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-stroked-button (click)="getMessage()" *ngIf="!body">Wczytaj treść</button>
|
||||||
|
<button mat-stroked-button (click)="getRcpts()" *ngIf="!rcpts">Wczytaj odbiorców</button>
|
||||||
|
<mat-spinner diameter="32" color="accent" *ngIf="loading"></mat-spinner>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card-footer>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mat-card-title {
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MessageComponent } from './message.component';
|
||||||
|
import { AdminCommService } from 'src/app/admin-view/admin-comm.service';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
describe('MessageComponent', () => {
|
||||||
|
let component: MessageComponent;
|
||||||
|
let fixture: ComponentFixture<MessageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const acMock = {
|
||||||
|
|
||||||
|
}
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [MessageComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatCardModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MessageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.item = {_id: "test", sentDate: moment(), title: "Test"}
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<p>Wysłane wiadomości:</p>
|
||||||
|
<div class="cardContainer">
|
||||||
|
@for (item of messages; track $index) {
|
||||||
|
<app-message [item]="item"></app-message>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.cardContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1ch;
|
||||||
|
margin: 1ch;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OutboxComponent } from './outbox.component';
|
||||||
|
import { AdminCommService } from '../../admin-comm.service';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
describe('OutboxComponent', () => {
|
||||||
|
let component: OutboxComponent;
|
||||||
|
let fixture: ComponentFixture<OutboxComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const acMock = {
|
||||||
|
notif: {
|
||||||
|
outbox: {
|
||||||
|
getSent: jasmine.createSpy("getSent").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [OutboxComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(OutboxComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
42
src/app/admin-view/notifications/outbox/outbox.component.ts
Normal file
42
src/app/admin-view/notifications/outbox/outbox.component.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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!: {
|
||||||
|
_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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<mat-accordion>
|
<mat-accordion>
|
||||||
|
<!-- #region Rooms-->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<!-- TODO: Make more ergonomic -->
|
<!-- TODO: Make more ergonomic -->
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
@@ -8,6 +9,8 @@
|
|||||||
<p>Kliknij listę aby edytować</p>
|
<p>Kliknij listę aby edytować</p>
|
||||||
<app-list-editor [converter]="usettings.rooms" (edit)="saveRoom($event)"></app-list-editor>
|
<app-list-editor [converter]="usettings.rooms" (edit)="saveRoom($event)"></app-list-editor>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
|
<!-- #region Room grade reasons-->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>Powody nieczystości</mat-panel-title>
|
<mat-panel-title>Powody nieczystości</mat-panel-title>
|
||||||
@@ -16,6 +19,8 @@
|
|||||||
<p>Kliknij listę aby edytować</p>
|
<p>Kliknij listę aby edytować</p>
|
||||||
<app-list-editor [list]="usettings.cleanThings" (edit)="saveCleanThings($event)"></app-list-editor>
|
<app-list-editor [list]="usettings.cleanThings" (edit)="saveCleanThings($event)"></app-list-editor>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
|
<!-- #region Key rooms-->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>Sale z kluczami</mat-panel-title>
|
<mat-panel-title>Sale z kluczami</mat-panel-title>
|
||||||
@@ -23,6 +28,68 @@
|
|||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
<app-list-editor [list]="usettings.keyrooms" (edit)="saveKeyrooms($event)"></app-list-editor>
|
<app-list-editor [list]="usettings.keyrooms" (edit)="saveKeyrooms($event)"></app-list-editor>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
|
<!-- #region Default menu items-->
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>Domyślne wpisy jadłospisu</mat-panel-title>
|
||||||
|
<mat-panel-description></mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<table>
|
||||||
|
<caption>Domyślne wpisy w jadłospisie dla danych pozycji</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Śniadanie</th>
|
||||||
|
<th>Kolacja</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<app-list-editor [list]="usettings.menu.defaultItems.sn" (edit)="saveSn($event)"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-list-editor [list]="usettings.menu.defaultItems.kol" (edit)="saveKol($event)"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
|
<!-- #region Security-->
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>Bezpieczeństwo</mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<mat-tab-group color="accent">
|
||||||
|
<mat-tab label="Konta">
|
||||||
|
<p>
|
||||||
|
Domyślne hasło użytkownika po wygenerowaniu konto to <code>pierwszelogowanie</code><br>
|
||||||
|
Reset hasła powoduje zmianę na <code>reset</code>
|
||||||
|
</p>
|
||||||
|
<form [formGroup]="accSec" (submit)="saveAccSecTimeouts()">
|
||||||
|
<p>Ograniczenia logowania</p>
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Dozwolone próby logowania</mat-label>
|
||||||
|
<input matInput type="number" formControlName="attempts">
|
||||||
|
</mat-form-field><br>
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Okres liczenia prób</mat-label>
|
||||||
|
<input matInput type="number" formControlName="time">
|
||||||
|
<mat-hint>Podaj w minutach</mat-hint>
|
||||||
|
</mat-form-field><br>
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Czas blokady konta</mat-label>
|
||||||
|
<input matInput type="number" formControlName="lockout">
|
||||||
|
<mat-hint>Podaj w minutach</mat-hint>
|
||||||
|
</mat-form-field><br>
|
||||||
|
<button mat-flat-button color="accent">Zapisz</button>
|
||||||
|
</form>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
|
<!-- #region Program control-->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>Sterowanie programem</mat-panel-title>
|
<mat-panel-title>Sterowanie programem</mat-panel-title>
|
||||||
@@ -41,4 +108,5 @@
|
|||||||
Wyloguj wszystkich użytkowników
|
Wyloguj wszystkich użytkowników
|
||||||
</button> -->
|
</button> -->
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
<!-- #endregion -->
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
@@ -1,14 +1,49 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { SettingsComponent } from './settings.component';
|
import { SettingsComponent } from './settings.component';
|
||||||
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
|
import { MatExpansionModule } from '@angular/material/expansion';
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
|
||||||
|
@Component({selector: 'app-list-editor', template: ''})
|
||||||
|
class ListEditorStub {
|
||||||
|
@Input() converter?: any[];
|
||||||
|
@Input() list?: string[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
describe('SettingsComponent', () => {
|
describe('SettingsComponent', () => {
|
||||||
let component: SettingsComponent;
|
let component: SettingsComponent;
|
||||||
let fixture: ComponentFixture<SettingsComponent>;
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const acMock = {
|
||||||
|
settings: {
|
||||||
|
getAll: jasmine.createSpy("getAll").and.returnValue(of())
|
||||||
|
}
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [SettingsComponent]
|
declarations: [SettingsComponent, ListEditorStub],
|
||||||
|
providers: [
|
||||||
|
{provide: AdminCommService, useValue: acMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatExpansionModule,
|
||||||
|
MatTabsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatIconModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
MatInputModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { AdminCommService } from '../admin-comm.service';
|
import { AdminCommService } from '../admin-comm.service';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@@ -8,13 +9,21 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
styleUrl: './settings.component.scss'
|
styleUrl: './settings.component.scss'
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
usettings!: IUSettings
|
usettings: IUSettings = {cleanThings: [], keyrooms: [], menu: {defaultItems: {kol: [], sn: []}}, rooms: [], security: {loginTimeout: {attempts: 0, lockout: 0, time: 0}}}
|
||||||
reloadTimeout: boolean = false;
|
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 {
|
ngOnInit(): void {
|
||||||
this.acu.settings.getAll().subscribe((r) => {
|
this.acu.settings.getAll().subscribe((r) => {
|
||||||
this.usettings = r
|
this.usettings = r
|
||||||
|
this.accSecTimeouts = r.security.loginTimeout
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +40,35 @@ export class SettingsComponent implements OnInit {
|
|||||||
this.send()
|
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() {
|
send() {
|
||||||
this.acu.settings.post(this.usettings).subscribe((s) => {
|
this.acu.settings.post(this.usettings).subscribe((s) => {
|
||||||
if (s.status == 200) {
|
if (s.status == 200) {
|
||||||
@@ -63,4 +101,17 @@ export interface IUSettings {
|
|||||||
keyrooms: string[];
|
keyrooms: string[];
|
||||||
rooms: string[];
|
rooms: string[];
|
||||||
cleanThings: string[];
|
cleanThings: string[];
|
||||||
|
menu: {
|
||||||
|
defaultItems: {
|
||||||
|
sn: string[];
|
||||||
|
kol: string[];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
security: {
|
||||||
|
loginTimeout: {
|
||||||
|
attempts: number;
|
||||||
|
time: number;
|
||||||
|
lockout: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1
src/app/admin-view/start/start.component.html
Normal file
1
src/app/admin-view/start/start.component.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<span class="main"><mat-icon class="icon">arrow_upward</mat-icon><span>Wybierz zakładkę w menu</span></span>
|
||||||
13
src/app/admin-view/start/start.component.scss
Normal file
13
src/app/admin-view/start/start.component.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
27
src/app/admin-view/start/start.component.spec.ts
Normal file
27
src/app/admin-view/start/start.component.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StartAdminComponent } from './start.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
|
describe('StartAdminComponent', () => {
|
||||||
|
let component: StartAdminComponent;
|
||||||
|
let fixture: ComponentFixture<StartAdminComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [StartAdminComponent],
|
||||||
|
imports: [
|
||||||
|
MatIconModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(StartAdminComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
10
src/app/admin-view/start/start.component.ts
Normal file
10
src/app/admin-view/start/start.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-start',
|
||||||
|
templateUrl: './start.component.html',
|
||||||
|
styleUrl: './start.component.scss'
|
||||||
|
})
|
||||||
|
export class StartAdminComponent {
|
||||||
|
|
||||||
|
}
|
||||||
14
src/app/admin-view/toolbar/toolbar.component.html
Normal file
14
src/app/admin-view/toolbar/toolbar.component.html
Normal 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>
|
||||||
0
src/app/admin-view/toolbar/toolbar.component.scss
Normal file
0
src/app/admin-view/toolbar/toolbar.component.scss
Normal file
27
src/app/admin-view/toolbar/toolbar.component.spec.ts
Normal file
27
src/app/admin-view/toolbar/toolbar.component.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ToolbarComponent } from './toolbar.component';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
|
||||||
|
describe('ToolbarComponent', () => {
|
||||||
|
let component: ToolbarComponent;
|
||||||
|
let fixture: ComponentFixture<ToolbarComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ToolbarComponent],
|
||||||
|
imports: [MatToolbarModule, MatIconModule, MatMenuModule]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ToolbarComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/app/admin-view/toolbar/toolbar.component.ts
Normal file
26
src/app/admin-view/toolbar/toolbar.component.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() { }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,6 @@ import { LocalStorageService } from './services/local-storage.service';
|
|||||||
|
|
||||||
export const adminGuard: CanActivateChildFn = (childRoute, state) => {
|
export const adminGuard: CanActivateChildFn = (childRoute, state) => {
|
||||||
const router = inject(Router)
|
const router = inject(Router)
|
||||||
if (inject(LocalStorageService).admin == false) return router.parseUrl('/')
|
if (inject(LocalStorageService).admin == undefined) return router.parseUrl('/')
|
||||||
return true
|
return true
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ 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';
|
||||||
|
import { StartAdminComponent } from './admin-view/start/start.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "", redirectTo: "login", pathMatch: "full"},
|
{path: "", redirectTo: "login", pathMatch: "full"},
|
||||||
@@ -30,10 +32,14 @@ const routes: Routes = [
|
|||||||
{path: "grades", component: PersonalComponent, title: "Konto"}
|
{path: "grades", component: PersonalComponent, title: "Konto"}
|
||||||
]},
|
]},
|
||||||
{path: "admin", component: AdminViewComponent, title: "Panel administracyjny", canActivateChild: [authGuard, adminGuard], children: [
|
{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: "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: [
|
||||||
|
|||||||
@@ -12,22 +12,24 @@ import { of } from 'rxjs';
|
|||||||
describe('AppViewComponent', () => {
|
describe('AppViewComponent', () => {
|
||||||
let component: AppViewComponent;
|
let component: AppViewComponent;
|
||||||
let fixture: ComponentFixture<AppViewComponent>;
|
let fixture: ComponentFixture<AppViewComponent>;
|
||||||
let authClient: jasmine.SpyObj<AuthClient>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const authSpy = jasmine.createSpyObj('AuthClient', ['check'])
|
const authSpy = jasmine.createSpyObj('AuthClient', ['check'])
|
||||||
const pushSpy = jasmine.createSpyObj('SwPush', ['requestSubscription'])
|
const pushSpy = jasmine.createSpyObj('SwPush', ['requestSubscription'])
|
||||||
const updatesSpy = jasmine.createSpyObj('UpdatesService', ['postNotif'])
|
const updatesSpy = jasmine.createSpyObj('UpdatesService', {
|
||||||
|
newsCheck: of()
|
||||||
|
})
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [AppViewComponent],
|
declarations: [AppViewComponent],
|
||||||
providers: [{provide: AuthClient, useValue: authSpy},
|
providers: [
|
||||||
|
{provide: AuthClient, useValue: authSpy},
|
||||||
{provide: SwPush, useValue: pushSpy},
|
{provide: SwPush, useValue: pushSpy},
|
||||||
{provide: UpdatesService, useValue: updatesSpy}],
|
{provide: UpdatesService, useValue: updatesSpy}
|
||||||
|
],
|
||||||
imports: [MatTabsModule, RouterModule.forRoot([]), MatIconModule]
|
imports: [MatTabsModule, RouterModule.forRoot([]), MatIconModule]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(AppViewComponent);
|
fixture = TestBed.createComponent(AppViewComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
authClient = TestBed.inject(AuthClient) as jasmine.SpyObj<AuthClient>
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { Link } from '../types/link';
|
|||||||
import { LocalStorageService } from '../services/local-storage.service';
|
import { LocalStorageService } from '../services/local-storage.service';
|
||||||
import { interval } from 'rxjs';
|
import { interval } from 'rxjs';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { NotifDialogComponent } from './notif-dialog/notif-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-app-view',
|
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() {
|
subscribeToNotif() {
|
||||||
if (this.swPush.isEnabled && this.ls.capCheck(4)) {
|
if (this.swPush.isEnabled && this.ls.capCheck(4)) {
|
||||||
@@ -45,6 +54,13 @@ export class AppViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newsCheck() {
|
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;
|
if (this.ls.newsflag) return;
|
||||||
this.us.newsCheck().subscribe((s) => {
|
this.us.newsCheck().subscribe((s) => {
|
||||||
if (s.hash != this.ls.newsCheck.hash) {
|
if (s.hash != this.ls.newsCheck.hash) {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let i of ls.defaultItems.sn">{{i}}</li>
|
<li *ngFor="let i of ls.defaultItems.sn">{{i}}</li>
|
||||||
<li *ngFor="let i of getsn.fancy">{{i.charAt(0).toUpperCase()+i.substring(1)}}</li>
|
<li *ngFor="let i of getsn.fancy">{{capitalize(i)}}</li>
|
||||||
<li *ngIf="getsn.second">{{getsn.second.charAt(0).toUpperCase()+getsn.second.substring(1)}}</li>
|
<li *ngIf="getsn.second">{{capitalize(getsn.second)}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<button mat-icon-button (click)="vote('kol', '-')"><mat-icon [color]="menu!.kolv == '-' ? 'warn' : null">thumb_down</mat-icon></button>
|
<button mat-icon-button (click)="vote('kol', '-')"><mat-icon [color]="menu!.kolv == '-' ? 'warn' : null">thumb_down</mat-icon></button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
<mat-card *ngIf="!(getkol || getob || getsn || loading)">
|
<mat-card *ngIf="!(getkol || getob || getsn || loading || gettitle)">
|
||||||
<mat-card-content id="no-data">
|
<mat-card-content id="no-data">
|
||||||
Brak danych, wybierz inny dzień.
|
Brak danych, wybierz inny dzień.
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|||||||
@@ -14,13 +14,17 @@ import { MatInputModule } from '@angular/material/input';
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatBottomSheet, MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
import { MatBottomSheet, MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
describe('MenuComponent', () => {
|
describe('MenuComponent', () => {
|
||||||
let component: MenuComponent;
|
let component: MenuComponent;
|
||||||
let fixture: ComponentFixture<MenuComponent>;
|
let fixture: ComponentFixture<MenuComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const updatesSpy = jasmine.createSpyObj('UpdatesService', ['getMenu'])
|
const updatesSpy = jasmine.createSpyObj('UpdatesService', {
|
||||||
|
getMenu: of()
|
||||||
|
})
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ MenuComponent, DateSelectorComponent],
|
declarations: [ MenuComponent, DateSelectorComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -30,7 +34,17 @@ describe('MenuComponent', () => {
|
|||||||
{provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS},
|
{provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS},
|
||||||
{provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: {useUtc: true}},
|
{provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: {useUtc: true}},
|
||||||
],
|
],
|
||||||
imports: [MatIconModule, MatFormFieldModule, MatDatepickerModule, MatCardModule, ReactiveFormsModule, MatInputModule, BrowserAnimationsModule, MatBottomSheetModule]
|
imports: [
|
||||||
|
MatIconModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatDatepickerModule,
|
||||||
|
MatCardModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatInputModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
MatBottomSheetModule,
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -32,17 +32,34 @@ export class MenuComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu?: Menu;
|
menu?: Menu;
|
||||||
get getsn() {return (this.menu && this.menu.sn) ? this.menu.sn : null}
|
get getsn() {return (this.menu && this.checkIfAnyProperty(this.menu.sn)) ? this.menu.sn : null}
|
||||||
get getob() {return (this.menu && this.menu.ob) ? this.menu.ob : 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 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}
|
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) {
|
updateMenu(silent?: boolean) {
|
||||||
this.loading = !silent
|
this.loading = !silent
|
||||||
if (!silent) this.menu = undefined
|
if (!silent) this.menu = undefined
|
||||||
this.uc.getMenu(this.day).subscribe(m => {
|
this.uc.getMenu(this.day).subscribe(m => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.menu = m
|
this.menu = m
|
||||||
|
console.log(m);
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,8 @@
|
|||||||
<p>{{item.date | date:'d-LL-yyyy HH:mm'}}</p>
|
<p>{{item.date | date:'d-LL-yyyy HH:mm'}}</p>
|
||||||
</mat-card-footer>
|
</mat-card-footer>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
<mat-card *ngIf="news.length == 0">
|
||||||
|
<p>
|
||||||
|
Brak wiadomości.
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
@@ -29,3 +29,7 @@ mat-card-footer p {
|
|||||||
mat-card-content p {
|
mat-card-content p {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mat-card p {
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { NewsComponent } from './news.component';
|
import { NewsComponent } from './news.component';
|
||||||
import { UpdatesService } from 'src/app/services/updates.service';
|
import { UpdatesService } from 'src/app/services/updates.service';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { LocalStorageService } from 'src/app/services/local-storage.service';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
|
||||||
describe('NewsComponent', () => {
|
describe('NewsComponent', () => {
|
||||||
let component: NewsComponent;
|
let component: NewsComponent;
|
||||||
@@ -12,11 +16,20 @@ describe('NewsComponent', () => {
|
|||||||
const updatesMock = jasmine.createSpyObj('UpdatesService', {
|
const updatesMock = jasmine.createSpyObj('UpdatesService', {
|
||||||
getNews: of()
|
getNews: of()
|
||||||
})
|
})
|
||||||
|
const lsMock = {
|
||||||
|
news: []
|
||||||
|
}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ NewsComponent ],
|
declarations: [ NewsComponent ],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: UpdatesService, useValue: updatesMock}
|
{provide: UpdatesService, useValue: updatesMock},
|
||||||
|
{provide: LocalStorageService, useValue: lsMock}
|
||||||
],
|
],
|
||||||
|
imports: [
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
|
MatCardModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
10
src/app/app-view/notif-dialog/notif-dialog.component.html
Normal file
10
src/app/app-view/notif-dialog/notif-dialog.component.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<h1 mat-dialog-title>{{data.message.title}}</h1>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<p>
|
||||||
|
{{data.message.body}}
|
||||||
|
</p>
|
||||||
|
<div>{{data.sentDate.format("[Wysłano] dddd DD MMMM YYYYr. o HH:mm")}}</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-raised-button color="primary" (click)="ack()">Odczytano</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
37
src/app/app-view/notif-dialog/notif-dialog.component.spec.ts
Normal file
37
src/app/app-view/notif-dialog/notif-dialog.component.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NotifDialogComponent } from './notif-dialog.component';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { UpdatesService } from 'src/app/services/updates.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
describe('NotifDialogComponent', () => {
|
||||||
|
let component: NotifDialogComponent;
|
||||||
|
let fixture: ComponentFixture<NotifDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const uMock = jasmine.createSpyObj<UpdatesService>("UpdatesService", {
|
||||||
|
postInfoAck: of()
|
||||||
|
})
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [NotifDialogComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: MAT_DIALOG_DATA, useValue: {message: "Test"}},
|
||||||
|
{provide: MatDialogRef, useValue: {}},
|
||||||
|
{provide: UpdatesService, useValue: uMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NotifDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/app/app-view/notif-dialog/notif-dialog.component.ts
Normal file
27
src/app/app-view/notif-dialog/notif-dialog.component.ts
Normal file
@@ -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<NotifDialogComponent>,
|
||||||
|
private uc: UpdatesService
|
||||||
|
) {
|
||||||
|
data.sentDate = moment(data.sentDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
ack () {
|
||||||
|
this.uc.postInfoAck(this.data._id).subscribe((v) => {
|
||||||
|
this.dialogRef.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AboutComponent } from './about.component';
|
import { AboutComponent } from './about.component';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatListModule } from '@angular/material/list';
|
||||||
|
|
||||||
describe('AboutComponent', () => {
|
describe('AboutComponent', () => {
|
||||||
let component: AboutComponent;
|
let component: AboutComponent;
|
||||||
@@ -8,7 +10,11 @@ describe('AboutComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [AboutComponent]
|
declarations: [AboutComponent],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatListModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,37 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { CleanComponent } from './clean.component';
|
import { CleanComponent } from './clean.component';
|
||||||
|
import { UpdatesService } from 'src/app/services/updates.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatDatepicker } from '@angular/material/datepicker';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
@Component({selector: "app-date-selector", template: ''})
|
||||||
|
class DateSelectorStub {
|
||||||
|
@Input() date: moment.Moment = moment.utc().startOf('day');
|
||||||
|
@Output() dateChange = new EventEmitter<moment.Moment>();
|
||||||
|
@Input() filter: (date: moment.Moment | null) => boolean = () => true
|
||||||
|
}
|
||||||
|
|
||||||
describe('CleanComponent', () => {
|
describe('CleanComponent', () => {
|
||||||
let component: CleanComponent;
|
let component: CleanComponent;
|
||||||
let fixture: ComponentFixture<CleanComponent>;
|
let fixture: ComponentFixture<CleanComponent>;
|
||||||
|
let updates: jasmine.SpyObj<UpdatesService>
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
updates = jasmine.createSpyObj<UpdatesService>("UpdatesService", {
|
||||||
|
getClean: of()
|
||||||
|
})
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [CleanComponent]
|
declarations: [CleanComponent, DateSelectorStub],
|
||||||
|
providers: [
|
||||||
|
{provide: UpdatesService, useValue: updates}
|
||||||
|
],
|
||||||
|
imports: [MatDialogModule, MatIconModule, MatFormFieldModule, MatDatepicker]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
14
src/app/app-view/personal/extra/extra.component.html
Normal file
14
src/app/app-view/personal/extra/extra.component.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<h1 mat-dialog-title>Dodatkowe ustawienia</h1>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<mat-action-list>
|
||||||
|
@for (link of LINKS; track link) {
|
||||||
|
<button mat-list-item (click)="open(link.component)">
|
||||||
|
<mat-icon matListItemIcon *ngIf="link.icon">{{link.icon}}</mat-icon>
|
||||||
|
<div matListItemTitle>{{link.title}}</div>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</mat-action-list>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-dialog-close mat-button>Zamknij</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
29
src/app/app-view/personal/extra/extra.component.spec.ts
Normal file
29
src/app/app-view/personal/extra/extra.component.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ExtraComponent } from './extra.component';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatListModule } from '@angular/material/list';
|
||||||
|
|
||||||
|
describe('ExtraComponent', () => {
|
||||||
|
let component: ExtraComponent;
|
||||||
|
let fixture: ComponentFixture<ExtraComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ExtraComponent],
|
||||||
|
imports: [
|
||||||
|
MatDialogModule,
|
||||||
|
MatListModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ExtraComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
29
src/app/app-view/personal/extra/extra.component.ts
Normal file
29
src/app/app-view/personal/extra/extra.component.ts
Normal file
@@ -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<Link, "href"> & {component: ComponentType<any>})[] = [
|
||||||
|
{ 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<any>) {
|
||||||
|
this.dialog.open(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<h1 mat-dialog-title>Domyślna strona po logowaniu</h1>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<p>Wpisz link względem /ipwa/ w poniższym polu.</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>
|
||||||
|
<input matInput type="text" [(ngModel)]="redirect">
|
||||||
|
<mat-label>Link</mat-label>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-dialog-close mat-button>Anuluj</button>
|
||||||
|
<button (click)="save()" mat-flat-button>Zapisz</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MatInputHarness } from '@angular/material/input/testing'
|
||||||
|
import { RedirectComponent } from './redirect.component';
|
||||||
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { AuthClient } from 'src/app/services/auth.client';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('RedirectComponent', () => {
|
||||||
|
let component: RedirectComponent;
|
||||||
|
let fixture: ComponentFixture<RedirectComponent>;
|
||||||
|
let loader: HarnessLoader
|
||||||
|
let authMock
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
authMock = jasmine.createSpyObj<AuthClient>("AuthClient", {}, {redirect: ''})
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [RedirectComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: MatDialogRef, useValue: {}},
|
||||||
|
{provide: AuthClient, useValue: authMock}
|
||||||
|
],
|
||||||
|
imports: [MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule, NoopAnimationsModule]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(RedirectComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
loader = TestbedHarnessEnvironment.loader(fixture)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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<RedirectComponent>, private ac: AuthClient) {
|
||||||
|
this.redirect = ac.redirect
|
||||||
|
}
|
||||||
|
|
||||||
|
protected save() {
|
||||||
|
this.ac.redirect = this.redirect
|
||||||
|
this.dialogRef.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { KeyComponent } from './key.component';
|
import { KeyComponent } from './key.component';
|
||||||
|
import { UpdatesService } from 'src/app/services/updates.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
describe('KeyComponent', () => {
|
describe('KeyComponent', () => {
|
||||||
let component: KeyComponent;
|
let component: KeyComponent;
|
||||||
let fixture: ComponentFixture<KeyComponent>;
|
let fixture: ComponentFixture<KeyComponent>;
|
||||||
|
let uMock: jasmine.SpyObj<UpdatesService>
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
uMock = jasmine.createSpyObj<UpdatesService>("UpdatesService", {
|
||||||
|
getKeys: of()
|
||||||
|
})
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [KeyComponent]
|
declarations: [KeyComponent],
|
||||||
|
providers: [
|
||||||
|
{provide: UpdatesService, useValue: uMock}
|
||||||
|
],
|
||||||
|
imports: [MatDialogModule, MatIconModule]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
<div matListItemTitle>Panel administracyjny</div>
|
<div matListItemTitle>Panel administracyjny</div>
|
||||||
<div matListItemLine>Poprzednio Tryb edycji</div>
|
<div matListItemLine>Poprzednio Tryb edycji</div>
|
||||||
</button>
|
</button>
|
||||||
|
<button mat-list-item (click)="openExtra()">
|
||||||
|
<mat-icon matListItemIcon>settings_applications</mat-icon>
|
||||||
|
<div matListItemTitle>Dodatkowe ustawienia</div>
|
||||||
|
</button>
|
||||||
<button mat-list-item (click)="openAbout()">
|
<button mat-list-item (click)="openAbout()">
|
||||||
<mat-icon matListItemIcon>info</mat-icon>
|
<mat-icon matListItemIcon>info</mat-icon>
|
||||||
<div matListItemTitle>O programie</div>
|
<div matListItemTitle>O programie</div>
|
||||||
|
|||||||
@@ -3,23 +3,30 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { PersonalComponent } from './personal.component';
|
import { PersonalComponent } from './personal.component';
|
||||||
import { AuthClient } from 'src/app/services/auth.client';
|
import { AuthClient } from 'src/app/services/auth.client';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { SwUpdate } from '@angular/service-worker';
|
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { AppUpdateService } from 'src/app/services/app-update.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
describe('PersonalComponent', () => {
|
describe('PersonalComponent', () => {
|
||||||
let component: PersonalComponent;
|
let component: PersonalComponent;
|
||||||
let fixture: ComponentFixture<PersonalComponent>;
|
let fixture: ComponentFixture<PersonalComponent>;
|
||||||
|
let auMock: jasmine.SpyObj<AppUpdateService>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
auMock = jasmine.createSpyObj("aumock", {
|
||||||
|
checkForUpdate: of()
|
||||||
|
})
|
||||||
const authMock = jasmine.createSpyObj('AuthClient', ['s'])
|
const authMock = jasmine.createSpyObj('AuthClient', ['s'])
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [PersonalComponent],
|
declarations: [PersonalComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: AuthClient, useValue: authMock},
|
{provide: AuthClient, useValue: authMock},
|
||||||
|
{provide: AppUpdateService, useValue: auMock}
|
||||||
],
|
],
|
||||||
imports: [MatDialogModule, MatSnackBarModule, MatListModule, BrowserAnimationsModule]
|
imports: [MatDialogModule, MatSnackBarModule, MatListModule, NoopAnimationsModule, MatIconModule]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(PersonalComponent);
|
fixture = TestBed.createComponent(PersonalComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { LocalStorageService } from 'src/app/services/local-storage.service';
|
|||||||
import { KeyComponent } from './key/key.component';
|
import { KeyComponent } from './key/key.component';
|
||||||
import { CleanComponent } from './clean/clean.component';
|
import { CleanComponent } from './clean/clean.component';
|
||||||
import { AboutComponent } from './about/about.component';
|
import { AboutComponent } from './about/about.component';
|
||||||
|
import { ExtraComponent } from './extra/extra.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-personal',
|
selector: 'app-personal',
|
||||||
@@ -63,6 +64,10 @@ export class PersonalComponent {
|
|||||||
this.ac.check()
|
this.ac.check()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected openExtra() {
|
||||||
|
this.dialog.open(ExtraComponent)
|
||||||
|
}
|
||||||
|
|
||||||
protected openAbout() {
|
protected openAbout() {
|
||||||
this.dialog.open(AboutComponent)
|
this.dialog.open(AboutComponent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { StartComponent } from './start.component';
|
import { StartComponent } from './start.component';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { MatListModule } from '@angular/material/list';
|
||||||
|
|
||||||
describe('StartComponent', () => {
|
describe('StartComponent', () => {
|
||||||
let component: StartComponent;
|
let component: StartComponent;
|
||||||
@@ -8,7 +10,11 @@ describe('StartComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [StartComponent]
|
declarations: [StartComponent],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forRoot([]),
|
||||||
|
MatListModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
import { AppUpdateService } from './services/app-update.service';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
|
let auMock
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
auMock = {}
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [
|
|
||||||
RouterTestingModule
|
|
||||||
],
|
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: AppUpdateService, useValue: auMock}
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule
|
||||||
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@ describe('AppComponent', () => {
|
|||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should have as title 'ipwa'`, () => {
|
it(`should have as title 'Internat'`, () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.componentInstance;
|
const app = fixture.componentInstance;
|
||||||
expect(app.title).toEqual('Internat');
|
expect(app.title).toEqual('Internat');
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ import { AttendenceComponent } from './admin-view/grades/attendence/attendence.c
|
|||||||
import { AttendenceSummaryComponent } from './admin-view/grades/attendence-summary/attendence-summary.component';
|
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 { HourDisplayComponent } from './admin-view/grades/attendence-summary/hour-display/hour-display.component';
|
||||||
import { AboutComponent } from './app-view/personal/about/about.component';
|
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';
|
||||||
|
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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -121,6 +130,14 @@ import { AboutComponent } from './app-view/personal/about/about.component';
|
|||||||
AttendenceSummaryComponent,
|
AttendenceSummaryComponent,
|
||||||
HourDisplayComponent,
|
HourDisplayComponent,
|
||||||
AboutComponent,
|
AboutComponent,
|
||||||
|
ExtraComponent,
|
||||||
|
RedirectComponent,
|
||||||
|
OutboxComponent,
|
||||||
|
ToolbarComponent,
|
||||||
|
MessageComponent,
|
||||||
|
NotifDialogComponent,
|
||||||
|
UserSearchComponent,
|
||||||
|
StartAdminComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@@ -160,7 +177,7 @@ import { AboutComponent } from './app-view/personal/about/about.component';
|
|||||||
A11yModule,
|
A11yModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||||
enabled: !isDevMode(),
|
enabled: environment.production,
|
||||||
// Register the ServiceWorker as soon as the application is stable
|
// Register the ServiceWorker as soon as the application is stable
|
||||||
// or after 30 seconds (whichever comes first).
|
// or after 30 seconds (whichever comes first).
|
||||||
registrationStrategy: 'registerWhenStable:30000'
|
registrationStrategy: 'registerWhenStable:30000'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { RoomChooserComponent } from './room-chooser.component';
|
import { RoomChooserComponent } from './room-chooser.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
|
||||||
describe('RoomChooserComponent', () => {
|
describe('RoomChooserComponent', () => {
|
||||||
let component: RoomChooserComponent;
|
let component: RoomChooserComponent;
|
||||||
@@ -8,7 +9,8 @@ describe('RoomChooserComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [RoomChooserComponent]
|
declarations: [RoomChooserComponent],
|
||||||
|
imports: [MatIconModule]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<div role="group" class="app-user-search-container" (focusin)="onFocusIn($event)" (focusout)="onFocusOut($event)">
|
||||||
|
<input type="text" [matAutocomplete]="ac" [formControl]="control" #inputComponent class="input-element">
|
||||||
|
<mat-spinner color="accent" diameter="16" *ngIf="loading" matSuffix></mat-spinner>
|
||||||
|
<mat-autocomplete #ac="matAutocomplete" autoActiveFirstOption (optionSelected)="saveValue($event)" [displayWith]="displayFn">
|
||||||
|
@for (item of list; track $index) {
|
||||||
|
<mat-option [value]="item">
|
||||||
|
@if (item.fname) {
|
||||||
|
{{item.fname}} {{item.surname}} <span *ngIf="item.room" class="room">({{item.room}})</span>
|
||||||
|
} @else {
|
||||||
|
{{item.uname}}
|
||||||
|
}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-autocomplete>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
outline: 0;
|
||||||
|
font: inherit;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserSearchComponent } from './user-search.component';
|
||||||
|
import { AdminCommService } from 'src/app/admin-view/admin-comm.service';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
describe('UserSearchComponent', () => {
|
||||||
|
let component: UserSearchComponent;
|
||||||
|
let fixture: ComponentFixture<UserSearchComponent>;
|
||||||
|
let acMock
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
acMock = {
|
||||||
|
|
||||||
|
}
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [UserSearchComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: AdminCommService, useValue: acMock }
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatAutocompleteModule,
|
||||||
|
MatInputModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserSearchComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
193
src/app/commonComponents/user-search/user-search.component.ts
Normal file
193
src/app/commonComponents/user-search/user-search.component.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
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 { MatFormFieldControl } from '@angular/material/form-field';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { AdminCommService } from 'src/app/admin-view/admin-comm.service';
|
||||||
|
|
||||||
|
export 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: MatFormFieldControl,
|
||||||
|
useExisting: UserSearchComponent
|
||||||
|
}
|
||||||
|
],
|
||||||
|
host: {
|
||||||
|
'(blur)': '_onTouched()'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class UserSearchComponent implements ControlValueAccessor, MatFormFieldControl<UserSearchResult>, OnDestroy, DoCheck {
|
||||||
|
protected loading: boolean = false
|
||||||
|
control: FormControl = new FormControl();
|
||||||
|
protected list: UserSearchResult[] = []
|
||||||
|
private timeout?: NodeJS.Timeout
|
||||||
|
private _onChange!: (_: UserSearchResult) => void
|
||||||
|
private _onTouched!: any
|
||||||
|
|
||||||
|
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<void>();
|
||||||
|
|
||||||
|
@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(() => {
|
||||||
|
this.acu.userFilter(this.control.value).subscribe(v => {
|
||||||
|
this.list = v
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ngDoCheck(): void {
|
||||||
|
if (this.ngControl) {
|
||||||
|
this.updateErrorState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private updateErrorState() {
|
||||||
|
const parent = this._parentFormGroup || this._parentForm
|
||||||
|
|
||||||
|
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 {
|
||||||
|
this._onChange = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this._onTouched = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
protected displayFn(u: UserSearchResult): string {
|
||||||
|
if (!u) return ''
|
||||||
|
return u.fname ? `${u.fname} ${u.surname}` : u.uname
|
||||||
|
}
|
||||||
|
|
||||||
|
protected saveValue(e: MatAutocompleteSelectedEvent) {
|
||||||
|
this.autofilled = true
|
||||||
|
this.value = e.option.value
|
||||||
|
this._onChange(this.value!)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user