improve filter, add url parameter, overview improvements
This commit is contained in:
@@ -12,6 +12,7 @@ import { PageTurnover } from './pages/turnover/turnover.page';
|
||||
import { PageTurnoversManage } from './pages/turnovers/manage/manage.page';
|
||||
import { PageTurnovers } from './pages/turnovers/turnovers.page';
|
||||
import { PageUnavailable } from './pages/unavailable/unavailable.page';
|
||||
import { PageUserCreate } from './pages/users/create/users.create.page';
|
||||
import { PageUsers } from './pages/users/users.page';
|
||||
import { UiMain } from './ui/main/main.ui';
|
||||
|
||||
@@ -28,6 +29,7 @@ const routes: Routes = [
|
||||
{ path: 't', component: PageTurnoversManage, canActivate: [AuthAdminGuard] },
|
||||
{ path: 'm', component: PageManagement, canActivate: [AuthAdminGuard] },
|
||||
{ path: 'u', component: PageUsers, canActivate: [AuthAdminGuard] },
|
||||
{ path: 'user', component: PageUserCreate, canActivate: [AuthAdminGuard] },
|
||||
{ path: 'u/:username', component: PageProfile, canActivate: [AuthAdminGuard] },
|
||||
{ path: 'unavailable', component: PageUnavailable },
|
||||
{ path: '**', component: PageNotFound, pathMatch: 'full', canActivate: [AuthenticatedGuard] }
|
||||
|
||||
@@ -22,18 +22,19 @@ import { UiMain } from './ui/main/main.ui';
|
||||
import { I18nEmptyPipe, I18nPipe } from './utils/i18n.pipe';
|
||||
import { MomentPipe } from './utils/moment.pipe';
|
||||
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
import { PageManagement } from './pages/management/management.page';
|
||||
import { PagePassword } from './pages/password/password.page';
|
||||
import { PageTurnover } from './pages/turnover/turnover.page';
|
||||
import { PageTurnoversManage } from './pages/turnovers/manage/manage.page';
|
||||
import { PageTurnovers } from './pages/turnovers/turnovers.page';
|
||||
import { PageUserCreate } from './pages/users/create/users.create.page';
|
||||
import { PageUsers } from './pages/users/users.page';
|
||||
import { I18nPaginatorIntl, I18nService } from './services/i18n.service';
|
||||
import { UiTurnovers } from './ui/turnovers/turnovers.ui';
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { ConfirmDialog } from './ui/confirm/confirm.component';
|
||||
import { PageManagement } from './pages/management/management.page';
|
||||
import { UiTurnovers } from './ui/turnovers/turnovers.ui';
|
||||
|
||||
|
||||
export function fetchI18n(i18n: I18nService) {
|
||||
@@ -80,6 +81,7 @@ export class XhrInterceptor implements HttpInterceptor {
|
||||
PageProfile,
|
||||
PageUnavailable,
|
||||
PageUsers,
|
||||
PageUserCreate,
|
||||
UiMain,
|
||||
UiTurnovers,
|
||||
ConfirmDialog
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
}
|
||||
|
||||
<div class="flex wrap filter-container">
|
||||
<a mat-icon-button (click)="filterOpen=!filterOpen" title="{{'turnovers.filter' | i18n}}"
|
||||
[color]="filterOpen ? 'primary': 'accent'">
|
||||
<mat-icon>filter_alt</mat-icon>
|
||||
</a>
|
||||
|
||||
@if (filterOpen) {
|
||||
<form class="flex wrap filter">
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'management.filter.created' | i18n}}</mat-label>
|
||||
@@ -43,19 +49,30 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (entries) {
|
||||
<div class="scroll-container">
|
||||
<table class="default-table" mat-table [dataSource]="entries.results || []" matSort
|
||||
@if (entries && entries.total == 0) {
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<p>{{'paginator.empty' | i18n}}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
}
|
||||
|
||||
@if (entries && entries.total) {
|
||||
<div class="flex scroll-container grow">
|
||||
<table class="default-table grow" mat-table [dataSource]="entries.results || []" multiTemplateDataRows matSort
|
||||
(matSortChange)="applySort($event)" [matSortDisableClear]="true">
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header [disableClear]="false">{{'user.username' |
|
||||
i18n}}
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header [disableClear]="false">
|
||||
{{'user.username' | i18n}}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let entry">
|
||||
<div class="flex middle">
|
||||
{{entry[0]}}
|
||||
<a class="select-user"
|
||||
[ngClass]="{'selected': entries.filter && entries.filter.username == entry[0]}"
|
||||
(click)="selectUser(entry[0])">{{entry[0]}}</a>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -88,21 +105,40 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="menu">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let entry">
|
||||
@if (entries.filter && entries.filter.username == entry[0]) {
|
||||
<button mat-icon-button (click)="expanded = !expanded">
|
||||
@if (expanded) {
|
||||
<mat-icon>keyboard_arrow_up</mat-icon>
|
||||
} @else {
|
||||
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="expanded">
|
||||
<td mat-cell *matCellDef="let entry" [attr.colspan]="columns.length">
|
||||
@if (expanded && entries.total && entries.filter && entries.filter.username == entry[0]) {
|
||||
<ui-turnovers class="flex column fill" [turnovers]="turnovers" (page)="applyTurnoverPage($event)"
|
||||
[enableSort]="false"></ui-turnovers>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
|
||||
<tr class="entry" mat-row *matRowDef="let row; columns: columns;"></tr>
|
||||
|
||||
<tr class="expanded-row" [ngClass]="{'visible' : expanded}" mat-row
|
||||
*matRowDef="let row; columns: ['expanded']"></tr>
|
||||
</table>
|
||||
</div>
|
||||
@if (entries.total == 0) {
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<p>{{'paginator.empty' | i18n}}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
}
|
||||
|
||||
@if (!entries.filter || !entries.filter.username) {
|
||||
<span class="spacer"></span>
|
||||
|
||||
<div class="mat-mdc-paginator flex">
|
||||
<span class="spacer"></span>
|
||||
<mat-paginator [pageSizeOptions]="pageSizeOptions" [pageIndex]="entries.offset / entries.limit"
|
||||
@@ -110,6 +146,7 @@
|
||||
</mat-paginator>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (!entries || !entries.results && !entries.error) {
|
||||
<mat-progress-bar *ngIf="" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
.filter-container {
|
||||
padding-left: 15px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.filter {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&>* {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expanded-row {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
transition: height 500ms ease-in-out;
|
||||
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
a.select-user {
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { debounceTime, Observable, switchMap } from 'rxjs';
|
||||
import { TurnoverManagementService } from 'src/app/services/turnover.management.service';
|
||||
import { UserManagementService } from 'src/app/services/user.management.service';
|
||||
@@ -17,41 +18,122 @@ export class PageManagement implements OnInit {
|
||||
pageSizeOptions: number[] = [1, 2, 3, 4, 5, 10, 15, 30, 50, 100];
|
||||
sort: string = "username";
|
||||
descending: boolean = false;
|
||||
filterOpen: boolean = true;
|
||||
|
||||
columns: string[] = ['username', 'price', 'timeInvestment'];
|
||||
|
||||
columns: string[] = ['username', 'price', 'timeInvestment', 'menu'];
|
||||
expanded: boolean = false;
|
||||
|
||||
users: Observable<any>;
|
||||
usersFormControl = new FormControl();
|
||||
|
||||
turnovers: any;
|
||||
|
||||
constructor(
|
||||
private turnoverManagementService: TurnoverManagementService,
|
||||
private userManagementService: UserManagementService
|
||||
private userManagementService: UserManagementService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.entries = {};
|
||||
this.update();
|
||||
this.turnovers = {};
|
||||
this.users = this.usersFormControl
|
||||
.valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
switchMap(value => this.userManagementService.pick(value))
|
||||
);
|
||||
|
||||
this.route.queryParams.subscribe({
|
||||
next: (params) => {
|
||||
this.entries = { filter: {} };
|
||||
this.expanded = false;
|
||||
if (params['l']) {
|
||||
this.entries.limit = +params['l'];
|
||||
if (this.entries.limit < 1) {
|
||||
this.entries.limit = 1;
|
||||
}
|
||||
}
|
||||
if (params['o']) {
|
||||
this.entries.offset = +params['o'];
|
||||
if (this.entries.offset < 0) {
|
||||
this.entries.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (params['s']) {
|
||||
this.sort = params['s'];
|
||||
}
|
||||
|
||||
if (params['a']) {
|
||||
this.descending = false;
|
||||
} else {
|
||||
this.descending = true;
|
||||
}
|
||||
|
||||
for (const param in params) {
|
||||
if (param != 'l' && param != 'o' && param != 's' && param != 'a') {
|
||||
this.entries.filter[param] = params[param];
|
||||
}
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
refresh() {
|
||||
const filter = JSON.parse(JSON.stringify(this.entries.filter || {}));
|
||||
this.turnoverManagementService.overview(this.entries.limit || 15, this.entries.offset || 0, this.sort, this.descending, filter).subscribe({
|
||||
next: (data: any) => {
|
||||
this.entries = data;
|
||||
this.entries.filter = filter;
|
||||
if (filter.username) {
|
||||
this.applyUser(filter.username);
|
||||
}
|
||||
}, error: (error) => {
|
||||
this.entries = { error: error };
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
update() {
|
||||
const filter = JSON.parse(JSON.stringify(this.entries.filter || {}));
|
||||
|
||||
const params: Params = { l: null, o: null, s: null, a: null };
|
||||
|
||||
if ((this.entries.limit || 15) != 15) {
|
||||
params['l'] = this.entries.limit;
|
||||
}
|
||||
|
||||
if ((this.entries.offset || 0) != 0) {
|
||||
params['o'] = this.entries.offset;
|
||||
}
|
||||
|
||||
if (this.sort != 'username') {
|
||||
params['s'] = this.sort;
|
||||
}
|
||||
|
||||
if (!this.descending) {
|
||||
params['a'] = true;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
for (const param in filter) {
|
||||
params[param] = filter[param] || null;
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.route,
|
||||
queryParams: params,
|
||||
queryParamsHandling: 'replace'
|
||||
});
|
||||
}
|
||||
|
||||
applyPage(event: PageEvent) {
|
||||
this.entries.limit = event.pageSize;
|
||||
this.entries.offset = event.pageSize * event.pageIndex;
|
||||
@@ -76,4 +158,36 @@ export class PageManagement implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
selectUser(username: string) {
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.route,
|
||||
queryParams: { username: this.entries.filter && this.entries.filter.username == username ? null : username },
|
||||
queryParamsHandling: 'merge'
|
||||
});
|
||||
}
|
||||
|
||||
applyUser(username: string) {
|
||||
this.usersFormControl.setValue(username);
|
||||
if (this.entries.total) {
|
||||
this.expanded = true;
|
||||
const filter = JSON.parse(JSON.stringify(this.entries.filter || {}));
|
||||
this.turnoverManagementService.fetch(this.turnovers.limit || 100, this.turnovers.offset || 0, 'created', true, filter).subscribe({
|
||||
next: (data: any) => {
|
||||
this.turnovers = data;
|
||||
this.turnovers.filter = filter;
|
||||
}, error: (error) => {
|
||||
this.turnovers = { error: error };
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
applyTurnoverPage(event: PageEvent) {
|
||||
this.turnovers.limit = event.pageSize;
|
||||
this.turnovers.offset = event.pageSize * event.pageIndex;
|
||||
this.applyUser(this.usersFormControl.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export class PageProfile implements OnInit, OnDestroy {
|
||||
if (result) {
|
||||
this.userManagementService.deleteUser(this.user.username).subscribe({
|
||||
next: () => {
|
||||
this.router.navigateByUrl('/');
|
||||
this.router.navigateByUrl('/u');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,12 +2,6 @@ mat-form-field {
|
||||
display: block;
|
||||
margin: 20px 0 !important;
|
||||
}
|
||||
|
||||
mat-chip mat-icon.mat-icon-inline {
|
||||
margin-top: -12px;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 5px;
|
||||
min-width: 390px;
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<mat-icon>filter_alt</mat-icon>
|
||||
</a>
|
||||
|
||||
<form class="flex wrap filter" *ngIf="filterOpen">
|
||||
@if(filterOpen) {
|
||||
<form class="flex wrap filter">
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.created' | i18n}}</mat-label>
|
||||
<mat-date-range-input [rangePicker]="picker">
|
||||
@@ -24,6 +25,7 @@
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.username' | i18n}}</mat-label>
|
||||
<input type="text" matInput [matAutocomplete]="auto" [formControl]="usersFormControl"
|
||||
[value]="turnovers && turnovers.filter && turnovers.filter.username || ''"
|
||||
(change)="setInputFilter('username', $event.target)">
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="setFilter('username', $event.option.value)">
|
||||
@for (user of users | async; track user.username) {
|
||||
@@ -34,14 +36,17 @@
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.customer' | i18n}}</mat-label>
|
||||
<input type="text" matInput (input)="setInputFilter('customer', $event.target)">
|
||||
<input type="text" matInput [value]="turnovers && turnovers.filter && turnovers.filter.customer || ''"
|
||||
(input)="setInputFilter('customer', $event.target)">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.motif' | i18n}}</mat-label>
|
||||
<input type="text" matInput (input)="setInputFilter('motif', $event.target)">
|
||||
<input type="text" matInput [value]="turnovers && turnovers.filter && turnovers.filter.motif || ''"
|
||||
(input)="setInputFilter('motif', $event.target)">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
<ui-turnovers class="flex column grow" [turnovers]="turnovers" (page)="applyPage($event)" (sort)="applySort($event)"
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
align-items: center;
|
||||
|
||||
.filter {
|
||||
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
max-height: 70px;
|
||||
|
||||
&>* {
|
||||
margin-top: 5px;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { debounceTime, Observable, switchMap } from 'rxjs';
|
||||
import { TurnoverManagementService } from 'src/app/services/turnover.management.service';
|
||||
import { UserManagementService } from 'src/app/services/user.management.service';
|
||||
@@ -23,24 +24,64 @@ export class PageTurnoversManage implements OnInit {
|
||||
|
||||
constructor(
|
||||
private turnoverManagementService: TurnoverManagementService,
|
||||
private userManagementService: UserManagementService
|
||||
private userManagementService: UserManagementService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.turnovers = {};
|
||||
this.update();
|
||||
this.users = this.usersFormControl
|
||||
.valueChanges
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
switchMap(value => this.userManagementService.pick(value))
|
||||
);
|
||||
this.route.queryParams.subscribe({
|
||||
next: (params) => {
|
||||
this.turnovers = { filter: {} };
|
||||
if (params['l']) {
|
||||
this.turnovers.limit = +params['l'];
|
||||
if (this.turnovers.limit < 1) {
|
||||
this.turnovers.limit = 1;
|
||||
}
|
||||
}
|
||||
if (params['o']) {
|
||||
this.turnovers.offset = +params['o'];
|
||||
if (this.turnovers.offset < 0) {
|
||||
this.turnovers.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (params['s']) {
|
||||
this.sort = params['s'];
|
||||
}
|
||||
|
||||
if (params['a']) {
|
||||
this.descending = false;
|
||||
} else {
|
||||
this.descending = true;
|
||||
}
|
||||
|
||||
for (const param in params) {
|
||||
if (param != 'l' && param != 'o' && param != 's' && param != 'a') {
|
||||
this.filterOpen = true;
|
||||
this.turnovers.filter[param] = params[param];
|
||||
if (param == 'username') {
|
||||
this.usersFormControl.setValue(params[param]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
refresh() {
|
||||
const filter = JSON.parse(JSON.stringify(this.turnovers.filter || {}));
|
||||
this.turnoverManagementService.fetch(this.turnovers.limit || 15, this.turnovers.offset || 0, this.sort, this.descending, this.turnovers.filter).subscribe({
|
||||
this.turnoverManagementService.fetch(this.turnovers.limit || 15, this.turnovers.offset || 0, this.sort, this.descending, filter).subscribe({
|
||||
next: (data: any) => {
|
||||
this.turnovers = data;
|
||||
this.turnovers.filter = filter;
|
||||
@@ -50,6 +91,42 @@ export class PageTurnoversManage implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
update() {
|
||||
const filter = JSON.parse(JSON.stringify(this.turnovers.filter || {}));
|
||||
|
||||
const params: Params = { l: null, o: null, s: null, a: null };
|
||||
|
||||
if ((this.turnovers.limit || 15) != 15) {
|
||||
params['l'] = this.turnovers.limit;
|
||||
}
|
||||
|
||||
if ((this.turnovers.offset || 0) != 0) {
|
||||
params['o'] = this.turnovers.offset;
|
||||
}
|
||||
|
||||
if (this.sort != 'created') {
|
||||
params['s'] = this.sort;
|
||||
}
|
||||
|
||||
if (!this.descending) {
|
||||
params['a'] = true;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
for (const param in filter) {
|
||||
params[param] = filter[param] || null;
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.route,
|
||||
queryParams: params,
|
||||
queryParamsHandling: 'replace'
|
||||
});
|
||||
}
|
||||
|
||||
applyPage(event: PageEvent) {
|
||||
this.turnovers.limit = event.pageSize;
|
||||
this.turnovers.offset = event.pageSize * event.pageIndex;
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<mat-icon>filter_alt</mat-icon>
|
||||
</a>
|
||||
|
||||
<form class="flex wrap filter" *ngIf="filterOpen">
|
||||
@if (filterOpen) {
|
||||
<form class="flex wrap filter">
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.created' | i18n}}</mat-label>
|
||||
<mat-date-range-input [rangePicker]="picker">
|
||||
@@ -22,14 +23,17 @@
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.customer' | i18n}}</mat-label>
|
||||
<input type="text" matInput (input)="setInputFilter('customer', $event.target)">
|
||||
<input type="text" matInput [value]="turnovers && turnovers.filter && turnovers.filter.customer || ''"
|
||||
(input)="setInputFilter('customer', $event.target)">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'turnovers.filter.motif' | i18n}}</mat-label>
|
||||
<input type="text" matInput (input)="setInputFilter('motif', $event.target)">
|
||||
<input type="text" matInput [value]="turnovers && turnovers.filter && turnovers.filter.motif || ''"
|
||||
(input)="setInputFilter('motif', $event.target)">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
<ui-turnovers class="flex column grow" [turnovers]="turnovers" [overview]="overview" (page)="applyPage($event)"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
max-height: 70px;
|
||||
|
||||
&>* {
|
||||
margin-top: 5px;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { debounceTime, Observable, switchMap } from 'rxjs';
|
||||
import { TurnoverService } from 'src/app/services/turnover.service';
|
||||
import { UserManagementService } from 'src/app/services/user.management.service';
|
||||
@@ -18,20 +19,54 @@ export class PageTurnovers implements OnInit {
|
||||
sort: string = "created";
|
||||
descending: boolean = true;
|
||||
filterOpen: boolean = false;
|
||||
|
||||
users: Observable<any>;
|
||||
usersFormControl = new FormControl();
|
||||
init: boolean = true;
|
||||
|
||||
constructor(
|
||||
private turnoverService: TurnoverService
|
||||
private turnoverService: TurnoverService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.turnovers = {};
|
||||
this.update();
|
||||
this.route.queryParams.subscribe({
|
||||
next: (params) => {
|
||||
this.turnovers = { filter: {} };
|
||||
if (params['l']) {
|
||||
this.turnovers.limit = +params['l'];
|
||||
if (this.turnovers.limit < 1) {
|
||||
this.turnovers.limit = 1;
|
||||
}
|
||||
}
|
||||
if (params['o']) {
|
||||
this.turnovers.offset = +params['o'];
|
||||
if (this.turnovers.offset < 0) {
|
||||
this.turnovers.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (params['s']) {
|
||||
this.sort = params['s'];
|
||||
}
|
||||
|
||||
if (params['a']) {
|
||||
this.descending = false;
|
||||
} else {
|
||||
this.descending = true;
|
||||
}
|
||||
|
||||
for (const param in params) {
|
||||
if (param != 'l' && param != 'o' && param != 's' && param != 'a') {
|
||||
this.filterOpen = true;
|
||||
this.turnovers.filter[param] = params[param];
|
||||
}
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
refresh() {
|
||||
const filter = JSON.parse(JSON.stringify(this.turnovers.filter || {}));
|
||||
this.turnoverService.fetch(this.turnovers.limit || 15, this.turnovers.offset || 0, this.sort, this.descending, this.turnovers.filter).subscribe({
|
||||
next: (data: any) => {
|
||||
@@ -54,6 +89,42 @@ export class PageTurnovers implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
update() {
|
||||
const filter = JSON.parse(JSON.stringify(this.turnovers.filter || {}));
|
||||
|
||||
const params: Params = { l: null, o: null, s: null, a: null };
|
||||
|
||||
if ((this.turnovers.limit || 15) != 15) {
|
||||
params['l'] = this.turnovers.limit;
|
||||
}
|
||||
|
||||
if ((this.turnovers.offset || 0) != 0) {
|
||||
params['o'] = this.turnovers.offset;
|
||||
}
|
||||
|
||||
if (this.sort != 'created') {
|
||||
params['s'] = this.sort;
|
||||
}
|
||||
|
||||
if (!this.descending) {
|
||||
params['a'] = true;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
for (const param in filter) {
|
||||
params[param] = filter[param] || null;
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(
|
||||
[],
|
||||
{
|
||||
relativeTo: this.route,
|
||||
queryParams: params,
|
||||
queryParamsHandling: 'replace'
|
||||
});
|
||||
}
|
||||
|
||||
applyPage(event: PageEvent) {
|
||||
this.turnovers.limit = event.pageSize;
|
||||
this.turnovers.offset = event.pageSize * event.pageIndex;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<div class="flex column fill middle">
|
||||
<form [formGroup]="form" (ngSubmit)="createUser()" #formDirective="ngForm">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.username' | i18n}}</mat-label>
|
||||
<input matInput formControlName="username" type="text" [required]="true">
|
||||
<mat-error *ngFor="let error of form.get('username').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.name' | i18n}}</mat-label>
|
||||
<input matInput formControlName="name" type="text">
|
||||
<mat-error *ngFor="let error of form.get('name').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.email' | i18n}}</mat-label>
|
||||
<input matInput formControlName="email" type="email">
|
||||
<mat-error *ngFor="let error of form.get('email').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'user.password' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
<mat-error *ngFor="let error of form.get('password').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle class="margin" (change)="isAdmin=$event.checked">
|
||||
{{'user.admin' | i18n}}
|
||||
</mat-slide-toggle>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
@if (!working) {
|
||||
<button type="submit" mat-raised-button color="primary" [disabled]="form.invalid">{{'user.create' |
|
||||
i18n}}<mat-icon style="font-size: 1em;">person_add</mat-icon></button>
|
||||
}
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
mat-form-field {
|
||||
display: block;
|
||||
margin: 20px 0 !important;
|
||||
}
|
||||
form {
|
||||
margin: 5px;
|
||||
min-width: 390px;
|
||||
|
||||
@media screen and (min-width: 576px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: 80%;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { UserManagementService } from 'src/app/services/user.management.service';
|
||||
|
||||
@Component({
|
||||
selector: 'page-user-create',
|
||||
templateUrl: './users.create.page.html',
|
||||
styleUrls: ['./users.create.page.scss']
|
||||
})
|
||||
export class PageUserCreate implements OnInit {
|
||||
|
||||
form: FormGroup;
|
||||
isAdmin: boolean = false;
|
||||
working: boolean = false;
|
||||
|
||||
constructor(
|
||||
private userManagementService: UserManagementService,
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = this.formBuilder.group({
|
||||
username: ['', Validators.required],
|
||||
name: ['', Validators.nullValidator],
|
||||
email: ['', Validators.nullValidator],
|
||||
password: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
createUser() {
|
||||
this.working = true;
|
||||
const user = {
|
||||
username: this.form.get("username").value,
|
||||
name: this.form.get("name").value,
|
||||
email: this.form.get("email").value,
|
||||
roles: this.isAdmin ? ['ROLE_ADMIN'] : []
|
||||
}
|
||||
|
||||
const request = this.form.get("username").disabled ? this.userManagementService.update(user) : this.userManagementService.create(user);
|
||||
|
||||
request.subscribe({
|
||||
next: (result: any) => {
|
||||
this.userManagementService.setPassword(result.username, this.form.get("password").value).subscribe({
|
||||
next: (result) => {
|
||||
this.working = false;
|
||||
this.router.navigateByUrl('/u');
|
||||
},
|
||||
error: (error) => {
|
||||
this.form.get("username").disable();
|
||||
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,38 @@
|
||||
@if (users && users.error) {
|
||||
<div class="flex column fill">
|
||||
<mat-card class="accent box">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ 'users.error.' + users.error.status | i18n}}</mat-card-title>
|
||||
<mat-card-subtitle>{{'users.error' | i18n}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>
|
||||
{{ 'users.error.' + users.error.status + '.text' | i18n}}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
@if (users && users.error) {
|
||||
<div class="flex column fill">
|
||||
<mat-card class="accent box">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ 'users.error.' + users.error.status | i18n}}</mat-card-title>
|
||||
<mat-card-subtitle>{{'users.error' | i18n}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>
|
||||
{{ 'users.error.' + users.error.status + '.text' | i18n}}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (users) {
|
||||
<div class="flex column fill">
|
||||
<div class="flex wrap filter-container">
|
||||
<a mat-icon-button (click)="filterOpen=!filterOpen" title="{{'users.filter' | i18n}}"
|
||||
[color]="filterOpen ? 'primary': 'accent'">
|
||||
<mat-icon>filter_alt</mat-icon>
|
||||
</a>
|
||||
|
||||
@if (filterOpen) {
|
||||
<form class="flex wrap filter">
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'users.filter.search' | i18n}}</mat-label>
|
||||
<input type="text" matInput [value]="users && users.filter && users.filter || ''"
|
||||
(input)="setFilter( $event.target)">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (users) {
|
||||
<div class="scroll-container">
|
||||
<table class="default-table" mat-table [dataSource]="users.results || []" matSort
|
||||
(matSortChange)="applySort($event)" [matSortDisableClear]="true">
|
||||
@@ -51,7 +68,8 @@
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
|
||||
<tr class="user" mat-row *matRowDef="let user; columns: columns;" [routerLink]="'/u/' + user.username"></tr>
|
||||
<tr class="user" mat-row *matRowDef="let user; columns: columns;" [routerLink]="'/u/' + user.username">
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@if (users.total == 0) {
|
||||
@@ -64,47 +82,17 @@
|
||||
|
||||
<span class="spacer"></span>
|
||||
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="createUser()" #formDirective="ngForm">
|
||||
<div class="flex wrap middle">
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.username' | i18n}}</mat-label>
|
||||
<input matInput formControlName="username" type="text" [required]="true">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.name' | i18n}}</mat-label>
|
||||
<input matInput formControlName="name" type="text">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'profile.email' | i18n}}</mat-label>
|
||||
<input matInput formControlName="email" type="email">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="margin">
|
||||
<mat-label>{{'user.password' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle class="margin" (change)="isAdmin=$event.checked">
|
||||
{{'user.admin' | i18n}}
|
||||
</mat-slide-toggle>
|
||||
|
||||
<button type="submit" mat-raised-button color="primary" [disabled]="form.invalid">{{'user.create' |
|
||||
i18n}}<mat-icon style="font-size: 1em;">person_add</mat-icon></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mat-mdc-paginator flex">
|
||||
<div class="mat-mdc-paginator flex middle">
|
||||
<a class="margin" routerLink="/user" mat-raised-button color="primary">{{'user.create' | i18n}}<mat-icon
|
||||
style="font-size: 1em;">person_add</mat-icon></a>
|
||||
<span class="spacer"></span>
|
||||
<mat-paginator [pageSizeOptions]="pageSizeOptions" [pageIndex]="users.offset / users.limit"
|
||||
[length]="users.total" [pageSize]="users.limit" (page)="applyPage($event)" showFirstLastButtons>
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (!users || !users.results && !users.error) {
|
||||
<mat-progress-bar *ngIf="" mode="indeterminate"></mat-progress-bar>
|
||||
}
|
||||
@if (!users || !users.results && !users.error) {
|
||||
<mat-progress-bar *ngIf="" mode="indeterminate"></mat-progress-bar>
|
||||
}
|
||||
</div>
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Component, HostListener, Input, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { UserManagementService } from 'src/app/services/user.management.service';
|
||||
import { ConfirmDialog } from 'src/app/ui/confirm/confirm.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-users',
|
||||
@@ -18,12 +16,10 @@ export class PageUsers implements OnInit {
|
||||
pageSizeOptions: number[] = [1, 2, 3, 4, 5, 10, 15, 30, 50, 100];
|
||||
sort: string = "username";
|
||||
descending: boolean = false;
|
||||
filterOpen: boolean = true;
|
||||
|
||||
columns: string[] = [];
|
||||
|
||||
form: FormGroup;
|
||||
isAdmin: boolean = false;
|
||||
|
||||
username: string = "";
|
||||
|
||||
constructor(
|
||||
@@ -35,12 +31,6 @@ export class PageUsers implements OnInit {
|
||||
this.users = {};
|
||||
this.update();
|
||||
this.applyResize(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth);
|
||||
this.form = this.formBuilder.group({
|
||||
username: ['', Validators.required],
|
||||
name: ['', Validators.nullValidator],
|
||||
email: ['', Validators.nullValidator],
|
||||
password: ['', Validators.required]
|
||||
});
|
||||
|
||||
this.authService.auth.subscribe({
|
||||
next: (auth) => {
|
||||
@@ -66,9 +56,11 @@ export class PageUsers implements OnInit {
|
||||
}
|
||||
|
||||
update() {
|
||||
this.userManagementService.fetch(this.users.limit || 15, this.users.offset || 0, this.sort, this.descending).subscribe({
|
||||
const filter = this.users.filter || "";
|
||||
this.userManagementService.fetch(this.users.limit || 15, this.users.offset || 0, this.sort, this.descending, filter).subscribe({
|
||||
next: (data: any) => {
|
||||
this.users = data;
|
||||
this.users.filter = filter;
|
||||
}, error: (error) => {
|
||||
this.users = { error: error };
|
||||
}
|
||||
@@ -87,48 +79,13 @@ export class PageUsers implements OnInit {
|
||||
this.update();
|
||||
}
|
||||
|
||||
createUser() {
|
||||
const user = {
|
||||
username: this.form.get("username").value,
|
||||
name: this.form.get("name").value,
|
||||
email: this.form.get("email").value,
|
||||
roles: this.isAdmin ? ['ROLE_ADMIN'] : []
|
||||
setFilter(target: EventTarget) {
|
||||
const value = (target as HTMLInputElement).value;
|
||||
if (value != this.users.filter) {
|
||||
this.users.filter = value;
|
||||
this.users.offset = 0;
|
||||
this.update();
|
||||
}
|
||||
this.userManagementService.create(user).subscribe({
|
||||
next: (result: any) => {
|
||||
this.userManagementService.setPassword(result.username, this.form.get("password").value).subscribe({
|
||||
next: (result) => {
|
||||
this.update();
|
||||
},
|
||||
error: (error) => {
|
||||
if (error.status == 422) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
if (error.status == 422) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export class UserManagementService {
|
||||
constructor(private http: HttpClient, private abstractService: AbstractService) {
|
||||
}
|
||||
|
||||
fetch(limit: number, offset: number, sort: string, descending: boolean, filter: string = "") {
|
||||
return this.abstractService.fetch("/users/manage", limit, offset, sort, descending, filter);
|
||||
fetch(limit: number, offset: number, sort: string, descending: boolean, search: string = "") {
|
||||
return this.abstractService.fetch("/users/manage", limit, offset, sort, descending, { filter: search });
|
||||
}
|
||||
|
||||
pick(search: string) {
|
||||
|
||||
@@ -239,7 +239,6 @@ export class UiMain {
|
||||
}
|
||||
|
||||
random() {
|
||||
console.log("Random?");
|
||||
this.debugService.random().subscribe({
|
||||
next: () => {
|
||||
window.location.reload();
|
||||
|
||||
@@ -15,10 +15,19 @@
|
||||
}
|
||||
|
||||
@if (turnovers) {
|
||||
@if(turnovers.results) {
|
||||
|
||||
@if (turnovers.total == 0) {
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<p>{{'paginator.empty' | i18n}}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
}
|
||||
|
||||
@if(turnovers.total) {
|
||||
<div class="scroll-container">
|
||||
<table class="default-table" mat-table [dataSource]="turnovers.results" matSort (matSortChange)="sort.emit($event)"
|
||||
[matSortDisableClear]="true">
|
||||
<table class="default-table" mat-table [dataSource]="turnovers.results" matSort [matSortDisabled]="!enableSort"
|
||||
(matSortChange)="sort.emit($event)" [matSortDisableClear]="true">
|
||||
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{'turnover.username' | i18n}}</th>
|
||||
@@ -114,13 +123,7 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (turnovers.total == 0) {
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<p>{{'paginator.empty' | i18n}}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
}
|
||||
|
||||
|
||||
<span class="spacer"></span>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export class UiTurnovers implements OnInit {
|
||||
@Input() showFilter: boolean = true;
|
||||
@Input() linkTurnover: boolean = true;
|
||||
@Input() username: boolean = false;
|
||||
@Input() enableSort: boolean = true;
|
||||
@Output() page: EventEmitter<PageEvent> = new EventEmitter<PageEvent>();
|
||||
@Output() sort: EventEmitter<Sort> = new EventEmitter<Sort>();
|
||||
|
||||
@@ -35,12 +36,14 @@ export class UiTurnovers implements OnInit {
|
||||
|
||||
applyResize(width: number) {
|
||||
if (width < 992) {
|
||||
this.columns = ['customer', 'motif'];
|
||||
this.columns = ['customer', 'price'];
|
||||
} else {
|
||||
this.columns = ['customer', 'motif', 'price', 'timeInvestment', 'remark', 'materialConsumption', 'updated'];
|
||||
}
|
||||
if (this.username) {
|
||||
this.columns.unshift('username');
|
||||
} else {
|
||||
this.columns.push('motif');
|
||||
}
|
||||
this.columns.unshift('created');
|
||||
}
|
||||
|
||||
@@ -152,11 +152,18 @@
|
||||
"confirmDelete": "Möchtest du den User '{0}' wirklich löschen? ALLE Buchungen dieses Users werden ebenfalls gelöscht!",
|
||||
"create": "Neuen User erstellen",
|
||||
"delete": "User löschen",
|
||||
"error": {
|
||||
"ALREADY_EXISTS": "User existiert bereits"
|
||||
},
|
||||
"manage": "Verwalten",
|
||||
"password": "Passwort",
|
||||
"username": "User"
|
||||
},
|
||||
"users": {
|
||||
".": "User verwalten"
|
||||
".": "User verwalten",
|
||||
"filter": {
|
||||
".": "Filter",
|
||||
"search": "Nach Username filtern"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user