fix: Remade accmgmt comm
This commit is contained in:
@@ -1,37 +1,39 @@
|
||||
<div id="upper-bar">
|
||||
<mat-form-field>
|
||||
<mat-form-field subscriptSizing="dynamic">
|
||||
<mat-label>Wyszukaj</mat-label>
|
||||
<input matInput (keyup)="filter($event)">
|
||||
</mat-form-field>
|
||||
<button mat-icon-button (click)="openUserCard()"><mat-icon>add</mat-icon></button>
|
||||
</div>
|
||||
@if (loading) {
|
||||
<mat-spinner></mat-spinner>
|
||||
}
|
||||
<table mat-table [dataSource]="users">
|
||||
<div matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Imię</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.fname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="surname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nazwisko</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.surname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="room">
|
||||
<th mat-header-cell *matHeaderCellDef>Pokój</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.room}}</td>
|
||||
</div>
|
||||
<div matColumnDef="uname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nazwa użytkownika</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.uname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Karta użytkownika</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-mini-fab (click)="openUserCard(element._id)"><mat-icon>manage_accounts</mat-icon></button>
|
||||
</td>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: collumns"></tr>
|
||||
</table>
|
||||
<mat-paginator pageSize="9" [pageSizeOptions]="[9, 15, 20, 50, 160]"></mat-paginator>
|
||||
<div class="mainc">
|
||||
@if (ac.state() != STATE.LOADED) {
|
||||
<app-load-shade [state]="ac.state()" [error]="ac.error()" (refresh)="ac.refresh()"/>
|
||||
}
|
||||
<table mat-table [dataSource]="users">
|
||||
<div matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Imię</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.fname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="surname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nazwisko</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.surname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="room">
|
||||
<th mat-header-cell *matHeaderCellDef>Pokój</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.room}}</td>
|
||||
</div>
|
||||
<div matColumnDef="uname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nazwa użytkownika</th>
|
||||
<td mat-cell *matCellDef="let element">{{element.uname}}</td>
|
||||
</div>
|
||||
<div matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Karta użytkownika</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-mini-fab (click)="openUserCard(element._id)"><mat-icon>manage_accounts</mat-icon></button>
|
||||
</td>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="collumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: collumns"></tr>
|
||||
</table>
|
||||
<mat-paginator pageSize="9" [pageSizeOptions]="[9, 15, 20, 50, 160]"></mat-paginator>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mainc {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
mat-paginator {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@@ -6,26 +6,31 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'
|
||||
import { MatFormFieldModule } from '@angular/material/form-field'
|
||||
import { MatIconModule } from '@angular/material/icon'
|
||||
import { MatPaginatorModule } from '@angular/material/paginator'
|
||||
import { of } from 'rxjs'
|
||||
import { MatTableModule } from '@angular/material/table'
|
||||
import { MatInputModule } from '@angular/material/input'
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
|
||||
import { AccountMgmtService } from './account-mgmt.service'
|
||||
import { LoadShadeComponent } from 'src/app/commonComponents/load-shade/load-shade.component'
|
||||
import { of } from 'rxjs'
|
||||
import { signal } from '@angular/core'
|
||||
import { STATE } from 'src/app/types/state'
|
||||
|
||||
xdescribe('AccountMgmtComponent', () => {
|
||||
describe('AccountMgmtComponent', () => {
|
||||
let component: AccountMgmtComponent
|
||||
let fixture: ComponentFixture<AccountMgmtComponent>
|
||||
let acMock
|
||||
|
||||
beforeEach(async () => {
|
||||
acMock = {
|
||||
accs: {
|
||||
getAccs: jasmine.createSpy('getAccs').and.returnValue(of()),
|
||||
},
|
||||
accs: of([]),
|
||||
state: signal(STATE.NOT_LOADED),
|
||||
refresh: jasmine.createSpy('getAccs'),
|
||||
error: signal(undefined)
|
||||
}
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AccountMgmtComponent],
|
||||
// providers: [{ provide: AdminCommService, useValue: acMock }],
|
||||
declarations: [AccountMgmtComponent, LoadShadeComponent],
|
||||
providers: [{ provide: AccountMgmtService, useValue: acMock }],
|
||||
imports: [
|
||||
MatDialogModule,
|
||||
MatSnackBarModule,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
|
||||
import { AfterViewInit, Component, ViewChild } from '@angular/core'
|
||||
import { MatDialog } from '@angular/material/dialog'
|
||||
import { MatTableDataSource } from '@angular/material/table'
|
||||
import { MatPaginator } from '@angular/material/paginator'
|
||||
import { MatSnackBar } from '@angular/material/snack-bar'
|
||||
import { UserEditComponent } from './user-edit/user-edit.component'
|
||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||
import { Group } from 'src/app/types/group'
|
||||
import User from 'src/app/types/user'
|
||||
import { AccountMgmtService } from './account-mgmt.service'
|
||||
import { STATE } from 'src/app/types/state'
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-mgmt',
|
||||
@@ -15,16 +15,14 @@ import { AccountMgmtService } from './account-mgmt.service'
|
||||
styleUrls: ['./account-mgmt.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class AccountMgmtComponent implements OnInit, AfterViewInit {
|
||||
export class AccountMgmtComponent implements AfterViewInit {
|
||||
protected groups: Group[] = []
|
||||
users: MatTableDataSource<Omit<User, 'pass'>>
|
||||
loading = false
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator
|
||||
|
||||
constructor(
|
||||
readonly ac: AccountMgmtService,
|
||||
protected ac: AccountMgmtService,
|
||||
private dialog: MatDialog,
|
||||
private sb: MatSnackBar,
|
||||
protected readonly ls: LocalStorageService
|
||||
) {
|
||||
this.users = new MatTableDataSource<Omit<User, 'pass'>>()
|
||||
@@ -43,21 +41,20 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit {
|
||||
const filternew = filter.trim().toLowerCase()
|
||||
return dataStr.indexOf(filternew) != -1
|
||||
}
|
||||
this.ac.refresh()
|
||||
this.ac.accs.subscribe(d => {
|
||||
this.users.data = d ?? []
|
||||
})
|
||||
}
|
||||
|
||||
protected get STATE(): typeof STATE {
|
||||
return STATE
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.users.paginator = this.paginator
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loading = true
|
||||
this.ac.getAccs().subscribe(data => {
|
||||
this.loading = false
|
||||
this.users.data = data.users
|
||||
this.groups = data.groups
|
||||
})
|
||||
}
|
||||
|
||||
filter(event: Event) {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
this.users.filter = value.toLowerCase().trim()
|
||||
@@ -74,7 +71,7 @@ export class AccountMgmtComponent implements OnInit, AfterViewInit {
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe(r => {
|
||||
if (r) this.ngOnInit()
|
||||
if (r) this.ac.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,43 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AccountMgmtService } from './account-mgmt.service';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
xdescribe('AccountMgmtService', () => {
|
||||
describe('AccountMgmtService', () => {
|
||||
let service: AccountMgmtService;
|
||||
let httpTesting: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting()
|
||||
]
|
||||
});
|
||||
httpTesting = TestBed.inject(HttpTestingController);
|
||||
service = TestBed.inject(AccountMgmtService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get user accounts', () => {
|
||||
service.refresh()
|
||||
const req = httpTesting.expectOne(environment.apiEndpoint+"/admin/accs", "Request to load all users")
|
||||
|
||||
expect(req.request.method).toBe("GET")
|
||||
|
||||
req.flush([])
|
||||
httpTesting.verify()
|
||||
})
|
||||
|
||||
it('should create a user account and refresh list', () => {
|
||||
service.postAcc({
|
||||
uname: "test",
|
||||
groups: []
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
import { BehaviorSubject, catchError, of } from 'rxjs';
|
||||
import { Group } from 'src/app/types/group';
|
||||
import { STATE } from 'src/app/types/state';
|
||||
import { Status } from 'src/app/types/status';
|
||||
import User from 'src/app/types/user';
|
||||
import { environment } from 'src/environments/environment';
|
||||
@@ -12,15 +14,37 @@ export class AccountMgmtService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getAccs() {
|
||||
return this.http.get<{
|
||||
private _accs = new BehaviorSubject<Omit<User, 'pass'>[]>([])
|
||||
public readonly accs = this._accs.asObservable()
|
||||
private _state = signal(STATE.NOT_LOADED);
|
||||
public readonly state = this._state.asReadonly();
|
||||
private _error = signal<string | undefined>(undefined);
|
||||
public readonly error = this._error.asReadonly();
|
||||
|
||||
public refresh() {
|
||||
this.getAccs()
|
||||
}
|
||||
|
||||
private getAccs() {
|
||||
this._state.set(STATE.PENDING)
|
||||
this.http.get<{
|
||||
users: Omit<User, 'pass'>[]
|
||||
groups: Group[]
|
||||
}>(environment.apiEndpoint + `/admin/accs`, { withCredentials: true })
|
||||
.pipe(catchError((err: Error) => {
|
||||
this._state.set(STATE.ERROR)
|
||||
this._error.set(err.message)
|
||||
return of()
|
||||
}))
|
||||
.subscribe(v => {
|
||||
this._error.set(undefined)
|
||||
this._accs.next(v.users ?? [])
|
||||
this._state.set(STATE.LOADED)
|
||||
})
|
||||
}
|
||||
|
||||
postAcc(item: any) {
|
||||
return this.http.post<Status>(
|
||||
postAcc(item: Omit<User, "pass" | "_id" | "regDate">) {
|
||||
return this.http.post<Omit<User, "pass">>(
|
||||
environment.apiEndpoint + `/admin/accs`,
|
||||
item,
|
||||
{ withCredentials: true }
|
||||
@@ -38,7 +62,7 @@ export class AccountMgmtService {
|
||||
resetPass(id: string) {
|
||||
return this.http.patch<Status>(
|
||||
environment.apiEndpoint + `/admin/accs/${id}/reset`,
|
||||
{},
|
||||
undefined,
|
||||
{ withCredentials: true }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@ import { MessageComponent } from './admin-view/notifications/outbox/message/mess
|
||||
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'
|
||||
import { provideLuxonDateAdapter } from '@angular/material-luxon-adapter'
|
||||
import { provideLuxonDateAdapter } from '@angular/material-luxon-adapter';
|
||||
import { LoadShadeComponent } from './commonComponents/load-shade/load-shade.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -137,6 +138,7 @@ import { provideLuxonDateAdapter } from '@angular/material-luxon-adapter'
|
||||
NotifDialogComponent,
|
||||
UserSearchComponent,
|
||||
StartAdminComponent,
|
||||
LoadShadeComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
@switch (state()) {
|
||||
@case (STATE.PENDING) {
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
}
|
||||
@case (STATE.ERROR) {
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Błąd wczytywania</mat-card-title>
|
||||
<mat-card-subtitle>Podczas wczytywania treści wystąpił błąd.</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>{{ error() }}</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions align="end">
|
||||
<button (click)="refresh.emit()" mat-flat-button color="accent">Odśwież</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
}
|
||||
@default {
|
||||
<h1>{{state()}}</h1>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
position: absolute;
|
||||
background-color: hsla(0, 0%, 0%, 0.5);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoadShadeComponent } from './load-shade.component';
|
||||
|
||||
describe('LoadShadeComponent', () => {
|
||||
let component: LoadShadeComponent;
|
||||
let fixture: ComponentFixture<LoadShadeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [LoadShadeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoadShadeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
18
src/app/commonComponents/load-shade/load-shade.component.ts
Normal file
18
src/app/commonComponents/load-shade/load-shade.component.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import { STATE } from 'src/app/types/state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-load-shade',
|
||||
standalone: false,
|
||||
templateUrl: './load-shade.component.html',
|
||||
styleUrl: './load-shade.component.scss',
|
||||
})
|
||||
export class LoadShadeComponent {
|
||||
state = input(STATE.NOT_LOADED)
|
||||
error = input<string | undefined>(undefined)
|
||||
refresh = output<void>()
|
||||
|
||||
public get STATE(): typeof STATE {
|
||||
return STATE
|
||||
}
|
||||
}
|
||||
6
src/app/types/state.ts
Normal file
6
src/app/types/state.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum STATE {
|
||||
NOT_LOADED,
|
||||
PENDING,
|
||||
LOADED,
|
||||
ERROR
|
||||
}
|
||||
Reference in New Issue
Block a user