Als Nutzer möchte ich Datenänderungen nach verfolgen können #246 #248

Merged
Jonas merged 17 commits from #246 into 1.0.0 2023-03-14 18:31:37 +01:00
21 changed files with 305 additions and 33 deletions
Showing only changes of commit 51f0ee5744 - Show all commits

View File

@ -1,6 +1,6 @@
{
"name": "kdb-web",
"version": "1.0.dev247",
"version": "1.0.dev246",
"scripts": {
"ng": "ng",
"update-version": "ts-node-esm update-version.ts",
@ -50,4 +50,4 @@
"tslib": "^2.4.1",
"typescript": "~4.9.5"
}
}
}

View File

@ -2,3 +2,16 @@ export interface Data {
createdAt?: string;
modifiedAt?: string;
}
export interface DataWithHistory {
createdAt?: string;
modifiedAt?: string;
history?: History[];
}
export interface History {
deleted?: boolean;
dateFrom?: string;
dateTo?: string;
[x: string | number | symbol]: unknown;
}

View File

@ -1,11 +1,11 @@
import { Data } from "./data.model";
import { DataWithHistory } from "./data.model";
import { Level, LevelFilter } from "./level.model";
import { Server, ServerFilter } from "./server.model";
import { UserJoinedServer } from "./user_joined_server.model";
import { UserJoinedVoiceChannel } from "./user_joined_voice_channel.model";
import { UserJoinedGameServer } from "./user_joined_game_server.model";
export interface User extends Data {
export interface User extends DataWithHistory {
id?: number;
discordId?: number;
name?: string;
@ -23,6 +23,17 @@ export interface User extends Data {
userJoinedGameServerCount?: number;
userJoinedGameServers?: UserJoinedGameServer[];
// history?: UserHistory[];
}
export interface UserHistory extends History {
id?: number;
discordId?: number;
xp?: number;
level?: number;
server?: number;
leftServer?: boolean;
}
export interface UserFilter {

View File

@ -115,6 +115,17 @@ export class Queries {
createdAt
modifiedAt
history {
id
discordId
xp
server
leftServer
deleted
dateFrom
dateTo
}
}
}
}

View File

@ -0,0 +1,21 @@
<button pButton class="btn icon-btn" icon="pi pi-history" (click)="openHistory()"></button>
<p-sidebar styleClass="history p-sidebar-md" [(visible)]="showSidebar" position="right" [baseZIndex]="10000">
<h1>{{translationKey | translate}} {{'common.history.header' | translate}}</h1>
<div class="entry-list">
<div class="entry" *ngFor="let entry of history">
<div class="attribute" *ngFor="let item of entry | keyvalue">
<div class="key">
{{getAttributeTranslationKey(item.key) | translate}}
</div>
<div class="seperator">
->
</div>
<div class="value">
{{item.value}}
</div>
</div>
</div>
</div>
</p-sidebar>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryBtnComponent } from './history-btn.component';
describe('HistoryBtnComponent', () => {
let component: HistoryBtnComponent;
let fixture: ComponentFixture<HistoryBtnComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HistoryBtnComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(HistoryBtnComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
edraft marked this conversation as resolved
Review

Tests schreiben üben wir aber nochmal ^^

Tests schreiben üben wir aber nochmal ^^

View File

@ -0,0 +1,31 @@
import { Component, Input, OnInit } from "@angular/core";
import { History } from "../../../../models/data/data.model";
@Component({
selector: "app-history-btn",
templateUrl: "./history-btn.component.html",
styleUrls: ["./history-btn.component.scss"]
})
export class HistoryBtnComponent implements OnInit {
@Input() history: History[] = [];
@Input() translationKey: string = "";
showSidebar = false;
edraft marked this conversation as resolved
Review

Darf showSidebar keinen Typen bekommen? :(

public showSidebar: boolean = false;

Je nachdem was showSidebar tut, wäre hier vielleicht ein Store als "Single-Source-Of-Truth" sinnvoll, damit wir hier ein Observable haben.
So kann sich egal wo dieser boolische Wert anpassen und DOM-Elemente anschließened gerendert werden oder eben nicht.
Ist aber wie gesagt abhängig vom Use-Case.

Darf showSidebar keinen Typen bekommen? :( public showSidebar: boolean = false; Je nachdem was showSidebar tut, wäre hier vielleicht ein Store als "Single-Source-Of-Truth" sinnvoll, damit wir hier ein Observable haben. So kann sich egal wo dieser boolische Wert anpassen und DOM-Elemente anschließened gerendert werden oder eben nicht. Ist aber wie gesagt abhängig vom Use-Case.
Review

Generell hast du recht. Wäre für das was die Komponente macht jedoch OP.

Generell hast du recht. Wäre für das was die Komponente macht jedoch OP.
constructor() {
}
ngOnInit(): void {
}
openHistory(): void {
console.log("history", this.history);
this.showSidebar = true;
}
getAttributeTranslationKey(key: string) {
return `common.history.${key}`;
}
}

View File

@ -0,0 +1 @@
<p>history works!</p>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HistoryComponent } from './history.component';
describe('HistoryComponent', () => {
let component: HistoryComponent;
let fixture: ComponentFixture<HistoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HistoryComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(HistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-history',
templateUrl: './history.component.html',
styleUrls: ['./history.component.scss']
})
export class HistoryComponent {
}

View File

@ -1,28 +1,30 @@
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { DynamicDialogModule } from 'primeng/dynamicdialog';
import { InputTextModule } from 'primeng/inputtext';
import { MenuModule } from 'primeng/menu';
import { PasswordModule } from 'primeng/password';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
import { TableModule } from 'primeng/table';
import { ToastModule } from 'primeng/toast';
import { AuthRolePipe } from './pipes/auth-role.pipe';
import { IpAddressPipe } from './pipes/ip-address.pipe';
import { BoolPipe } from './pipes/bool.pipe';
import { PanelMenuModule } from 'primeng/panelmenu';
import { CommonModule } from "@angular/common";
import { HttpClientModule } from "@angular/common/http";
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { TranslateModule } from "@ngx-translate/core";
import { ButtonModule } from "primeng/button";
import { CheckboxModule } from "primeng/checkbox";
import { ConfirmDialogModule } from "primeng/confirmdialog";
import { DialogModule } from "primeng/dialog";
import { DropdownModule } from "primeng/dropdown";
import { DynamicDialogModule } from "primeng/dynamicdialog";
import { InputTextModule } from "primeng/inputtext";
import { MenuModule } from "primeng/menu";
import { PasswordModule } from "primeng/password";
import { ProgressSpinnerModule } from "primeng/progressspinner";
import { TableModule } from "primeng/table";
import { ToastModule } from "primeng/toast";
import { AuthRolePipe } from "./pipes/auth-role.pipe";
import { IpAddressPipe } from "./pipes/ip-address.pipe";
import { BoolPipe } from "./pipes/bool.pipe";
import { PanelMenuModule } from "primeng/panelmenu";
import { PanelModule } from "primeng/panel";
import { InputNumberModule } from 'primeng/inputnumber';
import { InputNumberModule } from "primeng/inputnumber";
import { ImageModule } from "primeng/image";
import { SidebarModule } from "primeng/sidebar";
import { HistoryComponent } from "./components/history/history.component";
import { HistoryBtnComponent } from './components/history-btn/history-btn.component';
@NgModule({
@ -30,6 +32,8 @@ import { ImageModule } from "primeng/image";
AuthRolePipe,
IpAddressPipe,
BoolPipe,
HistoryComponent,
HistoryBtnComponent,
],
imports: [
CommonModule,
@ -52,7 +56,8 @@ import { ImageModule } from "primeng/image";
PanelMenuModule,
PanelModule,
InputNumberModule,
ImageModule
ImageModule,
SidebarModule,
],
exports: [
ButtonModule,
@ -77,7 +82,9 @@ import { ImageModule } from "primeng/image";
IpAddressPipe,
BoolPipe,
InputNumberModule,
ImageModule
ImageModule,
SidebarModule,
HistoryBtnComponent
]
})
export class SharedModule { }

View File

@ -224,6 +224,7 @@
</td>
<td>
<div class="btn-wrapper">
<app-history-btn [history]="member.history" translationKey="view.server.members.header"></app-history-btn>
<button *ngIf="!editing" pButton pInitEditableRow class="btn icon-btn" icon="pi pi-pencil"
(click)="onRowEditInit(dt, member, ri)"></button>
<button *ngIf="!editing" pButton pInitEditableRow class="btn icon-btn" icon="pi pi-user"

View File

@ -3,7 +3,7 @@
"WebVersion": {
"Major": "1",
"Minor": "0",
"Micro": "dev247"
"Micro": "dev246"
},
"Themes": [
{
@ -23,4 +23,4 @@
"Name": "sh-edraft-dark-theme"
}
]
}
}

View File

@ -134,7 +134,18 @@
"modified_at": "Bearbeitet am",
"no_entries_found": "Keine Einträge gefunden",
"of": "von",
"reset_filters": "Filter zurücksetzen"
"reset_filters": "Filter zurücksetzen",
"history": {
"header": "Historie",
"id": "Id",
"discordId": "Discord Id",
"deleted": "Gelöscht",
"dateFrom": "Von",
"dateTo": "Bis",
"server": "Server",
"leftServer": "Gegangen",
"xp": "XP"
}
},
"dialog": {
"abort": "Abbrechen",

View File

@ -5,13 +5,39 @@
@import "./styles/primeng-fixes.scss";
@import "./styles/constants.scss";
html,
body {
height: 100%;
height: 100vh;
padding: 0;
margin: 0;
font-size: 1rem;
$scrollbarColor: #272727;
$scrollbarBackgroundColor: #888;
/* width */
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background-color: $scrollbarBackgroundColor;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: $scrollbarColor;
border: 3px solid $scrollbarBackgroundColor;
border-radius: 10px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: $scrollbarColor;
}
}
main {
@ -332,6 +358,43 @@ header {
}
}
.history {
width: 300px;
.entry-list {
display: flex;
flex-direction: column;
gap: 15px;
.entry {
padding-bottom: 10px;
display: flex;
flex-direction: column;
gap: 5px;
.attribute {
display: flex;
gap: 5px;
.key {
width: 30%;
}
.seperator {
width: 10%;
}
.value {
width: 60%;
}
}
}
}
}
.p-dialog-header {
padding: 20px 20px 20px 20px !important;
}

View File

@ -175,6 +175,17 @@
}
}
.history {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
.entry-list {
.entry {
border-bottom: 1px solid $primaryHeaderColor;
}
}
}
.p-dialog-header {
background-color: $secondaryBackgroundColor !important;
color: $primaryTextColor !important;

View File

@ -175,6 +175,17 @@
}
}
.history {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
.entry-list {
.entry {
border-bottom: 1px solid $primaryHeaderColor;
}
}
}
.p-dialog-header {
background-color: $secondaryBackgroundColor !important;
color: $primaryTextColor !important;

View File

@ -19,6 +19,7 @@
background-color: $primaryBackgroundColor;
h1,
h2 {
color: $primaryHeaderColor;
@ -175,6 +176,18 @@
}
}
.history {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
.entry-list {
.entry {
border-bottom: 1px solid $primaryHeaderColor;
}
}
}
.p-dialog-header {
background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important;

View File

@ -175,6 +175,17 @@
}
}
.history {
background-color: $primaryBackgroundColor;
color: $primaryTextColor;
.entry-list {
.entry {
border-bottom: 1px solid $primaryHeaderColor;
}
}
}
.p-dialog-header {
background-color: $secondaryBackgroundColor !important;
color: $primaryTextColor !important;