feat: add admin user frontend contract
This commit is contained in:
@@ -28,6 +28,17 @@ describe('app routing', () => {
|
||||
expect(router.url).toBe('/auth/login');
|
||||
});
|
||||
|
||||
it('redirects the protected users route to login when logged out', async () => {
|
||||
await router.navigateByUrl('/users');
|
||||
expect(router.url).toBe('/auth/login');
|
||||
});
|
||||
|
||||
it('allows an authenticated user to open the users route', async () => {
|
||||
authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });
|
||||
await router.navigateByUrl('/users');
|
||||
expect(router.url).toBe('/users');
|
||||
});
|
||||
|
||||
it('redirects the root path to team-select when logged in', async () => {
|
||||
authStore.setSession('token', { id: 1, email: 'a@b.de', firstName: 'A', lastName: 'B' });
|
||||
await router.navigateByUrl('/');
|
||||
|
||||
@@ -38,6 +38,11 @@ export const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
loadComponent: () => import('./features/team-select/team-select').then((m) => m.TeamSelect),
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [authGuard],
|
||||
loadComponent: () => import('./features/users/users').then((m) => m.Users),
|
||||
},
|
||||
{
|
||||
path: 't/:token/:playerId',
|
||||
loadComponent: () => import('./features/public-team/public-player').then((m) => m.PublicPlayer),
|
||||
|
||||
@@ -23,7 +23,6 @@ export interface RegistrationRequest {
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
linkPlayerId: number;
|
||||
}
|
||||
|
||||
export interface CreateInviteRequest extends InviteDetails {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AuthStore } from './auth-store';
|
||||
import { RoleId } from '../../models/role.model';
|
||||
|
||||
describe('AuthStore', () => {
|
||||
beforeEach(() => {
|
||||
@@ -76,4 +77,34 @@ describe('AuthStore', () => {
|
||||
expect(localStorage.getItem('tw_token')).toBe('jwt-token');
|
||||
expect(JSON.parse(localStorage.getItem('tw_user')!)).toEqual(updated);
|
||||
});
|
||||
|
||||
it('derives the global admin presentation hint from the current user role', () => {
|
||||
TestBed.configureTestingModule({});
|
||||
const store = TestBed.inject(AuthStore);
|
||||
store.setSession('jwt-token', {
|
||||
id: 1,
|
||||
email: 'admin@example.de',
|
||||
firstName: 'Ada',
|
||||
lastName: 'Admin',
|
||||
role: { id: RoleId.Admin, name: 'admin' },
|
||||
});
|
||||
|
||||
expect(store.currentGlobalRole()).toBe(RoleId.Admin);
|
||||
expect(store.isGlobalAdmin()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not treat a signed-in standard user as a global admin', () => {
|
||||
TestBed.configureTestingModule({});
|
||||
const store = TestBed.inject(AuthStore);
|
||||
store.setSession('jwt-token', {
|
||||
id: 2,
|
||||
email: 'user@example.de',
|
||||
firstName: 'Ute',
|
||||
lastName: 'User',
|
||||
role: { id: RoleId.User, name: 'user' },
|
||||
});
|
||||
|
||||
expect(store.currentGlobalRole()).toBe(RoleId.User);
|
||||
expect(store.isGlobalAdmin()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, computed, signal } from '@angular/core';
|
||||
import { RoleId } from '../../models/role.model';
|
||||
import { User } from '../../models/user.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -12,6 +13,11 @@ export class AuthStore {
|
||||
readonly token = this.tokenSignal.asReadonly();
|
||||
readonly currentUser = this.userSignal.asReadonly();
|
||||
readonly isLoggedIn = computed(() => this.tokenSignal() !== null);
|
||||
readonly currentGlobalRole = computed<RoleId | null>(() => {
|
||||
const roleId = this.userSignal()?.role?.id;
|
||||
return roleId === RoleId.Admin || roleId === RoleId.User ? roleId : null;
|
||||
});
|
||||
readonly isGlobalAdmin = computed(() => this.currentGlobalRole() === RoleId.Admin);
|
||||
|
||||
setSession(token: string, user: User): void {
|
||||
localStorage.setItem(AuthStore.TOKEN_KEY, token);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { AuthStore } from '../../auth/auth-store';
|
||||
import { MyTeamsStore } from '../../team/my-teams-store';
|
||||
import { TeamStore } from '../../team/team-store';
|
||||
import { Team } from '../../../models/team.model';
|
||||
import { UserTeamReference } from '../../../models/user-directory.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shell',
|
||||
@@ -41,9 +41,9 @@ export class Shell {
|
||||
|
||||
protected readonly myTeams = computed(() => {
|
||||
const seen = new Set<number>();
|
||||
const teams: Team[] = [];
|
||||
const teams: UserTeamReference[] = [];
|
||||
for (const player of this.myTeamsStore.players()) {
|
||||
if (player.team && !seen.has(player.team.id)) {
|
||||
if (!seen.has(player.team.id)) {
|
||||
seen.add(player.team.id);
|
||||
teams.push(player.team);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,16 @@ import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { MyTeamsStore } from './my-teams-store';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Player } from '../../models/player.model';
|
||||
|
||||
describe('MyTeamsStore', () => {
|
||||
let store: MyTeamsStore;
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
const player: Player = {
|
||||
const player = {
|
||||
id: 1,
|
||||
firstName: 'A',
|
||||
lastName: 'B',
|
||||
balance: 0,
|
||||
active: true,
|
||||
team: { id: 5, name: 'Team A', alias: 'team-a', balance: 0 },
|
||||
team: { id: 5, name: 'Team A' },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { Player } from '../../models/player.model';
|
||||
import { UserTeamMembership } from '../../models/user-directory.model';
|
||||
import { TeamsApi } from './teams-api';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MyTeamsStore {
|
||||
private readonly teamsApi = inject(TeamsApi);
|
||||
|
||||
private readonly playersSignal = signal<Player[]>([]);
|
||||
private readonly playersSignal = signal<UserTeamMembership[]>([]);
|
||||
private readonly loadingSignal = signal(false);
|
||||
private readonly loadedForUserId = signal<number | null>(null);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ describe('TeamsApi', () => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('fetches the players/teams belonging to a user', () => {
|
||||
const players: Player[] = [{ id: 1, firstName: 'A', lastName: 'B', balance: 0, active: true }];
|
||||
it('fetches the reduced bootstrap memberships belonging to a user', () => {
|
||||
const players = [{ id: 1, firstName: 'A', lastName: 'B', team: { id: 5, name: 'Team A' } }];
|
||||
|
||||
service.loadMyTeams(42).subscribe((response) => {
|
||||
expect(response).toEqual(players);
|
||||
|
||||
@@ -2,9 +2,10 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Player } from '../../models/player.model';
|
||||
import { Team } from '../../models/team.model';
|
||||
import { PlayerTransaction } from '../../models/transaction.model';
|
||||
import { UserTeamMembership } from '../../models/user-directory.model';
|
||||
import { Player } from '../../models/player.model';
|
||||
|
||||
export interface CreatePlayerRequest {
|
||||
firstName: string;
|
||||
@@ -16,8 +17,8 @@ export interface CreatePlayerRequest {
|
||||
export class TeamsApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
loadMyTeams(userId: number): Observable<Player[]> {
|
||||
return this.http.get<Player[]>(`${environment.apiUrl}users/${userId}/teams`);
|
||||
loadMyTeams(userId: number): Observable<UserTeamMembership[]> {
|
||||
return this.http.get<UserTeamMembership[]>(`${environment.apiUrl}users/${userId}/teams`);
|
||||
}
|
||||
|
||||
loadTeamOverview(teamId: number): Observable<Team> {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import {
|
||||
AdminPlayerFilters,
|
||||
AdminPlayerPage,
|
||||
AdminUserDirectorySummary,
|
||||
AdminUserProfileRequest,
|
||||
AdminUserRoleRequest,
|
||||
AdminUserStatusRequest,
|
||||
} from '../../models/user-directory.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AdminUsersApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly baseUrl = `${environment.apiUrl}admin/users`;
|
||||
|
||||
updateProfile(userId: number, request: AdminUserProfileRequest): Observable<AdminUserDirectorySummary> {
|
||||
return this.http.patch<AdminUserDirectorySummary>(`${this.baseUrl}/${userId}/profile`, request);
|
||||
}
|
||||
|
||||
updateRole(userId: number, request: AdminUserRoleRequest): Observable<AdminUserDirectorySummary> {
|
||||
return this.http.patch<AdminUserDirectorySummary>(`${this.baseUrl}/${userId}/role`, request);
|
||||
}
|
||||
|
||||
updateStatus(userId: number, request: AdminUserStatusRequest): Observable<AdminUserDirectorySummary> {
|
||||
return this.http.patch<AdminUserDirectorySummary>(`${this.baseUrl}/${userId}/status`, request);
|
||||
}
|
||||
|
||||
loadPlayers(filters: AdminPlayerFilters = {}): Observable<AdminPlayerPage> {
|
||||
return this.http.get<AdminPlayerPage>(`${this.baseUrl}/players`, { params: this.toParams(filters) });
|
||||
}
|
||||
|
||||
assignPlayer(userId: number, playerId: number): Observable<AdminUserDirectorySummary> {
|
||||
return this.http.put<AdminUserDirectorySummary>(`${this.baseUrl}/${userId}/players/${playerId}`, null);
|
||||
}
|
||||
|
||||
unlinkPlayer(userId: number, playerId: number): Observable<AdminUserDirectorySummary> {
|
||||
return this.http.delete<AdminUserDirectorySummary>(`${this.baseUrl}/${userId}/players/${playerId}`);
|
||||
}
|
||||
|
||||
private toParams(filters: AdminPlayerFilters): HttpParams {
|
||||
let params = new HttpParams();
|
||||
if (filters.search !== undefined) params = params.set('search', filters.search);
|
||||
if (filters.teamId !== undefined) params = params.set('teamId', filters.teamId);
|
||||
if (filters.assignment !== undefined) params = params.set('assignment', filters.assignment);
|
||||
if (filters.page !== undefined) params = params.set('page', filters.page);
|
||||
if (filters.limit !== undefined) params = params.set('limit', filters.limit);
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { AdminUsersApi } from './admin-users-api';
|
||||
import { UsersApi } from './users-api';
|
||||
|
||||
describe('UsersApi', () => {
|
||||
let service: UsersApi;
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
|
||||
service = TestBed.inject(UsersApi);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => httpMock.verify());
|
||||
|
||||
it('loads the safe user directory with supplied pagination and search filters', () => {
|
||||
service.loadDirectory({ page: 2, limit: 10, search: 'Alex Muster' }).subscribe();
|
||||
|
||||
const request = httpMock.expectOne(
|
||||
`${environment.apiUrl}users/directory?page=2&limit=10&search=Alex%20Muster`,
|
||||
);
|
||||
expect(request.request.method).toBe('GET');
|
||||
request.flush({ data: [], page: 2, limit: 10, total: 0, hasNextPage: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminUsersApi', () => {
|
||||
let service: AdminUsersApi;
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
|
||||
service = TestBed.inject(AdminUsersApi);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => httpMock.verify());
|
||||
|
||||
it('uses the dedicated admin profile endpoint and payload', () => {
|
||||
service.updateProfile(7, { firstName: 'Alex', lastName: null }).subscribe();
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/profile`);
|
||||
expect(request.request.method).toBe('PATCH');
|
||||
expect(request.request.body).toEqual({ firstName: 'Alex', lastName: null });
|
||||
request.flush({});
|
||||
});
|
||||
|
||||
it('uses the dedicated admin role endpoint and payload', () => {
|
||||
service.updateRole(7, { role: 1 }).subscribe();
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/role`);
|
||||
expect(request.request.method).toBe('PATCH');
|
||||
expect(request.request.body).toEqual({ role: 1 });
|
||||
request.flush({});
|
||||
});
|
||||
|
||||
it('uses the dedicated admin status endpoint and payload', () => {
|
||||
service.updateStatus(7, { status: 2 }).subscribe();
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/status`);
|
||||
expect(request.request.method).toBe('PATCH');
|
||||
expect(request.request.body).toEqual({ status: 2 });
|
||||
request.flush({});
|
||||
});
|
||||
|
||||
it('loads players with the admin filters', () => {
|
||||
service.loadPlayers({ search: 'Alex', teamId: 5, assignment: 'unassigned', page: 2, limit: 10 }).subscribe();
|
||||
const request = httpMock.expectOne(
|
||||
`${environment.apiUrl}admin/users/players?search=Alex&teamId=5&assignment=unassigned&page=2&limit=10`,
|
||||
);
|
||||
expect(request.request.method).toBe('GET');
|
||||
request.flush({ data: [], page: 2, limit: 10, total: 0, hasNextPage: false });
|
||||
});
|
||||
|
||||
it('assigns a player through the dedicated admin endpoint', () => {
|
||||
service.assignPlayer(7, 9).subscribe();
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/players/9`);
|
||||
expect(request.request.method).toBe('PUT');
|
||||
expect(request.request.body).toBeNull();
|
||||
request.flush({});
|
||||
});
|
||||
|
||||
it('unlinks a player through the dedicated admin endpoint', () => {
|
||||
service.unlinkPlayer(7, 9).subscribe();
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/players/9`);
|
||||
expect(request.request.method).toBe('DELETE');
|
||||
request.flush({});
|
||||
});
|
||||
});
|
||||
25
myteamwallet_frontend_modern/src/app/core/users/users-api.ts
Normal file
25
myteamwallet_frontend_modern/src/app/core/users/users-api.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { UserDirectoryFilters, UserDirectoryPage } from '../../models/user-directory.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UsersApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly baseUrl = `${environment.apiUrl}users`;
|
||||
|
||||
loadDirectory(filters: UserDirectoryFilters = {}): Observable<UserDirectoryPage> {
|
||||
return this.http.get<UserDirectoryPage>(`${this.baseUrl}/directory`, {
|
||||
params: this.toParams(filters),
|
||||
});
|
||||
}
|
||||
|
||||
private toParams(filters: UserDirectoryFilters): HttpParams {
|
||||
let params = new HttpParams();
|
||||
if (filters.page !== undefined) params = params.set('page', filters.page);
|
||||
if (filters.limit !== undefined) params = params.set('limit', filters.limit);
|
||||
if (filters.search !== undefined) params = params.set('search', filters.search);
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ describe('Register', () => {
|
||||
|
||||
afterEach(() => httpMock.verify());
|
||||
|
||||
it('verifies the invitation and links its player during registration', () => {
|
||||
it('verifies the invitation and registers without linking its player', () => {
|
||||
const fixture = TestBed.createComponent(Register);
|
||||
const navigateSpy = vi.spyOn(router, 'navigate');
|
||||
fixture.detectChanges();
|
||||
@@ -50,7 +50,12 @@ describe('Register', () => {
|
||||
fixture.componentInstance['onSubmit']();
|
||||
|
||||
const request = httpMock.expectOne(`${environment.apiUrl}auth/email/register`);
|
||||
expect(request.request.body.linkPlayerId).toBe(7);
|
||||
expect(request.request.body).toEqual({
|
||||
email: 'alex@example.de',
|
||||
password: 'secret1',
|
||||
firstName: 'Alex',
|
||||
lastName: 'Muster',
|
||||
});
|
||||
request.flush(null);
|
||||
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['/auth/login'], { replaceUrl: true });
|
||||
|
||||
@@ -78,7 +78,6 @@ export class Register {
|
||||
password: value.password,
|
||||
firstName: value.firstName,
|
||||
lastName: value.lastName,
|
||||
linkPlayerId: invitation.playerId,
|
||||
})
|
||||
.subscribe({
|
||||
next: () => {
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<h1>Team auswählen</h1>
|
||||
<mat-nav-list>
|
||||
@for (player of players(); track player.id) {
|
||||
<a mat-list-item [routerLink]="['/team', player.team?.id, 'overview']">
|
||||
<span matListItemTitle>{{ player.team?.name }}</span>
|
||||
<a mat-list-item [routerLink]="['/team', player.team.id, 'overview']">
|
||||
<span matListItemTitle>{{ player.team.name }}</span>
|
||||
<span matListItemLine>{{ player.firstName }} {{ player.lastName }}</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
</header>
|
||||
|
||||
<section class="link-grid">
|
||||
<a routerLink="/users"
|
||||
><mat-card
|
||||
><mat-icon>group</mat-icon>
|
||||
<div><strong>Benutzer</strong><span>Benutzerverzeichnis öffnen</span></div>
|
||||
<mat-icon>chevron_right</mat-icon></mat-card
|
||||
></a
|
||||
>
|
||||
<a routerLink="penalties"
|
||||
><mat-card
|
||||
><mat-icon>gavel</mat-icon>
|
||||
|
||||
@@ -31,6 +31,9 @@ describe('More', () => {
|
||||
expect(fixture.nativeElement.textContent).toContain('Profil');
|
||||
expect(fixture.nativeElement.textContent).toContain('Öffentliche Freigabe');
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('Benutzer');
|
||||
expect(fixture.nativeElement.querySelector('a[href="/users"]')).not.toBeNull();
|
||||
|
||||
fixture.componentInstance['logout']();
|
||||
await fixture.whenStable();
|
||||
expect(clearSession).toHaveBeenCalled();
|
||||
|
||||
13
myteamwallet_frontend_modern/src/app/features/users/users.ts
Normal file
13
myteamwallet_frontend_modern/src/app/features/users/users.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users',
|
||||
template: `
|
||||
<header>
|
||||
<p class="eyebrow">Organisation</p>
|
||||
<h1>Benutzer</h1>
|
||||
<p>Benutzerverzeichnis wird vorbereitet.</p>
|
||||
</header>
|
||||
`,
|
||||
})
|
||||
export class Users {}
|
||||
@@ -0,0 +1,113 @@
|
||||
export interface UserDirectoryReference {
|
||||
id: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface UserDirectoryTeam {
|
||||
id: number;
|
||||
name: string;
|
||||
alias: string;
|
||||
}
|
||||
|
||||
export interface UserDirectoryAssignment {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
active: boolean;
|
||||
team: UserDirectoryTeam;
|
||||
teamRole: UserDirectoryReference | null;
|
||||
}
|
||||
|
||||
export interface UserDirectorySummary {
|
||||
id: number;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
status: UserDirectoryReference | null;
|
||||
assignments: UserDirectoryAssignment[];
|
||||
}
|
||||
|
||||
export interface AdminUserDirectorySummary extends UserDirectorySummary {
|
||||
email: string | null;
|
||||
role: UserDirectoryReference | null;
|
||||
}
|
||||
|
||||
export type UserDirectoryRecord = UserDirectorySummary | AdminUserDirectorySummary;
|
||||
|
||||
export interface UserDirectoryPage {
|
||||
data: UserDirectoryRecord[];
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export interface UserDirectoryFilters {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface AdminUserProfileRequest {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
}
|
||||
|
||||
export interface AdminUserRoleRequest {
|
||||
role: number;
|
||||
}
|
||||
|
||||
export interface AdminUserStatusRequest {
|
||||
status: number;
|
||||
}
|
||||
|
||||
export type AdminPlayerAssignmentFilter = 'all' | 'assigned' | 'unassigned';
|
||||
|
||||
export interface AdminPlayerFilters {
|
||||
search?: string;
|
||||
teamId?: number;
|
||||
assignment?: AdminPlayerAssignmentFilter;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface AdminPlayerTeam {
|
||||
id: number;
|
||||
name: string;
|
||||
alias: string;
|
||||
}
|
||||
|
||||
export interface AdminPlayerCurrentUser {
|
||||
id: number;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
status: UserDirectoryReference | null;
|
||||
}
|
||||
|
||||
export interface AdminPlayerSummary {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
active: boolean;
|
||||
team: AdminPlayerTeam;
|
||||
currentUser: AdminPlayerCurrentUser | null;
|
||||
}
|
||||
|
||||
export interface AdminPlayerPage {
|
||||
data: AdminPlayerSummary[];
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export interface UserTeamReference {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserTeamMembership {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
team: UserTeamReference;
|
||||
}
|
||||
Reference in New Issue
Block a user