Show user warnings in profile & lazy load other stuff #402
This commit is contained in:
		| @@ -5,6 +5,7 @@ import { UserJoinedServer } from "./user_joined_server.model"; | ||||
| import { UserJoinedVoiceChannel } from "./user_joined_voice_channel.model"; | ||||
| import { UserJoinedGameServer } from "./user_joined_game_server.model"; | ||||
| import { Achievement } from "./achievement.model"; | ||||
| import { UserWarning } from "./user_warning.model"; | ||||
|  | ||||
| export interface User extends DataWithHistory { | ||||
|   id?: number; | ||||
| @@ -29,6 +30,9 @@ export interface User extends DataWithHistory { | ||||
|  | ||||
|   achievementCount?: number; | ||||
|   achievements?: Achievement[]; | ||||
|  | ||||
|   userWarningCount?: number; | ||||
|   userWarnings?: UserWarning[]; | ||||
| } | ||||
|  | ||||
| export interface UserFilter { | ||||
|   | ||||
							
								
								
									
										16
									
								
								kdb-web/src/app/models/data/user_warning.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								kdb-web/src/app/models/data/user_warning.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { DataWithHistory } from "./data.model"; | ||||
| import { User, UserFilter } from "./user.model"; | ||||
|  | ||||
| export interface UserWarning extends DataWithHistory { | ||||
|   id?: number; | ||||
|   user?: User; | ||||
|   description?: string; | ||||
|   author?: User; | ||||
| } | ||||
|  | ||||
| export interface UserWarningFilter { | ||||
|   id?: number; | ||||
|   user?: UserFilter; | ||||
|   description?: string; | ||||
|   author?: UserFilter; | ||||
| } | ||||
| @@ -208,7 +208,7 @@ export class Queries { | ||||
|     query { | ||||
|       shortRoleNamePositions | ||||
|     } | ||||
|   ` | ||||
|   `; | ||||
|  | ||||
|   static shortRoleNameQuery = ` | ||||
|     query ShortRoleNameList($serverId: ID, $filter: ShortRoleNameFilter, $page: Page, $sort: Sort) { | ||||
| @@ -279,58 +279,94 @@ export class Queries { | ||||
|  | ||||
|   static userProfile = ` | ||||
|     query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) { | ||||
|       servers(filter: {id: $serverId}) { | ||||
|         userCount | ||||
|         users(filter: {id: $userId}, page: $page, sort: $sort) { | ||||
|       userCount | ||||
|       users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) { | ||||
|         id | ||||
|         discordId | ||||
|         name | ||||
|         xp | ||||
|         ontime | ||||
|         level { | ||||
|           id | ||||
|           discordId | ||||
|           name | ||||
|           xp | ||||
|           ontime | ||||
|           level { | ||||
|         } | ||||
|         leftServer | ||||
|         server { | ||||
|           id | ||||
|           name | ||||
|         } | ||||
|  | ||||
|         joinedServerCount | ||||
|         joinedServers { | ||||
|           id | ||||
|           joinedOn | ||||
|           leavedOn | ||||
|         } | ||||
|  | ||||
|         createdAt | ||||
|         modifiedAt | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static userProfileAchievements = ` | ||||
|     query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) { | ||||
|       users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) { | ||||
|         achievementCount | ||||
|         achievements { | ||||
|           id | ||||
|           name | ||||
|           description | ||||
|           createdAt | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static userProfileVoiceChannelJoins = ` | ||||
|     query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) { | ||||
|       users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) { | ||||
|         joinedVoiceChannelCount | ||||
|         joinedVoiceChannels { | ||||
|           id | ||||
|           channelId | ||||
|           channelName | ||||
|           time | ||||
|           joinedOn | ||||
|           leavedOn | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static userProfileGameserverJoins = ` | ||||
|     query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) { | ||||
|       users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) { | ||||
|         userJoinedGameServerCount | ||||
|         userJoinedGameServers { | ||||
|           id | ||||
|           gameServer | ||||
|           time | ||||
|           joinedOn | ||||
|           leavedOn | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static userProfileWarnings = ` | ||||
|     query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) { | ||||
|       users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) { | ||||
|         userWarningCount | ||||
|         userWarnings { | ||||
|           id | ||||
|           description | ||||
|           author { | ||||
|             id | ||||
|             name | ||||
|           } | ||||
|           leftServer | ||||
|           server { | ||||
|             id | ||||
|             name | ||||
|           } | ||||
|  | ||||
|           joinedServerCount | ||||
|           joinedServers { | ||||
|             id | ||||
|             joinedOn | ||||
|             leavedOn | ||||
|           } | ||||
|  | ||||
|           joinedVoiceChannelCount | ||||
|           joinedVoiceChannels { | ||||
|             id | ||||
|             channelId | ||||
|             channelName | ||||
|             time | ||||
|             joinedOn | ||||
|             leavedOn | ||||
|           } | ||||
|  | ||||
|           userJoinedGameServerCount | ||||
|           userJoinedGameServers { | ||||
|             id | ||||
|             gameServer | ||||
|             time | ||||
|             joinedOn | ||||
|             leavedOn | ||||
|           } | ||||
|  | ||||
|           achievements { | ||||
|             id | ||||
|             name | ||||
|             createdAt | ||||
|           } | ||||
|  | ||||
|           createdAt | ||||
|           modifiedAt | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { TechnicianConfig } from "../config/technician-config.model"; | ||||
| import { ServerConfig } from "../config/server-config.model"; | ||||
| import { ShortRoleName } from "../data/short_role_name.model"; | ||||
| import { FeatureFlag } from "../config/feature-flags.model"; | ||||
| import { UserWarning } from "../data/user_warning.model"; | ||||
|  | ||||
| export interface Query { | ||||
|   serverCount: number; | ||||
| @@ -31,6 +32,11 @@ export interface UserListQuery { | ||||
|   users: User[]; | ||||
| } | ||||
|  | ||||
| export interface UserWarningQuery { | ||||
|   userWarningCount: number; | ||||
|   userWarnings: UserWarning[]; | ||||
| } | ||||
|  | ||||
| export interface GameServerListQuery { | ||||
|   gameServerCount: number; | ||||
|   gameServers: GameServer[]; | ||||
|   | ||||
| @@ -76,9 +76,103 @@ | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <div> | ||||
|       <div class="content-divider"></div> | ||||
|       <p-table #dt [value]="(user.userWarnings ?? [])" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'" dataKey="id" editMode="row"> | ||||
|         <ng-template pTemplate="caption"> | ||||
|           <div class="table-caption"> | ||||
|             <div class="table-caption-table-info"> | ||||
|               <div class="table-caption-text"> | ||||
|                 <h3>{{'common.user_warnings' | translate}}</h3> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="table-caption-btn-wrapper btn-wrapper"> | ||||
|               <button pButton label="{{'common.add' | translate}}" class="icon-btn btn" | ||||
|                       icon="pi pi-plus" (click)="addNewUserWarning(dt)"> | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </ng-template> | ||||
|         <ng-template pTemplate="header"> | ||||
|           <tr> | ||||
|             <th> | ||||
|               <div class="table-header-label"> | ||||
|                 <div class="table-header-text">{{'common.description' | translate}}</div> | ||||
|               </div> | ||||
|             </th> | ||||
|  | ||||
|             <th> | ||||
|               <div class="table-header-label"> | ||||
|                 <div class="table-header-text">{{'common.author' | translate}}</div> | ||||
|               </div> | ||||
|             </th> | ||||
|  | ||||
|             <th> | ||||
|               <div class="table-header-label"> | ||||
|                 <div class="table-header-text">{{'common.created_at' | translate}}</div> | ||||
|               </div> | ||||
|             </th> | ||||
|  | ||||
|             <th class="table-header-actions"> | ||||
|               <div class="table-header-label"> | ||||
|                 <div class="table-header-text">{{'common.actions' | translate}}</div> | ||||
|               </div> | ||||
|             </th> | ||||
|           </tr> | ||||
|         </ng-template> | ||||
|         <ng-template pTemplate="body" let-value let-editing="editing" let-ri="rowIndex"> | ||||
|           <tr [pEditableRow]="value"> | ||||
|             <td> | ||||
|               <p-cellEditor> | ||||
|                 <ng-template pTemplate="input"> | ||||
|                   <input class="table-edit-input" pInputText type="text" [(ngModel)]="value.description"> | ||||
|                 </ng-template> | ||||
|                 <ng-template pTemplate="output"> | ||||
|                   {{value.description}} | ||||
|                 </ng-template> | ||||
|               </p-cellEditor> | ||||
|             </td> | ||||
|             <td> | ||||
|               <p-cellEditor> | ||||
|                 <ng-template pTemplate="input"> | ||||
|                   {{value.author.name}} | ||||
|                 </ng-template> | ||||
|                 <ng-template pTemplate="output"> | ||||
|                   {{value.author.name}} | ||||
|                 </ng-template> | ||||
|               </p-cellEditor> | ||||
|             </td> | ||||
|  | ||||
|             <td> | ||||
|               <span class="p-column-title">{{'common.created_at' | translate}}:</span> | ||||
|               <p-cellEditor> | ||||
|                 <ng-template pTemplate="input"> | ||||
|                   {{value.createdAt | date:'dd.MM.yy HH:mm'}} | ||||
|                 </ng-template> | ||||
|                 <ng-template pTemplate="output"> | ||||
|                   {{value.createdAt | date:'dd.MM.yy HH:mm'}} | ||||
|                 </ng-template> | ||||
|               </p-cellEditor> | ||||
|             </td> | ||||
|             <td> | ||||
|               <div class="btn-wrapper"> | ||||
|                 <button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash" (click)="deleteUserWarning(ri)"></button> | ||||
|  | ||||
|                 <button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check" (click)="editSaveUserWarning(value, ri)"></button> | ||||
|                 <button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn" icon="pi pi-times" | ||||
|                         (click)="editCancelUserWarning(ri)"></button> | ||||
|               </div> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </ng-template> | ||||
|       </p-table> | ||||
|       <br> | ||||
|     </div> | ||||
|  | ||||
|     <div class="content-divider"></div> | ||||
|  | ||||
|     <p-panel header="{{'view.server.profile.achievements.header' | translate}}" [toggleable]="true"> | ||||
|     <p-panel header="{{'view.server.profile.achievements.header' | translate}}" [toggleable]="true" [collapsed]="true" | ||||
|              (onBeforeToggle)="onBeforeToggle($event.event, $event.collapsed)"> | ||||
|       <div *ngFor="let achievement of user.achievements;"> | ||||
|         <div class="content-row"> | ||||
|           <div class="content-column"> | ||||
| @@ -86,6 +180,11 @@ | ||||
|             <div class="content-data-value">{{achievement.name}}</div> | ||||
|           </div> | ||||
|  | ||||
|           <div class="content-column"> | ||||
|             <div class="content-data-name">{{'common.description' | translate}}:</div> | ||||
|             <div class="content-data-value">{{achievement.description}}</div> | ||||
|           </div> | ||||
|  | ||||
|           <div class="content-column"> | ||||
|             <div class="content-data-name">{{'view.server.profile.achievements.time' | translate}}:</div> | ||||
|             <div class="content-data-value">{{achievement.createdAt | date:'dd.MM.yyyy HH:mm:ss'}}</div> | ||||
| @@ -94,7 +193,8 @@ | ||||
|       </div> | ||||
|     </p-panel> | ||||
|  | ||||
|     <p-panel header="{{'view.server.profile.joined_voice_channel.header' | translate}}" [toggleable]="true"> | ||||
|     <p-panel header="{{'view.server.profile.joined_voice_channel.header' | translate}}" [toggleable]="true" [collapsed]="true" | ||||
|              (onBeforeToggle)="onBeforeToggle($event.event, $event.collapsed)"> | ||||
|       <div *ngFor="let join of user.joinedVoiceChannels;"> | ||||
|         <div class="content-row"> | ||||
|           <div class="content-column"> | ||||
| @@ -120,7 +220,8 @@ | ||||
|       </div> | ||||
|     </p-panel> | ||||
|  | ||||
|     <p-panel header="{{'view.server.profile.joined_game_server.header' | translate}}" [toggleable]="true"> | ||||
|     <p-panel header="{{'view.server.profile.joined_game_server.header' | translate}}" [toggleable]="true" [collapsed]="true" | ||||
|              (onBeforeToggle)="onBeforeToggle($event.event, $event.collapsed)"> | ||||
|       <div *ngFor="let join of user.userJoinedGameServers;"> | ||||
|         <div class="content-row"> | ||||
|           <div class="content-column"> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { Component, OnDestroy, OnInit } from "@angular/core"; | ||||
| import { ActivatedRoute, Router } from "@angular/router"; | ||||
| import { Queries } from "../../../../models/graphql/queries.model"; | ||||
| import { UserListQuery } from "../../../../models/graphql/query.model"; | ||||
| import { UserListQuery, UserWarningQuery } from "../../../../models/graphql/query.model"; | ||||
| import { SpinnerService } from "../../../../services/spinner/spinner.service"; | ||||
| import { DataService } from "../../../../services/data/data.service"; | ||||
| import { User } from "../../../../models/data/user.model"; | ||||
| @@ -10,8 +10,9 @@ import { AuthService } from "src/app/services/auth/auth.service"; | ||||
| import { ToastService } from "src/app/services/toast/toast.service"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { Server } from "../../../../models/data/server.model"; | ||||
| import { Subject } from "rxjs"; | ||||
| import { forkJoin, Subject } from "rxjs"; | ||||
| import { takeUntil } from "rxjs/operators"; | ||||
| import { Table } from "primeng/table"; | ||||
|  | ||||
| @Component({ | ||||
|   selector: "app-profile", | ||||
| @@ -58,16 +59,27 @@ export class ProfileComponent implements OnInit, OnDestroy { | ||||
|         this.data.query<UserListQuery>(Queries.userProfile, { | ||||
|             serverId: this.server.id, | ||||
|             userId: params["memberId"] | ||||
|           }, | ||||
|           (x: { servers: Server[] }) => { | ||||
|             return x.servers[0]; | ||||
|           } | ||||
|         ).subscribe(users => { | ||||
|           if (!users.users[0]) { | ||||
|             this.router.navigate([`/server/${server.id}`]); | ||||
|           } | ||||
|           this.user = users.users[0]; | ||||
|           this.spinner.hideSpinner(); | ||||
|  | ||||
|           this.data.query<UserWarningQuery>(Queries.userProfileWarnings, { | ||||
|               serverId: this.server.id, | ||||
|               userId: this.user.id | ||||
|             }, | ||||
|             (data: UserListQuery) => { | ||||
|               return data.users[0]; | ||||
|             } | ||||
|           ).subscribe(result => { | ||||
|             this.user.userWarningCount = result.userWarningCount; | ||||
|             this.user.userWarnings = result.userWarnings; | ||||
|             console.log(result); | ||||
|  | ||||
|             this.spinner.hideSpinner(); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| @@ -77,4 +89,68 @@ export class ProfileComponent implements OnInit, OnDestroy { | ||||
|     this.unsubscriber.next(); | ||||
|     this.unsubscriber.complete(); | ||||
|   } | ||||
|  | ||||
|   public onBeforeToggle(event: Event, collapsed: boolean) { | ||||
|     const filterUser = (x: { users: User[] }) => { | ||||
|       const users = x.users ?? []; | ||||
|       return users[0]; | ||||
|     }; | ||||
|  | ||||
|     if (collapsed) { | ||||
|       this.spinner.showSpinner(); | ||||
|       forkJoin([ | ||||
|         this.data.query<User>(Queries.userProfileAchievements, { | ||||
|             serverId: this.server.id, | ||||
|             userId: this.user.id | ||||
|           }, | ||||
|           filterUser | ||||
|         ), | ||||
|         this.data.query<User>(Queries.userProfileVoiceChannelJoins, { | ||||
|             serverId: this.server.id, | ||||
|             userId: this.user.id | ||||
|           }, | ||||
|           filterUser | ||||
|         ), | ||||
|         this.data.query<User>(Queries.userProfileGameserverJoins, { | ||||
|             serverId: this.server.id, | ||||
|             userId: this.user.id | ||||
|           }, | ||||
|           filterUser | ||||
|         ) | ||||
|       ]).subscribe(data => { | ||||
|         this.user.achievementCount = data[0].achievementCount; | ||||
|         this.user.achievements = data[0].achievements; | ||||
|  | ||||
|         this.user.joinedVoiceChannelCount = data[1].joinedVoiceChannelCount; | ||||
|         this.user.joinedVoiceChannels = data[1].joinedVoiceChannels; | ||||
|  | ||||
|         this.user.userJoinedGameServerCount = data[2].userJoinedGameServerCount; | ||||
|         this.user.userJoinedGameServers = data[2].userJoinedGameServers; | ||||
|         this.spinner.hideSpinner(); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     this.user.achievementCount = 0; | ||||
|     this.user.achievements = []; | ||||
|  | ||||
|     this.user.userJoinedGameServerCount = 0; | ||||
|     this.user.userJoinedGameServers = []; | ||||
|  | ||||
|     this.user.joinedVoiceChannelCount = 0; | ||||
|     this.user.joinedVoiceChannels = []; | ||||
|   } | ||||
|  | ||||
|   addNewUserWarning(table: Table) { | ||||
|   } | ||||
|  | ||||
|   deleteUserWarning(index: number) { | ||||
|   } | ||||
|  | ||||
|   editSaveUserWarning(value: any, index: number) { | ||||
|   } | ||||
|  | ||||
|   editCancelUserWarning(index: number) { | ||||
|   } | ||||
|  | ||||
|   protected readonly visualViewport = visualViewport; | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; | ||||
| import { ServerRoutingModule } from './server-routing.module'; | ||||
| import { SharedModule } from '../../shared/shared.module'; | ||||
| import { ProfileComponent } from './profile/profile.component'; | ||||
| import { MembersComponent } from './members/members.component'; | ||||
| import { ClientComponent } from './server-dashboard/components/client/client.component'; | ||||
|  | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { CommonModule } from "@angular/common"; | ||||
| import { ServerDashboardComponent } from "./server-dashboard/server-dashboard.component"; | ||||
| import { ServerRoutingModule } from "./server-routing.module"; | ||||
| import { SharedModule } from "../../shared/shared.module"; | ||||
| import { ProfileComponent } from "./profile/profile.component"; | ||||
| import { MembersComponent } from "./members/members.component"; | ||||
| import { ClientComponent } from "./server-dashboard/components/client/client.component"; | ||||
|  | ||||
|  | ||||
| @NgModule({ | ||||
|   | ||||
| @@ -194,7 +194,6 @@ export class SidebarService { | ||||
|       let user: UserDTO | null = authUser?.users?.find(u => u.server == this.server?.id) ?? null; | ||||
|       let isTechnician = (authUser?.users?.map(u => u.isTechnician).filter(u => u) ?? []).length > 0; | ||||
|       let isTechnicianAndFullAccessActive = this.hasFeature("TechnicianFullAccess") && isTechnician; | ||||
|       console.log(this.hasFeature("TechnicianFullAccess")) | ||||
|  | ||||
|       if (build || this.menuItems$.value.length == 0) { | ||||
|         await this.buildMenu(user, hasPermission, isTechnician); | ||||
|   | ||||
| @@ -122,6 +122,8 @@ | ||||
|     } | ||||
|   }, | ||||
|   "common": { | ||||
|     "user_warnings": "Verwarnungen", | ||||
|     "author": "Autor", | ||||
|     "404": "404 - Der Eintrag konnte nicht gefunden werden", | ||||
|     "actions": "Aktionen", | ||||
|     "active": "Aktiv", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "WebVersion": { | ||||
|     "Major": "1", | ||||
|     "Minor": "1", | ||||
|     "Micro": "10" | ||||
|   } | ||||
| } | ||||
|     "WebVersion": { | ||||
|         "Major": "1", | ||||
|         "Minor": "1", | ||||
|         "Micro": "dev402" | ||||
|     } | ||||
| } | ||||
| @@ -201,10 +201,10 @@ header { | ||||
|  | ||||
|               font-size: 18px; | ||||
|             } | ||||
|           } | ||||
|  | ||||
|             .content-divider { | ||||
|               margin: 5px 0; | ||||
|             } | ||||
|           .content-divider { | ||||
|             margin: 10px 0; | ||||
|           } | ||||
|  | ||||
|           p-panel { | ||||
| @@ -493,7 +493,7 @@ header { | ||||
|   } | ||||
|  | ||||
|   .content-divider { | ||||
|     margin: 5px 0; | ||||
|     margin: 10px 0; | ||||
|   } | ||||
|  | ||||
|   .content-input-field { | ||||
|   | ||||
| @@ -20,7 +20,8 @@ | ||||
|   background-color: $primaryBackgroundColor; | ||||
|  | ||||
|   h1, | ||||
|   h2 { | ||||
|   h2, | ||||
|   h3 { | ||||
|     color: $primaryHeaderColor; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,8 @@ | ||||
|   background-color: $primaryBackgroundColor; | ||||
|  | ||||
|   h1, | ||||
|   h2 { | ||||
|   h2, | ||||
|   h3 { | ||||
|     color: $primaryHeaderColor; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,8 @@ | ||||
|  | ||||
|  | ||||
|   h1, | ||||
|   h2 { | ||||
|   h2, | ||||
|   h3 { | ||||
|     color: $primaryHeaderColor; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,8 @@ | ||||
|   background-color: $primaryBackgroundColor; | ||||
|  | ||||
|   h1, | ||||
|   h2 { | ||||
|   h2, | ||||
|   h3 { | ||||
|     color: $primaryHeaderColor; | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user