diff --git a/kdb-bot/src/bot_graphql/model/server.gql b/kdb-bot/src/bot_graphql/model/server.gql
index db94dc89..90f07f95 100644
--- a/kdb-bot/src/bot_graphql/model/server.gql
+++ b/kdb-bot/src/bot_graphql/model/server.gql
@@ -16,6 +16,9 @@ type Server implements TableWithHistoryQuery {
userCount: Int
users(filter: UserFilter, page: Page, sort: Sort): [User]
+ achievementCount: Int
+ achievements(filter: AchievementFilter, page: Page, sort: Sort): [Achievement]
+
createdAt: String
modifiedAt: String
diff --git a/kdb-bot/src/bot_graphql/queries/server_query.py b/kdb-bot/src/bot_graphql/queries/server_query.py
index 39012d49..52d53d23 100644
--- a/kdb-bot/src/bot_graphql/queries/server_query.py
+++ b/kdb-bot/src/bot_graphql/queries/server_query.py
@@ -1,6 +1,7 @@
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
+from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC
@@ -9,8 +10,8 @@ from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoic
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.server import Server
from bot_data.model.server_history import ServerHistory
-from bot_graphql.abc.data_query_abc import DataQueryABC
from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC
+from bot_graphql.filter.achievement_filter import AchievementFilter
from bot_graphql.filter.auto_role_filter import AutoRoleFilter
from bot_graphql.filter.client_filter import ClientFilter
from bot_graphql.filter.level_filter import LevelFilter
@@ -28,6 +29,7 @@ class ServerQuery(DataQueryWithHistoryABC):
users: UserRepositoryABC,
ujs: UserJoinedServerRepositoryABC,
ujvs: UserJoinedVoiceChannelRepositoryABC,
+ achievements: AchievementRepositoryABC,
):
DataQueryWithHistoryABC.__init__(self, "Server", "ServersHistory", ServerHistory, db)
@@ -54,6 +56,9 @@ class ServerQuery(DataQueryWithHistoryABC):
)
self.add_collection("level", lambda server, *_: self._levels.get_levels_by_server_id(server.id), LevelFilter)
self.add_collection("user", lambda server, *_: self._users.get_users_by_server_id(server.id), UserFilter)
+ self.add_collection(
+ "achievement", lambda server, *_: achievements.get_achievements_by_server_id(server.id), AchievementFilter
+ )
@staticmethod
def resolve_id(server: Server, *_):
diff --git a/kdb-web/src/app/models/data/achievement.model.ts b/kdb-web/src/app/models/data/achievement.model.ts
new file mode 100644
index 00000000..0b1af096
--- /dev/null
+++ b/kdb-web/src/app/models/data/achievement.model.ts
@@ -0,0 +1,20 @@
+import { DataWithHistory } from "./data.model";
+import { Server, ServerFilter } from "./server.model";
+
+export interface Achievement extends DataWithHistory {
+ id?: number;
+ name?: string;
+ attribute?: string;
+ operator?: string;
+ value?: string;
+ server?: Server;
+}
+
+export interface AchievementFilter {
+ id?: number;
+ name?: string;
+ attribute?: string;
+ operator?: string;
+ value?: string;
+ server?: ServerFilter;
+}
diff --git a/kdb-web/src/app/models/graphql/mutations.model.ts b/kdb-web/src/app/models/graphql/mutations.model.ts
index 0cba6ef3..db95bd86 100644
--- a/kdb-web/src/app/models/graphql/mutations.model.ts
+++ b/kdb-web/src/app/models/graphql/mutations.model.ts
@@ -121,4 +121,46 @@ export class Mutations {
}
}
`;
+
+ static createAchievement = `
+ mutation createAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) {
+ level {
+ createAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) {
+ id
+ name
+ attribute
+ operator
+ value
+ server {
+ id
+ }
+ }
+ }
+ }
+ `;
+
+ static updateAchievement = `
+ mutation updateAchievement($name: String, $attribute: String, $operator: String, $value: String, $serverId: ID) {
+ level {
+ updateAchievement(input: { name: $name, attribute: $attribute, operator: $operator, value: $value}) {
+ id
+ name
+ attribute
+ operator
+ value
+ }
+ }
+ }
+ `;
+
+ static deleteAchievement = `
+ mutation deleteAchievement($id: ID) {
+ level {
+ deleteLevel(id: $id) {
+ id
+ name
+ }
+ }
+ }
+ `;
}
diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts
index f3d68257..df3cf125 100644
--- a/kdb-web/src/app/models/graphql/queries.model.ts
+++ b/kdb-web/src/app/models/graphql/queries.model.ts
@@ -90,6 +90,50 @@ export class Queries {
}
`;
+ static achievementQuery = `
+ query AchievementList($serverId: ID, $filter: AchievementFilter, $page: Page, $sort: Sort) {
+ servers(filter: {id: $serverId}) {
+ achievementCount
+ achievements(filter: $filter, page: $page, sort: $sort) {
+ id
+ name
+ attribute
+ operator
+ value
+ server {
+ id
+ name
+ }
+ createdAt
+ modifiedAt
+ }
+ }
+ }
+ `;
+
+ static achievementWithHistoryQuery = `
+ query AchievementHistory($serverId: ID, $id: ID) {
+ servers(filter: {id: $serverId}) {
+ achievementCount
+ achievements(filter: {id: $id}) {
+ id
+
+ history {
+ id
+ name
+ attribute
+ operator
+ value
+ server
+ deleted
+ dateFrom
+ dateTo
+ }
+ }
+ }
+ }
+ `;
+
static usersQuery = `
query UsersList($serverId: ID, $filter: UserFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
diff --git a/kdb-web/src/app/models/graphql/query.model.ts b/kdb-web/src/app/models/graphql/query.model.ts
index 3f7e85a1..60e1964d 100644
--- a/kdb-web/src/app/models/graphql/query.model.ts
+++ b/kdb-web/src/app/models/graphql/query.model.ts
@@ -3,6 +3,7 @@ import { User } from "../data/user.model";
import { AutoRole, AutoRoleRule } from "../data/auto_role.model";
import { Guild } from "../data/discord.model";
import { Level } from "../data/level.model";
+import { Achievement } from "../data/achievement.model";
export interface Query {
serverCount: number;
@@ -23,6 +24,11 @@ export interface LevelListQuery {
levels: Level[];
}
+export interface AchievementListQuery {
+ achievementCount: number;
+ achievements: Achievement[];
+}
+
export interface AutoRoleQuery {
autoRoleCount: number;
autoRoles: AutoRole[];
diff --git a/kdb-web/src/app/models/graphql/result.model.ts b/kdb-web/src/app/models/graphql/result.model.ts
index d3d6e4a4..d77c2cdc 100644
--- a/kdb-web/src/app/models/graphql/result.model.ts
+++ b/kdb-web/src/app/models/graphql/result.model.ts
@@ -2,6 +2,7 @@ import { User } from "../data/user.model";
import { AutoRole, AutoRoleRule } from "../data/auto_role.model";
import { Level } from "../data/level.model";
import { Server } from "../data/server.model";
+import { Achievement } from "../data/achievement.model";
export interface GraphQLResult {
data: {
@@ -45,3 +46,11 @@ export interface LevelMutationResult {
deleteLevel?: Level
};
}
+
+export interface AchievementMutationResult {
+ achievement: {
+ createAchievement?: Achievement
+ updateAchievement?: Achievement
+ deleteAchievement?: Achievement
+ };
+}
diff --git a/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts b/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts
new file mode 100644
index 00000000..ae847fb9
--- /dev/null
+++ b/kdb-web/src/app/modules/view/server/achievements/achievements-routing.module.ts
@@ -0,0 +1,16 @@
+import {NgModule} from "@angular/core";
+import {RouterModule, Routes} from "@angular/router";
+import { AchievementComponent } from "./components/achievement/achievement.component";
+
+const routes: Routes = [
+
+ {path: '', component: AchievementComponent},
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class AchievementsRoutingModule {
+
+}
diff --git a/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts b/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts
new file mode 100644
index 00000000..ccf7e426
--- /dev/null
+++ b/kdb-web/src/app/modules/view/server/achievements/achievements.module.ts
@@ -0,0 +1,18 @@
+import { NgModule } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { AchievementComponent } from "./components/achievement/achievement.component";
+import { AchievementsRoutingModule } from "./achievements-routing.module";
+import { SharedModule } from "../../../shared/shared.module";
+
+
+@NgModule({
+ declarations: [
+ AchievementComponent
+ ],
+ imports: [
+ CommonModule,
+ AchievementsRoutingModule,
+ SharedModule
+ ]
+})
+export class AchievementsModule { }
diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html
new file mode 100644
index 00000000..83879d48
--- /dev/null
+++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.html
@@ -0,0 +1,215 @@
+
+ {{'view.server.achievements.header' | translate}}
+
+
+
diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.scss b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts
new file mode 100644
index 00000000..771e808b
--- /dev/null
+++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AchievementComponent } from './achievement.component';
+
+describe('AchievementComponent', () => {
+ let component: AchievementComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ AchievementComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(AchievementComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts
new file mode 100644
index 00000000..5eb90432
--- /dev/null
+++ b/kdb-web/src/app/modules/view/server/achievements/components/achievement/achievement.component.ts
@@ -0,0 +1,268 @@
+import { Component, OnDestroy, OnInit } from "@angular/core";
+import { Achievement, AchievementFilter } from "../../../../../../models/data/achievement.model";
+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 { 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 { AchievementListQuery, Query } from "../../../../../../models/graphql/query.model";
+import { catchError, debounceTime, takeUntil } from "rxjs/operators";
+import { LazyLoadEvent } from "primeng/api";
+import { Table } from "primeng/table";
+import { User } from "../../../../../../models/data/user.model";
+import { AchievementMutationResult, UpdateUserMutationResult } from "../../../../../../models/graphql/result.model";
+import { Mutations } from "../../../../../../models/graphql/mutations.model";
+
+@Component({
+ selector: "app-achievement",
+ templateUrl: "./achievement.component.html",
+ styleUrls: ["./achievement.component.scss"]
+})
+export class AchievementComponent implements OnInit, OnDestroy {
+ public achievements: Achievement[] = [];
+ public loading = true;
+
+ public isEditingNew: boolean = false;
+
+ public filterForm!: FormGroup<{
+ id: FormControl,
+ name: FormControl,
+ color: FormControl,
+ min_xp: FormControl,
+ permissions: FormControl,
+ }>;
+
+ public filter: AchievementFilter = {};
+ public page: Page = {
+ pageSize: undefined,
+ pageIndex: undefined
+ };
+ public sort: Sort = {
+ sortColumn: undefined,
+ sortDirection: undefined
+ };
+
+ public totalRecords: number = 0;
+
+ public clonedAchievements: { [s: string]: Achievement; } = {};
+
+ private unsubscriber = new Subject();
+ private server: Server = {};
+ public user: UserDTO | null = null;
+
+ query: string = Queries.achievementWithHistoryQuery;
+
+ 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) {
+ }
+
+ public ngOnInit(): void {
+ this.setFilterForm();
+ this.data.getServerFromRoute(this.route).then(async server => {
+ this.server = server;
+ this.loadNextPage();
+ 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.loading = true;
+ this.data.query(Queries.achievementQuery, {
+ serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort
+ },
+ (data: Query) => {
+ return data.servers[0];
+ }
+ ).subscribe(data => {
+ this.totalRecords = data.achievementCount;
+ this.achievements = data.achievements;
+ this.spinner.hideSpinner();
+ this.loading = false;
+ });
+ }
+
+ public setFilterForm(): void {
+ this.filterForm = this.fb.group({
+ id: new FormControl(null),
+ name: new FormControl(null),
+ color: new FormControl(null),
+ min_xp: new FormControl(null),
+ permissions: new FormControl(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.name) {
+ this.filter.name = changes.name;
+ } else {
+ this.filter.name = undefined;
+ }
+
+ if (this.page.pageSize)
+ this.page.pageSize = 10;
+
+ if (this.page.pageIndex)
+ this.page.pageIndex = 0;
+
+ this.loadNextPage();
+ });
+ }
+
+ public newAchievementTemplate: Achievement = {
+ id: 0,
+ 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, user: User, index: number): void {
+ this.clonedAchievements[index] = { ...user };
+ }
+
+ public onRowEditSave(table: Table, newAchievement: Achievement, index: number): void {
+ if (this.isEditingNew && JSON.stringify(newAchievement) === JSON.stringify(this.newAchievementTemplate)) {
+ this.isEditingNew = false;
+ this.achievements.splice(index, 1);
+ return;
+ }
+
+ if (!newAchievement.id && !this.isEditingNew || !newAchievement.name && !newAchievement.attribute && !newAchievement?.operator && !newAchievement?.value) {
+ return;
+ }
+
+ if (this.isEditingNew) {
+ this.spinner.showSpinner();
+ this.data.mutation(Mutations.createAchievement, {
+ name: newAchievement.name,
+ attribute: newAchievement.attribute,
+ operator: newAchievement.operator,
+ value: newAchievement.value,
+ serverId: this.server.id
+ }
+ ).pipe(catchError(err => {
+ this.isEditingNew = false;
+ this.spinner.hideSpinner();
+ this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_create_failed"), this.translate.instant("view.server.achievements.message.achievement_create_failed_d"));
+ return throwError(err);
+ })).subscribe(result => {
+ this.isEditingNew = false;
+ this.spinner.hideSpinner();
+ this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_create"), this.translate.instant("view.server.achievements.message.achievement_create_d", { name: result.achievement.createAchievement?.name }));
+ this.loadNextPage();
+ });
+ return;
+ }
+
+ this.spinner.showSpinner();
+ this.data.mutation(Mutations.updateAchievement, {
+ id: newAchievement.id,
+ name: newAchievement.name,
+ attribute: newAchievement.attribute,
+ operator: newAchievement.operator,
+ value: newAchievement.value
+ }
+ ).pipe(catchError(err => {
+ this.spinner.hideSpinner();
+ this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_update_failed"), this.translate.instant("view.server.achievements.message.achievement_update_failed_d", { name: newAchievement.name }));
+ return throwError(err);
+ })).subscribe(_ => {
+ this.spinner.hideSpinner();
+ this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_update"), this.translate.instant("view.server.achievements.message.achievement_update_d", { name: newAchievement.name }));
+ this.loadNextPage();
+ });
+
+ }
+
+ public onRowEditCancel(index: number): void {
+ if (this.isEditingNew) {
+ this.achievements.splice(index, 1);
+ delete this.clonedAchievements[index];
+ this.isEditingNew = false;
+ return;
+ }
+
+ this.achievements[index] = this.clonedAchievements[index];
+ delete this.clonedAchievements[index];
+ }
+
+ public deleteAchievement(achievement: Achievement): void {
+ this.confirmDialog.confirmDialog(
+ this.translate.instant("view.server.achievements.message.achievement_delete"), this.translate.instant("view.server.achievements.message.achievement_delete_q", { name: achievement.name }),
+ () => {
+ this.spinner.showSpinner();
+ this.data.mutation(Mutations.deleteAchievement, {
+ id: achievement.id
+ }
+ ).pipe(catchError(err => {
+ this.spinner.hideSpinner();
+ this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_delete_failed"), this.translate.instant("view.server.achievements.message.achievement_delete_failed_d", { name: achievement.name }));
+ return throwError(err);
+ })).subscribe(l => {
+ this.spinner.hideSpinner();
+ this.toastService.success(this.translate.instant("view.server.achievements.message.achievement_deleted"), this.translate.instant("view.server.achievements.message.achievement_deleted_d", { name: achievement.name }));
+ this.loadNextPage();
+ });
+ });
+ }
+
+ public addAchievement(table: Table): void {
+ const newAchievement = JSON.parse(JSON.stringify(this.newAchievementTemplate));
+ newAchievement.id = Math.max.apply(Math, this.achievements.map(l => {
+ return l.id ?? 0;
+ })) + 1;
+
+ this.achievements.push(newAchievement);
+
+ table.initRowEdit(newAchievement);
+
+ const index = this.achievements.findIndex(l => l.id == newAchievement.id);
+ this.onRowEditInit(table, newAchievement, index);
+
+ this.isEditingNew = true;
+ }
+}
diff --git a/kdb-web/src/app/modules/view/server/server-routing.module.ts b/kdb-web/src/app/modules/view/server/server-routing.module.ts
index 147c40ea..a83dab96 100644
--- a/kdb-web/src/app/modules/view/server/server-routing.module.ts
+++ b/kdb-web/src/app/modules/view/server/server-routing.module.ts
@@ -9,7 +9,8 @@ const routes: Routes = [
{ path: "members", component: MembersComponent },
{ path: "members/:memberId", component: ProfileComponent },
{ path: "auto-roles", loadChildren: () => import("./auto-role/auto-role.module").then(m => m.AutoRoleModule) },
- { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule) }
+ { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule) },
+ { path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule) }
];
@NgModule({
diff --git a/kdb-web/src/app/services/sidebar/sidebar.service.ts b/kdb-web/src/app/services/sidebar/sidebar.service.ts
index 1bd9476a..81ab4548 100644
--- a/kdb-web/src/app/services/sidebar/sidebar.service.ts
+++ b/kdb-web/src/app/services/sidebar/sidebar.service.ts
@@ -24,6 +24,7 @@ export class SidebarService {
serverMembers: MenuItem = {};
serverAutoRoles: MenuItem = {};
serverLevels: MenuItem = {};
+ serverAchievements: MenuItem = {};
serverMenu: MenuItem = {};
adminConfig: MenuItem = {};
adminUsers: MenuItem = {};
@@ -102,12 +103,19 @@ export class SidebarService {
routerLink: `server/${this.server$.value?.id}/levels`
};
+ this.serverAchievements = {
+ label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.achievements") : "",
+ icon: "pi pi-angle-double-up",
+ visible: true,
+ routerLink: `server/${this.server$.value?.id}/achievements`
+ };
+
this.serverMenu = {
label: this.isSidebarOpen ? this.server$.value?.name : "",
icon: "pi pi-server",
visible: false,
expanded: true,
- items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels]
+ items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements]
};
this.adminConfig = {
label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "",
@@ -142,6 +150,7 @@ export class SidebarService {
this.serverMembers.visible = !!user?.isModerator;
this.serverAutoRoles.visible = !!user?.isModerator;
this.serverLevels.visible = !!user?.isModerator;
+ this.serverAchievements.visible = !!user?.isModerator;
} else {
this.serverMenu.visible = false;
}
diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json
index 7a1cdf40..4078a0be 100644
--- a/kdb-web/src/assets/i18n/de.json
+++ b/kdb-web/src/assets/i18n/de.json
@@ -397,6 +397,32 @@
"level_update_failed_d": "Die Bearbeitung des Levels ist fehlgeschlagen!"
}
},
+ "achievements": {
+ "header": "Errungenschaften",
+ "headers": {
+ "name": "Name",
+ "attribute": "Attribut",
+ "operator": "Operator",
+ "value": "Wert"
+ },
+ "achievements": "Errungenschaften",
+ "message": {
+ "achievement_create": "Errungenschaft erstellt",
+ "achievement_create_d": "Errungenschaft {{name}} erfolgreich erstellt",
+ "achievement_create_failed": "Errungenschaft Erstellung fehlgeschlagen",
+ "achievement_create_failed_d": "Die Erstellung der Errungenschaft ist fehlgeschlagen!",
+ "achievement_delete": "Errungenschaft löschen",
+ "achievement_delete_failed": "Errungenschaft Löschung fehlgeschlagen",
+ "achievement_delete_failed_d": "Die Löschung der Errungenschaft {{name}} ist fehlgeschlagen!",
+ "achievement_delete_q": "Sind Sie sich sicher, dass Sie das Errungenschaft {{name}} löschen möchten?",
+ "achievement_deleted": "Errungenschaft gelöscht",
+ "achievement_deleted_d": "Errungenschaft {{name}} erfolgreich gelöscht",
+ "achievement_update": "Errungenschaft bearbeitet",
+ "achievement_update_d": "Errungenschaft {{name}} erfolgreich bearbeitet",
+ "achievement_update_failed": "Errungenschaft Bearbeitung fehlgeschlagen",
+ "achievement_update_failed_d": "Die Bearbeitung der Errungenschaft ist fehlgeschlagen!"
+ }
+ },
"members": {
"header": "Mitglieder",
"headers": {