first commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthGuard } from './core/guards/auth.guard';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'auth',
|
||||
loadChildren: () => import('./features/auth/auth.module').then(m => m.AuthModule),
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./features/dashboard/dashboard.module').then(m => m.DashboardModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'sales',
|
||||
loadChildren: () => import('./features/sales/sales.module').then(m => m.SalesModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'favorites',
|
||||
loadChildren: () => import('./features/favorites/favorites.module').then(m => m.FavoritesModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'pictures',
|
||||
loadChildren: () => import('./features/pictures/pictures.module').then(m => m.PicturesModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'customers',
|
||||
loadChildren: () => import('./features/customers/customers.module').then(m => m.CustomersModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
loadChildren: () => import('./features/users/users.module').then(m => m.UsersModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./features/account/account.module').then(m => m.AccountModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'icons',
|
||||
loadChildren: () => import('./features/icons/icons.module').then(m => m.IconsModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'typography',
|
||||
loadChildren: () => import('./features/typography/typography.module').then(m => m.TypographyModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: () => import('./features/about/about.module').then(m => m.AboutModule),
|
||||
canActivate: [AuthGuard]
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: 'dashboard',
|
||||
pathMatch: 'full'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(appRoutes)
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: []
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `<router-outlet></router-outlet>`
|
||||
})
|
||||
export class AppComponent {}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { CustomMaterialModule } from './custom-material/custom-material.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { LoggerModule } from 'ngx-logger';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
CoreModule,
|
||||
SharedModule,
|
||||
CustomMaterialModule.forRoot(),
|
||||
AppRoutingModule,
|
||||
LoggerModule.forRoot({
|
||||
serverLoggingUrl: `http://my-api/logs`,
|
||||
level: environment.logLevel,
|
||||
serverLogLevel: environment.serverLogLevel
|
||||
})
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,49 @@
|
||||
import { NgModule, Optional, SkipSelf, ErrorHandler } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { MediaMatcher } from '@angular/cdk/layout';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
||||
import { SpinnerInterceptor } from './interceptors/spinner.interceptor';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
import { throwIfAlreadyLoaded } from './guards/module-import.guard';
|
||||
import { GlobalErrorHandler } from './services/globar-error.handler';
|
||||
import { AdminGuard } from './guards/admin.guard';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
HttpClientModule
|
||||
],
|
||||
declarations: [
|
||||
],
|
||||
providers: [
|
||||
AuthGuard,
|
||||
AdminGuard,
|
||||
MediaMatcher,
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: SpinnerInterceptor,
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: GlobalErrorHandler
|
||||
},
|
||||
{ provide: NGXLogger, useClass: NGXLogger },
|
||||
{ provide: 'LOCALSTORAGE', useValue: window.localStorage }
|
||||
],
|
||||
exports: [
|
||||
]
|
||||
})
|
||||
export class CoreModule {
|
||||
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
throwIfAlreadyLoaded(parentModule, 'CoreModule');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { AdminGuard } from './admin.guard';
|
||||
|
||||
describe('AdminGuard', () => {
|
||||
|
||||
let router;
|
||||
let authService;
|
||||
|
||||
beforeEach(() => {
|
||||
router = jasmine.createSpyObj(['navigate']);
|
||||
authService = jasmine.createSpyObj(['getCurrentUser']);
|
||||
});
|
||||
|
||||
it('create an instance', () => {
|
||||
const guard = new AdminGuard(router, authService);
|
||||
expect(guard).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns true if user is admin', () => {
|
||||
const user = { 'isAdmin': true };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AdminGuard(router, authService);
|
||||
|
||||
const result = guard.canActivate();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if user does not exist', () => {
|
||||
authService.getCurrentUser.and.returnValue(null);
|
||||
const guard = new AdminGuard(router, authService);
|
||||
|
||||
const result = guard.canActivate();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if user is not admin', () => {
|
||||
const user = { 'isAdmin': false };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AdminGuard(router, authService);
|
||||
|
||||
const result = guard.canActivate();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('redirects to root if user is not an admin', () => {
|
||||
const user = { 'isAdmin': false };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AdminGuard(router, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/']);
|
||||
});
|
||||
|
||||
it('redirects to root if user does not exist', () => {
|
||||
authService.getCurrentUser.and.returnValue(null);
|
||||
const guard = new AdminGuard(router, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/']);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthenticationService } from '../services/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminGuard {
|
||||
|
||||
constructor(private router: Router,
|
||||
private authService: AuthenticationService) { }
|
||||
|
||||
canActivate() {
|
||||
const user = this.authService.getCurrentUser();
|
||||
|
||||
if (user && user.isAdmin) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import * as moment from 'moment';
|
||||
|
||||
describe('AuthGuard', () => {
|
||||
|
||||
let router;
|
||||
let authService;
|
||||
let notificationService;
|
||||
|
||||
beforeEach(() => {
|
||||
router = jasmine.createSpyObj(['navigate']);
|
||||
authService = jasmine.createSpyObj(['getCurrentUser']);
|
||||
notificationService = jasmine.createSpyObj(['openSnackBar']);
|
||||
});
|
||||
|
||||
it('create an instance', () => {
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
expect(guard).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false if user is null', () => {
|
||||
authService.getCurrentUser.and.returnValue(null);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
const result = guard.canActivate();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('redirects to login if user is null', () => {
|
||||
authService.getCurrentUser.and.returnValue(null);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['auth/login']);
|
||||
});
|
||||
|
||||
it('does not display expired notification if user is null', () => {
|
||||
authService.getCurrentUser.and.returnValue(null);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(notificationService.openSnackBar).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('redirects to login if user session has expired', () => {
|
||||
const user = { expiration: moment().add(-1, 'minutes') };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['auth/login']);
|
||||
});
|
||||
|
||||
it('displays notification if user session has expired', () => {
|
||||
const user = { expiration: moment().add(-1, 'seconds') };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
guard.canActivate();
|
||||
|
||||
expect(notificationService.openSnackBar)
|
||||
.toHaveBeenCalledWith('Your session has expired');
|
||||
});
|
||||
|
||||
it('returns true if user session is valid', () => {
|
||||
const user = { expiration: moment().add(1, 'minutes') };
|
||||
authService.getCurrentUser.and.returnValue(user);
|
||||
const guard = new AuthGuard(router, notificationService, authService);
|
||||
|
||||
const result = guard.canActivate();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { AuthenticationService } from '../services/auth.service';
|
||||
import { NotificationService } from '../services/notification.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard {
|
||||
|
||||
constructor(private router: Router,
|
||||
private notificationService: NotificationService,
|
||||
private authService: AuthenticationService) { }
|
||||
|
||||
canActivate() {
|
||||
const user = this.authService.getCurrentUser();
|
||||
|
||||
if (user && user.expiration) {
|
||||
|
||||
if (moment() < moment(user.expiration)) {
|
||||
return true;
|
||||
} else {
|
||||
this.notificationService.openSnackBar('Your session has expired');
|
||||
this.router.navigate(['auth/login']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(['auth/login']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
|
||||
if (parentModule) {
|
||||
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
|
||||
import { HttpRequest } from '@angular/common/http';
|
||||
import { HttpHandler } from '@angular/common/http';
|
||||
import { HttpEvent } from '@angular/common/http';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { AuthenticationService } from '../services/auth.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private authService: AuthenticationService,
|
||||
private router: Router,
|
||||
private dialog: MatDialog) { }
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
const user = this.authService.getCurrentUser();
|
||||
|
||||
if (user && user.token) {
|
||||
|
||||
const cloned = req.clone({
|
||||
headers: req.headers.set('Authorization',
|
||||
'Bearer ' + user.token)
|
||||
});
|
||||
|
||||
return next.handle(cloned).pipe(tap(() => { }, (err: any) => {
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
if (err.status === 401) {
|
||||
this.dialog.closeAll();
|
||||
this.router.navigate(['/auth/login']);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
} else {
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpResponse } from '@angular/common/http';
|
||||
import { HttpRequest } from '@angular/common/http';
|
||||
import { HttpHandler } from '@angular/common/http';
|
||||
import { HttpEvent } from '@angular/common/http';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { SpinnerService } from './../services/spinner.service';
|
||||
|
||||
@Injectable()
|
||||
export class SpinnerInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private spinnerService: SpinnerService) { }
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
this.spinnerService.show();
|
||||
|
||||
return next
|
||||
.handle(req)
|
||||
.pipe(
|
||||
tap((event: HttpEvent<any>) => {
|
||||
if (event instanceof HttpResponse) {
|
||||
this.spinnerService.hide();
|
||||
}
|
||||
}, (error) => {
|
||||
this.spinnerService.hide();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { LotInfo } from './model/lotInfo.interface';
|
||||
import { SaleInfo } from './model/saleInfo.interface';
|
||||
import { Sale } from './model/sale.interface';
|
||||
import { Lot } from './model/lot.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiService {
|
||||
ApiURL = "http://localhost:3000/api";
|
||||
|
||||
constructor(private http: HttpClient){}
|
||||
|
||||
// Lot
|
||||
getLotInfo(url: string): Observable<LotInfo> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<LotInfo>(this.ApiURL+'/lot/getInfos/'+encodeUrl);
|
||||
}
|
||||
|
||||
getPictures(url: string): Observable<String[]> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<String[]>(this.ApiURL+'/lot/getPictures/'+encodeUrl);
|
||||
}
|
||||
|
||||
getLotsBySale(_id: String): Observable<Lot[]> {
|
||||
return this.http.get<Lot[]>(this.ApiURL+'/lot/getLotsBySale/'+_id);
|
||||
}
|
||||
|
||||
// Sale
|
||||
getSaleInfos(url: string): Observable<SaleInfo> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
|
||||
return this.http.get<SaleInfo>(this.ApiURL+'/sale/getSaleInfos/'+encodeUrl);
|
||||
}
|
||||
|
||||
prepareSale(saleInfo: SaleInfo): Observable<any> {
|
||||
|
||||
let follow = this.http.get<any>(this.ApiURL+'/sale/prepareSale/'+saleInfo._id);
|
||||
return follow
|
||||
}
|
||||
|
||||
followSale(saleInfo: SaleInfo): Observable<any> {
|
||||
|
||||
let follow = this.http.get<any>(this.ApiURL+'/sale/followSale/'+saleInfo._id);
|
||||
return follow
|
||||
}
|
||||
|
||||
// CRUD DB Sale
|
||||
getSale(_id: String): Observable<Sale> {
|
||||
return this.http.get<Sale>(this.ApiURL+'/sale/sale/'+_id);
|
||||
}
|
||||
|
||||
saveSale(saleInfo: SaleInfo): Observable<SaleInfo> {
|
||||
return this.http.post<SaleInfo>(this.ApiURL+'/sale/sale', saleInfo);
|
||||
}
|
||||
|
||||
updateSale(saleInfo: SaleInfo): Observable<SaleInfo> {
|
||||
return this.http.put<SaleInfo>(this.ApiURL+'/sale/sale/'+saleInfo._id, saleInfo);
|
||||
}
|
||||
|
||||
deleteSale(_id: String): Observable<any> {
|
||||
return this.http.delete<any>(this.ApiURL+'/sale/sale/'+_id);
|
||||
}
|
||||
|
||||
// Function DB Sale
|
||||
|
||||
getAllSale(): Observable<SaleInfo[]> {
|
||||
return this.http.get<SaleInfo[]>(this.ApiURL+'/sale/getAll');
|
||||
}
|
||||
|
||||
postProcessing(_id: String): Observable<any> {
|
||||
return this.http.get<any>(this.ApiURL+'/sale/postProcessing/'+_id);
|
||||
}
|
||||
|
||||
|
||||
//Favorites
|
||||
saveFavorite(lotInfo: LotInfo, saleInfo: SaleInfo, picture: String, dateTime: string, buyProject: boolean, maxPrice: number, Note: string): Observable<any> {
|
||||
return this.http.post(this.ApiURL+'/favorite/save', {lotInfo, saleInfo, picture, dateTime, buyProject, maxPrice, Note});
|
||||
}
|
||||
|
||||
getAllFavorite(): Observable<any> {
|
||||
return this.http.get(this.ApiURL+'/favorite/getAll');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { delay, map } from 'rxjs/operators';
|
||||
import * as jwt_decode from 'jwt-decode';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { of, EMPTY } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthenticationService {
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
@Inject('LOCALSTORAGE') private localStorage: Storage) {
|
||||
}
|
||||
|
||||
login(email: string, password: string) {
|
||||
return of(true)
|
||||
.pipe(delay(1000),
|
||||
map((/*response*/) => {
|
||||
// set token property
|
||||
// const decodedToken = jwt_decode(response['token']);
|
||||
|
||||
// store email and jwt token in local storage to keep user logged in between page refreshes
|
||||
this.localStorage.setItem('currentUser', JSON.stringify({
|
||||
token: 'aisdnaksjdn,axmnczm',
|
||||
isAdmin: true,
|
||||
email: 'john.doe@gmail.com',
|
||||
id: '12312323232',
|
||||
alias: 'john.doe@gmail.com'.split('@')[0],
|
||||
expiration: moment().add(1, 'days').toDate(),
|
||||
fullName: 'John Doe'
|
||||
}));
|
||||
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
// clear token remove user from local storage to log user out
|
||||
this.localStorage.removeItem('currentUser');
|
||||
}
|
||||
|
||||
getCurrentUser(): any {
|
||||
// TODO: Enable after implementation
|
||||
// return JSON.parse(this.localStorage.getItem('currentUser'));
|
||||
return {
|
||||
token: 'aisdnaksjdn,axmnczm',
|
||||
isAdmin: true,
|
||||
email: 'john.doe@gmail.com',
|
||||
id: '12312323232',
|
||||
alias: 'john.doe@gmail.com'.split('@')[0],
|
||||
expiration: moment().add(1, 'days').toDate(),
|
||||
fullName: 'John Doe'
|
||||
};
|
||||
}
|
||||
|
||||
passwordResetRequest(email: string) {
|
||||
return of(true).pipe(delay(1000));
|
||||
}
|
||||
|
||||
changePassword(email: string, currentPwd: string, newPwd: string) {
|
||||
return of(true).pipe(delay(1000));
|
||||
}
|
||||
|
||||
passwordReset(email: string, token: string, password: string, confirmPassword: string): any {
|
||||
return of(true).pipe(delay(1000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ErrorHandler, Injectable, Injector } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler implements ErrorHandler {
|
||||
|
||||
|
||||
constructor(private injector: Injector) { }
|
||||
|
||||
handleError(error: Error) {
|
||||
// Obtain dependencies at the time of the error
|
||||
// This is because the GlobalErrorHandler is registered first
|
||||
// which prevents constructor dependency injection
|
||||
const logger = this.injector.get(NGXLogger);
|
||||
|
||||
const err = {
|
||||
message: error.message ? error.message : error.toString(),
|
||||
stack: error.stack ? error.stack : ''
|
||||
};
|
||||
|
||||
// Log the error
|
||||
logger.error(err);
|
||||
|
||||
// Re-throw the error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
export interface Bid {
|
||||
timestamp: string;
|
||||
amount: number;
|
||||
auctioned_type: string;
|
||||
}
|
||||
|
||||
export interface Auctioned {
|
||||
timestamp: string;
|
||||
amount: number;
|
||||
auctioned_type: string;
|
||||
sold: boolean;
|
||||
}
|
||||
|
||||
export interface Lot {
|
||||
_id: {
|
||||
$oid: string;
|
||||
};
|
||||
idPlatform: string;
|
||||
platform: string;
|
||||
timestamp: string;
|
||||
lotNumber: string;
|
||||
RawData?: Object;
|
||||
sale_id: {
|
||||
$oid: string;
|
||||
};
|
||||
Bids?: Bid[];
|
||||
auctioned?: Auctioned;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface LotInfo {
|
||||
idLotInterencheres: string;
|
||||
url: string;
|
||||
title: string;
|
||||
lotNumber: string;
|
||||
EstimateLow: number;
|
||||
EstimateHigh: number;
|
||||
Description: string;
|
||||
feesText: string;
|
||||
fees: string;
|
||||
saleInfo: {
|
||||
idSaleInterencheres: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
export interface PostProcessing {
|
||||
nbrLots: number;
|
||||
duration: number;
|
||||
durationPerLots: string;
|
||||
totalAmount: number;
|
||||
averageAmount: string;
|
||||
medianAmount: string;
|
||||
}
|
||||
|
||||
export interface Sale {
|
||||
_id: {
|
||||
$oid: string;
|
||||
};
|
||||
idPlatform: string;
|
||||
platform: string;
|
||||
url: string;
|
||||
title: string;
|
||||
date: string;
|
||||
location: string;
|
||||
saleHouseName: string;
|
||||
status: string;
|
||||
postProcessing?: PostProcessing;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface SaleInfo {
|
||||
_id: string;
|
||||
idPlatform: string;
|
||||
platform: string;
|
||||
url: string;
|
||||
title: string;
|
||||
date: string;
|
||||
location: string;
|
||||
saleHouseName: string;
|
||||
status: string;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
|
||||
constructor(private snackBar: MatSnackBar) { }
|
||||
|
||||
public openSnackBar(message: string) {
|
||||
this.snackBar.open(message, '', {
|
||||
duration: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { SpinnerConsumer } from '../../shared/mocks/spinner-consumer';
|
||||
import { SpinnerService } from './spinner.service';
|
||||
|
||||
describe('BusyIndicatorService', () => {
|
||||
let component: SpinnerService;
|
||||
let consumer1: SpinnerConsumer;
|
||||
let consumer2: SpinnerConsumer;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new SpinnerService();
|
||||
consumer1 = new SpinnerConsumer(component);
|
||||
consumer2 = new SpinnerConsumer(component);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialise visibility to false', () => {
|
||||
component.visibility.subscribe((value: boolean) => {
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should broadcast visibility to all consumers', () => {
|
||||
expect(consumer1.isBusy).toBe(false);
|
||||
expect(consumer2.isBusy).toBe(false);
|
||||
});
|
||||
|
||||
it('should broadcast visibility to all consumers when the value changes', () => {
|
||||
component.visibility.next(true);
|
||||
|
||||
expect(consumer1.isBusy).toBe(true);
|
||||
expect(consumer2.isBusy).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SpinnerService {
|
||||
|
||||
visibility = new BehaviorSubject(false);
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visibility.next(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visibility.next(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { NgModule, LOCALE_ID } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatMomentDateModule, MomentDateAdapter, MAT_MOMENT_DATE_FORMATS } from '@angular/material-moment-adapter';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatRadioModule} from '@angular/material/radio';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import {MatSliderModule} from '@angular/material/slider';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatGridListModule } from '@angular/material/grid-list';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import {MatTabsModule} from '@angular/material/tabs';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import {MatProgressBarModule} from '@angular/material/progress-bar';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { SelectCheckAllComponent } from './select-check-all/select-check-all.component';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
|
||||
|
||||
// Prime NG
|
||||
import { GalleriaModule } from 'primeng/galleria';
|
||||
import { ImageModule } from 'primeng/image';
|
||||
import { SkeletonModule } from 'primeng/skeleton';
|
||||
|
||||
export const MY_FORMATS = {
|
||||
parse: {
|
||||
dateInput: 'DD MMM YYYY',
|
||||
},
|
||||
display: {
|
||||
dateInput: 'DD MMM YYYY',
|
||||
monthYearLabel: 'MMM YYYY',
|
||||
dateA11yLabel: 'LL',
|
||||
monthYearA11yLabel: 'MMMM YYYY'
|
||||
}
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatMomentDateModule,
|
||||
MatSidenavModule, MatIconModule, MatToolbarModule, MatButtonModule,
|
||||
MatListModule, MatGridListModule, MatCardModule, MatProgressBarModule, MatInputModule,
|
||||
MatSnackBarModule, MatProgressSpinnerModule, MatDatepickerModule,
|
||||
MatAutocompleteModule, MatTableModule, MatDialogModule, MatTabsModule,
|
||||
MatTooltipModule, MatSelectModule, MatPaginatorModule, MatChipsModule,
|
||||
MatButtonToggleModule, MatSlideToggleModule, MatBadgeModule, MatCheckboxModule,
|
||||
MatExpansionModule, DragDropModule, MatSortModule,
|
||||
|
||||
GalleriaModule,ImageModule,SkeletonModule
|
||||
],
|
||||
exports: [
|
||||
CommonModule,
|
||||
MatSidenavModule, MatIconModule, MatToolbarModule, MatButtonModule,
|
||||
MatListModule, MatGridListModule, MatCardModule, MatProgressBarModule, MatInputModule,
|
||||
MatSnackBarModule, MatMenuModule, MatProgressSpinnerModule, MatDatepickerModule,
|
||||
MatAutocompleteModule, MatTableModule, MatDialogModule, MatTabsModule,
|
||||
MatTooltipModule, MatSelectModule, MatPaginatorModule, MatChipsModule,
|
||||
MatButtonToggleModule, MatSlideToggleModule, MatBadgeModule, MatCheckboxModule,
|
||||
MatExpansionModule, SelectCheckAllComponent, DragDropModule, MatSortModule,
|
||||
|
||||
GalleriaModule,ImageModule,SkeletonModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
|
||||
{ provide: LOCALE_ID, useValue: 'en-gb' }
|
||||
],
|
||||
declarations: [SelectCheckAllComponent]
|
||||
})
|
||||
export class CustomMaterialModule {
|
||||
static forRoot() {
|
||||
return {
|
||||
ngModule: CustomMaterialModule,
|
||||
providers: [
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
app-select-check-all .mat-checkbox-layout,
|
||||
app-select-check-all .mat-checkbox-label {
|
||||
width:100% !important;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<mat-checkbox class="mat-option" [indeterminate]="isIndeterminate()" [checked]="isChecked()" (click)="$event.stopPropagation()"
|
||||
(change)="toggleSelection($event)">
|
||||
{{text}}
|
||||
</mat-checkbox>
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||
@Component({
|
||||
selector: 'app-select-check-all',
|
||||
templateUrl: './select-check-all.component.html',
|
||||
styleUrls: ['./select-check-all.component.css'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SelectCheckAllComponent {
|
||||
@Input()
|
||||
model: UntypedFormControl = new UntypedFormControl;
|
||||
@Input() values = [];
|
||||
@Input() text = 'Select All';
|
||||
|
||||
constructor() { }
|
||||
|
||||
isChecked(): boolean {
|
||||
return this.model.value && this.values.length
|
||||
&& this.model.value.length === this.values.length;
|
||||
}
|
||||
|
||||
isIndeterminate(): boolean {
|
||||
return this.model.value && this.values.length && this.model.value.length
|
||||
&& this.model.value.length < this.values.length;
|
||||
}
|
||||
|
||||
toggleSelection(change: MatCheckboxChange): void {
|
||||
if (change.checked) {
|
||||
this.model.setValue(this.values);
|
||||
} else {
|
||||
this.model.setValue([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-icon {
|
||||
color: rgb(200, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2>About</h2>
|
||||
|
||||
<p>
|
||||
Built on top of <a rel="noreferrer noopener" aria-label="Angular (opens in a new tab)"
|
||||
href="http://angular.io" target="_blank">Angular</a> & <a rel="noreferrer noopener"
|
||||
aria-label="Angular Material (opens in a new tab)" href="http://material.angular.io"
|
||||
target="_blank">Angular Material</a>, angular-material-template provides a simple template that you can use for your next project.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Support the project by starring it on <a href="https://github.com/umutesen/angular-material-template"
|
||||
target="_blank">
|
||||
GitHub
|
||||
</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Made with <mat-icon>favorite</mat-icon> by <a href="https://onthecode.co.uk" target="_blank"
|
||||
aria-label="onthecode (opens in a new tab)">onthecode</a>.
|
||||
</p>
|
||||
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AboutPageComponent } from './about-page.component';
|
||||
|
||||
describe('AboutHomeComponent', () => {
|
||||
let component: AboutPageComponent;
|
||||
let fixture: ComponentFixture<AboutPageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AboutPageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AboutPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-page',
|
||||
templateUrl: './about-page.component.html',
|
||||
styleUrls: ['./about-page.component.css']
|
||||
})
|
||||
export class AboutPageComponent {
|
||||
|
||||
constructor() { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { LayoutComponent } from '../../shared/layout/layout.component';
|
||||
import { AboutPageComponent } from './about-page/about-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: AboutPageComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AboutRoutingModule { }
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AboutRoutingModule } from './about-routing.module';
|
||||
import { AboutPageComponent } from './about-page/about-page.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AboutPageComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AboutRoutingModule
|
||||
]
|
||||
})
|
||||
export class AboutModule { }
|
||||
@@ -0,0 +1,35 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2>My Profile</h2>
|
||||
|
||||
<div fxLayout="row" fxLayout.sm="column" fxLayout.xs="column">
|
||||
|
||||
<div fxFlex="30%" fxFlex.sm="95%" fxFlex.xs="95%">
|
||||
<app-profile-details></app-profile-details>
|
||||
</div>
|
||||
|
||||
<div fxFlex></div>
|
||||
|
||||
<div fxFlex="65%" fxFlex.sm="95%" fxFlex.xs="950%">
|
||||
|
||||
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Change Password">
|
||||
<app-change-password></app-change-password>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-page',
|
||||
templateUrl: './account-page.component.html',
|
||||
styleUrls: ['./account-page.component.css']
|
||||
})
|
||||
export class AccountPageComponent implements OnInit {
|
||||
|
||||
constructor(private titleService: Title) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Account');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
|
||||
import { AccountPageComponent } from './account-page/account-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: 'profile', component: AccountPageComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AccountRoutingModule { }
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AccountRoutingModule } from './account-routing.module';
|
||||
import { AccountPageComponent } from './account-page/account-page.component';
|
||||
import { ChangePasswordComponent } from './change-password/change-password.component';
|
||||
import { ProfileDetailsComponent } from './profile-details/profile-details.component';
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AccountRoutingModule
|
||||
],
|
||||
declarations: [AccountPageComponent, ChangePasswordComponent, ProfileDetailsComponent],
|
||||
exports: [AccountPageComponent]
|
||||
})
|
||||
export class AccountModule { }
|
||||
@@ -0,0 +1,6 @@
|
||||
.password-rules .mat-divider {
|
||||
position: unset !important;
|
||||
}
|
||||
.container{
|
||||
padding-top: 20px;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<form [formGroup]="form">
|
||||
<p>Use the form below to change your password.</p>
|
||||
|
||||
<div fxLayout="row">
|
||||
|
||||
<div fxFlex="40%" fxFlex.md="60%" fxFlex.sm="50%" fxFlex.xs="100%">
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input matInput placeholder="Current Password" formControlName="currentPassword" [type]="hideCurrentPassword ? 'password' : 'text'"
|
||||
autocomplete="current-password">
|
||||
<mat-icon matSuffix (click)="hideCurrentPassword = !hideCurrentPassword">
|
||||
{{hideCurrentPassword ? 'visibility' : 'visibility_off'}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-error *ngIf="form.controls['currentPassword'].hasError('required')">
|
||||
Please enter a your current password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input matInput placeholder="New Password" formControlName="newPassword" [type]="hideNewPassword ? 'password' : 'text'" autocomplete="new-password">
|
||||
<mat-icon matSuffix (click)="hideNewPassword = !hideNewPassword">
|
||||
{{hideNewPassword ? 'visibility' : 'visibility_off'}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-error *ngIf="form.controls['newPassword'].hasError('required')">
|
||||
Please enter a new password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input matInput placeholder="Confirm New Password" formControlName="newPasswordConfirm" [type]="hideNewPassword ? 'password' : 'text'"
|
||||
autocomplete="new-password">
|
||||
<mat-icon matSuffix (click)="hideNewPassword = !hideNewPassword">
|
||||
{{hideNewPassword ? 'visibility' : 'visibility_off'}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-error *ngIf="form.controls['newPasswordConfirm'].hasError('required')">
|
||||
Please confirm your new password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button color="primary" [disabled]="form.invalid || disableSubmit" (click)="changePassword()">Save</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- <div class="password-rules" fxFlex="65%" fxFlex.sm="90%" fxFlex.xs="95%">
|
||||
Password rules:
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
Must be at least 6 characters
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item>
|
||||
Must contain at least one non alphanumeric character
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item>
|
||||
Must contain at least one lowercase ('a'-'z')
|
||||
</mat-list-item>
|
||||
|
||||
<mat-list-item>
|
||||
Must contain at least one uppercase ('A'-'Z')
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div> -->
|
||||
@@ -0,0 +1,75 @@
|
||||
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { SpinnerService } from 'src/app/core/services/spinner.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-change-password',
|
||||
templateUrl: './change-password.component.html',
|
||||
styleUrls: ['./change-password.component.css']
|
||||
})
|
||||
export class ChangePasswordComponent implements OnInit {
|
||||
|
||||
form!: UntypedFormGroup;
|
||||
hideCurrentPassword: boolean;
|
||||
hideNewPassword: boolean;
|
||||
currentPassword!: string;
|
||||
newPassword!: string;
|
||||
newPasswordConfirm!: string;
|
||||
disableSubmit!: boolean;
|
||||
|
||||
constructor(private authService: AuthenticationService,
|
||||
private logger: NGXLogger,
|
||||
private spinnerService: SpinnerService,
|
||||
private notificationService: NotificationService) {
|
||||
|
||||
this.hideCurrentPassword = true;
|
||||
this.hideNewPassword = true;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.form = new UntypedFormGroup({
|
||||
currentPassword: new UntypedFormControl('', Validators.required),
|
||||
newPassword: new UntypedFormControl('', Validators.required),
|
||||
newPasswordConfirm: new UntypedFormControl('', Validators.required),
|
||||
});
|
||||
|
||||
this.form.get('currentPassword')?.valueChanges
|
||||
.subscribe(val => { this.currentPassword = val; });
|
||||
|
||||
this.form.get('newPassword')?.valueChanges
|
||||
.subscribe(val => { this.newPassword = val; });
|
||||
|
||||
this.form.get('newPasswordConfirm')?.valueChanges
|
||||
.subscribe(val => { this.newPasswordConfirm = val; });
|
||||
|
||||
this.spinnerService.visibility.subscribe((value) => {
|
||||
this.disableSubmit = value;
|
||||
});
|
||||
}
|
||||
|
||||
changePassword() {
|
||||
|
||||
if (this.newPassword !== this.newPasswordConfirm) {
|
||||
this.notificationService.openSnackBar('New passwords do not match.');
|
||||
return;
|
||||
}
|
||||
|
||||
const email = this.authService.getCurrentUser().email;
|
||||
|
||||
this.authService.changePassword(email, this.currentPassword, this.newPassword)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.logger.info(`User ${email} changed password.`);
|
||||
this.form.reset();
|
||||
this.notificationService.openSnackBar('Your password has been changed.');
|
||||
},
|
||||
error => {
|
||||
this.notificationService.openSnackBar(error.error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.profile-card {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="profile-card">
|
||||
<img src="assets/images/user.png" [alt]="fullName">
|
||||
|
||||
<h2 class="title">
|
||||
{{fullName}}
|
||||
</h2>
|
||||
|
||||
<label>
|
||||
{{alias}}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{email}}
|
||||
</label>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile-details',
|
||||
templateUrl: './profile-details.component.html',
|
||||
styleUrls: ['./profile-details.component.css']
|
||||
})
|
||||
export class ProfileDetailsComponent implements OnInit {
|
||||
|
||||
fullName: string = "";
|
||||
email: string = "";
|
||||
alias: string = "";
|
||||
|
||||
constructor(private authService: AuthenticationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.fullName = this.authService.getCurrentUser().fullName;
|
||||
this.email = this.authService.getCurrentUser().email;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { PasswordResetRequestComponent } from './password-reset-request/password-reset-request.component';
|
||||
import { PasswordResetComponent } from './password-reset/password-reset.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'password-reset-request', component: PasswordResetRequestComponent },
|
||||
{ path: 'password-reset', component: PasswordResetComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AuthRoutingModule { }
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { AuthRoutingModule } from './auth-routing.module';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { PasswordResetRequestComponent } from './password-reset-request/password-reset-request.component';
|
||||
import { PasswordResetComponent } from './password-reset/password-reset.component';
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AuthRoutingModule
|
||||
],
|
||||
declarations: [LoginComponent, PasswordResetRequestComponent, PasswordResetComponent]
|
||||
})
|
||||
export class AuthModule { }
|
||||
@@ -0,0 +1,47 @@
|
||||
<div class="container login-container" fxLayout="row" fxLayoutAlign="center center">
|
||||
<form [formGroup]="loginForm" fxFlex="30%" fxFlex.sm="50%" fxFlex.xs="90%">
|
||||
<mat-card>
|
||||
<mat-card-title>Jucundus</mat-card-title>
|
||||
<mat-card-subtitle>Log in to your account</mat-card-subtitle>
|
||||
<mat-card-content>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="emailInput" matInput placeholder="Email" formControlName="email" autocomplete="email"
|
||||
type="email">
|
||||
|
||||
<mat-error id="invalidEmailError" *ngIf="loginForm.controls['email'].hasError('email')">
|
||||
Please enter a valid email address
|
||||
</mat-error>
|
||||
<mat-error id="requiredEmailError" *ngIf="loginForm.controls['email'].hasError('required')">
|
||||
Email is
|
||||
<strong>required</strong>
|
||||
</mat-error>
|
||||
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="passwordInput" matInput placeholder="Password" formControlName="password" type="password"
|
||||
autocomplete="current-password">
|
||||
<mat-error id="requiredPasswordError" *ngIf="loginForm.controls['email'].hasError('required')">
|
||||
Password is
|
||||
<strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="full-width">
|
||||
<mat-slide-toggle formControlName="rememberMe">Remember my email address</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="login-actions">
|
||||
<button mat-raised-button id="login" color="primary" [disabled]="loginForm.invalid || loading"
|
||||
(click)="login()">Login</button>
|
||||
<button mat-button id="resetPassword" (click)="resetPassword()" type="button">Reset Password</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
||||
<mat-progress-bar *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { UntypedFormControl, Validators, UntypedFormGroup } from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
loginForm!: UntypedFormGroup;
|
||||
loading!: boolean;
|
||||
|
||||
constructor(private router: Router,
|
||||
private titleService: Title,
|
||||
private notificationService: NotificationService,
|
||||
private authenticationService: AuthenticationService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Login');
|
||||
this.authenticationService.logout();
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
private createForm() {
|
||||
const savedUserEmail = localStorage.getItem('savedUserEmail');
|
||||
|
||||
this.loginForm = new UntypedFormGroup({
|
||||
email: new UntypedFormControl(savedUserEmail, [Validators.required, Validators.email]),
|
||||
password: new UntypedFormControl('', Validators.required),
|
||||
rememberMe: new UntypedFormControl(savedUserEmail !== null)
|
||||
});
|
||||
}
|
||||
|
||||
login() {
|
||||
const email = this.loginForm.get('email')?.value;
|
||||
const password = this.loginForm.get('password')?.value;
|
||||
const rememberMe = this.loginForm.get('rememberMe')?.value;
|
||||
|
||||
this.loading = true;
|
||||
this.authenticationService
|
||||
.login(email.toLowerCase(), password)
|
||||
.subscribe(
|
||||
data => {
|
||||
if (rememberMe) {
|
||||
localStorage.setItem('savedUserEmail', email);
|
||||
} else {
|
||||
localStorage.removeItem('savedUserEmail');
|
||||
}
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
error => {
|
||||
this.notificationService.openSnackBar(error.error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
resetPassword() {
|
||||
this.router.navigate(['/auth/password-reset-request']);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<div class="container login-container" fxLayout="row" fxLayoutAlign="center center">
|
||||
<form [formGroup]="form" fxFlex="30%" fxFlex.sm="50%" fxFlex.xs="90%">
|
||||
<mat-card>
|
||||
<mat-card-title>Jucundus</mat-card-title>
|
||||
<mat-card-subtitle>Reset your password</mat-card-subtitle>
|
||||
<mat-card-content>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="emailInput" matInput placeholder="Email" formControlName="email" autocomplete="email" type="email">
|
||||
|
||||
<mat-error id="invalidEmailError" *ngIf="form.controls['email'].hasError('email')">
|
||||
Please enter a valid email address
|
||||
</mat-error>
|
||||
<mat-error id="requiredEmailError" *ngIf="form.controls['email'].hasError('required')">
|
||||
Email is
|
||||
<strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="login-actions">
|
||||
<button id="submit" mat-raised-button color="primary" [disabled]="form.invalid || loading"
|
||||
(click)="resetPassword()">Reset Password</button>
|
||||
<button id="cancel" mat-button (click)="cancel()">Cancel</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
||||
<mat-progress-bar *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||
</form>
|
||||
</div>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-password-reset-request',
|
||||
templateUrl: './password-reset-request.component.html',
|
||||
styleUrls: ['./password-reset-request.component.css']
|
||||
})
|
||||
export class PasswordResetRequestComponent implements OnInit {
|
||||
|
||||
private email!: string;
|
||||
form!: UntypedFormGroup;
|
||||
loading!: boolean;
|
||||
|
||||
constructor(private authService: AuthenticationService,
|
||||
private notificationService: NotificationService,
|
||||
private titleService: Title,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Password Reset Request');
|
||||
|
||||
this.form = new UntypedFormGroup({
|
||||
email: new UntypedFormControl('', [Validators.required, Validators.email])
|
||||
});
|
||||
|
||||
this.form.get('email')?.valueChanges
|
||||
.subscribe((val: string) => { this.email = val.toLowerCase(); });
|
||||
}
|
||||
|
||||
resetPassword() {
|
||||
this.loading = true;
|
||||
this.authService.passwordResetRequest(this.email)
|
||||
.subscribe(
|
||||
results => {
|
||||
this.router.navigate(['/auth/login']);
|
||||
this.notificationService.openSnackBar('Password verification mail has been sent to your email address.');
|
||||
},
|
||||
error => {
|
||||
this.loading = false;
|
||||
this.notificationService.openSnackBar(error.error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="container login-container" fxLayout="row" fxLayoutAlign="center center">
|
||||
<form [formGroup]="form" fxFlex="30%" fxFlex.sm="50%" fxFlex.xs="90%">
|
||||
<mat-card>
|
||||
<mat-card-title>Jucundus</mat-card-title>
|
||||
<mat-card-subtitle>Reset your password</mat-card-subtitle>
|
||||
<mat-card-content>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="emailInput" matInput readonly disabled [value]="email">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="passwordInput" matInput placeholder="New Password" formControlName="newPassword" [type]="hideNewPassword ? 'password' : 'text'" autocomplete="new-password">
|
||||
<mat-icon id="togglePasswordVisibility" matSuffix (click)="hideNewPassword = !hideNewPassword">
|
||||
{{hideNewPassword ? 'visibility' : 'visibility_off'}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-error *ngIf="form.controls['newPassword'].hasError('required')">
|
||||
Please enter a new password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full-width">
|
||||
<input id="passwordConfirmInput" matInput placeholder="New Password Confirmation" formControlName="newPasswordConfirm" [type]="hideNewPasswordConfirm ? 'password' : 'text'" autocomplete="new-password">
|
||||
<mat-icon id="togglePasswordConfirmVisibility" matSuffix (click)="hideNewPasswordConfirm = !hideNewPasswordConfirm">
|
||||
{{hideNewPasswordConfirm ? 'visibility' : 'visibility_off'}}
|
||||
</mat-icon>
|
||||
|
||||
<mat-error *ngIf="form.controls['newPasswordConfirm'].hasError('required')">
|
||||
Please enter a your current password
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="login-actions">
|
||||
<button id="submit" mat-raised-button color="primary" [disabled]="form.invalid || loading" (click)="resetPassword()">OK</button>
|
||||
<button id="cancel" mat-button (click)="cancel()">Back to Login</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
||||
<mat-progress-bar *ngIf="loading" mode="indeterminate"></mat-progress-bar>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
import { UntypedFormGroup, UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-password-reset',
|
||||
templateUrl: './password-reset.component.html',
|
||||
styleUrls: ['./password-reset.component.css']
|
||||
})
|
||||
export class PasswordResetComponent implements OnInit {
|
||||
|
||||
private token!: string;
|
||||
email!: string;
|
||||
form!: UntypedFormGroup;
|
||||
loading!: boolean;
|
||||
hideNewPassword: boolean;
|
||||
hideNewPasswordConfirm: boolean;
|
||||
|
||||
constructor(private activeRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private authService: AuthenticationService,
|
||||
private notificationService: NotificationService,
|
||||
private titleService: Title) {
|
||||
|
||||
this.titleService.setTitle('Jucundus - Password Reset');
|
||||
this.hideNewPassword = true;
|
||||
this.hideNewPasswordConfirm = true;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.activeRoute.queryParamMap.subscribe((params: ParamMap) => {
|
||||
this.token = params.get('token') + '';
|
||||
this.email = params.get('email') + '';
|
||||
|
||||
if (!this.token || !this.email) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
});
|
||||
|
||||
this.form = new UntypedFormGroup({
|
||||
newPassword: new UntypedFormControl('', Validators.required),
|
||||
newPasswordConfirm: new UntypedFormControl('', Validators.required)
|
||||
});
|
||||
}
|
||||
|
||||
resetPassword() {
|
||||
|
||||
const password = this.form.get('newPassword')?.value;
|
||||
const passwordConfirm = this.form.get('newPasswordConfirm')?.value;
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
this.notificationService.openSnackBar('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
this.authService.passwordReset(this.email, this.token, password, passwordConfirm)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.notificationService.openSnackBar('Your password has been changed.');
|
||||
this.router.navigate(['/auth/login']);
|
||||
},
|
||||
(error: any) => {
|
||||
this.notificationService.openSnackBar(error.error);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th.mat-sort-header-sorted {
|
||||
color: black;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2>Customers</h2>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="position">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> No. </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Weight Column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Weight </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.weight}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
|
||||
export interface PeriodicElement {
|
||||
name: string;
|
||||
position: number;
|
||||
weight: number;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const ELEMENT_DATA: PeriodicElement[] = [
|
||||
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
|
||||
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
|
||||
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
|
||||
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
|
||||
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
|
||||
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
|
||||
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
|
||||
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
|
||||
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
|
||||
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'app-customer-list',
|
||||
templateUrl: './customer-list.component.html',
|
||||
styleUrls: ['./customer-list.component.css']
|
||||
})
|
||||
export class CustomerListComponent implements OnInit {
|
||||
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
|
||||
dataSource = new MatTableDataSource(ELEMENT_DATA);
|
||||
|
||||
@ViewChild(MatSort, { static: true })
|
||||
sort: MatSort = new MatSort;
|
||||
|
||||
constructor(
|
||||
private logger: NGXLogger,
|
||||
private notificationService: NotificationService,
|
||||
private titleService: Title
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Customers');
|
||||
this.logger.log('Customers loaded');
|
||||
this.notificationService.openSnackBar('Customers loaded');
|
||||
this.dataSource.sort = this.sort;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
|
||||
import { CustomerListComponent } from './customer-list/customer-list.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: CustomerListComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class CustomersRoutingModule { }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CustomersRoutingModule } from './customers-routing.module';
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
import { CustomerListComponent } from './customer-list/customer-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CustomersRoutingModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
CustomerListComponent
|
||||
]
|
||||
})
|
||||
export class CustomersModule { }
|
||||
@@ -0,0 +1,17 @@
|
||||
.single-cards {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.single-card .mat-card-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.single-card .mat-icon {
|
||||
font-size: 55px;
|
||||
}
|
||||
|
||||
.projects-card>mat-card-content {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
|
||||
<div fxFlex="95%">
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
|
||||
<h2>Welcome back, {{currentUser.fullName}}!</h2>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="50%" class="text-center no-records animate">
|
||||
<mat-icon>dashboard</mat-icon>
|
||||
<p>This is the dashboard.</p>
|
||||
</div>
|
||||
<mat-icon> </mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { AuthenticationService } from 'src/app/core/services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard-home',
|
||||
templateUrl: './dashboard-home.component.html',
|
||||
styleUrls: ['./dashboard-home.component.css']
|
||||
})
|
||||
export class DashboardHomeComponent implements OnInit {
|
||||
currentUser: any;
|
||||
|
||||
constructor(private notificationService: NotificationService,
|
||||
private authService: AuthenticationService,
|
||||
private titleService: Title,
|
||||
private logger: NGXLogger) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.currentUser = this.authService.getCurrentUser();
|
||||
this.titleService.setTitle('Jucundus - Dashboard');
|
||||
this.logger.log('Dashboard loaded');
|
||||
|
||||
setTimeout(() => {
|
||||
this.notificationService.openSnackBar('Welcome!');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
|
||||
import { DashboardHomeComponent } from './dashboard-home/dashboard-home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: DashboardHomeComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DashboardRoutingModule { }
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { DashboardRoutingModule } from './dashboard-routing.module';
|
||||
import { DashboardHomeComponent } from './dashboard-home/dashboard-home.component';
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DashboardHomeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
DashboardRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class DashboardModule { }
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-icon {
|
||||
color: rgb(200, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>New Lot</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field>
|
||||
<mat-label>Url</mat-label>
|
||||
<input matInput placeholder="Ex. https://drouot.com/..." maxlength="255" st [(ngModel)]="url" >
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="openDialog()">Add</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card style="margin-top: 10px;">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Favorites</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Position Column -->
|
||||
<ng-container matColumnDef="picture">
|
||||
<th mat-header-cell *matHeaderCellDef> Picture </th>
|
||||
<td mat-cell *matCellDef="let element"><img [src]="element.picture" alt="Picture" style="width: 50px"></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="lot">
|
||||
<th mat-header-cell *matHeaderCellDef> Lot </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.lotInfo.lotNumber}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Weight Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef> Title </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.lotInfo.title}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Symbol Column -->
|
||||
<ng-container matColumnDef="estimate">
|
||||
<th mat-header-cell *matHeaderCellDef> Estimate </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.lotInfo.EstimateLow}} - {{element.lotInfo.EstimateHigh}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
templateUrl: './favorites-page.component.html',
|
||||
styleUrls: ['./favorites-page.component.css']
|
||||
})
|
||||
export class FavoritesPageComponent implements OnInit {
|
||||
|
||||
url: string = '';
|
||||
displayedColumns: string[] = ['picture', 'lot', 'title', 'estimate'];
|
||||
dataSource = []
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: apiService) {}
|
||||
|
||||
openDialog(): void {
|
||||
|
||||
this.router.navigate(['favorites/new', this.url]);
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.apiService.getAllFavorite().subscribe((data: any) => {
|
||||
this.dataSource = data;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
.example-card {
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Picture</mat-card-title>
|
||||
<mat-card-subtitle>select the picture</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<mat-grid-list cols="2" >
|
||||
<mat-grid-tile *ngFor="let picture of images">
|
||||
<div style="width: 300px; height: 300px;">
|
||||
<img src="{{picture}}" (click)="onSelectImage(picture)" style="width: 100%; height: 100%; object-fit: contain;">
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'change-image-dialog-dialog',
|
||||
templateUrl: './change-image-dialog.component.html',
|
||||
styleUrls: ['./change-image-dialog.component.css']
|
||||
})
|
||||
export class ChangeImageDialogComponent implements OnInit {
|
||||
|
||||
images: any[] = [];
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ChangeImageDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.images = this.data.images;
|
||||
console.log(this.images);
|
||||
}
|
||||
|
||||
onSelectImage(picture: any): void {
|
||||
this.dialogRef.close(picture);
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
mat-icon {
|
||||
color: rgb(200, 0, 0);
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<mat-card class="dialog-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Lot</mat-card-title>
|
||||
<mat-card-subtitle>Lot information</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<div fxLayout="column" fxLayoutGap="10px" fxFlex>
|
||||
<mat-form-field style="width: 100%">
|
||||
<mat-label>Title</mat-label><input matInput maxlength="255" [(ngModel)]="lotInfo.title"/>
|
||||
</mat-form-field>
|
||||
<mat-form-field style="width: 40%">
|
||||
<mat-label>Lot</mat-label><input matInput maxlength="30" [(ngModel)]="lotInfo.lotNumber"/>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<div fxLayout="column" fxLayoutAlign="center center" fxFlex>
|
||||
<div *ngIf="images.length == 0">
|
||||
<p-skeleton width="150px" height="150px"></p-skeleton>
|
||||
</div>
|
||||
<div *ngIf="images.length > 0">
|
||||
<img [src]="picture" style="width: 150px; height: 150px;" (click)="openChangeImage()"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 40%">
|
||||
<mat-label>Fees</mat-label><input matInput maxlength="2" [(ngModel)]="lotInfo.fees"/>
|
||||
<mat-icon matSuffix>percent</mat-icon>
|
||||
</mat-form-field>
|
||||
<p style="width: 100%">{{lotInfo.feesText}}</p>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 50%">
|
||||
<mat-label>Estimate Low</mat-label><input matInput maxlength="30" [(ngModel)]="lotInfo.EstimateLow"/>
|
||||
</mat-form-field>
|
||||
<mat-form-field style="width: 50%">
|
||||
<mat-label >Estimate High</mat-label><input matInput maxlength="30" [(ngModel)]="lotInfo.EstimateHigh"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row">
|
||||
<mat-form-field style="width: 100%">
|
||||
<mat-label >Description</mat-label><textarea matInput style="height: 200px;" [(ngModel)]="lotInfo.Description"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="dialog-card">
|
||||
|
||||
<mat-card-header>
|
||||
<mat-card-title>Sale</mat-card-title>
|
||||
<mat-card-subtitle>Sale information</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 100%">
|
||||
<mat-label>Title</mat-label><input matInput maxlength="255" [(ngModel)]="SaleInfo.title"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<div fxLayout="column" fxFlex="60">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Choose a date</mat-label>
|
||||
<input matInput [matDatepicker]="picker" [(ngModel)]="date">
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div fxLayout="column" fxFlex="40">
|
||||
<mat-form-field >
|
||||
<mat-label>Hour</mat-label><input matInput type="time" maxlength="5" [(ngModel)]="hour"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 50%">
|
||||
<mat-label>location</mat-label><input matInput maxlength="255" [(ngModel)]="SaleInfo.location"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 100%">
|
||||
<mat-label>Sale House</mat-label><input matInput maxlength="255" [(ngModel)]="SaleInfo.saleHouseName"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="dialog-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Perso</mat-card-title>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-checkbox [(ngModel)]="buyProject">Buy Project</mat-checkbox>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field style="width: 30%">
|
||||
<mat-label>Max price</mat-label><input matInput maxlength="255" type="number" [(ngModel)]="maxPrice"/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row">
|
||||
<mat-form-field style="width: 100%">
|
||||
<mat-label >Note</mat-label><textarea matInput style="height: 100px;" [(ngModel)]="Note"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<button mat-button color="warn" (click)="cancel()">Cancel</button>
|
||||
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
|
||||
import {ChangeImageDialogComponent} from './change-image-dialog/change-image-dialog.component';
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
//Services
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
|
||||
// Models
|
||||
import { LotInfo } from 'src/app/core/services/model/lotInfo.interface';
|
||||
import { SaleInfo } from 'src/app/core/services/model/saleInfo.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-new-favorites-page',
|
||||
templateUrl: './new-favorite-page.component.html',
|
||||
styleUrls: ['./new-favorite-page.component.css']
|
||||
})
|
||||
export class NewFavoritesPageComponent implements OnInit {
|
||||
|
||||
url: string = '';
|
||||
lotInfo: LotInfo;
|
||||
SaleInfo: SaleInfo;
|
||||
images: any[] = [];
|
||||
picture: String = "";
|
||||
date: Date;
|
||||
hour: string = "";
|
||||
buyProject: boolean = false;
|
||||
maxPrice: number = 0;
|
||||
Note: string = "";
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private ActivatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private notificationService: NotificationService,
|
||||
private apiService: apiService,) {
|
||||
|
||||
this.ActivatedRoute.params.subscribe(params => {
|
||||
this.url = params['url'];
|
||||
});
|
||||
|
||||
|
||||
this.lotInfo = {
|
||||
idLotInterencheres: '',
|
||||
url: '',
|
||||
title:'',
|
||||
lotNumber: '',
|
||||
EstimateLow: 0,
|
||||
EstimateHigh: 0,
|
||||
Description: "",
|
||||
feesText: "",
|
||||
fees: "",
|
||||
saleInfo: {
|
||||
idSaleInterencheres: "",
|
||||
url: ""
|
||||
}
|
||||
};
|
||||
|
||||
this.SaleInfo = {
|
||||
_id: "",
|
||||
idPlatform: "",
|
||||
platform: "",
|
||||
url: "",
|
||||
title: "",
|
||||
date: "",
|
||||
location: "",
|
||||
saleHouseName: "",
|
||||
status: ""
|
||||
};
|
||||
|
||||
this.date = new Date();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log("url: "+this.url);
|
||||
|
||||
// this.date = moment(this.SaleInfo.date).tz('Europe/Paris').toDate();
|
||||
// this.hour = moment(this.SaleInfo.date).tz('Europe/Paris').format('HH:mm');
|
||||
|
||||
this.apiService.getLotInfo(this.url).subscribe( lotInfo => {
|
||||
console.log(lotInfo);
|
||||
this.lotInfo = lotInfo;
|
||||
|
||||
this.apiService.getSaleInfos(this.lotInfo.saleInfo.url).subscribe( SaleInfo => {
|
||||
console.log(SaleInfo);
|
||||
this.SaleInfo = SaleInfo;
|
||||
|
||||
//hour
|
||||
// Europe/Paris is the timezone of the user
|
||||
this.date = moment(this.SaleInfo.date).tz('Europe/Paris').toDate();
|
||||
this.hour = moment(this.SaleInfo.date).tz('Europe/Paris').format('HH:mm');
|
||||
this.notificationService.openSnackBar("Loaded: "+lotInfo.title);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
this.apiService.getPictures(this.url).subscribe( pictures => {
|
||||
this.images = pictures;
|
||||
this.picture = pictures[0];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
openChangeImage(): void {
|
||||
const dialogRef = this.dialog.open(ChangeImageDialogComponent, {
|
||||
width: '300px',
|
||||
data: {images: this.images}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.picture = result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.router.navigate(['favorites']);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
// Europe/Paris is the timezone of the user
|
||||
let dateTime = moment.tz(`${this.date.toISOString().split('T')[0]}T${this.hour}`, 'Europe/Paris').format();
|
||||
|
||||
this.apiService.saveFavorite(this.lotInfo, this.SaleInfo, this.picture, dateTime, this.buyProject, this.maxPrice, this.Note).subscribe( res => {
|
||||
this.notificationService.openSnackBar("Favorite saved");
|
||||
this.router.navigate(['favorites']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { LayoutComponent } from '../../shared/layout/layout.component';
|
||||
import { FavoritesPageComponent } from './favorites-page/favorites-page.component';
|
||||
import { NewFavoritesPageComponent } from './favorites-page/new-favorite-page/new-favorite-page.component'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: FavoritesPageComponent },
|
||||
{ path: 'new/:url', component: NewFavoritesPageComponent}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FavoritesRoutingModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { FavoritesRoutingModule } from './favorites-routing.module';
|
||||
import { FavoritesPageComponent } from './favorites-page/favorites-page.component';
|
||||
import { NewFavoritesPageComponent } from './favorites-page/new-favorite-page/new-favorite-page.component';
|
||||
import { ChangeImageDialogComponent } from './favorites-page/new-favorite-page/change-image-dialog/change-image-dialog.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FavoritesPageComponent,
|
||||
NewFavoritesPageComponent,
|
||||
ChangeImageDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FavoritesRoutingModule,
|
||||
]
|
||||
})
|
||||
export class FavoritesModule { }
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
import { IconsComponent } from './icons/icons.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: IconsComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class IconsRoutingModule { }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { IconsRoutingModule } from './icons-routing.module';
|
||||
import { IconsComponent } from './icons/icons.component';
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [IconsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
IconsRoutingModule
|
||||
]
|
||||
})
|
||||
export class IconsModule { }
|
||||
@@ -0,0 +1,4 @@
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 650px;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2>Icons</h2>
|
||||
|
||||
<iframe src="https://design.google.com/icons/">
|
||||
<p>Your browser does not support iframes.</p>
|
||||
</iframe>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { IconsComponent } from './icons.component';
|
||||
|
||||
describe('IconsComponent', () => {
|
||||
let component: IconsComponent;
|
||||
let fixture: ComponentFixture<IconsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ IconsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IconsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-icons',
|
||||
templateUrl: './icons.component.html',
|
||||
styleUrls: ['./icons.component.css']
|
||||
})
|
||||
export class IconsComponent {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-icon {
|
||||
color: rgb(200, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
<mat-card style="margin-bottom: 20px;">
|
||||
<mat-card-content>
|
||||
<h2>Pictures</h2>
|
||||
<mat-form-field>
|
||||
<mat-label>Url</mat-label>
|
||||
<input matInput placeholder="Ex. https://drouot.com/..." maxlength="255" [(ngModel)]="url" >
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="getPictures()">Go</button>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card *ngIf="images.length > 0">
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
<p-galleria [value]="images" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5" [thumbnailsPosition]="position" >
|
||||
<ng-template pTemplate="item" let-item>
|
||||
<p-image [src]="item.itemImageSrc" width="600" [preview]="true"></p-image>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="thumbnail" let-item>
|
||||
<div class="grid grid-nogutter justify-content-center">
|
||||
<img [src]="item.thumbnailImageSrc" style="height: 100px;" />
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-galleria>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FavoritesPageComponent } from './favorites-page.component';
|
||||
|
||||
describe('AboutHomeComponent', () => {
|
||||
let component: FavoritesPageComponent;
|
||||
let fixture: ComponentFixture<FavoritesPageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FavoritesPageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FavoritesPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pictures-page',
|
||||
templateUrl: './pictures-page.component.html',
|
||||
styleUrls: ['./pictures-page.component.css']
|
||||
})
|
||||
|
||||
export class PicturesPageComponent implements OnInit {
|
||||
|
||||
@Input() value: any;
|
||||
@Output() valueChange = new EventEmitter<any>();
|
||||
|
||||
url: string = '';
|
||||
images: any[] = [];
|
||||
position: "bottom" | "top" | "left" | "right" | undefined = 'top';
|
||||
responsiveOptions: any[] | undefined;
|
||||
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
private apiService: apiService,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Users');
|
||||
this.responsiveOptions = [
|
||||
{
|
||||
breakpoint: '1024px',
|
||||
numVisible: 5
|
||||
},
|
||||
{
|
||||
breakpoint: '768px',
|
||||
numVisible: 3
|
||||
},
|
||||
{
|
||||
breakpoint: '560px',
|
||||
numVisible: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getPictures(): void {
|
||||
this.apiService.getPictures(this.url).subscribe( Pictures => {
|
||||
this.images = [];
|
||||
const newImages = [...this.images];
|
||||
|
||||
Pictures.forEach((picture: any) => {
|
||||
newImages.push({
|
||||
itemImageSrc: picture,
|
||||
thumbnailImageSrc: picture,
|
||||
alt: "img",
|
||||
title: "img1"
|
||||
});
|
||||
})
|
||||
this.images = newImages;
|
||||
console.log(this.images);
|
||||
this.updateValue(this.images);
|
||||
})
|
||||
}
|
||||
|
||||
updateValue(newValue: any) {
|
||||
this.value = newValue;
|
||||
this.valueChange.emit(this.value);
|
||||
}
|
||||
|
||||
openFullscreen(event: any) {
|
||||
const element = event.target;
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen();
|
||||
} else if (element.mozRequestFullScreen) { /* Firefox */
|
||||
element.mozRequestFullScreen();
|
||||
} else if (element.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
|
||||
element.webkitRequestFullscreen();
|
||||
} else if (element.msRequestFullscreen) { /* IE/Edge */
|
||||
element.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { LayoutComponent } from '../../shared/layout/layout.component';
|
||||
import { PicturesPageComponent } from './pictures-page/pictures-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: PicturesPageComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class PicturesRoutingModule { }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { PicturesRoutingModule } from './pictures-routing.module';
|
||||
import { PicturesPageComponent } from './pictures-page/pictures-page.component';
|
||||
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PicturesPageComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
PicturesRoutingModule,
|
||||
]
|
||||
})
|
||||
export class PicturesModule { }
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
.example-card {
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Sale</mat-card-title>
|
||||
<mat-card-subtitle>Sale informations</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div *ngIf="SaleInfo.title == ''" fxLayout="column" fxLayoutGap="5px">
|
||||
<p-skeleton width="250px" height="20px"></p-skeleton>
|
||||
<p-skeleton width="150px" height="20px"></p-skeleton>
|
||||
<p-skeleton width="200px" height="20px"></p-skeleton>
|
||||
<p-skeleton width="150px" height="20px"></p-skeleton>
|
||||
|
||||
</div>
|
||||
<div *ngIf="SaleInfo.title != ''" fxLayout="column" fxLayoutGap="5px">
|
||||
<p><strong>{{SaleInfo.title}}</strong></p>
|
||||
<p><mat-icon>event</mat-icon>{{date | date:'dd/MM/yyyy' }} - {{hour}}</p>
|
||||
<p><mat-icon>account_balance</mat-icon>{{SaleInfo.saleHouseName}}</p>
|
||||
<p><mat-icon>location_on</mat-icon>{{SaleInfo.location}}</p>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<button mat-button color="warn" (click)="cancel()">Cancel</button>
|
||||
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
//Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
|
||||
// Models
|
||||
import { SaleInfo } from 'src/app/core/services/model/saleInfo.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'loading-sale-dialog-dialog',
|
||||
templateUrl: './loading-sale-dialog.component.html',
|
||||
styleUrls: ['./loading-sale-dialog.component.css']
|
||||
})
|
||||
export class LoadingSaleDialogComponent implements OnInit {
|
||||
|
||||
url: string = '';
|
||||
SaleInfo: SaleInfo;
|
||||
date: Date;
|
||||
hour: string = "";
|
||||
|
||||
constructor(
|
||||
private apiService: apiService,
|
||||
public dialogRef: MatDialogRef<LoadingSaleDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.SaleInfo = {
|
||||
_id: "",
|
||||
idPlatform: "",
|
||||
platform: "",
|
||||
url: "",
|
||||
title: "",
|
||||
date: "",
|
||||
location: "",
|
||||
saleHouseName: "",
|
||||
status: ""
|
||||
};
|
||||
this.date = new Date();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.url = this.data.url;
|
||||
console.log(this.url);
|
||||
|
||||
this.apiService.getSaleInfos(this.url).subscribe( SaleInfo => {
|
||||
console.log(SaleInfo);
|
||||
this.SaleInfo = SaleInfo;
|
||||
|
||||
//hour
|
||||
// Europe/Paris is the timezone of the user
|
||||
this.date = moment(this.SaleInfo.date).tz('Europe/Paris').toDate();
|
||||
this.hour = moment(this.SaleInfo.date).tz('Europe/Paris').format('HH:mm');
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.apiService.saveSale(this.SaleInfo).subscribe( SaleInfo => {
|
||||
this.dialogRef.close(true);
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Sale Detail</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<h2>{{Sale.title}}</h2>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="40px">
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p><mat-icon>event</mat-icon> {{Sale.date | date:'dd/MM/yyyy' }} - {{Sale.date | date:'HH:mm'}} | </p>
|
||||
<p><mat-icon>account_balance</mat-icon> {{Sale.saleHouseName}}</p>
|
||||
<p><mat-icon>location_on</mat-icon> {{Sale.location}}</p>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p><mat-icon>tag</mat-icon>{{Sale.postProcessing?.nbrLots}} Lots</p>
|
||||
<p><mat-icon>hourglass_bottom</mat-icon>{{getDurationInMinutes(Sale.postProcessing?.duration ?? 0)}} min</p>
|
||||
<p><mat-icon>hourglass_bottom</mat-icon>/Lot {{getTimePerLot(parseToNumber(Sale.postProcessing?.durationPerLots ?? '0'))}}</p>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p>Total amount: {{Sale.postProcessing?.totalAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Average amount: {{Sale.postProcessing?.averageAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Median amount: {{Sale.postProcessing?.medianAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card style="margin-top: 10px;">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Lots</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-tab-group>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
Lots
|
||||
</ng-template>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<table mat-table [dataSource]="lotList" matSort class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Lotnum Column -->
|
||||
<ng-container matColumnDef="lotNum">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="idPlatform">#</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.lotNumber}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="title"> Title </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.title ? element.title : "Lot "+element.lotNumber}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- EstimateLow Column -->
|
||||
<ng-container matColumnDef="estimateLow">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="EstimateLow"> Estimate Low </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.EstimateLow ? element.EstimateLow : "-"}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- EstimateHigh Column -->
|
||||
<ng-container matColumnDef="estimateHigh">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="EstimateHigh"> Estimate High </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.EstimateHigh ? element.EstimateHigh : "-"}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Price Column -->
|
||||
<ng-container matColumnDef="price">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="auctionedAmount"> Price </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.auctionedAmount}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Price Column -->
|
||||
<ng-container matColumnDef="nbrBids">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="bidsLength"> nbrBids </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.bidsLength}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<mat-paginator [pageSizeOptions]="[10, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
<mat-tab label="Graphs">
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
</mat-tab-group>
|
||||
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
// Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
|
||||
//Models
|
||||
import { Sale } from 'src/app/core/services/model/sale.interface';
|
||||
import { Lot } from 'src/app/core/services/model/lot.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
templateUrl: './sale-detail-page.component.html',
|
||||
styleUrls: ['./sale-detail-page.component.css']
|
||||
})
|
||||
|
||||
|
||||
export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
||||
|
||||
id: any = '';
|
||||
Sale: Sale;
|
||||
displayedColumns: string[] = ['lotNum', 'title', 'estimateLow', 'estimateHigh', 'price', 'nbrBids'];
|
||||
|
||||
lotList: MatTableDataSource<Lot> = new MatTableDataSource<Lot>();
|
||||
@ViewChild(MatPaginator) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort) sort?: MatSort;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private apiService: apiService) {
|
||||
|
||||
this.Sale = {
|
||||
_id: { $oid: '' },
|
||||
idPlatform: '',
|
||||
platform: '',
|
||||
url: '',
|
||||
title: '',
|
||||
date: '',
|
||||
location: '',
|
||||
saleHouseName: '',
|
||||
status: '',
|
||||
postProcessing: {
|
||||
nbrLots: 0,
|
||||
duration: 0,
|
||||
durationPerLots: '',
|
||||
totalAmount: 0,
|
||||
averageAmount: '',
|
||||
medianAmount: ''
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.id = params.get('id');
|
||||
|
||||
this.getSale();
|
||||
this.getLotList();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
|
||||
}
|
||||
|
||||
getSale(){
|
||||
this.apiService.getSale(this.id).subscribe((sale: Sale) => {
|
||||
this.Sale = sale;
|
||||
});
|
||||
}
|
||||
|
||||
getLotList(){
|
||||
this.apiService.getLotsBySale(this.id).subscribe((lotList: Lot[]) => {
|
||||
|
||||
// adding the Bids length info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, bidsLength: lot.Bids ? lot.Bids.length : 0};
|
||||
});
|
||||
|
||||
// adding the Auctionned ammount info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, auctionedAmount: lot.auctioned?.amount ? lot.auctioned?.amount : 0};
|
||||
});
|
||||
|
||||
console.log(lotList);
|
||||
this.lotList = new MatTableDataSource(lotList);
|
||||
this.lotList.paginator = this.paginator ?? null;
|
||||
this.lotList.sort = this.sort ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
getDurationInMinutes(duration: number): number {
|
||||
return Math.floor(duration / 60);
|
||||
}
|
||||
|
||||
getTimePerLot(duration: number): string {
|
||||
|
||||
const minutes = Math.floor((duration % 3600) / 60);
|
||||
const seconds = duration % 60;
|
||||
|
||||
let returnString = '';
|
||||
|
||||
if (minutes > 0) {
|
||||
returnString += minutes + 'm ';
|
||||
}
|
||||
returnString += seconds + 's';
|
||||
|
||||
return returnString;
|
||||
}
|
||||
|
||||
parseToNumber(value: string | null): number {
|
||||
return parseFloat(value || '0');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-icon {
|
||||
color: rgb(200, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>New Sale</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field>
|
||||
<mat-label>Url</mat-label>
|
||||
<input matInput placeholder="Ex. https://drouot.com/..." maxlength="255" st [(ngModel)]="url" >
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" (click)="openLoadingSale()">Add</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card style="margin-top: 10px;">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Sales</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-tab-group>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
Current Sales
|
||||
<span matBadge="{{futureSales.length}}" matBadgeOverlap="false" style="margin: 5px;"> </span>
|
||||
</ng-template>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
|
||||
<table mat-table [dataSource]="futureSales" class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef> Title </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<a href="{{element.url}}" target="_blank">{{element.title}}</a>
|
||||
<div *ngIf="element.status == 'following'">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef> Date </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.date | date:'dd/MM/yyyy' }} - {{element.date | date:'HH:mm'}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Sale House Column -->
|
||||
<ng-container matColumnDef="house">
|
||||
<th mat-header-cell *matHeaderCellDef> Sale House </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.saleHouseName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- PLateform House Column -->
|
||||
<ng-container matColumnDef="plateform">
|
||||
<th mat-header-cell *matHeaderCellDef> Plateforme </th>
|
||||
<td mat-cell *matCellDef="let element"> Interencheres </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Prepare Column -->
|
||||
<ng-container matColumnDef="prepare">
|
||||
<th mat-header-cell *matHeaderCellDef> Prepare </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button *ngIf="element.status == 'ready'" mat-button (click)="prepareSale(element)"><mat-icon>format_list_numbered</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Follow Column -->
|
||||
<ng-container matColumnDef="follow">
|
||||
<th mat-header-cell *matHeaderCellDef> Follow </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button *ngIf="element.status == 'ready'" mat-button (click)="followSale(element)"><mat-icon>play_arrow</mat-icon></button>
|
||||
<button *ngIf="element.status == 'following'" mat-button (click)="stopFollowSale(element)"><mat-icon>stop</mat-icon></button>
|
||||
<div *ngIf="element.status == 'askStop'"><mat-progress-bar mode="indeterminate"></mat-progress-bar></div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Delete Column -->
|
||||
<ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef> Delete </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-button (click)="deleteSale(element._id)"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
<mat-tab label="Old Sales">
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<table mat-table [dataSource]="pastSales" class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef> Title </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<a (click)="navigateToSaleDetail(element._id)">{{element.title}}</a>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef> Date </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.date | date:'dd/MM/yyyy' }} - {{element.date | date:'HH:mm'}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Sale House Column -->
|
||||
<ng-container matColumnDef="house">
|
||||
<th mat-header-cell *matHeaderCellDef> Sale House </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.saleHouseName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- PLateform House Column -->
|
||||
<ng-container matColumnDef="plateform">
|
||||
<th mat-header-cell *matHeaderCellDef> Plateforme </th>
|
||||
<td mat-cell *matCellDef="let element"> Interencheres </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- PostProcessing Column -->
|
||||
<ng-container matColumnDef="postProcessing">
|
||||
<th mat-header-cell *matHeaderCellDef> Post Processing </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-button (click)="postProcessing(element._id)"><mat-icon>query_stats</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Delete Column -->
|
||||
<ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef> Delete </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-button (click)="deleteSale(element._id)"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumnsOld"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumnsOld;"></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
</mat-tab-group>
|
||||
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { LoadingSaleDialogComponent } from './loading-sale-dialog/loading-sale-dialog.component';
|
||||
import * as moment from 'moment';
|
||||
|
||||
// Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { SaleInfo } from 'src/app/core/services/model/saleInfo.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
templateUrl: './sales-page.component.html',
|
||||
styleUrls: ['./sales-page.component.css']
|
||||
})
|
||||
export class SalesPageComponent implements OnInit {
|
||||
|
||||
url: string = '';
|
||||
refreshSalesId: any;
|
||||
displayedColumns: string[] = ['title', 'date', 'house', 'plateform', 'prepare', 'follow', 'delete'];
|
||||
displayedColumnsOld: string[] = ['title', 'date', 'house', 'plateform', 'postProcessing', 'delete'];
|
||||
|
||||
futureSales = []
|
||||
pastSales = []
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private apiService: apiService) {}
|
||||
|
||||
openLoadingSale(): void {
|
||||
const dialogRef = this.dialog.open(LoadingSaleDialogComponent, {
|
||||
width: '300px',
|
||||
data: {url: this.url}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.refreshSales();
|
||||
this.url = "";
|
||||
this.notificationService.openSnackBar("Sale Added");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshSales(): void {
|
||||
this.apiService.getAllSale().subscribe((data: any) => {
|
||||
console.log(data);
|
||||
const today = moment();
|
||||
|
||||
this.futureSales = data
|
||||
.filter((element: SaleInfo) => moment(element.date).isAfter(today))
|
||||
.sort((a: any, b: any) => moment(a.date).isAfter(b.date) ? 1 : -1);
|
||||
|
||||
this.pastSales = data
|
||||
.filter((element: SaleInfo) => moment(element.date).isBefore(today))
|
||||
.sort((a: any, b: any) => moment(a.date).isAfter(b.date) ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refreshSales();
|
||||
this.refreshSalesId = setInterval(() => {
|
||||
this.refreshSales();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
prepareSale(saleInfo: SaleInfo): void {
|
||||
|
||||
this.apiService.prepareSale(saleInfo).subscribe( data => {
|
||||
console.log(data);
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Prepare Sale");
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
followSale(saleInfo: SaleInfo): void {
|
||||
|
||||
this.apiService.followSale(saleInfo).subscribe( data => {
|
||||
console.log(data);
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale followed");
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
stopFollowSale(saleInfo: SaleInfo): void {
|
||||
saleInfo.status = "askStop";
|
||||
this.apiService.updateSale(saleInfo).subscribe( data => {
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale Stopping...");
|
||||
})
|
||||
}
|
||||
|
||||
deleteSale(_id: string): void {
|
||||
this.apiService.deleteSale(_id).subscribe( data => {
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale deleted");
|
||||
})
|
||||
}
|
||||
|
||||
navigateToSaleDetail(id: string) {
|
||||
console.log(id);
|
||||
clearInterval(this.refreshSalesId);
|
||||
this.router.navigate(['/sales/detail', id]);
|
||||
}
|
||||
|
||||
postProcessing(id: string) {
|
||||
this.apiService.postProcessing(id).subscribe( data => {
|
||||
this.notificationService.openSnackBar("Sale processing");
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { LayoutComponent } from '../../shared/layout/layout.component';
|
||||
import { SalesPageComponent } from './sales-page/sales-page.component';
|
||||
import { SaleDetailPageComponent } from './sales-page/sale-detail-page/sale-detail-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: SalesPageComponent },
|
||||
{ path: 'detail/:id', component: SaleDetailPageComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SalesRoutingModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SalesRoutingModule } from './sales-routing.module';
|
||||
import { SalesPageComponent } from './sales-page/sales-page.component';
|
||||
import { LoadingSaleDialogComponent } from './sales-page/loading-sale-dialog/loading-sale-dialog.component';
|
||||
import { SaleDetailPageComponent } from './sales-page/sale-detail-page/sale-detail-page.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SalesPageComponent,
|
||||
LoadingSaleDialogComponent,
|
||||
SaleDetailPageComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SalesRoutingModule,
|
||||
]
|
||||
})
|
||||
export class SalesModule { }
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
import { TypographyComponent } from './typography/typography.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: TypographyComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class TypographyRoutingModule { }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user