update borrow + fix invite

This commit is contained in:
_Bastler
2021-10-27 17:07:44 +02:00
parent 59ac345933
commit 925a03fd46
45 changed files with 2792 additions and 398 deletions
+14
View File
@@ -27,6 +27,11 @@ import { InvitesComponent } from './pages/invites/invites.component';
import { UrlShortenerComponent, UrlShortenerPasswordComponent } from './pages/urlshortener/urlshortener.component';
import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component';
import { DividertestComponent } from './pages/dividertest/dividertest.component'
import { BorrowProvingComponent } from './pages/borrow/proving/proving.component';
import { BorrowItemEditComponent, BorrowItemsComponent } from './pages/borrow/items/items.component';
import { BorrowRequestsComponent } from './pages/borrow/requests/requests.component';
import { BorrowComponent } from './pages/borrow/borrow.component';
import { InviteCodeComponent } from './pages/invites/code/code.component';
const routes: Routes = [
{ path: 'profile/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] },
@@ -52,6 +57,14 @@ const routes: Routes = [
{ path: 'domains', component: DomainsComponent, canActivate: [ AuthenticatedGuard ] }
]
},
{
path: 'borrow', component: BorrowComponent, canActivate: [ AuthenticatedGuard ], children: [
{ path: '', redirectTo: "/borrow/items", pathMatch: 'full' },
{ path: 'items', component: BorrowItemsComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'requests', component: BorrowRequestsComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'proving', component: BorrowProvingComponent, canActivate: [ AuthenticatedGuard ] }
]
},
{ path: 'register', component: RegisterComponent, canActivate: [ AnonymousGuard ] },
{ path: 'tokens', component: TokensComponent, canActivate: [ AuthGuard ] },
{ path: 'jitsi', component: JitsiComponent, canActivate: [ AuthenticatedGuard ] },
@@ -62,6 +75,7 @@ const routes: Routes = [
{ path: 'urlshortener', component: UrlShortenerComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'urlshortener/:code', component: UrlShortenerPasswordComponent, canActivate: [ AuthUpdateGuard ] },
{ path: 'invites/:quota', component: InvitesComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'invite/:code', component: InviteCodeComponent, canActivate: [ AuthGuard ] },
{ path: 'unavailable', component: UnavailableComponent },
{ path: 'p/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] },
{ path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [ AuthUpdateGuard ] }, ]
+21 -10
View File
@@ -13,6 +13,7 @@ import { DatePipe } from '@angular/common';
import { AutofocusDirective } from './material/autofocus';
import { I18nPipe } from './utils/i18n.pipe';
import { MomentPipe } from './utils/moment.pipe';
import { MainComponent } from './ui/main/main.component';
import { AccountComponent } from './pages/account/account.component';
import { ServicesComponent } from './pages/services/services.component';
@@ -42,18 +43,25 @@ import { UsernameDialog } from './pages/register/username-dialog/username.dialog
import { UnavailableComponent } from './pages/unavailable/unavailable.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
import { HtmlComponent } from './utils/html/html.component';
import { ConfirmDialog } from './ui/confirm/confirm.component'
import { UserComponent } from './pages/user/user.component'
import { JitsiComponent, JitsiEditDialog, JitsiShareDialog } from './pages/jitsi/jitsi.component'
import { ParteyComponent } from './pages/partey/partey.component'
import { ParteyTimeslotsComponent, ParteyTimeslotDialog } from './pages/partey/timeslots/timeslots.compontent'
import { UrlShortenerComponent, UrlShortenerPasswordComponent, UrlShortenerShareDialog, UrlShortenerEditDialog } from './pages/urlshortener/urlshortener.component'
import { DividerComponent } from './ui/divider/divider.component'
import { DividertestComponent } from './pages/dividertest/dividertest.component'
import { ConfirmDialog } from './ui/confirm/confirm.component';
import { UserComponent } from './pages/user/user.component';
import { JitsiComponent, JitsiEditDialog, JitsiShareDialog } from './pages/jitsi/jitsi.component';
import { ParteyComponent } from './pages/partey/partey.component';
import { ParteyTimeslotsComponent, ParteyTimeslotDialog } from './pages/partey/timeslots/timeslots.compontent';
import { UrlShortenerComponent, UrlShortenerPasswordComponent, UrlShortenerShareDialog, UrlShortenerEditDialog } from './pages/urlshortener/urlshortener.component';
import { BorrowItemEditComponent, BorrowItemsComponent } from './pages/borrow/items/items.component';
import { BorrowRequestEditComponent, BorrowRequestsComponent } from './pages/borrow/requests/requests.component';
import { BorrowProvingComponent, BorrowProvingResultDialog } from './pages/borrow/proving/proving.component';
import { DividerComponent } from './ui/divider/divider.component';
import { DividertestComponent } from './pages/dividertest/dividertest.component';
import { I18nService } from './services/i18n.service';
import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component';
import { BorrowComponent } from './pages/borrow/borrow.component';
import { DurationpickerComponent } from './ui/durationpicker/durationpicker.component';
import { InviteCodeComponent } from './pages/invites/code/code.component';
import { InviteEditComponent } from './pages/invites/edit/invite.edit';
export function init_app(i18n: I18nService) {
@@ -75,6 +83,7 @@ export class XhrInterceptor implements HttpInterceptor {
declarations: [
AutofocusDirective,
I18nPipe,
MomentPipe,
MainComponent,
AppComponent,
AccountComponent,
@@ -82,7 +91,7 @@ export class XhrInterceptor implements HttpInterceptor {
FormLoginComponent,
FormLogin2FAComponent,
TokensComponent,
InvitesComponent,
InvitesComponent, InviteCodeComponent, InviteEditComponent,
ServicesComponent,
PermissionsComponent,
ProfileFieldsComponent, ProfileFieldDialog, ProfileFieldBlob, ProfileFieldPgpBlob,
@@ -110,7 +119,9 @@ export class XhrInterceptor implements HttpInterceptor {
ParteyComponent, ParteyTimeslotsComponent, ParteyTimeslotDialog,
MinetestAccountsComponent,
UrlShortenerComponent, UrlShortenerShareDialog, UrlShortenerEditDialog, UrlShortenerPasswordComponent,
DividerComponent, DividertestComponent
BorrowComponent, BorrowItemsComponent, BorrowItemEditComponent, BorrowRequestsComponent, BorrowRequestEditComponent, BorrowProvingComponent, BorrowProvingResultDialog,
DividerComponent, DividertestComponent,
DurationpickerComponent
],
imports: [
BrowserModule,
+3 -2
View File
@@ -47,10 +47,11 @@ import {FlexLayoutModule} from '@angular/flex-layout';
import {
NgxMatDatetimePickerModule,
NgxMatNativeDateModule,
NgxMatTimepickerModule
NgxMatNativeDateModule
} from '@angular-material-components/datetime-picker';
import { NgxMatTimepickerModule } from 'ngx-mat-timepicker';
@NgModule({
declarations: [],
imports: [
@@ -0,0 +1,15 @@
<h2>{{'borrow' | i18n}}</h2>
<nav mat-tab-nav-bar>
<a mat-tab-link routerLink="items" routerLinkActive="active-tab" #rlaitems="routerLinkActive"
[active]="rlaitems.isActive">{{'borrow.items'
| i18n}}</a>
<a mat-tab-link routerLink="requests" routerLinkActive="active-tab" #rlarequests="routerLinkActive"
[active]="rlarequests.isActive">{{'borrow.requests'
| i18n}}</a>
<a mat-tab-link routerLink="proving" routerLinkActive="active-tab" #rlaproving="routerLinkActive"
[active]="rlaproving.isActive">{{'borrow.proving'
| i18n}}</a>
</nav>
<router-outlet></router-outlet>
+10
View File
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-borrow',
templateUrl: './borrow.component.html',
styleUrls: [ './borrow.component.scss' ]
})
export class BorrowComponent {
}
+204
View File
@@ -0,0 +1,204 @@
<h2 mat-dialog-title>{{(create ? 'borrow.items.create' : 'borrow.items.edit') | i18n}}</h2>
<mat-dialog-content>
<form [formGroup]="form">
<mat-form-field>
<input matInput placeholder="{{'borrow.items.name' | i18n}}" formControlName="name">
<mat-error>
{{'borrow.items.error.name' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'borrow.items.description' | i18n}}" formControlName="description"></textarea>
<mat-error>
{{'borrow.items.error.description' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="{{'borrow.items.url' | i18n}}" formControlName="url">
<mat-error>
{{'borrow.items.error.url' | i18n}}
</mat-error>
</mat-form-field>
<div fxLayout="row wrap" fxLayoutGap="24px grid">
<mat-form-field floatLabel="always" appearance="none" fxFlex="50%" fxFlex.sm="100%" fxFlex.xs="100%">
<input matInput hidden formControlName="minDuration" />
<label>{{'borrow.items.minDuration' | i18n}}</label>
<app-durationpicker formControlName="minDuration"></app-durationpicker>
<mat-error>
{{'borrow.items.error.minDuration' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none" fxFlex="50%" fxFlex.sm="100%" fxFlex.xs="100%">
<input matInput hidden formControlName="maxDuration" />
<label>{{'borrow.items.maxDuration' | i18n}}</label>
<app-durationpicker formControlName="maxDuration"></app-durationpicker>
<mat-error>
{{'borrow.items.error.maxDuration' | i18n}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="24px grid">
<mat-form-field>
<mat-select formControlName="availability" placeholder="{{'borrow.items.availability' | i18n}}">
<mat-option value="ALWAYS">
{{'borrow.items.availability.ALWAYS' | i18n}}
</mat-option>
<mat-option value="PERIOD">
{{'borrow.items.availability.PERIOD' | i18n}}
</mat-option>
<mat-option value="MANUAL">
{{'borrow.items.availability.MANUAL' | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.availability' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle formControlName="autoAccept">{{'borrow.items.autoAccept' | i18n}}
</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle formControlName="emailNotification">
{{'borrow.items.emailNotification' | i18n}}
</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
<mat-form-field *ngIf="form.get('emailNotification').value" fxFlex="1 1 0%">
<input matInput type="email" placeholder="{{'borrow.items.email' | i18n}}" formControlName="email">
<mat-error>
{{'borrow.items.error.email' | i18n}}
</mat-error>
</mat-form-field>
</div>
<ng-container *ngIf="form.get('availability').value == 'MANUAL'">
<mat-divider></mat-divider><br>
<label>{{'borrow.items.slot.MANUAL' | i18n}}</label>
<button mat-icon-button (click)="addManualSlot('','','')" title="{{'borrow.items.slot.add' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<ng-container *ngFor="let slotForm of slots.controls; let i = index">
<div [formGroup]="slotForm" fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="24px grid">
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="slotStartPicker" formControlName="start"
placeholder="{{'borrow.items.slot.start' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="slotStartPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #slotStartPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.items.error.slot.start' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="slotEndPicker" formControlName="end"
placeholder="{{'borrow.items.slot.end' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="slotEndPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #slotEndPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.items.error.slot.end' | i18n}}
</mat-error>
</mat-form-field>
<div>
<button mat-icon-button (click)="dublicateSlot(i)" title="{{'borrow.items.slot.dublicate' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<button mat-icon-button (click)="deleteSlot(i)" title="{{'borrow.items.slot.delete' | i18n}}">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="form.get('availability').value == 'PERIOD'">
<mat-divider></mat-divider><br>
<label>{{'borrow.items.slot.PERIOD' | i18n}}</label>
<button mat-icon-button (click)="addPeriodSlot('','','','','')" title="{{'borrow.items.slot.add' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<ng-container *ngFor="let slotForm of slots.controls; let i = index">
<div [formGroup]="slotForm" fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="12px grid">
<mat-form-field>
<mat-select formControlName="startDay" placeholder="{{'borrow.items.slot.startDay' | i18n}}">
<mat-option *ngFor="let day of weekdays" [value]="day">
{{'borrow.items.slot.day.' + day | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.slot.startDay' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput format="24" [ngxMatTimepicker]="startTimePicker" formControlName="startTime"
placeholder="{{'borrow.items.slot.startTime' | i18n}}">
<mat-icon matSuffix (click)="startTimePicker.open()">
watch_later
</mat-icon>
<mat-error>
{{'borrow.items.error.slot.startTime' | i18n}}
</mat-error>
</mat-form-field>
<ngx-mat-timepicker #startTimePicker></ngx-mat-timepicker>
<mat-form-field>
<mat-select formControlName="endDay" placeholder="{{'borrow.items.slot.endDay' | i18n}}">
<mat-option *ngFor="let day of weekdays" [value]="day">
{{'borrow.items.slot.day.' + day | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.slot.endDay' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput format="24" [ngxMatTimepicker]="endTimePicker" formControlName="endTime"
placeholder="{{'borrow.items.slot.endTime' | i18n}}">
<mat-icon matSuffix (click)="endTimePicker.open()">
watch_later
</mat-icon>
<mat-error>
{{'borrow.items.error.slot.endTime' | i18n}}
</mat-error>
</mat-form-field>
<ngx-mat-timepicker #endTimePicker></ngx-mat-timepicker>
<div>
<button mat-icon-button (click)="dublicateSlot(i)" title="{{'borrow.items.slot.dublicate' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<button mat-icon-button (click)="deleteSlot(i)" title="{{'borrow.items.slot.delete' | i18n}}">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-container>
</ng-container>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<div fxLayout="row" fxLayoutAlign="space-between start">
<div>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="form.invalid" mat-raised-button (click)="save()" color="accent">
<mat-icon>save</mat-icon>{{(create ? 'borrow.items.create' : 'borrow.items.save') | i18n}}
</button>
</div>
<button *ngIf="borrowItemId" mat-button color="warn" (click)="confirmDelete()">
<mat-icon>delete</mat-icon> {{ 'borrow.items.delete' |
i18n}}
</button>
</div>
</mat-dialog-actions>
@@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-dialog-actions>div {
width: 100%;
}
@@ -0,0 +1,65 @@
<h3>{{'borrow.items' | i18n}}</h3>
<div *ngIf="borrowItems">
<div fxLayout="row" fxLayoutAlign="space-between start">
<form>
<mat-form-field>
<input matInput [formControl]="searchFormControl" placeholder="{{'borrow.items.search' | i18n}}">
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="ownerFormControl">{{'borrow.items.mine' | i18n}}</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
</form>
<button mat-raised-button (click)="create()" color="accent">{{'borrow.items.create' | i18n}}</button>
</div>
<table mat-table matSort [dataSource]="borrowItems.content" (matSortChange)="updateSort($event)">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'borrow.items.name' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ borrowItem.name}} </td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.description' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ borrowItem.description}} </td>
</ng-container>
<ng-container matColumnDef="availability">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.availability' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ 'borrow.items.availability.' + borrowItem.availability | i18n}}
</td>
</ng-container>
<ng-container matColumnDef="url">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.url' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem">
<a *ngIf="borrowItem.url" mat-button color="accent" href="{{ borrowItem.url }}" target="_blank">
{{ borrowItem.url }}
<mat-icon style="font-size: 1em;">open_in_new</mat-icon>
</a>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'borrow.items.actions' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem" class="text-right">
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="request(borrowItem)">pending_actions</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="edit(borrowItem)">edit</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="confirmDelete(borrowItem)">delete</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner != userId">
<mat-icon (click)="request(borrowItem)">pending_actions</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="borrowItemColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: borrowItemColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="borrowItems.totalElements" [pageSize]="borrowItems.size"
(page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div>
@@ -0,0 +1,21 @@
.mat-form-field+.mat-form-field, .mat-form-field+.mat-slide-toggle {
margin-left: 8px;
}
.mat-header-cell,
.mat-cell {
&.text-right {
text-align: right;
}
}
.mat-cell .mat-button {
padding-left: 0px;
padding-right: 0px;
}
.align-right{
display: flex;
padding: 21px 0;
justify-content: flex-end;
}
@@ -0,0 +1,410 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { PageEvent } from '@angular/material/paginator';
import { Sort } from '@angular/material/sort';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import * as moment from 'moment';
import { ConfirmDialog } from '../../../ui/confirm/confirm.component';
import { BorrowItemsService } from './../../../services/borrow.service';
import { AuthService } from './../../../services/auth.service';
import { BorrowRequestEditComponent } from '../requests/requests.component';
@Component({
selector: 'app-borrow-items',
templateUrl: './items.component.html',
styleUrls: [ './items.component.scss' ]
})
export class BorrowItemsComponent implements OnInit {
borrowItems: any[];
page: any = { page: 0, size: 10, sort: "id", desc: false };
pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
searchFormControl = new FormControl();
ownerFormControl = new FormControl();
userId: any;
borrowItemColumns = [ "name", "description", "availability", "url", "actions" ];
constructor(private borrowItemsService: BorrowItemsService,
private authService: AuthService,
public dialog: MatDialog) {
}
ngOnInit() {
this.authService.auth.subscribe((auth: any) => {
if (auth.principal && auth.principal.userId) {
this.userId = auth.principal.userId;
}
});
this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.borrowItemsService.getItems(0, this.page.size, this.page.sort, this.page.desc, value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
})
this.ownerFormControl.valueChanges.subscribe(value => {
this.borrowItemsService.getItems(0, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
})
this.refresh();
}
refresh(): void {
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
})
}
updatePages(event: PageEvent) {
this.page.page = event.pageIndex;
this.page.size = event.pageSize;
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
}
updateSort(sort: Sort) {
if (sort.direction == "") {
this.page.sort = "id";
this.page.desc = false;
} else {
this.page.sort = sort.active;
this.page.desc = sort.direction == "desc";
}
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
}
edit(borrowItem) {
const dialogRef = this.dialog.open(BorrowItemEditComponent, {
data: borrowItem,
minWidth: '80%'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
create() {
const dialogRef = this.dialog.open(BorrowItemEditComponent, {
data: {},
minWidth: '80%'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
request(borrowItem) {
const dialogRef = this.dialog.open(BorrowRequestEditComponent, {
data: {
"user": this.userId,
"borrowItem": borrowItem
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
confirmDelete(borrowItem) {
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowItem.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowItemsService.deleteItem(borrowItem.id).subscribe((result: any) => {
this.refresh();
})
}
});
}
}
@Component({
selector: 'app-borrow-item-edit',
templateUrl: './item.edit.html',
styleUrls: [ './item.edit.scss' ]
})
export class BorrowItemEditComponent {
borrowItemId: number;
create: boolean = false;
form: FormGroup;
weekdays: string[] = [ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY' ];
constructor(private borrowItemsService: BorrowItemsService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<BorrowItemEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
name: [ '', Validators.required ],
description: [ '', Validators.nullValidator ],
url: [ '', Validators.nullValidator ],
minDuration: [ '', Validators.nullValidator ],
maxDuration: [ '', Validators.nullValidator ],
availability: [ '', Validators.required ],
autoAccept: [ '', Validators.nullValidator ],
emailNotification: [ '', Validators.nullValidator ],
email: [ '', Validators.nullValidator ],
slots: this.formBuilder.array([])
}, { validators: [ durationValidator ] })
this.form.get('availability').valueChanges.subscribe((value) => {
this.slots.clear();
if (value == 'MANUAL') {
this.addManualSlot('', '', '');
} else if (value == 'PERIOD') {
this.addPeriodSlot('', '', '', '', '');
}
})
if (data.id) {
this.borrowItemId = + data.id;
} else {
this.create = true;
this.borrowItemId = undefined;
}
this.form.get('name').setValue(data.name, { emitEvent: false });
this.form.get('description').setValue(data.description, { emitEvent: false });
this.form.get('url').setValue(data.url, { emitEvent: false });
this.form.get('minDuration').setValue(data.minDuration, { emitEvent: false });
this.form.get('maxDuration').setValue(data.maxDuration, { emitEvent: false });
this.form.get('availability').setValue(data.availability || 'ALWAYS', { emitEvent: false });
this.form.get('autoAccept').setValue(data.autoAccept, { emitEvent: false });
this.form.get('emailNotification').setValue(data.emailNotification, { emitEvent: false });
this.form.get('email').setValue(data.email), { emitEvent: false };
if (data.slots) {
for (let slot of data.slots) {
if (data.availability == 'MANUAL') {
this.addManualSlot(slot.id, slot.start, slot.end);
} else if (data.availability == 'PERIOD') {
this.addPeriodSlot(slot.id, slot.startDay, slot.startTime, slot.endDay, slot.endTime);
}
}
}
}
formToBorrowItem(): any {
const borrowItem: any = {};
borrowItem.id = this.borrowItemId;
borrowItem.name = this.form.get('name').value;
borrowItem.description = this.form.get('description').value;
borrowItem.url = this.form.get('url').value;
borrowItem.minDuration = this.form.get('minDuration').value;
borrowItem.maxDuration = this.form.get('maxDuration').value;
borrowItem.availability = this.form.get('availability').value;
borrowItem.autoAccept = this.form.get('autoAccept').value;
borrowItem.emailNotification = this.form.get('emailNotification').value;
borrowItem.email = this.form.get('email').value;
const slots: any[] = [];
for (let slotForm of this.slots.controls) {
if (borrowItem.availability == 'MANUAL') {
slots.push({
id: slotForm.get('id').value,
start: slotForm.get('start').value,
end: slotForm.get('end').value,
type: 'MANUAL'
})
} else if (borrowItem.availability == 'PERIOD') {
slots.push({
id: slotForm.get('id').value,
startDay: slotForm.get('startDay').value,
startTime: slotForm.get('startTime').value,
endDay: slotForm.get('endDay').value,
endTime: slotForm.get('endTime').value,
type: 'PERIOD'
})
}
}
borrowItem.slots = slots;
return borrowItem;
}
get slots() {
return this.form.controls[ "slots" ] as FormArray;
}
deleteSlot(index: number) {
this.slots.removeAt(index);
}
dublicateSlot(index: number) {
this.slots.push(this.slots.at(index));
}
addManualSlot(id, start, end) {
const manualSlotForm = this.formBuilder.group({
id: [ id, Validators.required ],
start: [ start, Validators.required ],
end: [ end, Validators.required ]
}, { validators: [ manualSlotValidator ] });
this.slots.push(manualSlotForm);
}
addPeriodSlot(id, startDay: string, startTime: string, endDay: string, endTime: string) {
if (startTime.length == 8) {
startTime = startTime.substr(0, 5);
}
if (endTime.length == 8) {
endTime = endTime.substr(0, 5);
}
const perdiodSlotForm = this.formBuilder.group({
id: [ id, Validators.required ],
startDay: [ startDay, Validators.required ],
startTime: [ startTime, Validators.required ],
endDay: [ endDay, Validators.required ],
endTime: [ endTime, Validators.required ]
}, { validators: [ periodSlotValidator ] });
this.slots.push(perdiodSlotForm);
}
save() {
this.borrowItemsService.createOrUpdateItem(this.formToBorrowItem()).subscribe((data: any) => {
this.dialogRef.close(data);
}, (error) => {
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 ]);
}
}
});
}
confirmDelete() {
const borrowItem = this.formToBorrowItem();
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowItem.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowItemsService.deleteItem(borrowItem.id).subscribe((result: any) => {
this.dialogRef.close(true);
})
}
});
}
}
const durationValidator: ValidatorFn = (fg: FormGroup) => {
const minDuration = fg.get('minDuration').value;
const maxDuration = fg.get('maxDuration').value;
fg.get('minDuration').setErrors(null);
fg.get('maxDuration').setErrors(null);
if (minDuration && maxDuration && (moment.duration(minDuration).asMinutes() >= moment.duration(maxDuration).asMinutes())) {
fg.get('minDuration').setErrors([ 'INVALID' ]);
fg.get('maxDuration').setErrors([ 'INVALID' ]);
return { 'INVALID': true };
}
return null;
};
const manualSlotValidator: ValidatorFn = (fg: FormGroup) => {
const start = fg.get('start').value;
const end = fg.get('end').value;
fg.get('id').setErrors(null);
fg.get('start').setErrors(null);
fg.get('end').setErrors(null);
if (!start) {
fg.get('start').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!end) {
fg.get('end').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (start >= end) {
fg.get('start').setErrors([ 'INVALID_DATES' ]);
fg.get('end').setErrors([ 'INVALID_DATES' ]);
return { 'INVALID_DATES': true };
}
return null;
};
const periodSlotValidator: ValidatorFn = (fg: FormGroup) => {
const weekdays: string[] = [ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY' ];
const startDay = fg.get('startDay').value;
const startTime = fg.get('startTime').value;
const endDay = fg.get('endDay').value;
const endTime = fg.get('endTime').value;
fg.get('id').setErrors(null);
fg.get('startDay').setErrors(null);
fg.get('startTime').setErrors(null);
fg.get('endDay').setErrors(null);
fg.get('endTime').setErrors(null);
if (!startDay) {
fg.get('startDay').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!startTime) {
fg.get('startTime').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!endDay) {
fg.get('endDay').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!endTime) {
fg.get('endTime').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (weekdays.indexOf(startDay) > weekdays.indexOf(endDay)) {
fg.get('startDay').setErrors([ 'INVALID_DAY' ]);
fg.get('endDay').setErrors([ 'INVALID_DAY' ]);
return { 'INVALID_DAY': true };
}
if (weekdays.indexOf(startDay) == weekdays.indexOf(endDay) && startTime >= endTime) {
fg.get('startTime').setErrors([ 'INVALID_TIME' ]);
fg.get('endTime').setErrors([ 'INVALID_TIME' ]);
return { 'INVALID_TIME': true };
}
return null;
};
@@ -0,0 +1,30 @@
<video #qrCam></video>
<form fxLayout="row wrap" fxLayoutGap="24px grid">
<mat-form-field>
<mat-select [formControl]="camera" name="camera" placeholder="{{'borrow.proving.camera' | i18n}}">
<mat-option *ngFor="let camera of cameras" [value]="camera.id">
{{camera.label || camera.id}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="toggleFlash" disabled>{{'borrow.proving.flash' | i18n}}</mat-slide-toggle>
<input matInput hidden name="toggleFlash" />
</mat-form-field>
</form>
<mat-card class="warn" *ngIf="initialized && noCamera">
<mat-card-header>
<mat-card-title>
<mat-icon>no_photography</mat-icon>
</mat-card-title>
<mat-card-subtitle>{{'borrow.proving.noCamera' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'borrow.proving.noCamera.text' | i18n}}
</p>
</mat-card-content>
</mat-card>
@@ -0,0 +1,4 @@
video {
width: 100%;
height: auto;
}
@@ -0,0 +1,105 @@
import { AfterViewInit, Component, ElementRef, ViewChild, Inject } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { BorrowRequestsService } from 'src/app/services/borrow.service';
import QrScanner from 'qr-scanner';
import { HttpResponse } from '@angular/common/http';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-borrow-proving',
templateUrl: './proving.component.html',
styleUrls: [ './proving.component.scss' ]
})
export class BorrowProvingComponent implements AfterViewInit {
@ViewChild('qrCam') private qrCamVideo: ElementRef;
toggleFlash = new FormControl();
camera = new FormControl();
qrScanner: QrScanner;
cameras: QrScanner.Camera[];
noCamera: boolean = true;
initialized: boolean = false;
constructor(private borrowRequestService: BorrowRequestsService, private dialog: MatDialog) {
}
ngAfterViewInit() {
this.qrScanner = new QrScanner(this.qrCamVideo.nativeElement, (result) => {
this.onResult(result);
});
this.qrScanner.start().then(() => {
this.initialized = true;
QrScanner.listCameras(true).then((cameras: QrScanner.Camera[]) => {
this.cameras = cameras;
if (this.cameras.length) {
this.noCamera = false;
this.camera.setValue(this.cameras[ 0 ].id, { emitEvent: false });
this.camera.valueChanges.subscribe((value) => {
this.qrScanner.setCamera(value);
})
}
})
this.qrScanner.hasFlash().then(hasFlash => {
if (hasFlash) {
this.toggleFlash.enable();
this.toggleFlash.valueChanges.subscribe(value => {
if (value) {
this.qrScanner.turnFlashOn();
} else {
this.qrScanner.turnFlashOff();
}
});
} else {
this.toggleFlash.disable();
}
});
});
}
onResult(result: string) {
this.qrScanner.pause();
this.borrowRequestService.verifyRequest(result).subscribe((response) => {
this.openResultDialog(response, true);
}, (error) => {
this.openResultDialog(error, false);
});
}
openResultDialog(response, success: boolean) {
const dialogRef = this.dialog.open(BorrowProvingResultDialog, {
data: { "success": success, "response": response },
minWidth: '400px',
panelClass: "mat-card-dialog",
closeOnNavigation: false,
disableClose: true,
});
dialogRef.afterClosed().subscribe(result => {
this.qrScanner.start();
});
}
}
@Component({
selector: 'app-borrow-prooving-result',
templateUrl: 'proving.dialog.html',
styleUrls: [ './proving.dialog.scss' ]
})
export class BorrowProvingResultDialog {
result: any;
constructor(
@Inject(MAT_DIALOG_DATA) private data: any) {
this.result = JSON.parse(JSON.stringify(data));
console.log(this.result);
}
}
@@ -0,0 +1,55 @@
<mat-dialog-content>
<mat-card [ngClass]="result.success ? 'success' : (result.response.status == 412 ? 'accent' : 'warn')">
<mat-card-header>
<mat-card-title>
<mat-icon *ngIf="!result.success && result.response.status != 412">block</mat-icon>
<mat-icon *ngIf="!result.success && result.response.status == 412">help_outline</mat-icon>
<mat-icon *ngIf="result.success">check</mat-icon>
</mat-card-title>
<mat-card-subtitle>{{'borrow.proving.' + (result.success ? 'valid' : 'invalid') | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'borrow.proving.' + (result.success ? 'valid' : 'invalid') + '.text' | i18n}}
</p>
<p *ngFor="let error of result.response.error">
<ng-container [ngSwitch]="error.field">
<span *ngSwitchCase="'nbf'">
{{'borrow.proving.error.' + error.code | i18n:(error.defaultMessage | datef)}}
</span>
<span *ngSwitchCase="'exp'">
{{'borrow.proving.error.' + error.code | i18n:(error.defaultMessage | datef)}}
</span>
<span *ngSwitchDefault>
{{'borrow.proving.error.default.' + error.code | i18n:error.defaultMessage}}
</span>
</ng-container>
</p>
<table *ngIf="result.success">
<tr>
<th>{{'borrow.request.user' | i18n}}</th>
<td>{{result.response.user}}</td>
</tr>
<tr>
<th>{{'borrow.request.item' | i18n}}</th>
<td>{{result.response.name}}</td>
</tr>
<tr>
<th>{{'borrow.request.owner' | i18n}}</th>
<td>{{result.response.owner}}</td>
</tr>
<tr>
<th>{{'borrow.request.started' | i18n}}</th>
<td>{{result.response.nbf | datef}}</td>
</tr>
<tr>
<th>{{'borrow.request.ends' | i18n}}</th>
<td>{{result.response.exp | datef}}</td>
</tr>
</table>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button [mat-dialog-close]="false">{{'ok' | i18n}}</button>
</mat-card-actions>
</mat-card>
</mat-dialog-content>
@@ -0,0 +1 @@
@@ -0,0 +1,66 @@
<h2 mat-dialog-title>{{(create ? 'borrow.request.create' : 'borrow.request.edit') | i18n}}</h2>
<mat-dialog-content>
<h3>{{borrowRequest.borrowItem.name}}</h3>
<p>{{borrowRequest.borrowItem.description}}</p>
<a *ngIf="borrowRequest.borrowItem.url" mat-button color="accent" href="{{ borrowRequest.borrowItem.url }}"
target="_blank">
{{ borrowRequest.borrowItem.url }}
<mat-icon style="font-size: 1em;">open_in_new</mat-icon>
</a>
<br>
<br>
<mat-divider></mat-divider>
<ng-container *ngIf="borrowRequest.id">
<mat-chip-list *ngSwitch="borrowRequest.status">
<mat-chip *ngSwtichCase="'PENDING'" color="primary" selected></mat-chip>
<mat-chip color="accent" selected>Accent fish</mat-chip>
</mat-chip-list>
</ng-container>
<form [formGroup]="form">
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="startPicker" formControlName="start"
placeholder="{{'borrow.request.start' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #startPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.request.error.start' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="endPicker" formControlName="end"
placeholder="{{'borrow.request.end' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #endPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.request.error.end' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'borrow.request.comment' | i18n}}" formControlName="comment"></textarea>
<mat-error>
{{'borrow.request.error.comment' | i18n}}
</mat-error>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<div fxLayout="row" fxLayoutAlign="space-between start">
<div>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="form.invalid" mat-raised-button (click)="save()" color="accent">
<mat-icon>save</mat-icon>{{(create ? 'borrow.request.create' : 'borrow.request.save') | i18n}}
</button>
</div>
<button *ngIf="borrowRequest.id" mat-button color="warn" (click)="confirmDelete()">
<mat-icon>delete</mat-icon> {{ 'borrow.request.delete' |
i18n}}
</button>
</div>
</mat-dialog-actions>
@@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-dialog-actions>div {
width: 100%;
}
@@ -0,0 +1,55 @@
<h3>{{'borrow.requests' | i18n}}</h3>
<div *ngIf="borrowRequests">
<div fxLayout="row" fxLayoutAlign="space-between start">
<form>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="ownerFormControl">{{'borrow.requests.mine' | i18n}}</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
</form>
</div>
<table mat-table matSort [dataSource]="borrowRequests.content" (matSortChange)="updateSort($event)">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'borrow.item.name' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.borrowItem.name}} </td>
</ng-container>
<ng-container matColumnDef="starts">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.starts' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.starts | datef}}
</td>
</ng-container>
<ng-container matColumnDef="ends">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.ends' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.ends | datef}}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.status' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ 'borrow.requests.status.' + borrowRequest.status | i18n}}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'borrow.requests.actions' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest" class="text-right">
<a mat-icon-button *ngIf="borrowRequest.user == userId">
<mat-icon (click)="edit(borrowRequest)">edit</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowRequest.user == userId">
<mat-icon (click)="confirmDelete(borrowRequest)">delete</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowRequest.item.owner != userId">
<mat-icon (click)="updateStatus(borrowRequest)">pending_actions</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="borrowRequestColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: borrowRequestColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="borrowRequests.totalElements"
[pageSize]="borrowRequests.size" (page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div>
@@ -0,0 +1,21 @@
.mat-form-field+.mat-form-field, .mat-form-field+.mat-slide-toggle {
margin-left: 8px;
}
.mat-header-cell,
.mat-cell {
&.text-right {
text-align: right;
}
}
.mat-cell .mat-button {
padding-left: 0px;
padding-right: 0px;
}
.align-right{
display: flex;
padding: 21px 0;
justify-content: flex-end;
}
@@ -0,0 +1,181 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { PageEvent } from '@angular/material/paginator';
import { Sort } from '@angular/material/sort';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import * as moment from 'moment';
import { ConfirmDialog } from '../../../ui/confirm/confirm.component';
import { BorrowRequestsService } from './../../../services/borrow.service';
import { I18nService } from './../../../services/i18n.service';
import { AuthService } from './../../../services/auth.service';
@Component({
selector: 'app-borrow-requests',
templateUrl: './requests.component.html',
styleUrls: [ './requests.component.scss' ]
})
export class BorrowRequestsComponent implements OnInit {
borrowRequests: any[];
page: any = { page: 0, size: 10, sort: "id", desc: false };
pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
ownerFormControl = new FormControl();
userId: any;
borrowRequestColumns = [ "name", "status", "starts", "ends", "actions" ];
constructor(private borrowRequestsService: BorrowRequestsService,
private i18n: I18nService,
private authService: AuthService,
public dialog: MatDialog) {
}
ngOnInit() {
this.authService.auth.subscribe((auth: any) => {
if (auth.principal && auth.principal.userId) {
this.userId = auth.principal.userId;
}
});
this.ownerFormControl.valueChanges.subscribe(value => {
this.borrowRequestsService.getRequests(0, this.page.size, this.page.sort, this.page.desc, value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
})
this.refresh();
}
refresh(): void {
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
})
}
updatePages(event: PageEvent) {
this.page.page = event.pageIndex;
this.page.size = event.pageSize;
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
}
updateSort(sort: Sort) {
if (sort.direction == "") {
this.page.sort = "id";
this.page.desc = false;
} else {
this.page.sort = sort.active;
this.page.desc = sort.direction == "desc";
}
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
}
edit(borrowRequest) {
const dialogRef = this.dialog.open(BorrowRequestEditComponent, {
data: borrowRequest
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
confirmDelete(borrowRequest) {
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowRequest.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowRequestsService.deleteRequest(borrowRequest.id).subscribe((result: any) => {
this.refresh();
})
}
});
}
}
@Component({
selector: 'app-borrow-request-edit',
templateUrl: './request.edit.html',
styleUrls: [ './request.edit.scss' ]
})
export class BorrowRequestEditComponent {
borrowRequest: any;
create: boolean = false;
form: FormGroup;
constructor(private borrowRequestsService: BorrowRequestsService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<BorrowRequestEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
})
this.borrowRequest = data;
if (!this.borrowRequest.id) {
this.create = true;
}
}
formToBorrowRequest(): any {
const borrowRequest: any = {};
return borrowRequest;
}
save() {
this.borrowRequestsService.createOrUpdateRequest(this.formToBorrowRequest()).subscribe((data: any) => {
this.dialogRef.close(data);
}, (error) => {
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 ]);
}
}
});
}
confirmDelete() {
const borrowRequest = this.formToBorrowRequest();
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.requests.confirmDelete',
'args': [ borrowRequest.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowRequestsService.deleteRequest(borrowRequest.id).subscribe((result: any) => {
this.dialogRef.close(true);
})
}
});
}
}
@@ -0,0 +1,117 @@
<mat-card>
<mat-card-header>
<mat-card-title>{{'invites.register' | i18n}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<ng-container *ngIf="invite">
<h3>
<mat-icon inline=true>{{'invites.quota.' + invite.quota + ".icon" | i18n}}</mat-icon> {{'invites.quota.'
+
invite.quota
| i18n}}
</h3>
<p>{{'invites.quota.' + invite.quota +
".text" | i18n}}</p>
<p *ngIf="invite.url">
<a mat-button href="{{invite.url}}" target="_blank" color="accent">{{'invites.register.url' |
i18n}}</a>
</p>
<ng-container *ngIf="!success">
<blockquote *ngIf="invite.message && auth.principal.userId != invite.owner" class="message">
{{invite.message}}</blockquote>
<blockquote *ngIf="invite.note && auth.principal.userId != invite.owner" class="note">{{invite.note}}
</blockquote>
<form [formGroup]="form" *ngIf="!error">
<ng-container *ngIf="!auth.authenticated">
<mat-form-field>
<input matInput placeholder="{{'username' | i18n}}" formControlName="username" matAutofocus
tabindex="1">
<mat-error>
{{'username.error' | i18n}}
</mat-error>
<a mat-button matSuffix mat-icon-button (click)="genUsername()" tabindex="5">
<mat-icon>autorenew</mat-icon>
</a>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password' | i18n}}"
formControlName="password" tabindex="2">
<mat-error>
<div *ngFor="let error of form.get('password').errors | keyvalue">
{{'password.error.' + error.key | i18n}}<br>
</div>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password.confirm' | i18n}}"
formControlName="password2" tabindex="3">
<mat-error>
{{'password.not-match' | i18n}}
</mat-error>
</mat-form-field>
</ng-container>
<ng-container *ngIf="auth.principal.userId == invite.owner">
<mat-form-field>
<textarea matInput placeholder="{{'invite.message' | i18n}}"
formControlName="message"></textarea>
<mat-error>
{{'invite.errors.message' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'invite.note' | i18n}}" formControlName="note"></textarea>
<mat-error>
{{'invites.error.note' | i18n}}
</mat-error>
</mat-form-field>
</ng-container>
</form>
</ng-container>
<ng-container *ngIf="success">
<h2>{{'invites.register.success' | i18n}}</h2>
<p>{{'invites.register.success.text' | i18n}}</p>
</ng-container>
</ng-container>
<ng-container *ngIf="error">
<mat-error>
{{'invites.register.error.' + error | i18n}}
</mat-error>
</ng-container>
</mat-card-content>
<mat-card-actions>
<ng-container *ngIf="!success && invite">
<button *ngIf="!working && !auth.authenticated && !error" mat-raised-button color="primary"
[disabled]="form.invalid" (click)="register()" tabindex="4">
{{'invites.register' | i18n}}
</button>
<button *ngIf="auth.principal.userId == invite.owner && !error" mat-raised-button color="primary"
(click)="save()" tabindex="4">
{{'invites.edit.save' | i18n}}
</button>
</ng-container>
<ng-container *ngIf="success">
<a routerLink="/login" mat-raised-button color="primary">
{{'invites.register.login' | i18n}}
</a>
</ng-container>
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
</mat-card-actions>
<mat-card-footer>
<a href="https://wiki.bstly.de/services/webstly#invite" class="help-button"
matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent">
<mat-icon>contact_support</mat-icon>
</a>
</mat-card-footer>
</mat-card>
<div *ngIf="!success && permissions && permissions[0] && !error">
<h3>{{'permissions' | i18n}}</h3>
<app-permissions [permissions]="permissions"></app-permissions>
</div>
<div *ngIf="!success && quotas && quotas[0] && !error">
<h3>{{'quotas' | i18n}}</h3>
<app-quotas [quotas]="quotas"></app-quotas>
</div>
@@ -0,0 +1,23 @@
@import '../../../../variables.scss';
mat-form-field {
display: block;
}
mat-hint {
white-space: normal;
}
blockquote {
margin-left: 5px;
padding: 5px;
&.message {
border-left: 2px solid $accent;
}
&.note {
border-left: 2px solid $primary;
}
}
@@ -0,0 +1,166 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatchingValidator } from './../../../utils/matching.validator';
import { InviteService } from './../../../services/invites.service';
import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator';
import { I18nService } from 'src/app/services/i18n.service';
import { MatDialog } from '@angular/material/dialog';
import { InviteEditComponent } from '../edit/invite.edit';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-invite-code',
templateUrl: './code.component.html',
styleUrls: [ './code.component.scss' ]
})
export class InviteCodeComponent implements OnInit {
form: FormGroup;
error: string;
working: boolean = true;
success: boolean = false;
invite: any;
datetimeformat: string;
auth: any;
permissions = [];
quotas = [];
constructor(
private authService: AuthService,
private inviteService: InviteService,
private formBuilder: FormBuilder,
private i18n: I18nService,
public dialog: MatDialog,
private route: ActivatedRoute) {
this.authService.auth.subscribe(data => {
this.auth = data;
})
}
async ngOnInit() {
this.datetimeformat = this.i18n.get('format.datetime', []);
this.form = this.formBuilder.group({
username: [ '', Validators.required ],
password: [ '', Validators.nullValidator ],
password2: [ '', Validators.required ]
}, {
validators: MatchingValidator('password', 'password2')
});
const code = this.route.snapshot.paramMap.get('code');
this.inviteService.code(code).subscribe((data) => {
this.invite = data;
if (this.invite.redeemed) {
this.error = "ALREADY_REDEEMED";
}
if (this.invite.owner == this.auth.principal.userId) {
this.form = this.formBuilder.group({
message: [ '', Validators.nullValidator ],
note: [ '', Validators.nullValidator ],
});
this.form.get("message").setValue(this.invite.message);
this.form.get("note").setValue(this.invite.note);
}
this.working = false;
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
}
})
this.inviteService.permissions(code).subscribe((data: any) => {
this.permissions = data;
})
this.inviteService.quotas(code).subscribe((data: any) => {
this.quotas = data;
})
}
genUsername() {
const config: Config = {
dictionaries: [ adjectives, colors, animals ],
separator: "",
style: "capital",
length: 3
};
this.form.get("username").setValue(uniqueNamesGenerator(config));
}
register() {
if (this.form.valid && !this.working) {
this.working = true;
const model: any = {};
model.token = this.invite.code;
model.username = this.form.get("username").value;
model.password = this.form.get("password").value;
model.password2 = this.form.get("password2").value;
this.inviteService.register(model).subscribe((result: any) => {
this.working = false;
this.success = true;
}, (error) => {
this.working = false;
if (error.status == 401) {
this.error = "NO_CODE";
} else if (error.status == 406) {
this.error = "INVALID_CODE";
} else if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else 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 ]);
}
}
})
}
}
save() {
if (this.form.valid && !this.working) {
this.working = true;
this.invite.message = this.form.get("message").value;
this.invite.note = this.form.get("note").value;
this.inviteService.update(this.invite).subscribe((result: any) => {
this.invite = result;
this.working = false;
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
} if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else 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 ]);
}
}
})
}
}
}
@@ -0,0 +1,26 @@
<h2 mat-dialog-title>{{'invites.edit' | i18n}}</h2>
<mat-dialog-content>
<form [formGroup]="form">
<mat-error *ngIf="error">
{{'invites.register.error.' + error | i18n}}
</mat-error>
<mat-form-field>
<textarea matInput placeholder="{{'invite.message' | i18n}}" formControlName="message"></textarea>
<mat-error>
{{'invite.errors.message' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'invite.note' | i18n}}" formControlName="note" ></textarea>
<mat-error>
{{'invites.error.note' | i18n}}
</mat-error>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button mat-raised-button color="primary" [disabled]="form.invalid || working" (click)="save()">
{{'invites.edit.save' | i18n}}
</button>
</mat-dialog-actions>
@@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-hint {
white-space: normal;
}
+71
View File
@@ -0,0 +1,71 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatchingValidator } from '../../../utils/matching.validator';
import { InviteService } from '../../../services/invites.service';
import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-invite-edit',
templateUrl: './invite.edit.html',
styleUrls: [ './invite.edit.scss' ]
})
export class InviteEditComponent {
form: FormGroup;
error: string;
working: boolean = false;
success: boolean = false;
invite: any;
constructor(
private inviteService: InviteService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<InviteEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
message: [ '', Validators.nullValidator ],
note: [ '', Validators.nullValidator ],
});
this.invite = data;
this.form.get("message").setValue(this.invite.message);
this.form.get("note").setValue(this.invite.note);
}
save() {
if (this.form.valid && !this.working) {
this.working = true;
this.invite.message = this.form.get("message").value;
this.invite.note = this.form.get("note").value;
this.inviteService.update(this.invite).subscribe((result: any) => {
this.working = false;
this.dialogRef.close(result);
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
} if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else 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 ]);
}
}
})
}
}
}
+20 -8
View File
@@ -37,17 +37,17 @@
<td mat-cell *matCellDef="let invite"> {{ invite.expires | date:datetimeformat}} </td>
</ng-container>
<ng-container matColumnDef="link">
<th mat-header-cell *matHeaderCellDef> {{'invite.link' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <a href="{{ invite.codeLink}}" target="_blank" mat-button color="accent">
{{
invite.code}}</a> </td>
<td mat-cell *matCellDef="let invite">
<a href="{{ invite.codeLink }}" target="_blank" mat-button color="accent">{{invite.code}}</a>
</td>
</ng-container>
<ng-container matColumnDef="note">
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i *ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i
*ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i>
</td>
</ng-container>
@@ -65,10 +65,20 @@
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> {{'invite.actions' | i18n}} </th>
<td mat-cell *matCellDef="let invite">
<a mat-icon-button>
<mat-icon (click)="edit(invite)">edit</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="inviteColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: inviteColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="invites.totalElements" [pageSize]="invites.size" (page)="updatePages($event)" showFirstLastButtons></mat-paginator>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="invites.totalElements" [pageSize]="invites.size"
(page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div>
<mat-card>
@@ -117,7 +127,8 @@
<table mat-table matSort [dataSource]="others.content">
<ng-container matColumnDef="note">
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i *ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i> </td>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i
*ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i> </td>
</ng-container>
<ng-container matColumnDef="redeemed">
@@ -130,5 +141,6 @@
<tr mat-header-row *matHeaderRowDef="otherColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: otherColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="others.totalElements" [pageSize]="others.size" (page)="updateOthers($event)" showFirstLastButtons></mat-paginator>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="others.totalElements" [pageSize]="others.size"
(page)="updateOthers($event)" showFirstLastButtons></mat-paginator>
</div>
+44 -32
View File
@@ -1,19 +1,21 @@
import {Component, OnInit, ViewChild} from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {PageEvent} from '@angular/material/paginator';
import { ActivatedRoute, Router } from '@angular/router';
import { PageEvent } from '@angular/material/paginator';
import {AuthService} from '../../services/auth.service';
import {I18nService} from '../../services/i18n.service';
import {QuotaService} from '../../services/quota.service';
import {InviteService} from '../../services/invites.service';
import {FormControl} from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
import { AuthService } from '../../services/auth.service';
import { I18nService } from '../../services/i18n.service';
import { QuotaService } from '../../services/quota.service';
import { InviteService } from '../../services/invites.service';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { InviteEditComponent } from './edit/invite.edit';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'app-invites',
templateUrl: './invites.component.html',
styleUrls: ['./invites.component.scss']
styleUrls: [ './invites.component.scss' ]
})
export class InvitesComponent implements OnInit {
@@ -24,21 +26,20 @@ export class InvitesComponent implements OnInit {
success: boolean;
working: boolean;
datetimeformat: string;
pageSizeOptions: number[] = [5, 10, 25, 50];
pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
searchFormControl = new FormControl();
redeemedFormControl = new FormControl();
searchOthersFormControl = new FormControl();
redeemedOthersFormControl = new FormControl();
inviteColumns = ["starts", "expires", "link", "note", "message", "redeemed"];
otherColumns = ["note", "redeemed"];
inviteColumns = [ "starts", "expires", "link", "note", "message", "redeemed", "actions" ];
otherColumns = [ "note", "redeemed" ];
constructor(
private authService: AuthService,
private inviteService: InviteService,
private i18n: I18nService,
private quotaService: QuotaService,
private router: Router,
public dialog: MatDialog,
private route: ActivatedRoute) {
}
@@ -49,23 +50,23 @@ export class InvitesComponent implements OnInit {
this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.inviteService.getPages(this.quota, 0, this.invites.size, value, this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data;
}, (error) => {})
}, (error) => { })
})
this.redeemedFormControl.valueChanges.subscribe(value => {
this.inviteService.getPages(this.quota, 0, this.invites.size, this.searchFormControl.value ? this.searchFormControl.value : "", value).subscribe((data: any) => {
this.invites = data;
}, (error) => {})
}, (error) => { })
})
this.searchOthersFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.inviteService.getOthersPages(this.quota, 0, this.others.size, value, this.redeemedOthersFormControl.value).subscribe((data: any) => {
this.others = data;
}, (error) => {})
}, (error) => { })
})
this.redeemedOthersFormControl.valueChanges.subscribe(value => {
this.inviteService.getOthersPages(this.quota, 0, this.others.size, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", value).subscribe((data: any) => {
this.others = data;
}, (error) => {})
}, (error) => { })
})
this.update();
@@ -75,56 +76,67 @@ export class InvitesComponent implements OnInit {
update(): void {
this.inviteQuota = 0;
this.quotaService.quotas().subscribe((data: any) => {
for(let quota of data) {
if(quota.name == "invite_" + this.quota) {
for (let quota of data) {
if (quota.name == "invite_" + this.quota) {
this.inviteQuota = quota.value;
}
}
})
if(!this.invites) {
if (!this.invites) {
this.inviteService.get(this.quota).subscribe((data: any) => {
this.invites = data;
})
} else {
this.inviteService.getPages(this.quota, this.invites.number || 0, this.invites.size || 10, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data;
}, (error) => {})
}, (error) => { })
}
this.inviteService.getOthers(this.quota).subscribe((data: any) => {
this.others = data;
}, (error) => {})
}, (error) => { })
}
updatePages(event: PageEvent) {
this.inviteService.getPages(this.quota, event.pageIndex, event.pageSize, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data;
}, (error) => {})
}, (error) => { })
}
create(): void {
this.working = true;
this.inviteService.create(this.quota, {}).subscribe(response => {
this.update();
this.working = false;
}, (error) => {
this.working = false;
if(error.status == 409) {
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 of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
}
})
}
edit(invite) {
const dialogRef = this.dialog.open(InviteEditComponent, {
data: invite,
minWidth: "400px"
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.update();
}
});
}
updateOthers(event: PageEvent) {
this.inviteService.getOthersPages(this.quota, event.pageIndex, event.pageSize, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", this.redeemedOthersFormControl.value).subscribe((data: any) => {
this.others = data;
}, (error) => {})
}, (error) => { })
}
}
+5 -1
View File
@@ -3,7 +3,11 @@
<p *ngIf="!tags || tags.length == 0">{{'partey.tags.none' | i18n}}</p>
<mat-chip-list>
<mat-chip *ngFor="let tag of tags">{{tag.tag}}</mat-chip>
<ng-container *ngFor="let tag of tags">
<mat-chip *ngIf="activeTag(tag)" color="accent" selected>{{tag.tag}}</mat-chip>
<mat-chip *ngIf="upcomingTag(tag)" disabled matTooltip="{{'partey.tags.upcoming' | i18n:(tag.starts | datef)}}" selected>{{tag.tag}}</mat-chip>
<mat-chip *ngIf="expiringTag(tag)" color="primary" matTooltip="{{'partey.tags.expires' | i18n:(tag.expires | datef)}}" selected>{{tag.tag}}</mat-chip>
</ng-container>
</mat-chip-list>
+16
View File
@@ -21,4 +21,20 @@ export class ParteyComponent implements OnInit {
})
}
toDate(value : string) {
return new Date(value);
}
activeTag(tag: any) {
return !tag.starts && !tag.expires;
}
upcomingTag(tag: any) {
return tag.starts && (new Date() < this.toDate(tag.starts));
}
expiringTag(tag: any) {
return !(this.upcomingTag(tag)) && tag.expires;
}
}
+10 -8
View File
@@ -9,17 +9,17 @@
</mat-error>
<mat-form-field>
<input matInput placeholder="{{'username' | i18n}}" formControlName="username"
[(ngModel)]="model.username" required matAutofocus>
[(ngModel)]="model.username" required matAutofocus tabindex="1">
<mat-error>
{{'username.error' | i18n}}
</mat-error>
<a mat-button matSuffix mat-icon-button (click)="genUsername()">
<a mat-button matSuffix mat-icon-button (click)="genUsername()" tabindex="8">
<mat-icon>autorenew</mat-icon>
</a>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password"
[(ngModel)]="model.password" required>
[(ngModel)]="model.password" required tabindex="2">
<mat-error>
<div *ngFor="let error of form.get('password').errors | keyvalue">
{{'password.error.' + error.key | i18n}}<br>
@@ -28,14 +28,14 @@
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password.confirm' | i18n}}" formControlName="password2"
[(ngModel)]="model.password2" required>
[(ngModel)]="model.password2" required tabindex="3">
<mat-error>
{{'password.not-match' | i18n}}
</mat-error>
</mat-form-field>
<mat-slide-toggle formControlName="primaryEmail" [(ngModel)]="model.primaryEmail"
(change)="onPrimaryChange()">
(change)="onPrimaryChange()" tabindex="4">
{{'email.primary' | i18n}}
</mat-slide-toggle>
<mat-icon #primaryHint="matTooltip" (click)="primaryHint.toggle()" inline="true"
@@ -43,7 +43,7 @@
<mat-form-field *ngIf="model.primaryEmail">
<input matInput type="email" placeholder="{{'email' | i18n}}" formControlName="email"
[(ngModel)]="model.email" required>
[(ngModel)]="model.email" required tabindex="5">
<mat-error>
{{'email.invalid' | i18n}}
</mat-error>
@@ -59,14 +59,16 @@
<mat-divider></mat-divider>
</mat-card-content>
<mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid" tabindex="6">
{{'register' | i18n}}
</button>
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
</mat-card-actions>
<mat-card-footer>
<a href="https://wiki.bstly.de/services/webstly#registration" class="help-button" matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent">
<a href="https://wiki.bstly.de/services/webstly#registration" class="help-button"
matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent"
tabindex="7">
<mat-icon>contact_support</mat-icon>
</a>
</mat-card-footer>
+106
View File
@@ -0,0 +1,106 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class BorrowItemsService {
constructor(private http: HttpClient) {
}
getItems(page: number, size: number, sort: string, desc: boolean, search: string, owner: boolean) {
let httpParams = new HttpParams();
if (page != undefined) {
httpParams = httpParams.set("page", "" + page);
}
if (size != undefined) {
httpParams = httpParams.set("size", "" + size);
}
if (sort != undefined) {
httpParams = httpParams.set("sort", sort);
}
if (desc != undefined) {
httpParams = httpParams.set("desc", "" + desc);
}
if (search != undefined) {
httpParams = httpParams.set("search", "" + search);
}
if (owner != undefined && owner) {
httpParams = httpParams.set("owner", "" + owner);
}
return this.http.get(environment.apiUrl + "/borrow/items", { params: httpParams });
}
getItem(id) {
return this.http.get(environment.apiUrl + "/borrow/items/" + id);
}
createOrUpdateItem(borrowItem) {
return this.http.post(environment.apiUrl + "/borrow/items", borrowItem);
}
deleteItem(id) {
return this.http.delete(environment.apiUrl + "/borrow/items/" + id);
}
}
@Injectable({
providedIn: 'root',
})
export class BorrowRequestsService {
constructor(private http: HttpClient) {
}
getRequests(page: number, size: number, sort: string, desc: boolean, owner: boolean) {
let httpParams = new HttpParams();
if (page != undefined) {
httpParams = httpParams.set("page", "" + page);
}
if (size != undefined) {
httpParams = httpParams.set("size", "" + size);
}
if (sort != undefined) {
httpParams = httpParams.set("sort", sort);
}
if (desc != undefined) {
httpParams = httpParams.set("desc", "" + desc);
}
if (owner != undefined && owner) {
httpParams = httpParams.set("owner", "" + owner);
}
return this.http.get(environment.apiUrl + "/borrow/requests", { params: httpParams });
}
createOrUpdateRequest(borrowRequest) {
return this.http.post(environment.apiUrl + "/borrow/requests", borrowRequest);
}
deleteRequest(id) {
return this.http.delete(environment.apiUrl + "/borrow/requests/" + id);
}
setRequestStatus(borrowRequest) {
return this.http.put(environment.apiUrl + "/borrow/requests", borrowRequest);
}
getRequestCode(id) {
return this.http.get(environment.apiUrl + "/borrow/requests/code/" + id);
}
verifyRequest(serialized) {
return this.http.post(environment.apiUrl + "/borrow/requests/verify", serialized);
}
}
+32 -28
View File
@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../environments/environment';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
@@ -8,7 +8,7 @@ import {environment} from '../../environments/environment';
export class I18nService {
locale: string = "de-informal";
locales: any[] = ["de-informal"];
locales: any[] = [ "de-informal" ];
i18n: any;
constructor(private http: HttpClient) {
@@ -31,13 +31,13 @@ export class I18nService {
let browserLocale = navigator.language;
if(browserLocale.indexOf("-") != -1) {
browserLocale = browserLocale.split("-")[0];
if (browserLocale.indexOf("-") != -1) {
browserLocale = browserLocale.split("-")[ 0 ];
}
let locale = localStorage.getItem("bstly.locale") || browserLocale || this.locales[0];
let locale = localStorage.getItem("bstly.locale") || browserLocale || this.locales[ 0 ];
if(locale == 'de') {
if (locale == 'de') {
locale = 'de-informal';
}
@@ -45,54 +45,58 @@ export class I18nService {
await this.http.get(environment.apiUrl + "/i18n").toPromise().then((response: any) => {
this.locales = response;
});
} catch(e) {
} catch (e) {
console.debug("fallback to default locales");
}
if(this.locales.indexOf(locale) == -1) {
locale = this.locales[0];
if (this.locales.indexOf(locale) == -1) {
locale = this.locales[ 0 ];
}
this.setLocale(locale);
try {
this.i18n = await this.http.get(environment.apiUrl + "/i18n/" + locale).toPromise();
} catch(e) {
} catch (e) {
this.i18n = await this.http.get("/assets/i18n/" + locale + ".json").toPromise();
console.debug("fallback to default locale");
}
}
get(key, args: string[]): string {
return this.getInternal(key, args, this.i18n);
return this.getInternal(key, args, this.i18n, "");
}
getInternal(key, args: string[], from): string {
getInternal(key, args: string[], from, path): string {
key += '';
if(!from) {
return key;
} else if(from[key]) {
if(typeof from[key] === 'object') {
if(from[key]["."]) {
return this.insertArguments(from[key]["."], args);
if (!from) {
return this.empty(key, args, path);
} else if (from[ key ]) {
if (typeof from[ key ] === 'object') {
if (from[ key ][ "." ]) {
return this.insertArguments(from[ key ][ "." ], args);
}
return key;
return this.empty(key, args, path);
}
return this.insertArguments(from[key], args);
return this.insertArguments(from[ key ], args);
} else {
let keys = key.split(".");
if(from[keys[0]]) {
if (from[ keys[ 0 ] ]) {
key = keys.slice(1, keys.length).join(".");
return this.getInternal(key, args, from[keys[0]])
return this.getInternal(key, args, from[ keys[ 0 ] ], path + keys[ 0 ] + ".")
}
}
return key;
return this.empty(key, args, path);
}
empty(key, args: string[], path: string): string {
return (path ? path + (path.endsWith(".") ? "" : ".") : "") + key + (args && args.length > 0 ? (" [" + args + "]") : "");
}
insertArguments(label: string, args: string[]) {
if(args) {
for(let index in args) {
label = label.replace(`{${index}}`, this.get(args[index], []));
if (args) {
for (let index in args) {
label = label.replace(`{${index}}`, this.get(args[ index ], []));
}
}
return label;
+21 -4
View File
@@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {environment} from '../../environments/environment';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
@@ -19,6 +19,23 @@ export class InviteService {
return this.http.get(environment.apiUrl + "/invites" + (quota ? "?quota=" + quota + "&" : "?") + "page=" + page + "&size=" + size + "&search=" + search + (redeemed ? "&redeemed=" + redeemed : ""));
}
code(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code);
}
permissions(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code + "/permissions");
}
quotas(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code + "/quotas");
}
register(model: any) {
return this.http.post(environment.apiUrl + "/invites", model);
}
getOthers(quota: string) {
return this.http.get(environment.apiUrl + "/invites/" + quota + "/others");
}
@@ -32,7 +49,7 @@ export class InviteService {
}
update(invite: any) {
return this.http.post(environment.apiUrl + "/invites", invite);
return this.http.patch(environment.apiUrl + "/invites", invite);
}
}
@@ -0,0 +1,20 @@
<div fxLayout="row wrap" fxLayoutAlign="start stretch">
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" placeholder="{{'durationpicker.days' | i18n}}" [formControl]="days">
</mat-form-field>
</div>
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" max="23" placeholder="{{'durationpicker.hours' | i18n}}"
[formControl]="hours">
</mat-form-field>
</div>
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" max="59" placeholder="{{'durationpicker.minutes' | i18n}}"
[formControl]="minutes">
<mat-hint align="end">{{duration}}</mat-hint>
</mat-form-field>
</div>
</div>
@@ -0,0 +1,4 @@
mat-form-field {
display: block;
padding-right: 8px;
}
@@ -0,0 +1,98 @@
import { Component, OnInit } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as moment from 'moment';
@Component({
selector: 'app-durationpicker',
templateUrl: './durationpicker.component.html',
styleUrls: [ './durationpicker.component.scss' ],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: DurationpickerComponent
}
]
})
export class DurationpickerComponent implements OnInit, ControlValueAccessor {
days = new FormControl();
hours = new FormControl();
minutes = new FormControl();
duration: string;
model: moment.Duration;
onChange = (duration) => { };
onTouched = () => { };
isDisabled = false;
constructor() { }
ngOnInit(): void {
this.days.valueChanges.subscribe(value => {
if (value < this.model.days()) {
this.model.subtract((this.model.days() - value), "days");
} else {
this.model.add((value - this.model.days()), "days");
}
this.checkValue();
})
this.hours.valueChanges.subscribe(value => {
if (value < this.model.hours()) {
this.model.subtract((this.model.hours() - value), "hours");
} else {
this.model.add((value - this.model.hours()), "hours");
}
this.checkValue();
})
this.minutes.valueChanges.subscribe(value => {
if (value < this.model.minutes()) {
this.model.subtract((this.model.minutes() - value), "minutes");
} else {
this.model.add((value - this.model.minutes()), "minutes");
}
this.checkValue();
})
}
checkValue() {
if (this.model.asMinutes() <= 0) {
this.days.setValue(0, { emitEvent: false });
this.hours.setValue(0, { emitEvent: false });
this.minutes.setValue(0, { emitEvent: false });
this.duration = null;
} else {
this.duration = this.model.toISOString();
}
this.onChange(this.duration);
this.onTouched();
}
writeValue(duration: string): void {
this.duration = duration;
this.model = moment.duration(this.duration);
this.days.setValue(this.model.days(), { emitEvent: false });
this.hours.setValue(this.model.hours()), { emitEvent: false };
this.minutes.setValue(this.model.minutes(), { emitEvent: false });
this.onTouched();
}
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
registerOnTouched(onTouched: any): void {
this.onTouched = onTouched;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
}
+13
View File
@@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
@Pipe({ name: 'datef' })
export class MomentPipe implements PipeTransform {
transform(value: Date | moment.Moment, dateFormat: string): any {
if (!dateFormat) {
return moment(value).fromNow();
}
return moment(value).format(dateFormat);
}
}