Commit 5861a701 authored by El Mghazli Yacine's avatar El Mghazli Yacine

ngx initial commit before upgrade

parent a981291b
......@@ -10,7 +10,8 @@
"request": "launch",
"program": "${workspaceFolder}/cmake_targets/ran_build/build/lte-softmodem",
"args": [
"-O", "../ci-scripts/conf_files/rcc.band7.tm1.nfapi.conf",
"-O",
"../ci-scripts/conf_files/rcc.band7.tm1.nfapi.conf",
"--noS1"
],
"stopAtEntry": false,
......@@ -26,6 +27,44 @@
"ignoreFailures": true
}
]
},
{
"name": "Launch Chrome (4200 ng serve)",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}"
},
{
"name": "Launch Firefox (4200 ng serve)",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost:4200",
"pathMappings": [
{
"url": "webpack:///src",
"path": "${workspaceFolder}/src"
}
]
},
{
"name": "Launch Chrome (localhost)",
"type": "chrome",
"request": "launch",
"url": "http://localhost",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
},
"trace": true,
"runtimeArgs": [
"--remote-debugging-port=9222"
]
}
]
}
}
\ No newline at end of file
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
**/.classpath
**/.dockerignore
**/.env
/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/charts
**/docker-compose*
**/Dockerfile*
/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
README.md
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
/artifacts
/builds
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
config.toml
stages:
- install
- build
- publish
- deploy
variables:
TRION_CLI_VERSION: 12.2.4
.install:
cache:
key:
files:
- package-lock.json
paths:
- .npm
- node_modules
policy: pull
only:
- master
- dev
#
#jobs
#
install:
stage: install
extends: .install
image: trion/ng-cli:${TRION_CLI_VERSION}
script:
- npm ci
cache:
policy: pull-push
only:
changes:
- package-lock.json
build:
stage: build
extends: .install
image: trion/ng-cli-karma:${TRION_CLI_VERSION}
script:
- npm run lint
- npm run build
artifacts:
expire_in: 1 day
paths:
- dist/lsd-ngx
on-gitlab:
stage: publish
dependencies:
- build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:latest
only:
- dev
- master
on-heroku:
stage: publish
dependencies:
- build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
before_script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"registry.heroku.com\":{\"username\":\"_\",\"password\":\"$HEROKU_API_KEY\"}}}" > /kaniko/.docker/config.json
script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination registry.heroku.com/$HEROKU_APP_NAME/web
only:
- dev
herokuapp:
stage: deploy
image: node:lts-alpine
before_script:
- apk update && apk add curl bash
- rm -rf /var/cache/apk/*
- curl https://cli-assets.heroku.com/install.sh | sh
script:
- heroku container:release web --app $HEROKU_APP_NAME
environment:
name: heroku-dev
url: https://lsdngx.herokuapp.com
only:
- dev
on-gke:
stage: deploy
image: alpine/k8s:1.20.7
script:
- kubectl config use-context lsd5/k8s:gke-agent
- kubectl apply -f k8s/manifest.yaml --namespace=$NAMESPACE
environment:
name: gke-master
url: https://lsd.nutridata.io
only:
- master
FROM nginx:alpine
COPY dist/lsd-ngx /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
# sed replace $PORT by $PORT at runtime (on heroku with the correct port)
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": false,
"defaultCollection": "@angular-eslint/schematics"
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
"lsdngx": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/lsd-ngx",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/purple-green.css",
"src/styles.css"
],
"scripts": [],
"allowedCommonJsDependencies": [
"@firebase/auth",
"firebase"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "lsdngx:build:production"
},
"development": {
"browserTarget": "lsdngx:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "lsdngx:build"
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"defaultProject": "lsdngx"
}
\ No newline at end of file
version: '3.4'
services:
backend:
container_name: api.dev
image: registry.gitlab.com/lsd5/backend/dev:latest
pull_policy: always
environment:
- NODE_ENV=development
ports:
- 3000:3000
- 9229:9229
command:
[
'node',
'--inspect=0.0.0.0:9229',
'-r',
'module-alias/register',
'dist/main.js'
]
frontend:
container_name: ngx.dev
image: registry.gitlab.com/lsd5/ngx/dev:latest
pull_policy: always
# build:
# context: .
# dockerfile: ./Dockerfile
environment:
- PORT=80
ports:
- 80:80
# DO NOT USE command : the Dockerfile CMD must be called in order to replace $PORT with sed
mongo:
container_name: db.dev
image: mongo:5
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=dev
ports:
- 27017:27017
command: mongod
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: ['--headless', '--no-sandbox']
}
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () { }
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
\ No newline at end of file
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () { }
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
import { browser, logging } from 'protractor';
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
// it('should display welcome message', async () => {
// await page.navigateTo();
// expect(await page.getTitleText()).toEqual('example app is running!');
// });
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
}
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": ["jasmine", "node"]
}
}
server {
listen 0.0.0.0:$PORT;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
This diff is collapsed.
{
"name": "lsdngx",
"version": "2.0.0",
"scripts": {
"lint": "ng lint",
"build": "ng build",
"dev": "ng serve --configuration=development",
"prod": "ng serve"
},
"private": true,
"dependencies": {
"@angular/animations": "~12.2.4",
"@angular/cdk": "~12.2.4",
"@angular/common": "~12.2.4",
"@angular/compiler": "~12.2.4",
"@angular/core": "~12.2.4",
"@angular/fire": "^7.0.3",
"@angular/flex-layout": "^12.0.0-beta.34",
"@angular/forms": "~12.2.4",
"@angular/material": "^12.2.4",
"@angular/platform-browser": "~12.2.4",
"@angular/platform-browser-dynamic": "~12.2.4",
"@angular/router": "~12.2.4",
"rxjs": "^6.6.6",
"tslib": "^2.0.0",
"zone.js": "^0.11.4"
},
"devDependencies": {
"@angular-devkit/architect": "^0.1200.0",
"@angular-devkit/build-angular": "~12.2.4",
"@angular-eslint/builder": "12.1.0",
"@angular-eslint/eslint-plugin": "12.1.0",
"@angular-eslint/eslint-plugin-template": "12.1.0",
"@angular-eslint/schematics": "12.5.0",
"@angular-eslint/template-parser": "12.1.0",
"@angular/cli": "~12.2.4",
"@angular/compiler-cli": "~12.2.4",
"@angular/language-service": "^12.2.4",
"@types/node": "^12.20.23",
"@typescript-eslint/eslint-plugin": "4.23.0",
"@typescript-eslint/parser": "4.23.0",
"eslint": "^7.26.0",
"firebase-tools": "^9.0.0",
"fs-extra": "^10.0.0",
"fuzzy": "^0.1.3",
"husky": "^5.1.3",
"inquirer": "^6.2.2",
"inquirer-autocomplete-prompt": "^1.0.1",
"jasmine-core": "~3.7.1",
"jasmine-spec-reporter": "~5.0.0",
"jsonc-parser": "^3.0.0",
"open": "^7.0.3",
"ts-node": "^8.10.2",
"tslint-angular": "^3.0.3",
"typescript": "~4.2.4"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged && ng lint && ng test",
"pre-push": "ng build --aot true"
}
}
}
\ No newline at end of file
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 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';
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: '**', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule],
})
export class AppRoutingModule { }
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'lsd-ngx';
}
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';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
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';
@NgModule({
declarations: [
AppComponent,
BookingsComponent,
YearComponent,
MessagesComponent,
UserComponent,
CallbackComponent,
NavComponent,
ErrorDialogComponent,
NewBookingDialogComponent,
CleanersComponent,
NewCleanerDialogComponent,
],
imports: [
GoogleApiModule.forRoot({ provide: NG_GAPI_CONFIG, useValue: gapiClientConfig }),
AngularFireModule.initializeApp(FIREBASE),
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
HttpClientModule,
ClipboardModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
MatChipsModule,
MatProgressSpinnerModule,
MatIconModule,
MatButtonModule,
FlexLayoutModule,
MatDatepickerModule,
MatNativeDateModule,
MatToolbarModule,
MatSidenavModule,
MatListModule,
MatTableModule,
MatPaginatorModule,
MatSelectModule,
MatDialogModule,
MatSnackBarModule,
MatSlideToggleModule,
MatGridListModule,
MatCardModule,
MatMenuModule,
NgxMaterialTimepickerModule.setLocale('fr-FR'),
],
providers: [
// services
LoadingService,
UserService,
WorkerService,
// api
BookingsApi,
UsersApi,
// interceptors
InterceptorProviders,
],
bootstrap: [AppComponent],
entryComponents: [ErrorDialogComponent, NewBookingDialogComponent, NewCleanerDialogComponent],
})
export class AppModule { }
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;
}
This diff is collapsed.
/* 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
}
});
}
}
<div fxLayout="column" fxLayoutAlign="center center">
<h3>{{ data.title }}</h3>
<p>{{ data.body }}</p>
</div>
\ No newline at end of file
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-error-dialog',
templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.css'],
})
export class ErrorDialogComponent {
JSON = JSON
title = 'Angular-Interceptor';
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {
}
}
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());
}
}
.sidenav-container {
height: 100%;
}
.sidenav {
width: 200px;
}
.sidenav .mat-toolbar {
background: inherit;
}
.mat-toolbar.mat-primary {
position: sticky;
top: 0;
z-index: 1;
}
<mat-sidenav-container class="sidenav-container">
<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>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar>
<mat-toolbar-row fxLayout="row" fxLayoutAlign="space-between center">
<ng-template #not_loading>
<button mat-icon-button (click)="drawer.toggle()">
<mat-icon>menu</mat-icon>
</button>
<h1>LSD</h1>
</ng-template>
<ng-template #loading>
<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>
<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>
</mat-toolbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
\ No newline at end of file
import { Component } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
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({
selector: 'app-nav',
templateUrl: './nav.component.html',
styleUrls: ['./nav.component.css'],
})
export class NavComponent {
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
map((result) => result.matches),
shareReplay(),
);
constructor(
public loadingService: LoadingService, public userService: UserService, 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',
}
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 + ')';
}
}
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 { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { DialogService as DialogService } from '../services/dialog.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(public dialogService: DialogService) { }
intercept(request: HttpRequest<unknown>, next: HttpHandler) {
return next.handle(request).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
switch (event.status) {
case 201:
this.log(GREEN, request.method + ' ' + event.status + ' Success');
this.dialogService.openSnackBar(request.method + ' 201 Success');
break;
default:
break;
}
// return throwError(event.body);
}
}),
catchError((error: HttpErrorResponse) => {
switch (error.status) {
case 400:
case 403:
case 200:
case 500:
this.log(YELLOW, request.method + ' ' + error.status + ' Error: ' + error.error);
this.dialogService.openErrorDialog({
title: error.status + ' Error',
body: error.error,
});
break;
default:
break;
}
return throwError(error.error);
}),
);
}
private log = (color: string, txt: string) => console.log(color, '[API] ' + txt);
}
const RED = '\x1b[31m%s\x1b[0m';
const GREEN = '\x1b[32m%s\x1b[0m';
const YELLOW = '\x1b[33m%s\x1b[0m';
const MAGENTA = '\x1b[35m%s\x1b[0m';
const CYAN = '\x1b[36m%s%s\x1b[0m';
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 },
];
import { finalize } from 'rxjs/operators';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { LoadingService } from '../services/loading.service';
@Injectable()
export class SpinnerInterceptor implements HttpInterceptor {
activeRequests = 0;
constructor(private loadingService: LoadingService) { }
intercept(request: HttpRequest<unknown>, next: HttpHandler) {
if (this.activeRequests === 0) {
this.loadingService.startLoading();
}
this.activeRequests++;
return next.handle(request).pipe(
finalize(() => {
this.activeRequests--;
if (this.activeRequests === 0) {
this.loadingService.stopLoading();
}
}),
);
}
}
/* 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',
})
export class DialogService {
public isDialogOpen = false;
constructor(
private _dialog: MatDialog,
private _snackBar: MatSnackBar,
) { }
openErrorDialog(data: any): any {
if (this.isDialogOpen) {
return false;
}
this.isDialogOpen = true;
const dialogRef = this._dialog.open(ErrorDialogComponent, {
width: '300px',
data,
});
dialogRef.afterClosed().subscribe((_) => {
console.log('The dialog was closed');
this.isDialogOpen = false;
});
}
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,
horizontalPosition: 'center',
verticalPosition: 'bottom',
});
}
}
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class LoadingService {
isLoading$ = new Subject<boolean>();
constructor() { }
startLoading() {
this.isLoading$.next(true);
}
stopLoading() {
this.isLoading$.next(false);
}
}
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();
});
}
}
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: true,
backend: 'https://lsd.nutridata.io/api',
// backend: 'https://lsdapi.herokuapp.com',
WORKER_CLIENT_ID: '126536035414-pav49qj5h49rctdrj35ok4amqjnjje1m.apps.googleusercontent.com',
};
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
backend: 'http://localhost:3000/api',
WORKER_CLIENT_ID: '126536035414-pav49qj5h49rctdrj35ok4amqjnjje1m.apps.googleusercontent.com',
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>LsdNgx</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body class="mat-typography">
<app-root></app-root>
</body>
</html>
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* IE11 requires the following for NgClass support on SVG elements
*/
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/* You can add global styles to this file, and also import other style files */
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp,
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"]
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
\ No newline at end of file
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jasmine"]
},
"files": ["src/test.ts", "src/polyfills.ts"],
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}
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