update borrow + fix invite
This commit is contained in:
@@ -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
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-borrow',
|
||||
templateUrl: './borrow.component.html',
|
||||
styleUrls: [ './borrow.component.scss' ]
|
||||
})
|
||||
export class BorrowComponent {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 ]);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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) => { })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user