first commit

This commit is contained in:
2024-05-16 16:05:41 +02:00
commit adf8283ae0
197 changed files with 26666 additions and 0 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+75
View File
@@ -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 { }
+7
View File
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`
})
export class AppComponent {}
+32
View File
@@ -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 { }
+49
View File
@@ -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(['/']);
});
});
+22
View File
@@ -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);
});
});
+32
View File
@@ -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> &amp; <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']);
}
}
@@ -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>
@@ -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;
});
}
}
@@ -0,0 +1,4 @@
.example-card {
margin-bottom: 8px;
}
@@ -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>
@@ -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();
}
}
@@ -0,0 +1,3 @@
mat-icon {
color: rgb(200, 0, 0);
}
@@ -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>
@@ -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 { }
@@ -0,0 +1,4 @@
.example-card {
margin-bottom: 8px;
}
@@ -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>
@@ -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);
}
}
@@ -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>
@@ -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