rename folders

This commit is contained in:
2024-10-06 18:27:40 +02:00
parent 59fcb493b8
commit 8482770998
146 changed files with 0 additions and 3092 deletions
@@ -0,0 +1,158 @@
<div class="flex column fill">
@if (entries && entries.error) {
<div class="flex column fill">
<mat-card class="accent box">
<mat-card-header>
<mat-card-title>{{ 'management.error.' + entries.error.status | i18n}}</mat-card-title>
<mat-card-subtitle>{{'management.error' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{ 'management.error.' + entries.error.status + '.text' | i18n}}
</p>
</mat-card-content>
</mat-card>
</div>
}
<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.dueDate' | i18n}}</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input matStartDate placeholder="{{'turnovers.filter.dueDate.from' | i18n}}"
[value]="entries && entries.filter && entries.filter.from"
(dateChange)="setFilter('from', $event.value && $event.value.toISOString() || undefined)">
<input matEndDate placeholder="{{'turnovers.filter.dueDate.to' | i18n}}"
[value]="entries && entries.filter && entries.filter.to"
(dateChange)="setFilter('to', $event.value && $event.value.endOf('day').toISOString() || undefined)">
</mat-date-range-input>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field class="margin">
<mat-label>{{'management.filter.username' | i18n}}</mat-label>
<input type="text" matInput [matAutocomplete]="auto" [formControl]="usersFormControl"
(change)="setInputFilter('username', $event.target)">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="setFilter('username', $event.option.value)">
@for (user of users | async; track user.username) {
<mat-option [value]="user.username">{{user.username}}</mat-option>
}
</mat-autocomplete>
</mat-form-field>
</form>
}
<span class="spacer"></span>
<a class="margin" mat-icon-button (click)="export()" title="{{'turnovers.export' | i18n}}" color="primary" [disabled]="!entries.total">
<mat-icon>file_download</mat-icon>
</a>
</div>
@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="scroll-container">
<table class="default-table" 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>
<td mat-cell *matCellDef="let entry">
<div class="flex middle">
<a class="select-user"
[ngClass]="{'selected': entries.filter && entries.filter.username == entry[0]}"
(click)="selectUser(entry[0])">{{entry[0]}}</a>
</div>
</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<span class="spacer"></span>
<span>{{'turnover.price' | i18n}}</span>
</th>
<td mat-cell *matCellDef="let entry">
<div class="flex">
<span class="spacer"></span>
<span>{{entry[1] | number: '1.2-2'}}</span>
<span>&nbsp;{{'turnover.price.suffix' | i18n}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="timeInvestment">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<span class="spacer"></span>
<span>{{'turnover.timeInvestment' | i18n}}</span>
</th>
<td mat-cell *matCellDef="let entry">
<div class="flex">
<span class="spacer"></span>
<span>{{entry[2] | number: '1.1-1'}}</span>
<span> &nbsp;{{'turnover.timeInvestment.suffix' | i18n}}</span>
</div>
</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 #uiTurnovers 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 ? ['expanded'] : []"></tr>
</table>
</div>
@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"
[length]="entries.total" [pageSize]="entries.limit" (page)="applyPage($event)" showFirstLastButtons>
</mat-paginator>
</div>
}
}
@if (!entries || !entries.results && !entries.error) {
<mat-progress-bar *ngIf="" mode="indeterminate"></mat-progress-bar>
}
</div>
@@ -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;
}
}
@@ -0,0 +1,226 @@
import { Component, Input, OnInit, ViewChild } 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 { I18nService } from 'src/app/services/i18n.service';
import { TurnoverManagementService } from 'src/app/services/turnover.management.service';
import { UserManagementService } from 'src/app/services/user.management.service';
import { UiTurnovers } from 'src/app/ui/turnovers/turnovers.ui';
@Component({
selector: 'ui-management',
templateUrl: './management.page.html',
styleUrls: ['./management.page.scss']
})
export class PageManagement implements OnInit {
@Input() entries: any;
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', 'menu'];
expanded: boolean = false;
users: Observable<any>;
usersFormControl = new FormControl();
@ViewChild('uiTurnovers') uiTurnovers: UiTurnovers;
turnovers: any;
constructor(
private turnoverManagementService: TurnoverManagementService,
private userManagementService: UserManagementService,
private i18n: I18nService,
private router: Router,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.entries = {};
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();
}
});
}
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;
this.update();
}
applySort(event: Sort) {
this.sort = event.direction ? event.active : 'username';
this.descending = event.direction !== 'asc';
this.update();
}
setInputFilter(key: string, target: EventTarget) {
this.setFilter(key, (target as HTMLInputElement).value);
}
setFilter(key: string, value) {
if (value != this.entries.filter[key]) {
this.entries.filter[key] = value;
this.entries.offset = 0;
this.update();
}
}
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, 'dueDate', 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);
}
export() {
if (this.entries.total) {
let rows = [[this.i18n.get('user.username'), this.i18n.get('turnover.price'), this.i18n.get('turnover.price.suffix')]];
this.entries.results.forEach(result => {
rows[rows.length] = [result[0], result[1], [result[2]]]
});
if (this.uiTurnovers) {
rows.push(...this.uiTurnovers.getCsvRows());
}
if (rows.length) {
let csvContent = "data:text/csv;charset=utf-8,"
+ rows.map(e => e.join(";")).join("\n");
var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "export.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
}