Commit 51362d9c authored by El Mghazli Yacine's avatar El Mghazli Yacine

initial commit

parent 8ead6ae7
......@@ -10,7 +10,7 @@
],
"parserOptions": {
"project": [
"tsconfig.json"
"ngx/tsconfig.json"
],
"createDefaultProgram": true
},
......@@ -47,4 +47,4 @@
"rules": {}
}
]
}
}
\ No newline at end of file
......@@ -47,3 +47,5 @@ testem.log
.DS_Store
Thumbs.db
config.toml
/.angular
This diff is collapsed.
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { IBooking } from './bookings.api';
import { IUser } from './users.api';
const route = '/autosend';
@Injectable({
providedIn: 'root',
})
export class AutoSendApi {
constructor(private httpClient: HttpClient) { }
public autoSendMyBookings$ = (month: string) => this.httpClient.get<IBooking[]>(environment.backend + route + '/my/month/' + month);
public autoSendUsersBookings$ = (month: string) => this.httpClient.get<IBooking[]>(environment.backend + route + '/users/month/' + month);
public register$ = (code: string) => {
const params = new HttpParams().append('code', code);
return this.httpClient.post<IUser>(environment.backend + route, null, { params });
}
}
\ No newline at end of file
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/naming-convention */
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
export enum IState {
BOOKED = 'booked',
CHECKIN_HOUR_NOK = 'checkin nok',
WAITING_CHECKIN_HOUR = 'waiting checkin',
CHECKIN_HOUR_OK = 'checkin ok',
INSIDE = 'inside',
WAITING_CHECKOUT_HOUR = 'waiting checkout',
CHECKOUT_HOUR_OK = 'checkout ok',
OUTSIDE = 'outside',
TELFORGET = 'telforget',
}
export enum IPlatform {
DIRECT = 'direct',
AIRBNB = 'airbnb',
TRIPADVISOR = 'tripadvisor',
BOOKING = 'booking',
WIMDU = 'wimdu',
BEDYCASA = 'bedycasa',
HOUSETRIP = 'housetrip',
NINEFLATS = '9flats',
ONLY = 'only',
LEBONCOIN = 'leboncoin',
SEJOURNING = 'sejourning',
WAY2STAY = 'waytostay'
}
export interface IRange {
start: string;
end: string;
}
export interface IGuest {
given: string;
email: string;
}
export interface ICleaning {
cleanerEmail: string;
hours: number;
}
export interface INewBooking {
amount: number;
platform: IPlatform;
range: IRange;
guest: IGuest;
}
export interface IBooking extends INewBooking {
month: string;
id: string;
state: IState;
cleaning?: ICleaning;
}
const route = '/bookings';
@Injectable({
providedIn: 'root',
})
export class BookingsApi {
constructor(private httpClient: HttpClient) { }
public readBookings$ = (month: string) => this.httpClient.get<IBooking[]>(environment.backend + route + '/month/' + month);
public refreshBookings$ = (month: string) => this.httpClient.get<IBooking[]>(environment.backend + route + '/refresh/' + month);
public createBooking$ = (booking: INewBooking) => this.httpClient.post<IBooking>(environment.backend + route, booking);
public updateBooking$ = (booking: IBooking) => this.httpClient.put<string[]>(environment.backend + route, booking);
public readBookingsList$ = (ids: string[]) => this.httpClient.put<IBooking[]>(environment.backend + route + '/list', ids);
public deleteBooking$ = (id: string) => this.httpClient.delete(environment.backend + route + '/' + id);
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
export interface IOption {
key: string;
value: number;
}
export interface ICommand {
name: string;
options: IOption[];
}
export interface IStatus {
infos: string;
}
export interface ILog {
text: string;
}
const route = '/commands';
@Injectable({
providedIn: 'root',
})
export class CommandsApi {
constructor(private httpClient: HttpClient) { }
public readCommands$ = () => this.httpClient.get<ICommand[]>(environment.backend + route + '/list');
public readStatus$ = () => this.httpClient.get<IStatus>(environment.backend + route + '/status');
public runCommand$ = (command: ICommand) => this.httpClient.post<ICommand>(environment.backend + route, command);
public readLogs$ = () => this.httpClient.get<IStatus>(environment.backend + route + '/logs');
}
\ No newline at end of file
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
export interface IReport {
month: string;
bookings: string[];
brut: number;
cleanings: number;
nights: number;
occupation: number;
net: number;
nightRate?: number;
netRate?: number;
}
const route = '/reports';
@Injectable({
providedIn: 'root',
})
export class ReportsApi {
constructor(private httpClient: HttpClient) { }
public readReport$ = (month: string) => this.httpClient.get<IReport>(environment.backend + route + '/month/' + month);
public refreshReport$ = (month: string) => this.httpClient.put<IReport>(environment.backend + route + '/month/' + month, null);
public readReports$ = (year: string) => this.httpClient.get<IReport[]>(environment.backend + route + '/year/' + year);
public refreshReports$ = (year: string) => this.httpClient.put<IReport[]>(environment.backend + route + '/year/' + year, null);
}
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
export enum ISubject {
ALERT = 'Alert no email',
GENERAL_INFOS = 'Infos',
CHECKIN_METHOD = 'Checkin',
CHECKIN_QUESTION = 'Checkin question',
WELCOME = 'Welcome',
CHECKOUT_METHOD = 'Checkout',
CHECKOUT_QUESTION = 'Checkout question',
GOODBYE = 'Good Bye',
}
export interface IMessage {
subject: ISubject;
body: string;
}
export interface ICalendar {
summary: string;
id: string;
}
export interface ICleaner {
rate: number;
name: string;
email: string;
default: boolean;
}
export interface IUser {
subjectPrefix: string;
defaultCheckInHour: number;
defaultCheckOutHour: number;
defaultCleaningHours: number;
busyCal: ICalendar;
cleaningCal: ICalendar;
reelCal: ICalendar;
address: string;
name: string;
tel: string;
messages: IMessage[];
cleaners: ICleaner[];
isRegistered: boolean;
autoSend: boolean;
}
const route = '/users';
@Injectable({
providedIn: 'root',
})
export class UsersApi {
constructor(private httpClient: HttpClient) { }
public read$ = () => this.httpClient.get<IUser>(environment.backend + route);
public refresh$ = () => this.httpClient.get<IUser>(environment.backend + route + '/refresh');
public update$ = (user: IUser) => this.httpClient.put<string[]>(environment.backend + route, user);
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookingsComponent } from './components/bookings/bookings.component';
import { CallbackComponent } from './components/callback/callback.component';
import { CleanersComponent } from './components/cleaners/cleaners.component';
import { MessagesComponent } from './components/messages/messages.component';
import { UserComponent } from './components/user/user.component';
import { YearComponent } from './components/year/year.component';
import { AuthGuard } from './guards/auth.guard';
import { CommandsComponent } from './components/commands/commands.component';
const routes: Routes = [
{ path: '', redirectTo: '/', pathMatch: 'full' },
{ path: 'bookings', component: BookingsComponent, canActivate: [AuthGuard] },
{ path: 'year', component: YearComponent, canActivate: [AuthGuard] },
{ path: 'messages', component: MessagesComponent, canActivate: [AuthGuard] },
{ path: 'cleaners', component: CleanersComponent, canActivate: [AuthGuard] },
{ path: 'user', component: UserComponent, canActivate: [AuthGuard] },
{ path: 'oauth2callback', component: CallbackComponent },
{ path: 'commands', component: CommandsComponent },
{ path: '**', redirectTo: '' },
];
......
import { ClipboardModule } from '@angular/cdk/clipboard/';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire/compat';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips';
......@@ -20,51 +17,29 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { GoogleApiModule, NG_GAPI_CONFIG } from 'ng-gapi';
import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker';
import { BookingsApi } from './api/bookings.api';
import { UsersApi } from './api/users.api';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BookingsComponent } from './components/bookings/bookings.component';
import { YearComponent } from './components/year/year.component';
import { CallbackComponent } from './components/callback/callback.component';
import { CleanersComponent } from './components/cleaners/cleaners.component';
import { ErrorDialogComponent } from './components/error-dialog/error-dialog.component';
import { MessagesComponent } from './components/messages/messages.component';
import { NavComponent } from './components/nav/nav.component';
import { NewBookingDialogComponent } from './components/new-booking-dialog/new-booking.component';
import { NewCleanerDialogComponent } from './components/new-cleaner-dialog/new-cleaner.component';
import { UserComponent } from './components/user/user.component';
import { InterceptorProviders } from './interceptors/interceptors';
import { LoadingService } from './services/loading.service';
import { FIREBASE, UserService } from './services/user.service';
import { gapiClientConfig, WorkerService } from './services/worker.service';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { ReactiveFormsModule } from '@angular/forms';
import { CommandsComponent } from './components/commands/commands.component';
import { CommandsApi } from './api/commands.api';
import { MatSidenavModule } from '@angular/material/sidenav';
@NgModule({
declarations: [
AppComponent,
BookingsComponent,
YearComponent,
MessagesComponent,
UserComponent,
CallbackComponent,
NavComponent,
ErrorDialogComponent,
NewBookingDialogComponent,
CleanersComponent,
NewCleanerDialogComponent,
CommandsComponent,
ErrorDialogComponent
],
imports: [
GoogleApiModule.forRoot({ provide: NG_GAPI_CONFIG, useValue: gapiClientConfig }),
AngularFireModule.initializeApp(FIREBASE),
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
......@@ -91,21 +66,17 @@ import { gapiClientConfig, WorkerService } from './services/worker.service';
MatSlideToggleModule,
MatGridListModule,
MatCardModule,
MatMenuModule,
NgxMaterialTimepickerModule.setLocale('fr-FR'),
MatMenuModule
],
providers: [
// services
LoadingService,
UserService,
WorkerService,
// api
BookingsApi,
UsersApi,
CommandsApi,
// interceptors
InterceptorProviders,
],
bootstrap: [AppComponent],
entryComponents: [ErrorDialogComponent, NewBookingDialogComponent, NewCleanerDialogComponent],
entryComponents: [ErrorDialogComponent],
})
export class AppModule { }
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable no-shadow */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, OnInit } from '@angular/core';
import * as moment from 'moment';
import { Observable, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { AutoSendApi } from 'src/app/api/autosend.api';
import { BookingsApi, IPlatform, IState } from 'src/app/api/bookings.api';
import { IReport, ReportsApi } from 'src/app/api/reports.api';
import { BookingCtrl } from 'src/app/controls/booking.control';
import { Cleaner } from 'src/app/controls/cleaner.control';
import { CleaningCtrl } from 'src/app/controls/cleaning.control';
import { NewBookingCtrl } from 'src/app/controls/newbooking.control';
import { DialogService } from 'src/app/services/dialog.service';
import { LoadingService } from 'src/app/services/loading.service';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-bookings',
templateUrl: './bookings.component.html',
styleUrls: ['./bookings.component.css'],
})
export class BookingsComponent implements OnInit {
// table columns
DISPLAYED_COLUMNS: string[] = ['title', 'nights', 'checkinout', 'platform', 'autosend', 'cleaner'];
// Platforms
IPlatform = IPlatform;
platformValues = Object.values(IPlatform);
// States
IState = IState;
stateValues = Object.values(IState);
// variables
bookingsCtrls$: Observable<BookingCtrl[]> = of([]);
monthly$?: Observable<IReport>;
// month
month: string;
// Cleaners
cleaners: Cleaner[] = [];
get isToday() {
return this.month === moment().format('YYYY-MM');
}
constructor(
public bookingsApi: BookingsApi,
public autoSendApi: AutoSendApi,
public loadingService: LoadingService,
public userService: UserService,
public reportsApi: ReportsApi,
public dialogService: DialogService,
) {
const userCtrl = this.userService.userCtrl;
if (userCtrl) {
this.cleaners = userCtrl.api().cleaners;
} else {
// TODO
}
this.month = moment().format('YYYY-MM');
}
ngOnInit() {
this.bookingsCtrls$ = this.bookingsApi.readBookings$(this.month).pipe(
map((docs) => docs.map((doc) => new BookingCtrl(doc))),
);
// this.userService.read$().pipe(
// map(userCtrl => {
// this.cleaners = userCtrl.api().cleaners;
// })
// ).subscribe()
this.monthly$ = this.reportsApi.readReport$(this.month)
}
onRefresh() {
this.ngOnInit();
}
onSync() {
this.monthly$ = this.reportsApi.refreshReport$(this.month);
this.bookingsCtrls$ = this.bookingsApi.refreshBookings$(this.month).pipe(
map(ibookings => ibookings.map(ibooking => new BookingCtrl(ibooking)))
);
}
onChange(control: BookingCtrl) {
if (control.cleaningCtrl) {
control.cleaningCtrl = undefined;
} else if (this.cleaners.length) {
control.cleaningCtrl = new CleaningCtrl({
cleanerEmail: this.cleaners[0].email,
hours: 3
});
}
control.markAsDirty();
}
onToday() {
this.month = moment().format('YYYY-MM');
this.ngOnInit();
}
onNext() {
this.month = moment(this.month).add(1, 'months').format('YYYY-MM');
this.ngOnInit();
}
onPrevious() {
this.month = moment(this.month).subtract(1, 'months').format('YYYY-MM');
this.ngOnInit();
}
onSend() {
this.bookingsCtrls$ = this.autoSendApi.autoSendMyBookings$(this.month).pipe(
map((docs) => docs.map((doc) => new BookingCtrl(doc))),
tap(() => this.onRefresh())
);
}
onNew() {
const newBookingCtrl: NewBookingCtrl = new NewBookingCtrl({
range: {
start: moment(this.month).format(),
end: moment(this.month).add(3, 'days').format(),
},
guest: {
email: 'john@yahoo.com',
given: 'john',
},
amount: 200,
platform: IPlatform.AIRBNB
});
this.dialogService.openNewBookingDialog$(newBookingCtrl).pipe(
mergeMap(control => this.bookingsApi.createBooking$(control.api())),
map(ibooking => new BookingCtrl(ibooking))
)
.subscribe(() => this.onRefresh())
}
onSubmit(control: BookingCtrl) {
this.bookingsApi.updateBooking$(control.api()).pipe(
// take(1),
tap(() => control.markAsPristine())
).subscribe(() => this.onRefresh());
}
onDelete(control: BookingCtrl) {
if (control.id) {
this.bookingsApi.deleteBooking$(control.id).subscribe(() => this.onRefresh());
} else {
// TODO
}
}
}
import { Component, OnInit } from '@angular/core';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-callback',
templateUrl: './callback.component.html',
styleUrls: ['./callback.component.css'],
})
export class CallbackComponent implements OnInit {
constructor(private userService: UserService) { }
ngOnInit() {
this.userService.onRegisterSuccess();
}
}
mat-form-field {
width: 50%;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
img.resize-gdrive {
max-width: 5%;
max-height: 3%;
}
img.resize-gdoc {
max-width: 8%;
max-height: 6%;
}
<form *ngIf="userService.userCtrl$ | async as userCtrl" [formGroup]="userCtrl" fxLayout="column"
fxLayoutAlign="space-around" class="grid-container">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="row" fxLayoutAlign="start start">
<button mat-icon-button (click)="onRefresh()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>refresh</mat-icon>
</button>
<button mat-icon-button (click)="onSubmit(userCtrl)" [disabled]="userCtrl.pristine">
<mat-icon>save</mat-icon>
</button>
</div>
<button mat-icon-button (click)="onNew()">
<mat-icon>add</mat-icon>
</button>
</div>
<mat-grid-list cols="2" rowHeight="350px">
<mat-grid-tile *ngFor="let cleanerCtrl of cleanersCtrls(userCtrl)" [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center">
<mat-card-title> {{ cleanerCtrl.nameFC.value }} </mat-card-title>
<button mat-icon-button (click)="onDelete(cleanerCtrl)" fxFlexAlign="end">
<mat-icon>delete</mat-icon>
</button>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="space-around start" class="dashboard-card-content">
<mat-form-field fxFlexFill>
<mat-label>Name</mat-label>
<input input matInput [formControl]="cleanerCtrl.nameFC" />
</mat-form-field>
<mat-form-field fxFlexFill>
<mat-label>Email</mat-label>
<input input matInput [formControl]="cleanerCtrl.emailFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Rate</mat-label>
<input input matInput [formControl]="cleanerCtrl.rateFC" />
<span matSuffix>/hour</span>
</mat-form-field>
<mat-slide-toggle [formControl]="cleanerCtrl.defaultFC">default</mat-slide-toggle>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</form>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
// import { UsersApi } from 'src/app/api/users.api';
import { CleanerCtrl } from 'src/app/controls/cleaner.control';
import { UserCtrl } from 'src/app/controls/user.control';
import { LoadingService } from 'src/app/services/loading.service';
import { UserService } from 'src/app/services/user.service';
import { NewCleanerDialogComponent } from '../new-cleaner-dialog/new-cleaner.component';
@Component({
selector: 'app-cleaners',
templateUrl: './cleaners.component.html',
styleUrls: ['./cleaners.component.css'],
})
export class CleanersComponent implements OnInit {
constructor(
// private usersApi: UsersApi,
public loadingService: LoadingService,
public userService: UserService,
public dialog: MatDialog,
) { }
ngOnInit() {
this.userService.read$().subscribe();
}
onRefresh() {
this.ngOnInit();
}
cleanersCtrls(userCtrl: UserCtrl) {
return userCtrl.cleanersFA.controls as CleanerCtrl[];
}
onSubmit(userCtrl: UserCtrl) {
this.userService.update$(userCtrl).subscribe(() => this.onRefresh());
}
onDelete(control: CleanerCtrl) {
const userCtrl = this.userService.userCtrl;
if (userCtrl) {
const index = userCtrl.cleanersFA.controls.indexOf(control);
if (index > -1) {
userCtrl.cleanersFA.controls.splice(index, 1);
}
this.onSubmit(userCtrl);
} else {
// TODO
}
}
onNew() {
const dialogRef = this.dialog.open(NewCleanerDialogComponent, {
width: '400px',
data: CleanerCtrl.newCleanerCtrl(),
});
dialogRef.afterClosed().subscribe((control) => {
const userCtrl = this.userService.userCtrl;
if (userCtrl) {
userCtrl.cleanersFA.push(control);
this.onSubmit(userCtrl);
} else {
// TODO
}
});
}
}
<h1 mat-dialog-title>Status</h1>
<mat-form-field *ngIf="commandsCtrls$ | async as commands">
<mat-label>Command</mat-label>
<mat-select [formControl]="selectedCtrl!.nameFC" [value]="selectedCtrl!.nameFC.value">
<mat-option *ngFor="let command of commands" [value]="command">{{ command }}</mat-option>
</mat-select>
</mat-form-field>
\ No newline at end of file
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable no-shadow */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { CommandsApi } from 'src/app/api/commands.api';
import { CommandCtrl } from 'src/app/controls/command.control';
import { LoadingService } from 'src/app/services/loading.service';
@Component({
selector: 'app-commands',
templateUrl: './commands.component.html',
styleUrls: ['./commands.component.css'],
})
export class CommandsComponent {
commandsCtrls$: Observable<CommandCtrl[]>
selectedCtrl?: CommandCtrl
constructor(
public commandsApi: CommandsApi,
public autoSendApi: CommandsApi,
public loadingService: LoadingService,
) {
this.commandsCtrls$ = this.commandsApi.readCommands$().pipe(
map((docs) => docs.map((doc) => new CommandCtrl(doc))),
);
}
onSubmit(control: CommandCtrl) {
this.commandsApi.runCommand$(control.api()).pipe(
// take(1),
tap(() => control.markAsPristine())
).subscribe();
}
}
mat-form-field {
width: 50%;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
img.resize-gdrive {
max-width: 5%;
max-height: 3%;
}
img.resize-gdoc {
max-width: 8%;
max-height: 6%;
}
<form *ngIf="userService.userCtrl$ | async as userCtrl" [formGroup]="userCtrl" fxLayout="column"
fxLayoutAlign="space-around" class="grid-container">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="row" fxLayoutAlign="start start">
<button mat-icon-button (click)="onRefresh()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>refresh</mat-icon>
</button>
<button mat-icon-button (click)="onSubmit(userCtrl)" [disabled]="userCtrl.pristine">
<mat-icon>save</mat-icon>
</button>
</div>
<button mat-raised-button color="primary" (click)="userService.register()"
*ngIf="(userService.isRegistered$ | async) === false">
REGISTER
</button>
<button mat-raised-button color="primary" (click)="userService.unregister()"
*ngIf="userService.isRegistered$ | async">
UNREGISTER
</button>
<!--
<button mat-raised-button color="primary" (click)="userService.register()">
REGISTER
</button>
<button mat-raised-button color="primary" (click)="userService.unregister()">
UNREGISTER
</button> -->
</div>
<mat-grid-list cols="2" rowHeight="350px">
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center">
<mat-card-title> Autosend </mat-card-title>
<mat-slide-toggle [formControl]="userCtrl.autoSendFC" [disabled]="!userCtrl.isRegistered"></mat-slide-toggle>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="start start" class="dashboard-card-content">
<mat-form-field>
<mat-label>Email subject prefix</mat-label>
<span matPrefix>[</span>
<input input matInput [formControl]="userCtrl.subjectPrefixFC" />
<span matSuffix>]</span>
</mat-form-field>
<mat-form-field fxFlexFill>
<mat-label>Address</mat-label>
<input input matInput [formControl]="userCtrl.addressFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Name</mat-label>
<input input matInput [formControl]="userCtrl.nameFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Tel</mat-label>
<input input matInput [formControl]="userCtrl.telFC" />
</mat-form-field>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<div *ngIf="userCtrl.autoSendFC.value">
<mat-grid-tile *ngFor="let messageCtrl of messagesCtrls(userCtrl)" [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title> {{ messageCtrl.subject }} </mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<mat-form-field fxFlexFill>
<mat-label> Body </mat-label>
<textarea matInput [formControl]="messageCtrl.bodyFC" cdkTextareaAutosize></textarea>
</mat-form-field>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</div>
</mat-grid-list>
</form>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { UserCtrl } from 'src/app/controls/user.control';
import { MessageCtrl } from 'src/app/controls/message.control';
import { LoadingService } from 'src/app/services/loading.service';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html',
styleUrls: ['./messages.component.css'],
})
export class MessagesComponent implements OnInit {
constructor(public loadingService: LoadingService, public userService: UserService) { }
ngOnInit() {
this.userService.read$().subscribe();
}
onRefresh() {
this.ngOnInit();
}
messagesCtrls(userCtrl: UserCtrl) {
return userCtrl.messagesFA.controls as MessageCtrl[];
}
onSubmit(userCtrl: UserCtrl) {
this.userService.update$(userCtrl).subscribe(() => this.onRefresh());
}
}
......@@ -2,11 +2,8 @@
<mat-sidenav #drawer class="sidenav" fixedInViewport [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="(isHandset$ | async) === false">
<mat-nav-list>
<a mat-list-item routerLink="/bookings" routerLinkActive="active">Month</a>
<a mat-list-item routerLink="/year" routerLinkActive="active">Year</a>
<a mat-list-item routerLink="/messages" routerLinkActive="active">Autosend</a>
<a mat-list-item routerLink="/cleaners" routerLinkActive="active">Cleaners</a>
<a mat-list-item routerLink="/user" routerLinkActive="active">Calendars</a>
<a mat-list-item routerLink="/commands" routerLinkActive="active">Commands</a>
<a mat-list-item routerLink="/logs" routerLinkActive="active">Logs</a>
</mat-nav-list>
</mat-sidenav>
......@@ -18,31 +15,21 @@
<button mat-icon-button (click)="drawer.toggle()">
<mat-icon>menu</mat-icon>
</button>
<h1>LSD</h1>
<h1>OAI SoftModem</h1>
</ng-template>
<ng-template #loading>
<mat-progress-spinner mode="indeterminate" diameter="40">
</mat-progress-spinner>
<mat-progress-spinner mode="indeterminate" diameter="40"> </mat-progress-spinner>
</ng-template>
<div fxLayout="row" fxLayoutAlign="start center">
<ng-container *ngIf="loadingService.isLoading$ | async; then loading; else not_loading">
</ng-container>
</div>
<ng-template #notAuthenticated>
<button mat-raised-button color="primary" *ngIf="(userService.isAuthenticated$ | async) === false"
(click)="userService.login()">
login
</button>
</ng-template>
<ng-template #authenticated>
<ng-container fxLayout="row">
<a id="email">{{ userService.email }}</a>
<button mat-raised-button color="warn" href="#" (click)="userService.logout()">
logout
</button>
</ng-template>
<ng-container *ngIf="userService.isAuthenticated$ | async; then authenticated; else notAuthenticated"
fxLayout="row">
</ng-container>
</mat-toolbar-row>
......
import { Component } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { UserService } from 'src/app/services/user.service';
import { LoadingService } from 'src/app/services/loading.service';
@Component({
......@@ -17,5 +16,5 @@ export class NavComponent {
);
constructor(
public loadingService: LoadingService, public userService: UserService, private breakpointObserver: BreakpointObserver) { }
public loadingService: LoadingService, private breakpointObserver: BreakpointObserver) { }
}
<h1 mat-dialog-title>New Booking</h1>
<div mat-dialog-content>
<mat-form-field>
<mat-label>Given name</mat-label>
<input input matInput [formControl]="bookingCtrl.guestCtrl.givenFC" />
</mat-form-field>
<mat-form-field fxFlexFill>
<mat-label>Nights</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input matStartDate [formControl]="bookingCtrl.rangeCtrl.startDayFC" />
<input matEndDate [formControl]="bookingCtrl.rangeCtrl.endDayFC" />
</mat-date-range-input>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field>
<mat-label>Platform</mat-label>
<mat-select [formControl]="bookingCtrl.platformFC" [value]="bookingCtrl.platformFC.value">
<mat-option *ngFor="let platform of platformValues" [value]="platform">{{ platform }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>Email</mat-label>
<input input matInput [formControl]="bookingCtrl.guestCtrl.emailFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Amount</mat-label>
<input input matInput [formControl]="bookingCtrl.amountFC" />
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button [mat-dialog-close]="bookingCtrl" cdkFocusInitial>Ok</button>
</div>
\ No newline at end of file
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { IPlatform, IState } from 'src/app/api/bookings.api';
import { Cleaner } from 'src/app/controls/cleaner.control';
import { NewBookingCtrl } from 'src/app/controls/newbooking.control';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-new-booking',
templateUrl: './new-booking.component.html',
})
export class NewBookingDialogComponent {
// Platforms
IPlatform = IPlatform;
platformValues = Object.values(IPlatform);
// States
IState = IState;
stateValues = Object.values(IState);
// Cleaners
cleaners?: Cleaner[];
constructor(
public dialogRef: MatDialogRef<NewBookingDialogComponent>,
@Inject(MAT_DIALOG_DATA) public bookingCtrl: NewBookingCtrl,
public userService: UserService,
) {
const userCtrl = this.userService.userCtrl;
if (userCtrl) {
this.cleaners = userCtrl.api().cleaners;
} else {
// TODO
}
}
onNoClick() {
this.dialogRef.close();
}
}
<h1 mat-dialog-title>New Cleaner</h1>
<div mat-dialog-content>
<mat-form-field>
<mat-label>Name</mat-label>
<input input matInput [formControl]="cleanerCtrl.nameFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Email</mat-label>
<input input matInput [formControl]="cleanerCtrl.emailFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Rate</mat-label>
<input input matInput [formControl]="cleanerCtrl.rateFC" />
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button [mat-dialog-close]="cleanerCtrl" cdkFocusInitial>Ok</button>
</div>
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { CleanerCtrl } from '../../controls/cleaner.control';
@Component({
selector: 'app-new-cleaner',
templateUrl: './new-cleaner.component.html',
})
export class NewCleanerDialogComponent {
constructor(public dialogRef: MatDialogRef<NewCleanerDialogComponent>, @Inject(MAT_DIALOG_DATA) public cleanerCtrl: CleanerCtrl) { }
onNoClick() {
this.dialogRef.close();
}
}
mat-form-field {
width: 50%;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
img.resize-gdrive {
max-width: 5%;
max-height: 3%;
}
img.resize-gdoc {
max-width: 8%;
max-height: 6%;
}
<form *ngIf="userService.userCtrl$ | async as userCtrl" [formGroup]="userCtrl" fxLayout="column"
fxLayoutAlign="space-around" class="grid-container">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxLayout="row">
<button mat-icon-button (click)="onRefresh()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>refresh</mat-icon>
</button>
<button mat-icon-button (click)="onSubmit(userCtrl)" [disabled]="userCtrl.pristine">
<mat-icon>save</mat-icon>
</button>
</div>
<button mat-icon-button (click)="onSync()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>sync</mat-icon>
</button>
</div>
<mat-grid-list cols="2" rowHeight="150px">
<mat-grid-tile [colspan]="2" [rowspan]="2">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title> Settings </mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="start start" class="dashboard-card-content">
<mat-form-field>
<mat-label>Checkin Hour</mat-label>
<input input matInput [formControl]="userCtrl.defaultCheckInHourFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Checkout Hour</mat-label>
<input input matInput [formControl]="userCtrl.defaultCheckOutHourFC" />
</mat-form-field>
<mat-form-field>
<mat-label>Cleaning Time</mat-label>
<input input matInput [formControl]="userCtrl.defaultCleaningHoursFC" />
</mat-form-field>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="2" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Nights</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" class="dashboard-card-content">
<div fxLayout="row" fxFill>
<mat-form-field>
<mat-label>Calendar ID</mat-label>
<input input matInput [formControl]="userCtrl.busyCalCtrl.idFC" />
</mat-form-field>
<button mat-icon-button [cdkCopyToClipboard]="ical(userCtrl.busyCalCtrl.idFC.value)">
<mat-icon>content_copy</mat-icon>
</button>
<p>{{ userCtrl.busyCalCtrl.summaryFC.value }}</p>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="2" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Real</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" class="dashboard-card-content">
<div fxLayout="row" fxFill>
<mat-form-field>
<mat-label>Calendar ID</mat-label>
<input input matInput [formControl]="userCtrl.reelCalCtrl.idFC" />
</mat-form-field>
<button mat-icon-button [cdkCopyToClipboard]="ical(userCtrl.reelCalCtrl.idFC.value)">
<mat-icon>content_copy</mat-icon>
</button>
<p>{{ userCtrl.reelCalCtrl.summaryFC.value }}</p>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="2" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title> Cleanings </mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" class="dashboard-card-content">
<div fxLayout="row" fxFill>
<mat-form-field>
<mat-label> Calendar ID</mat-label>
<input input matInput [formControl]="userCtrl.cleaningCalCtrl.idFC" />
</mat-form-field>
<button mat-icon-button [cdkCopyToClipboard]="ical(userCtrl.cleaningCalCtrl.idFC.value)">
<mat-icon>content_copy</mat-icon>
</button>
<p>{{ userCtrl.cleaningCalCtrl.summaryFC.value }}</p>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</form>
\ No newline at end of file
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, OnInit } from '@angular/core';
import { UserCtrl } from 'src/app/controls/user.control';
import { LoadingService } from 'src/app/services/loading.service';
import { UserService } from './../../services/user.service';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css'],
})
export class UserComponent implements OnInit {
onRefresh = this.ngOnInit;
constructor(public userService: UserService, public loadingService: LoadingService) { }
ngOnInit() {
this.userService.read$().subscribe();
}
onSync() {
this.userService.refresh$().subscribe();
}
onSubmit(userCtrl: UserCtrl) {
this.userService.update$(userCtrl).subscribe(() => this.onRefresh());
}
ical(id: string) {
return 'https://calendar.google.com/calendar/ical/' + encodeURIComponent(id) + '/public/basic.ics';
}
}
mat-form-field {
width: 50%;
}
.status {
color: green;
font-weight: 800;
}
.ng-invalid .status {
color: red;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
<div class="grid-container">
<div fxLayout="row" fxLayoutAlign="space-between">
<div fxLayout="row" fxLayoutAlign="start end">
<button mat-icon-button (click)="onRefresh()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>refresh</mat-icon>
</button>
</div>
<div fxLayout="row" fxLayoutAlign="space-around end">
<button mat-icon-button (click)="onPrevious()">
<mat-icon>skip_previous</mat-icon>
</button>
<p>{{ year }}</p>
<button mat-icon-button (click)="onNext()">
<mat-icon>skip_next</mat-icon>
</button>
</div>
<div fxLayout="row" fxLayoutAlign="end end">
<button mat-icon-button (click)="onSync()" [disabled]="loadingService.isLoading$ | async">
<mat-icon>sync</mat-icon>
</button>
</div>
</div>
<mat-grid-list *ngIf="yearly$ | async as yearly" cols="5" rowHeight="150px">
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>{{ yearly.nights }} nights</mat-card-title>
</mat-card-header>
<mat-card-content fxLayoutAlign="center center" fxLAyoutAlign="center center" class="dashboard-card-content">
<h1> {{ yearly.occupation }}%</h1>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Brut</mat-card-title>
</mat-card-header>
<mat-card-content fxLayoutAlign="center center" class="dashboard-card-content">
<h1>{{ yearly.brut }}€</h1>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Cleanings</mat-card-title>
</mat-card-header>
<mat-card-content fxLayoutAlign="center center" fxLAyoutAlign="center center" class="dashboard-card-content">
<h1>{{ yearly.cleanings }}€</h1>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Net</mat-card-title>
</mat-card-header>
<mat-card-content fxLayoutAlign="center center" fxLAyoutAlign="center center" class="dashboard-card-content">
<h1>{{ yearly.net }}€</h1>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Night Rate</mat-card-title>
</mat-card-header>
<mat-card-content fxLayoutAlign="center center" fxLAyoutAlign="center center" class="dashboard-card-content">
<h1>{{ yearly.nightRate }}€</h1>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="5" [rowspan]="15">
<div fxLayout="column" fxLayoutAlign="start" fxFlexFill>
<div *ngIf="bookingsCtrls$ | async as bookingsCtrls">
<!-- <mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator> -->
<mat-table mat-table [dataSource]="bookingsCtrls" multiTemplateDataRows class="mat-elevation-z8">
<ng-container matColumnDef="dates">
<mat-header-cell *matHeaderCellDef>
<h2>Dates</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{dates(bookingCtrl)}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="guest">
<mat-header-cell *matHeaderCellDef>
<h2>Guest</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{bookingCtrl.guestCtrl.givenFC.value}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="nights">
<mat-header-cell *matHeaderCellDef>
<h2>Nights</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{nights(bookingCtrl)}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="platform">
<mat-header-cell *matHeaderCellDef>
<h2>Platform</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{ bookingCtrl.platformFC.value }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="rate">
<mat-header-cell *matHeaderCellDef>
<h2>Rate</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{ rate(bookingCtrl)}}
</mat-cell>
</ng-container>
<ng-container matColumnDef="brut">
<mat-header-cell *matHeaderCellDef fxLayout="column" fxLayoutAlign="start start">
<h2>Brut</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
<mat-form-field>
<mat-label>Amount</mat-label>
<input input matInput [formControl]="bookingCtrl.amountFC" />
</mat-form-field>
</mat-cell>
</ng-container>
<ng-container matColumnDef="cleanings">
<mat-header-cell *matHeaderCellDef fxLayout="column" fxLayoutAlign="start start">
<h2>Cleanings</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{ cleaning(bookingCtrl) }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="net">
<mat-header-cell *matHeaderCellDef>
<h2>Net</h2>
</mat-header-cell>
<mat-cell *matCellDef="let bookingCtrl" fxLayout="column" fxLayoutAlign="start start">
{{ brut(bookingCtrl) - cleaning(bookingCtrl) }}
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="DISPLAYED_COLUMNS"></mat-header-row>
<mat-row *matRowDef="let bookingCtrl; columns: DISPLAYED_COLUMNS" (keydown.enter)="onSubmit(bookingCtrl)">
</mat-row>
</mat-table>
</div>
</div>
</mat-grid-tile>
</mat-grid-list>
</div>
\ No newline at end of file
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable no-shadow */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/naming-convention */
import { Component, OnInit } from '@angular/core';
import * as moment from 'moment';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { AutoSendApi } from 'src/app/api/autosend.api';
import { BookingsApi, IPlatform } from 'src/app/api/bookings.api';
import { IReport, ReportsApi } from 'src/app/api/reports.api';
import { LoadingService } from 'src/app/services/loading.service';
import { UserService } from 'src/app/services/user.service';
import { BookingCtrl } from '../../controls/booking.control';
@Component({
selector: 'app-year',
templateUrl: './year.component.html',
styleUrls: ['./year.component.css'],
})
export class YearComponent implements OnInit {
DISPLAYED_COLUMNS: string[] = ['dates', 'guest', 'nights', 'platform', 'rate', 'brut', 'cleanings', 'net'];
year: string;
yearly$?: Observable<IReport>;
bookingsCtrls$?: Observable<BookingCtrl[]>;
rates = new Map<string, number>();
constructor(
public bookingsApi: BookingsApi,
public reportsApi: ReportsApi,
public autoSendApi: AutoSendApi,
public loadingService: LoadingService,
public userService: UserService,
) {
this.year = moment().format('YYYY');
const userCtrl = this.userService.userCtrl;
if (userCtrl) {
userCtrl.api().cleaners.forEach((cleaner) => this.rates.set(cleaner.email, cleaner.rate));
} else {
// TODO
}
}
onNext() {
this.year = moment(this.year).add(1, 'years').format('YYYY');
this.ngOnInit();
}
nights(control: BookingCtrl) {
return moment(control.rangeCtrl.endDayFC.value).diff(moment(control.rangeCtrl.startDayFC.value), 'days') + 1
}
dates(control: BookingCtrl) {
return moment(control.rangeCtrl.startDayFC.value).format('DD/MM') + ' to ' + moment(control.rangeCtrl.endDayFC.value).format('DD/MM')
}
rate(control: BookingCtrl) {
return Math.round(this.brut(control) / this.nights(control))
}
brut(control: BookingCtrl) {
let comission = 0
if (control.platformFC.value === IPlatform.BOOKING) comission = 0.2
return (1 - comission) * control.amountFC.value;
}
cleaning(control: BookingCtrl) {
return control.cleaningCtrl
? this.rates.get(control.cleaningCtrl.cleanerEmailFC.value)! * control.cleaningCtrl.hours
: 0
}
onPrevious() {
this.year = moment(this.year).subtract(1, 'years').format('YYYY');
this.ngOnInit();
}
onRefresh() {
this.ngOnInit();
}
onSync() {
this.yearly$ = this.reportsApi.refreshReports$(this.year).pipe(
map(this.processReports)
)
this.bookingsCtrls$ = this.yearly$.pipe(
mergeMap(yearly => this.bookingsApi.readBookingsList$(yearly.bookings)),
map(ibookings => ibookings.map(ibooking => new BookingCtrl(ibooking)))
);
}
ngOnInit() {
this.yearly$ = this.reportsApi.readReports$(this.year).pipe(
map(this.processReports)
)
this.bookingsCtrls$ = this.yearly$.pipe(
mergeMap(yearly => this.bookingsApi.readBookingsList$(yearly.bookings)),
map(ibookings => ibookings.map(ibooking => new BookingCtrl(ibooking)))
);
}
private processReports(ireports: IReport[]) {
let yearly: IReport = {
brut: 0,
cleanings: 0,
month: this.year, //FIXME
nights: 0,
occupation: 0,
net: 0,
bookings: []
};
ireports.forEach((ireport) => {
yearly.brut = yearly.brut + ireport.brut;
yearly.cleanings = yearly.cleanings + ireport.cleanings;
yearly.nights = yearly.nights + ireport.nights;
yearly.bookings = yearly.bookings.concat(ireport.bookings);
})
yearly.net = yearly.brut - yearly.cleanings;
yearly.netRate = Math.round(yearly.net / yearly.nights);
yearly.nightRate = Math.round(yearly.brut / yearly.nights);
yearly.occupation = Math.round(100 * yearly.nights / 365);
return yearly
}
onSubmit(control: BookingCtrl) {
this.bookingsApi.updateBooking$(control.api()).pipe(
// take(1),
tap(() => control.markAsPristine())
).subscribe();
}
}
/* eslint-disable no-shadow */
import { FormControl } from '@angular/forms';
import { IBooking, IState } from '../api/bookings.api';
import { Cleaning, CleaningCtrl } from './cleaning.control';
import { NewBooking, NewBookingCtrl } from './newbooking.control';
const enum BookingFCN {
state = 'state',
cleaning = 'cleaning',
}
export class Booking extends NewBooking {
id: string;
month: string;
state: IState;
cleaning?: Cleaning;
constructor(iBooking: IBooking) {
super(iBooking)
this.id = iBooking.id;
this.month = iBooking.month;
this.state = iBooking.state;
if (iBooking.cleaning) {
this.cleaning = new Cleaning(iBooking.cleaning);
} else {
this.cleaning = undefined;
}
}
}
export class BookingCtrl extends NewBookingCtrl {
id: string;
month: string;
constructor(ibooking: IBooking) {
super(ibooking);
const booking = new Booking(ibooking);
this.id = booking.id;
this.month = booking.month;
if (booking.cleaning) {
this.addControl(BookingFCN.cleaning, new CleaningCtrl(booking.cleaning));
}
this.addControl(BookingFCN.state, new FormControl(booking.state));
}
api() {
const doc: IBooking = {
id: this.id,
month: this.month,
state: this.stateFC.value,
amount: this.amountFC.value,
platform: this.platformFC.value,
range: this.rangeCtrl.api(),
guest: this.guestCtrl.api(),
cleaning: this.cleaningCtrl ? this.cleaningCtrl.api() : undefined,
};
return doc;
}
get cleaningCtrl() {
return this.get(BookingFCN.cleaning) as CleaningCtrl | undefined;
}
set cleaningCtrl(control: CleaningCtrl | undefined) {
if (this.cleaningCtrl && control) {
this.setControl(BookingFCN.cleaning, control);
} else if (control) {
this.addControl(BookingFCN.cleaning, control);
} else {
this.removeControl(BookingFCN.cleaning);
}
}
get stateFC() {
return this.get(BookingFCN.state) as FormControl;
}
set stateFC(control: FormControl) {
this.setControl(BookingFCN.state, control);
}
}
/* eslint-disable no-shadow */
import { FormControl, FormGroup } from '@angular/forms';
import { ICalendar } from '../api/users.api';
// export class Calendar implements ICalendar {
// summary: string = '';
// id: string = '';
// }
export class CalendarCtrl extends FormGroup {
constructor(calendar: ICalendar) {
super({});
this.addControl(CalendarFCN.summary, new FormControl(calendar.summary));
this.addControl(CalendarFCN.id, new FormControl(calendar.id));
}
get idFC() {
return this.get(CalendarFCN.id) as FormControl;
}
set idFC(control: FormControl) {
this.setControl(CalendarFCN.id, control);
}
get summaryFC() {
return this.get(CalendarFCN.summary) as FormControl;
}
set summaryFC(control: FormControl) {
this.setControl(CalendarFCN.summary, control);
}
api() {
const calendar: ICalendar = {
summary: this.summaryFC.value,
id: this.idFC.value,
};
return calendar;
}
}
enum CalendarFCN {
id = 'id',
summary = 'summary',
}
import { FormControl, FormGroup } from '@angular/forms';
import { ICleaner } from '../api/users.api';
export const DEFAULT_CLEANER_NAME = 'greenchap94@gmail.com';
export const DEFAULT_CLEANER: ICleaner = {
email: DEFAULT_CLEANER_NAME,
rate: 10,
name: 'test cleaner',
default: true
};
export class Cleaner implements ICleaner {
rate: number;
name: string;
email: string;
default: boolean;
constructor(iCleaner: ICleaner) {
this.rate = iCleaner.rate;
this.email = iCleaner.email;
this.name = iCleaner.name;
this.default = iCleaner.default;
}
}
export class CleanerCtrl extends FormGroup {
constructor(cleaner: Cleaner) {
super({});
this.addControl(CleanerFCN.rate, new FormControl(cleaner.rate));
this.addControl(CleanerFCN.email, new FormControl(cleaner.email));
this.addControl(CleanerFCN.name, new FormControl(cleaner.name));
this.addControl(CleanerFCN.default, new FormControl(cleaner.default));
}
static newCleanerCtrl() {
return new CleanerCtrl(DEFAULT_CLEANER);
}
get rateFC() {
return this.get(CleanerFCN.rate) as FormControl;
}
set rateFC(control: FormControl) {
this.setControl(CleanerFCN.rate, control);
}
get emailFC() {
return this.get(CleanerFCN.email) as FormControl;
}
set emailFC(control: FormControl) {
this.setControl(CleanerFCN.email, control);
}
get nameFC() {
return this.get(CleanerFCN.name) as FormControl;
}
set nameFC(control: FormControl) {
this.setControl(CleanerFCN.name, control);
}
get defaultFC() {
return this.get(CleanerFCN.default) as FormControl;
}
set defaultFC(control: FormControl) {
this.setControl(CleanerFCN.default, control);
}
api() {
const cleaner: ICleaner = {
rate: this.rateFC.value,
email: this.emailFC.value,
name: this.nameFC.value,
default: this.defaultFC.value
};
return cleaner;
}
}
enum CleanerFCN {
rate = 'rate',
email = 'email',
name = 'name',
default = 'default',
}
import { FormControl, FormGroup } from '@angular/forms';
import { ICleaning } from '../api/bookings.api';
export class Cleaning implements ICleaning {
cleanerEmail: string;
hours: number;
constructor(iCleaning: ICleaning) {
this.cleanerEmail = iCleaning.cleanerEmail;
this.hours = iCleaning.hours;
}
}
export class CleaningCtrl extends FormGroup {
hours: number;
constructor(cleaning: ICleaning) {
super({});
this.hours = cleaning.hours;
this.addControl(CleaningFCN.cleaner, new FormControl(cleaning.cleanerEmail));
}
get cleanerEmailFC() {
return this.get(CleaningFCN.cleaner) as FormControl;
}
set cleanerEmailFC(control: FormControl) {
this.setControl(CleaningFCN.cleaner, control);
}
api() {
const cleaning: ICleaning = {
hours: this.hours,
cleanerEmail: this.cleanerEmailFC.value,
};
return cleaning;
}
}
enum CleaningFCN {
cleaner = 'cleaner',
}
/* eslint-disable no-shadow */
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { ICommand } from '../api/commands.api';
import { OptionCtrl } from './option.control';
const enum CommandFCN {
name = 'name',
options = 'options',
}
export class CommandCtrl extends FormGroup {
constructor(icommand: ICommand) {
super({});
this.addControl(CommandFCN.name, new FormControl(icommand.name));
this.addControl(CommandFCN.options, new FormArray(icommand.options.map((ioption) => new OptionCtrl(ioption))));
}
api() {
const doc: ICommand = {
name: this.nameFC.value,
options: this.optionsFA.controls.map(control => (control as OptionCtrl).api()),
};
return doc;
}
get nameFC() {
return this.get(CommandFCN.name) as FormControl;
}
set nameFC(control: FormControl) {
this.setControl(CommandFCN.name, control);
}
get optionsFA() {
return this.get(CommandFCN.options) as FormArray;
}
set optionsFA(control: FormArray) {
this.setControl(CommandFCN.options, control);
}
}
import { FormControl, FormGroup } from '@angular/forms';
import { IGuest } from '../api/bookings.api';
export class Guest {
given: string;
email: string;
constructor(iGuest: IGuest) {
this.given = iGuest.given;
this.email = iGuest.email;
}
}
export class GuestCtrl extends FormGroup {
constructor(guest: Guest) {
super({});
this.addControl(GuestFCN.given, new FormControl(guest.given));
this.addControl(GuestFCN.email, new FormControl(guest.email));
}
get givenFC() {
return this.get(GuestFCN.given) as FormControl;
}
set givenFC(control: FormControl) {
this.setControl(GuestFCN.given, control);
}
get emailFC() {
return this.get(GuestFCN.email) as FormControl;
}
set emailFC(control: FormControl) {
this.setControl(GuestFCN.email, control);
}
api() {
const guest: IGuest = {
given: this.givenFC.value,
email: this.emailFC.value,
};
return guest;
}
}
enum GuestFCN {
given = 'given',
email = 'email',
}
/* eslint-disable no-shadow */
import { FormControl, FormGroup } from '@angular/forms';
import { IMessage, ISubject } from '../api/users.api';
// export class Message implements IMessage {
// subject: ISubject;
// body: string;
// }
export class MessageCtrl extends FormGroup {
subject: ISubject;
constructor(message: IMessage) {
super({});
this.subject = message.subject;
this.addControl(MessageFCN.body, new FormControl(message.body.replace(/<br\s*[\/]?>/gi, '\n')));
}
static newMessageCtrl() {
const iMessage: IMessage = {
subject: ISubject.WELCOME,
body: 'how are you ?',
};
return new MessageCtrl(iMessage);
}
get bodyFC() {
return this.get(MessageFCN.body) as FormControl;
}
set bodyFC(control: FormControl) {
this.setControl(MessageFCN.body, control);
}
api() {
const message: IMessage = {
subject: this.subject,
body: this.bodyFC.value,
};
return message;
}
}
enum MessageFCN {
body = 'body',
}
/* eslint-disable no-shadow */
import { FormControl, FormGroup } from '@angular/forms';
import { INewBooking, IPlatform } from '../api/bookings.api';
import { Guest, GuestCtrl } from './guest.control';
import { Range, RangeCtrl } from './range.control';
const enum NewBookingFCN {
guest = 'guest',
amount = 'amount',
platform = 'platform',
range = 'range'
}
export class NewBooking {
amount: number;
platform: IPlatform;
guest: Guest;
range: Range;
constructor(iNewBooking: INewBooking) {
this.amount = iNewBooking.amount!;
this.platform = iNewBooking.platform!;
this.range = new Range(iNewBooking.range!);
this.guest = new Guest(iNewBooking.guest!);
}
}
export class NewBookingCtrl extends FormGroup {
constructor(inewbooking: INewBooking) {
super({});
const newbooking = new NewBooking(inewbooking);
this.addControl(NewBookingFCN.amount, new FormControl(newbooking.amount));
this.addControl(NewBookingFCN.platform, new FormControl(newbooking.platform));
this.addControl(NewBookingFCN.range, new RangeCtrl(newbooking.range));
this.addControl(NewBookingFCN.guest, new GuestCtrl(newbooking.guest));
}
api() {
const doc: INewBooking = {
amount: this.amountFC.value,
platform: this.platformFC.value,
range: this.rangeCtrl.api(),
guest: this.guestCtrl.api(),
};
return doc;
}
get guestCtrl() {
return this.get(NewBookingFCN.guest) as GuestCtrl;
}
set guestCtrl(control: GuestCtrl) {
this.setControl(NewBookingFCN.guest, control);
}
get rangeCtrl() {
return this.get(NewBookingFCN.range) as RangeCtrl;
}
set rangeCtrl(control: RangeCtrl) {
this.setControl(NewBookingFCN.range, control);
}
get amountFC() {
return this.get(NewBookingFCN.amount) as FormControl;
}
set amountFC(control: FormControl) {
this.setControl(NewBookingFCN.amount, control);
}
get platformFC() {
return this.get(NewBookingFCN.platform) as FormControl;
}
set platformFC(control: FormControl) {
this.setControl(NewBookingFCN.platform, control);
}
get shortName() {
return this.guestCtrl.givenFC.value + ' (' + this.platformFC.value + ')';
}
}
/* eslint-disable no-shadow */
import { FormControl, FormGroup } from '@angular/forms';
import { IOption } from '../api/commands.api';
const enum OptionFCN {
key = 'key',
value = 'value',
}
export class Option {
key: string;
value: number;
constructor(ioption: IOption) {
this.key = ioption.key;
this.value = ioption.value;
}
}
export class OptionCtrl extends FormGroup {
constructor(ioption: IOption) {
super({});
const option = new Option(ioption);
this.addControl(OptionFCN.key, new FormControl(option.key));
this.addControl(OptionFCN.value, new FormControl(option.value));
}
api() {
const doc: IOption = {
key: this.keyFC.value,
value: this.valueFC.value,
};
return doc;
}
get keyFC() {
return this.get(OptionFCN.key) as FormControl;
}
set keyFC(control: FormControl) {
this.setControl(OptionFCN.key, control);
}
get valueFC() {
return this.get(OptionFCN.value) as FormControl;
}
setvalueFC(control: FormControl) {
this.setControl(OptionFCN.value, control);
}
}
import { FormControl, FormGroup } from '@angular/forms';
import * as moment from 'moment';
import { IRange } from '../api/bookings.api';
export class Range {
startDay: Date;
endDay: Date;
checkin: string;
checkout: string;
constructor(iRange: IRange) {
this.startDay = moment(iRange.start).local().toDate();
this.endDay = moment(iRange.end).local().toDate();
this.checkin = moment(iRange.start).local().format('HH:mm');
this.checkout = moment(iRange.end).local().format('HH:mm');
}
}
export class RangeCtrl extends FormGroup {
constructor(range: Range) {
super({});
this.addControl(RangeFCN.startDay, new FormControl(range.startDay));
this.addControl(RangeFCN.endDay, new FormControl(range.endDay));
this.addControl(RangeFCN.checkin, new FormControl(range.checkin));
this.addControl(RangeFCN.checkout, new FormControl(range.checkout));
}
get startDayFC() {
return this.get(RangeFCN.startDay) as FormControl;
}
set startDayFC(control: FormControl) {
this.setControl(RangeFCN.startDay, control);
}
get endDayFC() {
return this.get(RangeFCN.endDay) as FormControl;
}
set endDayFC(control: FormControl) {
this.setControl(RangeFCN.endDay, control);
}
get checkinFC() {
return this.get(RangeFCN.checkin) as FormControl;
}
set checkinFC(control: FormControl) {
this.setControl(RangeFCN.checkin, control); // TODO update booking state when specifying checkin hour
}
get checkoutFC() {
return this.get(RangeFCN.checkout) as FormControl;
}
set checkoutFC(control: FormControl) {
this.setControl(RangeFCN.checkout, control); // TODO update booking state when specifying checkin hour
}
api() {
const checkin = moment(this.checkinFC.value, 'LTS');
const start = moment(this.startDayFC.value).set({
hour: checkin.get('hour'),
minute: checkin.get('minute'),
});
const checkout = moment(this.checkoutFC.value, 'LTS');
const end = moment(this.endDayFC.value).set({
hour: checkout.get('hour'),
minute: checkout.get('minute'),
});
const range: IRange = {
start: start.format(),
end: end.format(),
};
return range;
}
}
enum RangeFCN {
startDay = 'start',
endDay = 'end',
checkin = 'checkin',
checkout = 'checkout',
}
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { IUser } from '../api/users.api';
import { CalendarCtrl } from './calendar.control';
import { CleanerCtrl } from './cleaner.control';
import { MessageCtrl } from './message.control';
// export class User implements IUser {
// subjectPrefix: string;
// busyCal: ICalendar;
// cleaningCal: ICalendar;
// reelCal: ICalendar;
// address: string
// messages : Message[]
// cleaners : Cleaner[]
// isRegistered : boolean
// autoSend : boolean
// name: string
// tel: string
// }
export class UserCtrl extends FormGroup {
isRegistered: boolean;
constructor(iuser: IUser) {
super({});
this.isRegistered = iuser.isRegistered;
this.addControl(UserFCN.busyCal, new CalendarCtrl(iuser.busyCal));
this.addControl(UserFCN.cleaningCal, new CalendarCtrl(iuser.cleaningCal));
this.addControl(UserFCN.reelCal, new CalendarCtrl(iuser.reelCal));
this.addControl(UserFCN.subjectPrefix, new FormControl(iuser.subjectPrefix));
this.addControl(UserFCN.address, new FormControl(iuser.address));
this.addControl(UserFCN.name, new FormControl(iuser.name));
this.addControl(UserFCN.tel, new FormControl(iuser.tel));
this.addControl(UserFCN.defaultCheckInHour, new FormControl(iuser.defaultCheckInHour));
this.addControl(UserFCN.defaultCheckOutHour, new FormControl(iuser.defaultCheckOutHour));
this.addControl(UserFCN.defaultCleaningHours, new FormControl(iuser.defaultCleaningHours));
this.addControl(UserFCN.messages, new FormArray(iuser.messages.map((imessage) => new MessageCtrl(imessage))));
this.addControl(UserFCN.cleaners, new FormArray(iuser.cleaners.map((icleaner) => new CleanerCtrl(icleaner))));
this.addControl(UserFCN.autoSend, new FormControl(iuser.autoSend));
}
get subjectPrefixFC() {
return this.get(UserFCN.subjectPrefix) as FormControl;
}
set subjectPrefixFC(control: FormControl) {
this.setControl(UserFCN.subjectPrefix, control);
}
get defaultCheckInHourFC() {
return this.get(UserFCN.defaultCheckInHour) as FormControl;
}
set defaultCheckInHourFC(control: FormControl) {
this.setControl(UserFCN.defaultCheckInHour, control);
}
get defaultCleaningHoursFC() {
return this.get(UserFCN.defaultCleaningHours) as FormControl;
}
set defaultCleaningHoursFC(control: FormControl) {
this.setControl(UserFCN.defaultCleaningHours, control);
}
get defaultCheckOutHourFC() {
return this.get(UserFCN.defaultCheckOutHour) as FormControl;
}
set defaultCheckOutHourFC(control: FormControl) {
this.setControl(UserFCN.defaultCheckOutHour, control);
}
get busyCalCtrl() {
return this.get(UserFCN.busyCal) as CalendarCtrl;
}
set busyCalCtrl(control: CalendarCtrl) {
this.setControl(UserFCN.busyCal, control);
}
get cleaningCalCtrl() {
return this.get(UserFCN.cleaningCal) as CalendarCtrl;
}
set cleaningCalCtrl(control: CalendarCtrl) {
this.setControl(UserFCN.cleaningCal, control);
}
get reelCalCtrl() {
return this.get(UserFCN.reelCal) as CalendarCtrl;
}
set reelCalCtrl(control: CalendarCtrl) {
this.setControl(UserFCN.reelCal, control);
}
get nameFC() {
return this.get(UserFCN.name) as FormControl;
}
set nameFC(control: FormControl) {
this.setControl(UserFCN.name, control);
}
get telFC() {
return this.get(UserFCN.tel) as FormControl;
}
set telFC(control: FormControl) {
this.setControl(UserFCN.tel, control);
}
get addressFC() {
return this.get(UserFCN.address) as FormControl;
}
set addressFC(control: FormControl) {
this.setControl(UserFCN.address, control);
}
get messagesFA() {
return this.get(UserFCN.messages) as FormArray;
}
set messagesFA(fa: FormArray) {
this.setControl(UserFCN.messages, fa);
}
get cleanersFA() {
return this.get(UserFCN.cleaners) as FormArray;
}
set cleanersFA(fa: FormArray) {
this.setControl(UserFCN.cleaners, fa);
}
get autoSendFC() {
return this.get(UserFCN.autoSend) as FormControl;
}
set autoSendFC(control: FormControl) {
this.setControl(UserFCN.autoSend, control);
}
api() {
const config: IUser = {
subjectPrefix: this.subjectPrefixFC.value,
defaultCheckInHour: this.defaultCheckInHourFC.value,
defaultCleaningHours: this.defaultCleaningHoursFC.value,
defaultCheckOutHour: this.defaultCheckOutHourFC.value,
busyCal: this.busyCalCtrl.value,
cleaningCal: this.cleaningCalCtrl.value,
reelCal: this.reelCalCtrl.value,
address: this.addressFC.value,
name: this.nameFC.value,
tel: this.telFC.value,
messages: this.messagesFA.controls.map((control) => (control as MessageCtrl).api()),
cleaners: this.cleanersFA.controls.map((control) => (control as CleanerCtrl).api()),
isRegistered: this.isRegistered,
autoSend: this.autoSendFC.value,
};
return config;
}
}
enum UserFCN {
subjectPrefix = 'subjectPrefix',
defaultCheckOutHour = 'defaultCheckOutHour',
defaultCleaningHours = 'defaultCleaningHours',
defaultCheckInHour = 'defaultCheckInHour',
busyCal = 'busyCal',
cleaningCal = 'cleaningCal',
reelCal = 'reelCal',
address = 'address',
name = 'name',
tel = 'tel',
messages = 'messages',
cleaners = 'cleaners',
autoSend = 'autoSend',
}
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { UserService } from 'src/app/services/user.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private userService: UserService) { }
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.userService.isAuthenticated$.pipe(tap((x) => console.log('You tried to go to ' + state.url + ' and AuthGuard said ' + x)));
}
}
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorInterceptor } from './error.interceptor';
import { SpinnerInterceptor } from './spinner.interceptor';
import { TokenInterceptor } from './token.interceptor';
export const InterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: SpinnerInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
];
/* eslint-disable @typescript-eslint/naming-convention */
import { HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService } from '../services/user.service';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(public userService: UserService) { }
intercept(request: HttpRequest<any>, next: HttpHandler) {
const accessToken = this.userService.accessToken!;
const idToken = this.userService.idToken!;
// Clone the request and set the new header in one step.
const authReq = request.clone({
setHeaders: {
'Content-Type': 'application/json',
'access-token': accessToken,
'id-token': idToken,
},
});
console.error(request.method + ' ' + request.url);
// send cloned request with header to the next handler.
return next.handle(authReq);
}
}
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { ErrorDialogComponent } from '../components/error-dialog/error-dialog.component';
import { NewBookingDialogComponent } from '../components/new-booking-dialog/new-booking.component';
import { NewBookingCtrl } from '../controls/newbooking.control';
@Injectable({
providedIn: 'root',
......@@ -36,22 +32,6 @@ export class DialogService {
});
}
openNewBookingDialog$(control: NewBookingCtrl) {
if (this.isDialogOpen) {
return of(undefined);
}
this.isDialogOpen = true;
const dialogRef = this._dialog.open(NewBookingDialogComponent, {
width: '800px',
data: control,
restoreFocus: false
});
return dialogRef.afterClosed().pipe(tap(() => this.isDialogOpen = false));
}
openSnackBar(title: string): void {
this._snackBar.open(title, undefined, {
duration: 500,
......
import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/compat/auth/';
import { Router } from '@angular/router';
import firebase from 'firebase/compat/app';
import { BehaviorSubject } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { UsersApi } from '../api/users.api';
import { UserCtrl } from '../controls/user.control';
import { WorkerService } from './worker.service';
const USER_SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/contacts',
'https://www.googleapis.com/auth/drive',
];
export const FIREBASE = {
apiKey: 'AIzaSyC5d_2nWqOViYzJltaOKKgCS9RR7aABYZg',
authDomain: 'serema-2d3df.firebaseapp.com',
databaseURL: 'https://serema-2d3df.firebaseio.com',
projectId: 'serema-2d3df',
storageBucket: 'serema-2d3df.appspot.com',
messagingSenderId: '126536035414',
appId: '1:126536035414:web:fa50b91256a64a3d9f9ff7',
measurementId: 'G-LRC0R7PPJQ',
};
@Injectable({
providedIn: 'root',
})
export class UserService {
public isAuthenticated$ = new BehaviorSubject<boolean>(false);
public userCtrl$ = new BehaviorSubject<UserCtrl | undefined>(undefined);
constructor(public afa: AngularFireAuth, public usersApi: UsersApi, public router: Router, public workerService: WorkerService) { }
get isRegistered$() {
return this.userCtrl$.pipe(map((userCtrl) => (userCtrl ? userCtrl.isRegistered : undefined)));
}
get userCtrl() {
return this.userCtrl$.getValue();
}
public register() {
this.workerService.loginRedirect();
}
public unregister() {
this.workerService.logout();
const userCtrl = this.userCtrl$.getValue();
if (userCtrl) {
userCtrl.isRegistered = false;
userCtrl.autoSendFC.setValue(false);
this.update$(userCtrl).subscribe((_) => {
this.userCtrl$.next(userCtrl);
});
} else {
// TODO
}
}
public onRegisterSuccess() {
this.workerService.loginRedirectCallback();
const userCtrl = this.userCtrl;
if (userCtrl) {
userCtrl.isRegistered = true;
this.userCtrl$.next(userCtrl);
} else {
// TODO
}
}
public login() {
const provider = new firebase.auth.GoogleAuthProvider();
USER_SCOPES.forEach((scope) => provider.addScope(scope));
// POPUP
return this.afa
.signInWithPopup(provider)
.then((result) => {
this.isAuthenticated$.next(true);
localStorage.setItem('authResult', JSON.stringify(result));
this.usersApi
.read$()
.pipe(map((iuser) => new UserCtrl(iuser)))
.subscribe((userCtrl) => {
this.userCtrl$ = new BehaviorSubject<UserCtrl | undefined>(userCtrl);
this.router.navigate(['/bookings']);
});
})
.catch((error) => {
window.alert(error);
});
}
public logout() {
return this.afa.signOut().then(() => {
localStorage.removeItem('authResult');
this.isAuthenticated$.next(false);
this.router.navigate(['/']);
});
}
private get authResult() {
const authResult = localStorage.getItem('authResult');
if (authResult) {
return JSON.parse(authResult);
} else {
return undefined;
}
}
get email() {
return this.authResult ? this.authResult.user.email : undefined;
}
get accessToken() {
return this.authResult ? this.authResult.credential.accessToken : undefined;
}
get idToken() {
return this.authResult ? this.authResult.credential.idToken : undefined;
}
read$() {
this.userCtrl$.next(undefined);
return this.usersApi.read$().pipe(
map((iuser) => new UserCtrl(iuser)),
tap((userCtrl) => this.userCtrl$.next(userCtrl)),
);
}
refresh$() {
this.userCtrl$.next(undefined);
return this.usersApi.refresh$().pipe(
map((iuser) => new UserCtrl(iuser)),
tap((userCtrl) => this.userCtrl$.next(userCtrl)),
);
}
update$(userCtrl: UserCtrl) {
this.userCtrl$.next(undefined);
return this.usersApi.update$(userCtrl.api()).pipe(tap((_) => this.userCtrl$.next(userCtrl)));
}
}
/* eslint-disable @typescript-eslint/naming-convention */
import { Injectable } from '@angular/core';
import { GoogleAuthService, NgGapiClientConfig } from 'ng-gapi';
import { environment } from 'src/environments/environment';
import { AutoSendApi } from '../api/autosend.api';
import GoogleUser = gapi.auth2.GoogleUser;
const WORKER_SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/contacts',
'https://www.googleapis.com/auth/drive',
];
export const gapiClientConfig: NgGapiClientConfig = {
discoveryDocs: ['https://analyticsreporting.googleapis.com/$discovery/rest?version=v4'],
/**
* The app's client ID, found and created in the Google Developers Console.
*/
// client_id?: string;
client_id: environment.WORKER_CLIENT_ID,
/**
* The domains for which to create sign-in cookies. Either a URI, single_host_origin, or none.
* Defaults to single_host_origin if unspecified.
*/
// cookie_policy: ,
/**
* The scopes to request, as a space-delimited string. Optional if fetch_basic_profile is not set to false.
*/
// scope?: string;
scope: WORKER_SCOPES.join(' '),
/**
* Fetch users' basic profile information when they sign in. Adds 'profile' and 'email' to the requested scopes. True if unspecified.
*/
// fetch_basic_profile?: boolean;
/**
* The Google Apps domain to which users must belong to sign in. This is susceptible to modification by clients,
* so be sure to verify the hosted domain property of the returned user. Use GoogleUser.getHostedDomain() on the client,
* and the hd claim in the ID Token on the server to verify the domain is what you expected.
*/
// hosted_domain?: string;
/**
* Used only for OpenID 2.0 client migration. Set to the value of the realm that you are currently using for OpenID 2.0,
* as described in <a href="https://developers.google.com/accounts/docs/OpenID#openid-connect">OpenID 2.0 (Migration)</a>.
*/
// openid_realm?: string;
/**
* The UX mode to use for the sign-in flow.
* By default, it will open the consent flow in a popup.
*/
// ux_mode?: "popup" | "redirect";
ux_mode: 'redirect',
/**
* If using ux_mode='redirect', this parameter allows you to override the
* default redirect_uri that will be used at the end of the consent flow.
* The default redirect_uri is the current URL stripped of query parameters and hash fragment.
*/
redirect_uri: window.location.origin + '/oauth2callback',
};
@Injectable({
providedIn: 'root',
})
export class WorkerService {
private user?: GoogleUser;
constructor(private autosendApi: AutoSendApi, private googleAuthService: GoogleAuthService) { }
get email() {
return this.user ? this.user.getBasicProfile().getEmail() : undefined;
}
// popup
public loginPopup() {
this.googleAuthService.getAuth().subscribe(async (auth) => {
this.user = await auth.signIn();
});
}
// redirect
public loginRedirect() {
this.googleAuthService.getAuth().subscribe(async (auth) => {
const { code } = await auth.grantOfflineAccess();
this.autosendApi.register$(code).subscribe();
});
}
public loginRedirectCallback() {
this.googleAuthService.getAuth().subscribe((auth) => {
this.user = auth.currentUser.get();
});
}
public logout() {
this.googleAuthService.getAuth().subscribe((auth) => {
auth.signOut();
});
}
}
......@@ -4,7 +4,5 @@
export const environment = {
production: true,
backend: 'https://lsd.nutridata.io/api',
// backend: 'https://lsdapi.herokuapp.com',
WORKER_CLIENT_ID: '126536035414-pav49qj5h49rctdrj35ok4amqjnjje1m.apps.googleusercontent.com',
backend: 'http://localhost:3000/api' //FIXME
};
......@@ -4,8 +4,7 @@
export const environment = {
production: false,
backend: 'http://localhost:3000/api',
WORKER_CLIENT_ID: '126536035414-pav49qj5h49rctdrj35ok4amqjnjje1m.apps.googleusercontent.com',
backend: 'http://localhost:3000/api'
};
/*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment