staging #447
| @@ -27,3 +27,4 @@ class FeatureFlagsEnum(Enum): | ||||
|     short_role_name = "ShortRoleName" | ||||
|     technician_full_access = "TechnicianFullAccess" | ||||
|     steam_special_offers = "SteamSpecialOffers" | ||||
|     scheduled_events = "ScheduledEvents" | ||||
|   | ||||
| @@ -29,6 +29,7 @@ class FeatureFlagsSettings(ConfigurationModelABC): | ||||
|         FeatureFlagsEnum.short_role_name.value: False,  # 28.09.2023 #378 | ||||
|         FeatureFlagsEnum.technician_full_access.value: False,  # 03.10.2023 #393 | ||||
|         FeatureFlagsEnum.steam_special_offers.value: False,  # 11.10.2023 #188 | ||||
|         FeatureFlagsEnum.scheduled_events.value: False,  # 14.11.2023 #410 | ||||
|     } | ||||
|  | ||||
|     def __init__(self, **kwargs: dict): | ||||
|   | ||||
| @@ -14,6 +14,7 @@ from bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC | ||||
| from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC | ||||
| from bot_data.abc.level_repository_abc import LevelRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC | ||||
| @@ -45,6 +46,7 @@ from bot_data.service.client_repository_service import ClientRepositoryService | ||||
| from bot_data.service.game_server_repository_service import GameServerRepositoryService | ||||
| from bot_data.service.known_user_repository_service import KnownUserRepositoryService | ||||
| from bot_data.service.level_repository_service import LevelRepositoryService | ||||
| from bot_data.service.scheduled_event_repository_service import ScheduledEventRepositoryService | ||||
| from bot_data.service.seeder_service import SeederService | ||||
| from bot_data.service.server_config_repository_service import ( | ||||
|     ServerConfigRepositoryService, | ||||
| @@ -115,6 +117,7 @@ class DataModule(ModuleABC): | ||||
|         services.add_transient(ServerConfigRepositoryABC, ServerConfigRepositoryService) | ||||
|         services.add_transient(ShortRoleNameRepositoryABC, ShortRoleNameRepositoryService) | ||||
|         services.add_transient(SteamSpecialOfferRepositoryABC, SteamSpecialOfferRepositoryService) | ||||
|         services.add_transient(ScheduledEventRepositoryABC, ScheduledEventRepositoryService) | ||||
|  | ||||
|         services.add_transient(SeederService) | ||||
|         services.add_transient(DataSeederABC, TechnicianConfigSeeder) | ||||
|   | ||||
							
								
								
									
										34
									
								
								web/src/app/models/data/scheduled_events.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								web/src/app/models/data/scheduled_events.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import { DataWithHistory } from "./data.model"; | ||||
| import { Server, ServerFilter } from "./server.model"; | ||||
|  | ||||
| export enum EventType { | ||||
|   stageInstance = 1, | ||||
|   voice = 2, | ||||
|   external = 3, | ||||
| } | ||||
|  | ||||
| export interface ScheduledEvent extends DataWithHistory { | ||||
|   id?: number; | ||||
|   interval?: string; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
|   channelId?: string; | ||||
|   startTime?: string; | ||||
|   endTime?: string; | ||||
|   entityType?: EventType; | ||||
|   location?: string; | ||||
|   server?: Server; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventFilter { | ||||
|   id?: number; | ||||
|   interval?: string; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
|   channelId?: string; | ||||
|   startTime?: string; | ||||
|   endTime?: string; | ||||
|   entityType?: number; | ||||
|   location?: string; | ||||
|   server?: ServerFilter; | ||||
| } | ||||
| @@ -173,6 +173,70 @@ export class Mutations { | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static createScheduledEvent = ` | ||||
|     mutation createScheduledEvent($interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) { | ||||
|       scheduledEvent { | ||||
|         createScheduledEvent(input: { | ||||
|           interval: $interval, | ||||
|           name: $name, | ||||
|           description: $description, | ||||
|           channelId: $channelId, | ||||
|           startTime: $startTime, | ||||
|           endTime: $endTime, | ||||
|           entityType: $entityType, | ||||
|           location: $location, | ||||
|           serverId: $serverId} | ||||
|         ) { | ||||
|           id | ||||
|           name | ||||
|           description | ||||
|           attribute | ||||
|           operator | ||||
|           value | ||||
|           server { | ||||
|             id | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static updateScheduledEvent = ` | ||||
|     mutation updateScheduledEvent($interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) { | ||||
|       scheduledEvent { | ||||
|         updateScheduledEvent(input: { | ||||
|           interval: $interval, | ||||
|           name: $name, | ||||
|           description: $description, | ||||
|           channelId: $channelId, | ||||
|           startTime: $startTime, | ||||
|           endTime: $endTime, | ||||
|           entityType: $entityType, | ||||
|           location: $location, | ||||
|           serverId: $serverId} | ||||
|         ) { | ||||
|           id | ||||
|           name | ||||
|           description | ||||
|           attribute | ||||
|           operator | ||||
|           value | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static deleteScheduledEvent = ` | ||||
|     mutation deleteScheduledEvent($id: ID) { | ||||
|       scheduledEvent { | ||||
|         deleteScheduledEvent(id: $id) { | ||||
|           id | ||||
|           name | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static createShortRoleName = ` | ||||
|     mutation createShortRoleName($shortName: String, $roleId: String, $position: String, $serverId: ID) { | ||||
|       shortRoleName { | ||||
|   | ||||
| @@ -229,6 +229,58 @@ export class Queries { | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static scheduledEventQuery = ` | ||||
|     query ScheduledEventList($serverId: ID, $filter: ScheduledEventFilter, $page: Page, $sort: Sort) { | ||||
|       servers(filter: {id: $serverId}) { | ||||
|         scheduledEventCount | ||||
|         scheduledEvents(filter: $filter, page: $page, sort: $sort) { | ||||
|           id | ||||
|           interval | ||||
|           name | ||||
|           description | ||||
|           channelId | ||||
|           startTime | ||||
|           endTime | ||||
|           entityType | ||||
|           location | ||||
|           server { | ||||
|             id | ||||
|             name | ||||
|           } | ||||
|           createdAt | ||||
|           modifiedAt | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static scheduledEventWithHistoryQuery = ` | ||||
|     query ScheduledEventHistory($serverId: ID, $id: ID) { | ||||
|       servers(filter: {id: $serverId}) { | ||||
|         scheduledEventCount | ||||
|         scheduledEvents(filter: {id: $id}) { | ||||
|           id | ||||
|  | ||||
|           history { | ||||
|             id | ||||
|             interval | ||||
|             name | ||||
|             description | ||||
|             channelId | ||||
|             startTime | ||||
|             endTime | ||||
|             entityType | ||||
|             location | ||||
|             server | ||||
|             deleted | ||||
|             dateFrom | ||||
|             dateTo | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|  | ||||
|   static shortRoleNamePositionsQuery = ` | ||||
|     query { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ 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"; | ||||
| import { ScheduledEvent } from "../data/scheduled_events.model"; | ||||
|  | ||||
| export interface Query { | ||||
|   serverCount: number; | ||||
| @@ -57,6 +58,11 @@ export interface AchievementListQuery { | ||||
|   achievements: Achievement[]; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventListQuery { | ||||
|   scheduledEventCount: number; | ||||
|   scheduledEvents: ScheduledEvent[]; | ||||
| } | ||||
|  | ||||
| export interface AutoRoleQuery { | ||||
|   autoRoleCount: number; | ||||
|   autoRoles: AutoRole[]; | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { TechnicianConfig } from "../config/technician-config.model"; | ||||
| import { ServerConfig } from "../config/server-config.model"; | ||||
| import { ShortRoleName } from "../data/short_role_name.model"; | ||||
| import { UserWarning } from "../data/user_warning.model"; | ||||
| import { ScheduledEvent } from "../data/scheduled_events.model"; | ||||
|  | ||||
| export interface GraphQLResult { | ||||
|   data: { | ||||
| @@ -71,6 +72,14 @@ export interface AchievementMutationResult { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventMutationResult { | ||||
|   scheduledEvent: { | ||||
|     createScheduledEvent?: ScheduledEvent | ||||
|     updateScheduledEvent?: ScheduledEvent | ||||
|     deleteScheduledEvent?: ScheduledEvent | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface ShortRoleNameMutationResult { | ||||
|   shortRoleName: { | ||||
|     createShortRoleName?: ShortRoleName | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import { InputTextModule } from "primeng/inputtext"; | ||||
| import { MenuModule } from "primeng/menu"; | ||||
| import { PasswordModule } from "primeng/password"; | ||||
| import { ProgressSpinnerModule } from "primeng/progressspinner"; | ||||
| import { SortableColumn, TableModule } from "primeng/table"; | ||||
| import { TableModule } from "primeng/table"; | ||||
| import { ToastModule } from "primeng/toast"; | ||||
| import { AuthRolePipe } from "./pipes/auth-role.pipe"; | ||||
| import { IpAddressPipe } from "./pipes/ip-address.pipe"; | ||||
| @@ -24,18 +24,20 @@ import { InputNumberModule } from "primeng/inputnumber"; | ||||
| import { ImageModule } from "primeng/image"; | ||||
| import { SidebarModule } from "primeng/sidebar"; | ||||
| import { HistoryBtnComponent } from "./components/history-btn/history-btn.component"; | ||||
| import { DataViewModule, DataViewLayoutOptions } from "primeng/dataview"; | ||||
| import { DataViewLayoutOptions, DataViewModule } from "primeng/dataview"; | ||||
| import { ConfigListComponent } from "./components/config-list/config-list.component"; | ||||
| import { MultiSelectModule } from "primeng/multiselect"; | ||||
| import { HideableColumnComponent } from './components/hideable-column/hideable-column.component'; | ||||
| import { HideableHeaderComponent } from './components/hideable-header/hideable-header.component'; | ||||
| import { MultiSelectColumnsComponent } from './base/multi-select-columns/multi-select-columns.component'; | ||||
| import { FeatureFlagListComponent } from './components/feature-flag-list/feature-flag-list.component'; | ||||
| import { HideableColumnComponent } from "./components/hideable-column/hideable-column.component"; | ||||
| import { HideableHeaderComponent } from "./components/hideable-header/hideable-header.component"; | ||||
| import { MultiSelectColumnsComponent } from "./base/multi-select-columns/multi-select-columns.component"; | ||||
| import { FeatureFlagListComponent } from "./components/feature-flag-list/feature-flag-list.component"; | ||||
| import { InputSwitchModule } from "primeng/inputswitch"; | ||||
| import { CalendarModule } from "primeng/calendar"; | ||||
| import { DataImportAndExportComponent } from './components/data-import-and-export/data-import-and-export.component'; | ||||
| import { DataImportAndExportComponent } from "./components/data-import-and-export/data-import-and-export.component"; | ||||
| import { FileUploadModule } from "primeng/fileupload"; | ||||
| import { SelectButtonModule } from "primeng/selectbutton"; | ||||
| import { TabViewModule } from "primeng/tabview"; | ||||
| import { RadioButtonModule } from "primeng/radiobutton"; | ||||
|  | ||||
|  | ||||
| const PrimeNGModules = [ | ||||
| @@ -66,7 +68,9 @@ const PrimeNGModules = [ | ||||
|   CalendarModule, | ||||
|   FileUploadModule, | ||||
|   SelectButtonModule, | ||||
| ] | ||||
|   TabViewModule, | ||||
|   RadioButtonModule | ||||
| ]; | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
| @@ -79,7 +83,7 @@ const PrimeNGModules = [ | ||||
|     HideableHeaderComponent, | ||||
|     MultiSelectColumnsComponent, | ||||
|     FeatureFlagListComponent, | ||||
|     DataImportAndExportComponent, | ||||
|     DataImportAndExportComponent | ||||
|   ], | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
|   | ||||
| @@ -0,0 +1,44 @@ | ||||
| <div class="edit-dialog"> | ||||
|   <p-dialog [header]="header" [(visible)]="visible" [modal]="true" | ||||
|             [draggable]="false" [resizable]="false" | ||||
|             [style]="{ width: '50vw' }"> | ||||
|     <form [formGroup]="inputForm"> | ||||
|       <p-tabView [(activeIndex)]="activeIndex"> | ||||
|         <p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.location.tab_name' | translate}}"> | ||||
|           <div *ngFor="let enum of EventType | keyvalue" class="field-checkbox"> | ||||
|             <p-radioButton [inputId]="enum.key" [value]="enum.value" formControlName="entityType"></p-radioButton> | ||||
|             <label [for]="enum.key" | ||||
|                    class="ml-2">{{'view.server.scheduled_events.edit_dialog.location.' + enum.key | translate}}</label> | ||||
|           </div> | ||||
|         </p-tabPanel> | ||||
|         <p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.event_info.tab_name' | translate}}"> | ||||
|           <p> | ||||
|             Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem | ||||
|             aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. | ||||
|             Nemo | ||||
|             enim | ||||
|             ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos | ||||
|             qui | ||||
|             ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi. | ||||
|           </p> | ||||
|         </p-tabPanel> | ||||
|       </p-tabView> | ||||
|       <ng-template pTemplate="footer"> | ||||
|         <div style="display: flex;"> | ||||
|           <div class="btn-wrapper" style="flex: 1;"> | ||||
|             <button pButton label="{{'common.back' | translate}}" class="text-btn" | ||||
|                     (click)="back()" [disabled]="activeIndex == 0"></button> | ||||
|           </div> | ||||
|           <div class="btn-wrapper"> | ||||
|             <button pButton label="{{'common.abort' | translate}}" class="btn danger-btn" | ||||
|                     (click)="visible = false"></button> | ||||
|             <button *ngIf="activeIndex < 1" pButton label="{{'common.continue' | translate}}" class="btn" | ||||
|                     (click)="next()"></button> | ||||
|             <button *ngIf="activeIndex == 1" pButton label="{{'common.save' | translate}}" class="btn" | ||||
|                     (click)="saveEvent()" [disabled]="!inputForm.valid"></button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </ng-template> | ||||
|     </form> | ||||
|   </p-dialog> | ||||
| </div> | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { EditScheduledEventDialogComponent } from './edit-scheduled-event-dialog.component'; | ||||
|  | ||||
| describe('EditScheduledEventDialogComponent', () => { | ||||
|   let component: EditScheduledEventDialogComponent; | ||||
|   let fixture: ComponentFixture<EditScheduledEventDialogComponent>; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ EditScheduledEventDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|  | ||||
|     fixture = TestBed.createComponent(EditScheduledEventDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,70 @@ | ||||
| import { Component, EventEmitter, Input, Output } from "@angular/core"; | ||||
| import { EventType, ScheduledEvent } from "../../../../../../models/data/scheduled_events.model"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { FormBuilder, FormControl, Validators } from "@angular/forms"; | ||||
|  | ||||
| @Component({ | ||||
|   selector: "app-edit-scheduled-event-dialog", | ||||
|   templateUrl: "./edit-scheduled-event-dialog.component.html", | ||||
|   styleUrls: ["./edit-scheduled-event-dialog.component.scss"] | ||||
| }) | ||||
| export class EditScheduledEventDialogComponent { | ||||
|   @Input() event?: ScheduledEvent; | ||||
|   @Output() save = new EventEmitter<ScheduledEvent>(); | ||||
|  | ||||
|   get visible() { | ||||
|     return this.event != undefined; | ||||
|   } | ||||
|  | ||||
|   set visible(val: boolean) { | ||||
|     if (!val) { | ||||
|       this.event = undefined; | ||||
|     } | ||||
|     this.visible = val; | ||||
|   } | ||||
|  | ||||
|   get header() { | ||||
|     if (this.event && this.event.createdAt === "" && this.event.modifiedAt === "") { | ||||
|       return this.translate.instant("view.server.scheduled_events.edit_dialog.add_header"); | ||||
|     } else { | ||||
|       return this.translate.instant("view.server.scheduled_events.edit_dialog.edit_header"); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public activeIndex: number = 0; | ||||
|   public inputForm = this.fb.group({ | ||||
|     id: new FormControl<number | undefined>(this.event?.id), | ||||
|     interval: new FormControl<string | undefined>(this.event?.interval, [Validators.required]), | ||||
|     entityType: new FormControl<number | undefined>(this.event?.entityType, [Validators.required]), | ||||
|     channelId: new FormControl<string | undefined>(this.event?.channelId, this.event?.entityType == EventType.voice || this.event?.entityType == EventType.stageInstance ? [Validators.required] : []), | ||||
|     location: new FormControl<string | undefined>(this.event?.location, this.event?.entityType == EventType.external ? [Validators.required] : []), | ||||
|     name: new FormControl<string | undefined>(this.event?.name, [Validators.required]), | ||||
|     startTime: new FormControl<string | undefined>(this.event?.startTime, [Validators.required]), | ||||
|     endTime: new FormControl<string | undefined>(this.event?.endTime), | ||||
|     description: new FormControl<string | undefined>(this.event?.description) | ||||
|   }); | ||||
|  | ||||
|   constructor( | ||||
|     private translate: TranslateService, | ||||
|     private fb: FormBuilder | ||||
|   ) { | ||||
|   } | ||||
|  | ||||
|   public saveEvent() { | ||||
|     this.save.emit(this.event); | ||||
|   } | ||||
|  | ||||
|   public next() { | ||||
|     this.activeIndex++; | ||||
|   } | ||||
|  | ||||
|   public back() { | ||||
|     this.activeIndex--; | ||||
|   } | ||||
|  | ||||
|   protected readonly EventType = { | ||||
|     stage: EventType.stageInstance, | ||||
|     voice: EventType.voice, | ||||
|     somewhere_else: EventType.external | ||||
|   }; | ||||
| } | ||||
| @@ -0,0 +1,307 @@ | ||||
| <app-edit-scheduled-event-dialog [event]="editableScheduledEvent" (save)="onRowEditSave($event)"></app-edit-scheduled-event-dialog> | ||||
|  | ||||
| <h1> | ||||
|   {{'view.server.scheduled_events.header' | translate}} | ||||
| </h1> | ||||
| <div class="content-wrapper"> | ||||
|   <div class="content"> | ||||
|     <p-table #dt [value]="scheduledEvents" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'" | ||||
|              dataKey="id" [rowHover]="true" [rows]="10" | ||||
|              [rowsPerPageOptions]="[10,25,50]" [paginator]="true" [loading]="loading" [totalRecords]="totalRecords" | ||||
|              [lazy]="true" (onLazyLoad)="nextPage($event)"> | ||||
|  | ||||
|       <ng-template pTemplate="caption"> | ||||
|         <div class="table-caption"> | ||||
|           <div class="table-caption-table-info"> | ||||
|             <div class="table-caption-text"> | ||||
|               <ng-container *ngIf="!loading">{{scheduledEvents.length}} {{'common.of' | translate}} | ||||
|                 {{dt.totalRecords}} | ||||
|               </ng-container> | ||||
|               {{'view.server.scheduled_events.scheduled_events' | translate}} | ||||
|             </div> | ||||
|  | ||||
|             <app-multi-select-columns [table]="name" [columns]="columns" | ||||
|                                       [(hiddenColumns)]="hiddenColumns"></app-multi-select-columns> | ||||
|           </div> | ||||
|  | ||||
|           <div class="table-caption-btn-wrapper btn-wrapper"> | ||||
|             <button pButton label="{{'common.add' | translate}}" class="icon-btn btn" | ||||
|                     icon="pi pi-plus" (click)="addScheduledEvent(dt)" | ||||
|                     [disabled]="isEditingNew || !user?.isModerator && !user?.isAdmin"> | ||||
|             </button> | ||||
|             <button pButton label="{{'common.reset_filters' | translate}}" icon="pi pi-undo" | ||||
|                     class="icon-btn btn" (click)="resetFilters()"> | ||||
|             </button> | ||||
|             <app-data-import-and-export name="scheduledEvent" [(data)]="scheduledEvents" | ||||
|                                         [callback]="callback" [validator]="validator"></app-data-import-and-export> | ||||
|           </div> | ||||
|         </div> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="header"> | ||||
|         <tr> | ||||
|           <th hideable-th="id" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.id' | translate}}</div> | ||||
|               <p-sortIcon field="id" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="interval" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.interval' | translate}}</div> | ||||
|               <p-sortIcon field="interval" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="name" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.name' | translate}}</div> | ||||
|               <p-sortIcon field="name" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="description" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.description' | translate}}</div> | ||||
|               <p-sortIcon field="description" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="channel_id" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.channel_id' | translate}}</div> | ||||
|               <p-sortIcon field="channelId" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="start_time" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.start_time' | translate}}</div> | ||||
|               <p-sortIcon field="startTime" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="end_time" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.end_time' | translate}}</div> | ||||
|               <p-sortIcon field="endTime" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="type" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.type' | translate}}</div> | ||||
|               <p-sortIcon field="entityType" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="location" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.location' | translate}}</div> | ||||
|               <p-sortIcon field="location" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.created_at' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.modified_at' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.actions' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|         </tr> | ||||
|  | ||||
|         <tr> | ||||
|           <th hideable-th="id" [parent]="this" class="table-header-small"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="text" pInputText formControlName="id" | ||||
|                      placeholder="{{'common.id' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th class="table-header-small"></th> | ||||
|  | ||||
|           <th hideable-th="name" [parent]="this"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="text" pInputText formControlName="name" | ||||
|                      placeholder="{{'common.name' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small"></th> | ||||
|           <th class="table-header-small-dropdown"></th> | ||||
|           <th class="table-header-small-dropdown"></th> | ||||
|           <th class="table-header-actions"></th> | ||||
|         </tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="body" let-scheduledEvent let-editing="editing" let-ri="rowIndex"> | ||||
|         <tr [pEditableRow]="scheduledEvent"> | ||||
|           <td hideable-td="id" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.id' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 {{scheduledEvent.id}} | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.id}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="interval" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.interval' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.interval"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.interval}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="name" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.name' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.name"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.name}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="description" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.description' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.description"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.description}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="channel_id" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.channel_id' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.channel_id"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.channel_id}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="start_time" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.start_time' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.start_time"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.start_time}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="end_time" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.end_time' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.end_time"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.end_time}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="type" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.type' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.type"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.type}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="location" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.location' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 <input class="table-edit-input" pInputText type="text" [(ngModel)]="scheduledEvent.location"> | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.location}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|  | ||||
|           <td> | ||||
|             <span class="p-column-title">{{'common.created_at' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 {{scheduledEvent.createdAt | date:'dd.MM.yy HH:mm'}} | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.createdAt | date:'dd.MM.yy HH:mm'}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|           <td> | ||||
|             <span class="p-column-title">{{'common.modified_at' | translate}}:</span> | ||||
|             <p-cellEditor> | ||||
|               <ng-template pTemplate="input"> | ||||
|                 {{scheduledEvent.modifiedAt | date:'dd.MM.yy HH:mm'}} | ||||
|               </ng-template> | ||||
|               <ng-template pTemplate="output"> | ||||
|                 {{scheduledEvent.modifiedAt | date:'dd.MM.yy HH:mm'}} | ||||
|               </ng-template> | ||||
|             </p-cellEditor> | ||||
|           </td> | ||||
|           <td> | ||||
|             <div class="btn-wrapper"> | ||||
|               <app-history-btn *ngIf="!isEditingNew" [id]="scheduledEvent.id" [query]="query" | ||||
|                                translationKey="view.server.scheduledEvent.header"></app-history-btn> | ||||
|               <button *ngIf="!editing" pButton class="btn icon-btn" icon="pi pi-pencil" | ||||
|                       (click)="onRowEditInit(dt, scheduledEvent, ri)" | ||||
|                       [disabled]="!user || !user.isModerator && !user.isAdmin"></button> | ||||
|               <button *ngIf="!editing" pButton class="btn icon-btn danger-icon-btn" icon="pi pi-trash" | ||||
|                       (click)="deleteScheduledEvent(scheduledEvent)" | ||||
|                       [disabled]="!user || !user.isModerator && !user.isAdmin"></button> | ||||
|             </div> | ||||
|           </td> | ||||
|         </tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="emptymessage"> | ||||
|         <tr></tr> | ||||
|         <tr> | ||||
|           <td colspan="14">{{'common.no_entries_found' | translate}}</td> | ||||
|         </tr> | ||||
|         <tr></tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="paginatorleft"> | ||||
|       </ng-template> | ||||
|     </p-table> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { ScheduledEventsComponent } from './scheduled-events.component'; | ||||
|  | ||||
| describe('ScheduledEventsComponent', () => { | ||||
|   let component: ScheduledEventsComponent; | ||||
|   let fixture: ComponentFixture<ScheduledEventsComponent>; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ScheduledEventsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|  | ||||
|     fixture = TestBed.createComponent(ScheduledEventsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,287 @@ | ||||
| import { Component, OnDestroy, OnInit } from "@angular/core"; | ||||
| import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; | ||||
| import { Page } from "../../../../../../models/graphql/filter/page.model"; | ||||
| import { Sort, SortDirection } from "../../../../../../models/graphql/filter/sort.model"; | ||||
| import { Subject, throwError } from "rxjs"; | ||||
| import { Server } from "../../../../../../models/data/server.model"; | ||||
| import { UserDTO } from "../../../../../../models/auth/auth-user.dto"; | ||||
| import { LazyLoadEvent } from "primeng/api"; | ||||
| import { Queries } from "../../../../../../models/graphql/queries.model"; | ||||
| import { AuthService } from "../../../../../../services/auth/auth.service"; | ||||
| import { SpinnerService } from "../../../../../../services/spinner/spinner.service"; | ||||
| import { ToastService } from "../../../../../../services/toast/toast.service"; | ||||
| import { ConfirmationDialogService } from "../../../../../../services/confirmation-dialog/confirmation-dialog.service"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { DataService } from "../../../../../../services/data/data.service"; | ||||
| import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; | ||||
| import { ActivatedRoute } from "@angular/router"; | ||||
| import { Query, ScheduledEventListQuery } from "../../../../../../models/graphql/query.model"; | ||||
| import { catchError, debounceTime, takeUntil } from "rxjs/operators"; | ||||
| import { Table } from "primeng/table"; | ||||
| import { ScheduledEventMutationResult } from "../../../../../../models/graphql/result.model"; | ||||
| import { Mutations } from "../../../../../../models/graphql/mutations.model"; | ||||
| import { ComponentWithTable } from "../../../../../../base/component-with-table"; | ||||
| import { EventType, ScheduledEvent, ScheduledEventFilter } from "../../../../../../models/data/scheduled_events.model"; | ||||
|  | ||||
| @Component({ | ||||
|   selector: "app-scheduled-events", | ||||
|   templateUrl: "./scheduled-events.component.html", | ||||
|   styleUrls: ["./scheduled-events.component.scss"] | ||||
| }) | ||||
| export class ScheduledEventsComponent extends ComponentWithTable implements OnInit, OnDestroy { | ||||
|   public scheduledEvents: ScheduledEvent[] = []; | ||||
|   public loading = true; | ||||
|  | ||||
|   public filterForm!: FormGroup<{ | ||||
|     id: FormControl<number | null>, | ||||
|     interval: FormControl<string | null>, | ||||
|     name: FormControl<string | null>, | ||||
|     description: FormControl<string | null>, | ||||
|     channelId: FormControl<string | null>, | ||||
|     startTime: FormControl<string | null>, | ||||
|     endTime: FormControl<string | null>, | ||||
|     entityType: FormControl<EventType | null>, | ||||
|     location: FormControl<string | null>, | ||||
|   }>; | ||||
|  | ||||
|   public filter: ScheduledEventFilter = {}; | ||||
|   public page: Page = { | ||||
|     pageSize: undefined, | ||||
|     pageIndex: undefined | ||||
|   }; | ||||
|   public sort: Sort = { | ||||
|     sortColumn: undefined, | ||||
|     sortDirection: undefined | ||||
|   }; | ||||
|  | ||||
|   public totalRecords: number = 0; | ||||
|  | ||||
|   private unsubscriber = new Subject<void>(); | ||||
|   private server: Server = {}; | ||||
|   public user: UserDTO | null = null; | ||||
|   public query: string = Queries.scheduledEventWithHistoryQuery; | ||||
|   public editableScheduledEvent?: ScheduledEvent = undefined; | ||||
|  | ||||
|   public constructor( | ||||
|     private authService: AuthService, | ||||
|     private spinner: SpinnerService, | ||||
|     private toastService: ToastService, | ||||
|     private confirmDialog: ConfirmationDialogService, | ||||
|     private fb: FormBuilder, | ||||
|     private translate: TranslateService, | ||||
|     private data: DataService, | ||||
|     private sidebar: SidebarService, | ||||
|     private route: ActivatedRoute) { | ||||
|     super("ScheduledEvent", ["id", "interval", "name", "description", "channel_id", "start_time", "end_time", "type", "location"], | ||||
|       (oldElement: ScheduledEvent, newElement: ScheduledEvent) => { | ||||
|         return oldElement.name === newElement.name; | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   public ngOnInit(): void { | ||||
|     this.loading = true; | ||||
|     this.setFilterForm(); | ||||
|     this.data.getServerFromRoute(this.route).then(async server => { | ||||
|       this.server = server; | ||||
|       let authUser = await this.authService.getLoggedInUser(); | ||||
|       this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public ngOnDestroy(): void { | ||||
|     this.unsubscriber.next(); | ||||
|     this.unsubscriber.complete(); | ||||
|   } | ||||
|  | ||||
|   public loadNextPage(): void { | ||||
|     this.data.query<ScheduledEventListQuery>(Queries.scheduledEventQuery, { | ||||
|         serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort | ||||
|       }, | ||||
|       (data: Query) => { | ||||
|         return data.servers[0]; | ||||
|       } | ||||
|     ).subscribe(data => { | ||||
|       this.totalRecords = data.scheduledEventCount; | ||||
|       this.scheduledEvents = data.scheduledEvents; | ||||
|       this.spinner.hideSpinner(); | ||||
|       this.loading = false; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public setFilterForm(): void { | ||||
|     this.filterForm = this.fb.group({ | ||||
|       id: new FormControl<number | null>(null), | ||||
|       interval: new FormControl<string | null>(null), | ||||
|       name: new FormControl<string | null>(null), | ||||
|       description: new FormControl<string | null>(null), | ||||
|       channelId: new FormControl<string | null>(null), | ||||
|       startTime: new FormControl<string | null>(null), | ||||
|       endTime: new FormControl<string | null>(null), | ||||
|       entityType: new FormControl<EventType | null>(null), | ||||
|       location: new FormControl<string | null>(null), | ||||
|     }); | ||||
|  | ||||
|     this.filterForm.valueChanges.pipe( | ||||
|       takeUntil(this.unsubscriber), | ||||
|       debounceTime(600) | ||||
|     ).subscribe(changes => { | ||||
|       if (changes.id) { | ||||
|         this.filter.id = changes.id; | ||||
|       } else { | ||||
|         this.filter.id = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.interval) { | ||||
|         this.filter.interval = changes.interval; | ||||
|       } else { | ||||
|         this.filter.interval = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.name) { | ||||
|         this.filter.name = changes.name; | ||||
|       } else { | ||||
|         this.filter.name = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.description) { | ||||
|         this.filter.description = changes.description; | ||||
|       } else { | ||||
|         this.filter.description = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.channelId) { | ||||
|         this.filter.channelId = changes.channelId; | ||||
|       } else { | ||||
|         this.filter.channelId = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.startTime) { | ||||
|         this.filter.startTime = changes.startTime; | ||||
|       } else { | ||||
|         this.filter.startTime = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.endTime) { | ||||
|         this.filter.endTime = changes.endTime; | ||||
|       } else { | ||||
|         this.filter.endTime = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.entityType) { | ||||
|         this.filter.entityType = changes.entityType; | ||||
|       } else { | ||||
|         this.filter.entityType = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.location) { | ||||
|         this.filter.location = changes.location; | ||||
|       } else { | ||||
|         this.filter.location = undefined; | ||||
|       } | ||||
|  | ||||
|       if (this.page.pageSize) | ||||
|         this.page.pageSize = 10; | ||||
|  | ||||
|       if (this.page.pageIndex) | ||||
|         this.page.pageIndex = 0; | ||||
|  | ||||
|       this.loadNextPage(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public newScheduledEventTemplate: ScheduledEvent = { | ||||
|     createdAt: "", | ||||
|     modifiedAt: "" | ||||
|   }; | ||||
|  | ||||
|   public nextPage(event: LazyLoadEvent): void { | ||||
|     this.page.pageSize = event.rows ?? 0; | ||||
|     if (event.first != null && event.rows != null) | ||||
|       this.page.pageIndex = event.first / event.rows; | ||||
|     this.sort.sortColumn = event.sortField ?? undefined; | ||||
|     this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC; | ||||
|  | ||||
|     this.loadNextPage(); | ||||
|   } | ||||
|  | ||||
|   public resetFilters(): void { | ||||
|     this.filterForm.reset(); | ||||
|   } | ||||
|  | ||||
|   public onRowEditInit(table: Table, event: ScheduledEvent, index: number): void { | ||||
|     this.editableScheduledEvent = event; | ||||
|   } | ||||
|  | ||||
|   public override onRowEditSave(newScheduledEvent: ScheduledEvent): void { | ||||
|     if (this.isEditingNew && JSON.stringify(newScheduledEvent) === JSON.stringify(this.newScheduledEventTemplate)) { | ||||
|       this.isEditingNew = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (this.isEditingNew) { | ||||
|       this.spinner.showSpinner(); | ||||
|       this.data.mutation<ScheduledEventMutationResult>(Mutations.createScheduledEvent, { | ||||
|           name: newScheduledEvent.name, | ||||
|           serverId: this.server.id | ||||
|         } | ||||
|       ).pipe(catchError(err => { | ||||
|         this.isEditingNew = false; | ||||
|         this.spinner.hideSpinner(); | ||||
|         return throwError(err); | ||||
|       })).subscribe(result => { | ||||
|         this.isEditingNew = false; | ||||
|         this.spinner.hideSpinner(); | ||||
|         this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_create"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_create_d", { name: result.scheduledEvent.createScheduledEvent?.name })); | ||||
|         this.loadNextPage(); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this.spinner.showSpinner(); | ||||
|     this.data.mutation<ScheduledEventMutationResult>(Mutations.updateScheduledEvent, { | ||||
|         id: newScheduledEvent.id, | ||||
|         name: newScheduledEvent.name, | ||||
|       } | ||||
|     ).pipe(catchError(err => { | ||||
|       this.spinner.hideSpinner(); | ||||
|       return throwError(err); | ||||
|     })).subscribe(_ => { | ||||
|       this.spinner.hideSpinner(); | ||||
|       this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_update"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_update_d", { name: newScheduledEvent.name })); | ||||
|       this.loadNextPage(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public deleteScheduledEvent(ScheduledEvent: ScheduledEvent): void { | ||||
|     this.confirmDialog.confirmDialog( | ||||
|       this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_delete"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_delete_q", { name: ScheduledEvent.name }), | ||||
|       () => { | ||||
|         this.spinner.showSpinner(); | ||||
|         this.data.mutation<ScheduledEventMutationResult>(Mutations.deleteScheduledEvent, { | ||||
|             id: ScheduledEvent.id | ||||
|           } | ||||
|         ).pipe(catchError(err => { | ||||
|           this.spinner.hideSpinner(); | ||||
|           return throwError(err); | ||||
|         })).subscribe(l => { | ||||
|           this.spinner.hideSpinner(); | ||||
|           this.toastService.success(this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_deleted"), this.translate.instant("view.server.ScheduledEvents.message.scheduled_event_deleted_d", { name: ScheduledEvent.name })); | ||||
|           this.loadNextPage(); | ||||
|         }); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   public addScheduledEvent(table: Table): void { | ||||
|     this.editableScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate)); | ||||
|     // const newScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate)); | ||||
|     // | ||||
|     // this.scheduledEvents = [newScheduledEvent, ...this.scheduledEvents]; | ||||
|     // | ||||
|     // table.initRowEdit(newScheduledEvent); | ||||
|     // | ||||
|     // const index = this.scheduledEvents.findIndex(l => l.id == newScheduledEvent.id); | ||||
|     // this.onRowEditInit(table, newScheduledEvent, index); | ||||
|     // | ||||
|     // this.isEditingNew = true; | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { RouterModule, Routes } from "@angular/router"; | ||||
| import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component"; | ||||
|  | ||||
| const routes: Routes = [ | ||||
|   { path: "", component: ScheduledEventsComponent } | ||||
| ]; | ||||
|  | ||||
| @NgModule({ | ||||
|   imports: [RouterModule.forChild(routes)], | ||||
|   exports: [RouterModule] | ||||
| }) | ||||
| export class ScheduledEventsRoutingModule { | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
|  | ||||
| import { ScheduledEventsRoutingModule } from './scheduled-events-routing.module'; | ||||
| import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component"; | ||||
| import { SharedModule } from "../../../shared/shared.module"; | ||||
| import { EditScheduledEventDialogComponent } from './components/edit-scheduled-event-dialog/edit-scheduled-event-dialog.component'; | ||||
|  | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
|     ScheduledEventsComponent, | ||||
|     EditScheduledEventDialogComponent | ||||
|   ], | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
|     SharedModule, | ||||
|     ScheduledEventsRoutingModule | ||||
|   ] | ||||
| }) | ||||
| export class ScheduledEventsModule { } | ||||
| @@ -19,6 +19,7 @@ const routes: Routes = [ | ||||
|   { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "short-role-names", loadChildren: () => import("./short-role-name/short-role-name.module").then(m => m.ShortRoleNameModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "scheduled-events", loadChildren: () => import("./scheduled-events/scheduled-events.module").then(m => m.ScheduledEventsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "config", loadChildren: () => import("./config/config.module").then(m => m.ConfigModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Admin } } | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,8 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { ShortRoleNamesComponent } from './components/short-role-names/short-role-names.component'; | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { CommonModule } from "@angular/common"; | ||||
| import { ShortRoleNamesComponent } from "./components/short-role-names/short-role-names.component"; | ||||
| import { ShortRoleNameRoutingModule } from "./short-role-name-routing.module"; | ||||
| import { ButtonModule } from "primeng/button"; | ||||
| import { InputTextModule } from "primeng/inputtext"; | ||||
| import { ReactiveFormsModule } from "@angular/forms"; | ||||
| import { SharedModule } from "../../../shared/shared.module"; | ||||
| import { TableModule } from "primeng/table"; | ||||
| import { TranslateModule } from "@ngx-translate/core"; | ||||
|  | ||||
|  | ||||
|  | ||||
| @NgModule({ | ||||
| @@ -18,13 +12,7 @@ import { TranslateModule } from "@ngx-translate/core"; | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
|     ShortRoleNameRoutingModule, | ||||
|     ButtonModule, | ||||
|     InputTextModule, | ||||
|     ReactiveFormsModule, | ||||
|     SharedModule, | ||||
|     SharedModule, | ||||
|     TableModule, | ||||
|     TranslateModule | ||||
|   ] | ||||
| }) | ||||
| export class ShortRoleNameModule { } | ||||
|   | ||||
| @@ -30,6 +30,7 @@ export class SidebarService { | ||||
|   serverAutoRoles: MenuItem = {}; | ||||
|   serverLevels: MenuItem = {}; | ||||
|   serverAchievements: MenuItem = {}; | ||||
|   serverScheduledEvents: MenuItem = {}; | ||||
|   serverShortRoleNames: MenuItem = {}; | ||||
|   serverConfig: MenuItem = {}; | ||||
|   serverMenu: MenuItem = {}; | ||||
| @@ -110,6 +111,13 @@ export class SidebarService { | ||||
|       routerLink: `server/${this.server?.id}/achievements` | ||||
|     }; | ||||
|  | ||||
|     this.serverScheduledEvents = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.scheduled_events") : "", | ||||
|       icon: "pi pi-calender", | ||||
|       visible: true, | ||||
|       routerLink: `server/${this.server?.id}/scheduled-events` | ||||
|     }; | ||||
|  | ||||
|     this.serverShortRoleNames = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.short_role_names") : "", | ||||
|       icon: "pi pi-list", | ||||
| @@ -129,7 +137,7 @@ export class SidebarService { | ||||
|       icon: "pi pi-server", | ||||
|       visible: false, | ||||
|       expanded: true, | ||||
|       items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverShortRoleNames, this.serverConfig] | ||||
|       items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverScheduledEvents, this.serverShortRoleNames, this.serverConfig] | ||||
|     }; | ||||
|     this.adminConfig = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", | ||||
| @@ -205,6 +213,7 @@ export class SidebarService { | ||||
|         this.serverAutoRoles.visible = isTechnicianAndFullAccessActive || this.hasFeature("AutoRoleModule") && user?.isModerator; | ||||
|         this.serverLevels.visible = isTechnicianAndFullAccessActive || this.hasFeature("LevelModule") && user?.isModerator; | ||||
|         this.serverAchievements.visible = isTechnicianAndFullAccessActive || this.hasFeature("AchievementsModule") && user?.isModerator; | ||||
|         this.serverScheduledEvents.visible = isTechnicianAndFullAccessActive || this.hasFeature("ScheduledEvents") && user?.isModerator; | ||||
|         this.serverShortRoleNames.visible = isTechnicianAndFullAccessActive || this.hasFeature("ShortRoleName") && user?.isAdmin; | ||||
|  | ||||
|         this.serverConfig.visible = isTechnicianAndFullAccessActive || user?.isAdmin; | ||||
|   | ||||
| @@ -124,12 +124,14 @@ | ||||
|   }, | ||||
|   "common": { | ||||
|     "404": "404 - Der Eintrag konnte nicht gefunden werden", | ||||
|     "abort": "Abbrechen", | ||||
|     "actions": "Aktionen", | ||||
|     "active": "Aktiv", | ||||
|     "add": "Hinzufügen", | ||||
|     "attribute": "Attribut", | ||||
|     "auth_role": "Rolle", | ||||
|     "author": "Autor", | ||||
|     "back": "Zurück", | ||||
|     "bool_as_string": { | ||||
|       "false": "Nein", | ||||
|       "true": "Ja" | ||||
| @@ -137,12 +139,14 @@ | ||||
|     "channel_id": "Kanal Id", | ||||
|     "channel_name": "Kanal", | ||||
|     "color": "Farbe", | ||||
|     "continue": "Weiter", | ||||
|     "created_at": "Erstellt am", | ||||
|     "description": "Beschreibung", | ||||
|     "discord_id": "Discord Id", | ||||
|     "edit": "Bearbeiten", | ||||
|     "email": "E-Mail", | ||||
|     "emoji": "Emoji", | ||||
|     "end_time": "Endzeit", | ||||
|     "error": "Fehler", | ||||
|     "export": "Exportieren", | ||||
|     "feature_flags": "Funktionen", | ||||
| @@ -176,11 +180,13 @@ | ||||
|     }, | ||||
|     "id": "Id", | ||||
|     "import": "Importieren", | ||||
|     "interval": "Interval", | ||||
|     "joined_at": "Beigetreten am", | ||||
|     "last_name": "Nachname", | ||||
|     "leaved_at": "Verlassen am", | ||||
|     "left_server": "Aktiv", | ||||
|     "level": "Level", | ||||
|     "location": "Ort", | ||||
|     "message_id": "Nachricht Id", | ||||
|     "min_xp": "Min. XP", | ||||
|     "modified_at": "Bearbeitet am", | ||||
| @@ -200,10 +206,12 @@ | ||||
|     "role": "Rolle", | ||||
|     "rule_count": "Regeln", | ||||
|     "save": "Speichern", | ||||
|     "start_time": "Startzeit", | ||||
|     "state": { | ||||
|       "off": "Aus", | ||||
|       "on": "Ein" | ||||
|     }, | ||||
|     "type": "Typ", | ||||
|     "user_warnings": "Verwarnungen", | ||||
|     "users": "Benutzer", | ||||
|     "value": "Wert", | ||||
| @@ -538,6 +546,33 @@ | ||||
|         "reaction_count": "Anzahl Reaktionen", | ||||
|         "xp": "XP" | ||||
|       }, | ||||
|       "scheduled_events": { | ||||
|         "edit_dialog": { | ||||
|           "add_header": "Event hinzufügen", | ||||
|           "edit_header": "Event bearbeiten", | ||||
|           "event_info": { | ||||
|             "description_input": "Erzähl den Leuten ein wenig mehr über dein Event. Markdown, neue Zeilen und Links werden unterstützt.", | ||||
|             "event_topic": "Thema", | ||||
|             "event_topic_input": "Worum geht es bei deinem Event?", | ||||
|             "header": "Worum geht es bei deinem Event?", | ||||
|             "start_date": "Startdatum", | ||||
|             "start_time": "Startzeit", | ||||
|             "tab_name": "Eventinformationen" | ||||
|           }, | ||||
|           "location": { | ||||
|             "header": "Wo ist dein Event?", | ||||
|             "somewhere_else": "Irgendwo anders", | ||||
|             "somewhere_else_input": "Ort eingeben", | ||||
|             "stage": "Stage-Kanal", | ||||
|             "stage_input": "Stage-Kanal auswählen", | ||||
|             "tab_name": "Verzeichnis", | ||||
|             "voice": "Sprachkanal", | ||||
|             "voice_input": "Sprachkanal auswählen" | ||||
|           } | ||||
|         }, | ||||
|         "header": "Geplante Events", | ||||
|         "scheduled_events": "Geplante Events" | ||||
|       }, | ||||
|       "short_role_names": { | ||||
|         "header": "Rollen Kürzel", | ||||
|         "message": { | ||||
|   | ||||
| @@ -124,12 +124,14 @@ | ||||
|   }, | ||||
|   "common": { | ||||
|     "404": "404 - Entry not found!", | ||||
|     "abort": "Abort", | ||||
|     "actions": "Actions", | ||||
|     "active": "Active", | ||||
|     "add": "Add", | ||||
|     "attribute": "Attribute", | ||||
|     "auth_role": "Role", | ||||
|     "author": "Author", | ||||
|     "back": "Back", | ||||
|     "bool_as_string": { | ||||
|       "false": "No", | ||||
|       "true": "Yes" | ||||
| @@ -137,6 +139,7 @@ | ||||
|     "channel_id": "Channel Id", | ||||
|     "channel_name": "Channel", | ||||
|     "color": "Color", | ||||
|     "continue": "Continue", | ||||
|     "created_at": "Created at", | ||||
|     "description": "Description", | ||||
|     "discord_id": "Discord Id", | ||||
| @@ -538,6 +541,33 @@ | ||||
|         "reaction_count": "Reaction count", | ||||
|         "xp": "XP" | ||||
|       }, | ||||
|       "scheduled_events": { | ||||
|         "edit_dialog": { | ||||
|           "add_header": "Add event", | ||||
|           "edit_header": "Edit event", | ||||
|           "event_info": { | ||||
|             "description_input": "Tell people a little more about your event. Markdown, new lines and links are supported.", | ||||
|             "event_topic": "Event topic", | ||||
|             "event_topic_input": "What's your event?", | ||||
|             "header": "What's your event about?", | ||||
|             "start_date": "Start date", | ||||
|             "start_time": "Start time", | ||||
|             "tab_name": "Event info" | ||||
|           }, | ||||
|           "location": { | ||||
|             "header": "Where is your event?", | ||||
|             "somewhere_else": "Somewhere else", | ||||
|             "somewhere_else_input": "Enter a location", | ||||
|             "stage": "Stage channel", | ||||
|             "stage_input": "Select a stage channel", | ||||
|             "tab_name": "Location", | ||||
|             "voice": "Voice channel", | ||||
|             "voice_input": "Select a voice channel" | ||||
|           } | ||||
|         }, | ||||
|         "header": "Scheduled events", | ||||
|         "scheduled_events": "Scheduled events" | ||||
|       }, | ||||
|       "short_role_names": { | ||||
|         "header": "Level", | ||||
|         "message": { | ||||
|   | ||||
| @@ -635,6 +635,18 @@ footer { | ||||
|   border: 0; | ||||
| } | ||||
|  | ||||
| .btn, | ||||
| .icon-btn, | ||||
| .text-btn, | ||||
| .danger-btn, | ||||
| .danger-icon-btn { | ||||
|   span { | ||||
|     transition-duration: unset !important; | ||||
|   } | ||||
|  | ||||
|   transition: none !important; | ||||
| } | ||||
|  | ||||
| .spinner-component-wrapper { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
| @@ -661,6 +673,27 @@ p-inputNumber { | ||||
|   border: none !important; | ||||
| } | ||||
|  | ||||
| .edit-dialog { | ||||
|   .p-dialog-content { | ||||
|     padding: 0 !important; | ||||
|  | ||||
|     .p-tabview-nav { | ||||
|       justify-content: space-between; | ||||
|  | ||||
|       li { | ||||
|         width: 100%; | ||||
|  | ||||
|         a { | ||||
|           width: 100%; | ||||
|           justify-content: center; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|  | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 720px) { | ||||
|   footer { | ||||
|     .left, | ||||
|   | ||||
| @@ -552,6 +552,7 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn, | ||||
|   .danger-icon-btn { | ||||
|     background-color: transparent !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -568,22 +569,6 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn { | ||||
|     background-color: $primaryErrorColor !important; | ||||
|     color: $primaryErrorColor !important; | ||||
|     border: 0 !important; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $primaryErrorColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|       border: 0; | ||||
|     } | ||||
|  | ||||
|     .pi { | ||||
|       font-size: 1.275rem !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-datatable .p-sortable-column.p-highlight, | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon, | ||||
|   .p-datatable .p-sortable-column:not(.p-highlight):hover { | ||||
| @@ -777,4 +762,26 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .edit-dialog { | ||||
|     .p-dialog-content { | ||||
|       .p-tabview { | ||||
|         .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { | ||||
|           box-shadow: none !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav li.p-highlight .p-tabview-nav-link { | ||||
|           color: $primaryHeaderColor !important; | ||||
|           border-color: $primaryHeaderColor !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav, | ||||
|         .p-tabview-nav li .p-tabview-nav-link, | ||||
|         .p-tabview-panels { | ||||
|           background-color: $secondaryBackgroundColor !important; | ||||
|           color: $primaryTextColor !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user