carga inicial
Carga inicial
This commit is contained in:
68
src/app/app-routing.module.ts
Normal file
68
src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// Angular
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
// Components
|
||||
import { BaseComponent } from './views/theme/base/base.component';
|
||||
import { ErrorPageComponent } from './views/theme/content/error-page/error-page.component';
|
||||
// Auth
|
||||
import { AuthGuard } from './core/auth';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'auth', loadChildren: () => import('../app/views/pages/auth/auth.module').then(m => m.AuthModule)},
|
||||
|
||||
{
|
||||
path: '',
|
||||
component: BaseComponent,
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{
|
||||
path: 'inicio',
|
||||
loadChildren: () => import('../app/views/pages/dashboard/dashboard.module').then(m => m.DashboardModule)
|
||||
},
|
||||
{
|
||||
path: 'catalogos',
|
||||
loadChildren: () => import('../app/views/pages/catalogos/catalogos.module').then(m => m.CatalogosModule)
|
||||
},
|
||||
{
|
||||
path: 'servicios',
|
||||
loadChildren: () => import('../app/views/pages/servicios/servicios.module').then(m => m.ServiciosModule)
|
||||
},
|
||||
{
|
||||
path: 'agenda',
|
||||
loadChildren: () => import('../app/views/pages/agenda/agenda.module').then(m => m.AgendaModule)
|
||||
},
|
||||
{
|
||||
path: 'reportes',
|
||||
loadChildren: () => import('../app/views/pages/reportes/reportes.module').then(m => m.ReportesModule)
|
||||
},
|
||||
{
|
||||
path: 'encuesta',
|
||||
loadChildren: () => import('../app/views/pages/encuesta/encuesta.module').then(m => m.EncuestaModule)
|
||||
},
|
||||
{
|
||||
path: 'error/403',
|
||||
component: ErrorPageComponent,
|
||||
data: {
|
||||
type: 'error-v6',
|
||||
code: 403,
|
||||
title: '403... Access forbidden',
|
||||
desc: 'Looks like you don\'t have permission to access for requested page.<br> Please, contact administrator'
|
||||
}
|
||||
},
|
||||
{path: 'error/:type', component: ErrorPageComponent},
|
||||
{path: '', redirectTo: 'inicio', pathMatch: 'full'},
|
||||
{path: '**', redirectTo: 'inicio', pathMatch: 'full'}
|
||||
]
|
||||
},
|
||||
{path: '', redirectTo: 'inicio', pathMatch: 'full'},
|
||||
{path: '**', redirectTo: 'error/403', pathMatch: 'full'},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {
|
||||
}
|
||||
2
src/app/app.component.html
Normal file
2
src/app/app.component.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<kt-splash-screen *ngIf="loader"></kt-splash-screen>
|
||||
<router-outlet></router-outlet>
|
||||
4
src/app/app.component.scss
Normal file
4
src/app/app.component.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
102
src/app/app.component.ts
Normal file
102
src/app/app.component.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Subscription } from 'rxjs';
|
||||
// Angular
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
// Layout
|
||||
import { LayoutConfigService, SplashScreenService, TranslationService } from './core/_base/layout';
|
||||
// language list
|
||||
import { locale as enLang } from './core/_config/i18n/en';
|
||||
import { locale as chLang } from './core/_config/i18n/ch';
|
||||
import { locale as esLang } from './core/_config/i18n/es';
|
||||
import { locale as jpLang } from './core/_config/i18n/jp';
|
||||
import { locale as deLang } from './core/_config/i18n/de';
|
||||
import { locale as frLang } from './core/_config/i18n/fr';
|
||||
import {ApiService} from "./core/api/api.service";
|
||||
import {MatSnackBar} from "@angular/material";
|
||||
|
||||
@Component({
|
||||
// tslint:disable-next-line:component-selector
|
||||
selector: 'body[kt-root]',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
// Public properties
|
||||
title = 'Metronic';
|
||||
loader: boolean;
|
||||
private unsubscribe: Subscription[] = []; // Read more: => https://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
*
|
||||
* @param translationService: TranslationService
|
||||
* @param router: Router
|
||||
* @param layoutConfigService: LayoutCongifService
|
||||
* @param splashScreenService: SplashScreenService
|
||||
*/
|
||||
constructor(private translationService: TranslationService,
|
||||
private router: Router,
|
||||
private api: ApiService,
|
||||
public snackBar: MatSnackBar,
|
||||
private layoutConfigService: LayoutConfigService,
|
||||
private splashScreenService: SplashScreenService) {
|
||||
|
||||
// register translations
|
||||
this.translationService.loadTranslations(enLang, chLang, esLang, jpLang, deLang, frLang);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
|
||||
this.api.error.subscribe((res: any) => {
|
||||
if (res.status == 401 && res.error == 'token_expired'){
|
||||
this.api.login.refreshToken().subscribe(res=>{
|
||||
location.reload();
|
||||
})
|
||||
return;
|
||||
}
|
||||
for (let msg of res.msg) {
|
||||
this.snackBar.open(msg, '',{
|
||||
duration: 5000,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
panelClass: [res.status]
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// enable/disable loader
|
||||
this.loader = this.layoutConfigService.getConfig('loader.enabled');
|
||||
|
||||
const routerSubscription = this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
// hide splash screen
|
||||
this.splashScreenService.hide();
|
||||
|
||||
// scroll to top on every route change
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
// to display back the body content
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('kt-page--loaded');
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
this.unsubscribe.push(routerSubscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* On Destroy
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe.forEach(sb => sb.unsubscribe());
|
||||
}
|
||||
}
|
||||
187
src/app/app.module.ts
Normal file
187
src/app/app.module.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
// Angular
|
||||
import {BrowserModule, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
|
||||
import {APP_INITIALIZER, LOCALE_ID, NgModule} from '@angular/core';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {
|
||||
GestureConfig,
|
||||
MAT_DATE_LOCALE,
|
||||
MatNativeDateModule,
|
||||
MatPaginatorIntl,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule
|
||||
} from '@angular/material';
|
||||
import {OverlayModule} from '@angular/cdk/overlay';
|
||||
// Angular in memory
|
||||
import {HttpClientInMemoryWebApiModule} from 'angular-in-memory-web-api';
|
||||
// Perfect Scroll bar
|
||||
import {PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface} from 'ngx-perfect-scrollbar';
|
||||
// SVG inline
|
||||
import {InlineSVGModule} from 'ng-inline-svg';
|
||||
// Env
|
||||
import {environment} from '../environments/environment';
|
||||
// Hammer JS
|
||||
import 'hammerjs';
|
||||
// NGX Permissions
|
||||
import {NgxPermissionsModule, NgxPermissionsService} from 'ngx-permissions';
|
||||
// NGRX
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {StoreRouterConnectingModule} from '@ngrx/router-store';
|
||||
import {StoreDevtoolsModule} from '@ngrx/store-devtools';
|
||||
// State
|
||||
import {metaReducers, reducers} from './core/reducers';
|
||||
// Copmponents
|
||||
import {AppComponent} from './app.component';
|
||||
// Modules
|
||||
import {AppRoutingModule} from './app-routing.module';
|
||||
import {CoreModule} from './core/core.module';
|
||||
import {ThemeModule} from './views/theme/theme.module';
|
||||
// Partials
|
||||
import {PartialsModule} from './views/partials/partials.module';
|
||||
// Layout Services
|
||||
import {
|
||||
DataTableService,
|
||||
FakeApiService,
|
||||
KtDialogService,
|
||||
LayoutConfigService,
|
||||
LayoutRefService,
|
||||
MenuAsideService,
|
||||
MenuConfigService,
|
||||
MenuHorizontalService,
|
||||
PageConfigService,
|
||||
SplashScreenService,
|
||||
SubheaderService
|
||||
} from './core/_base/layout';
|
||||
// Auth
|
||||
import {AuthModule} from './views/pages/auth/auth.module';
|
||||
import {AuthService} from './core/auth';
|
||||
// CRUD
|
||||
import {HttpUtilsService, LayoutUtilsService, TypesUtilsService} from './core/_base/crud';
|
||||
// Config
|
||||
import {LayoutConfig} from './core/_config/layout.config';
|
||||
// Highlight JS
|
||||
import {HIGHLIGHT_OPTIONS, HighlightLanguage} from 'ngx-highlightjs';
|
||||
import * as typescript from 'highlight.js/lib/languages/typescript';
|
||||
import * as scss from 'highlight.js/lib/languages/scss';
|
||||
import * as xml from 'highlight.js/lib/languages/xml';
|
||||
import * as json from 'highlight.js/lib/languages/json';
|
||||
import {TokenStorage} from "./core/auth/_services/token-storage.service";
|
||||
import {PaginatorConfig} from './paginator-config';
|
||||
|
||||
import {CalendarModule, DateAdapter} from 'angular-calendar';
|
||||
import {adapterFactory} from 'angular-calendar/date-adapters/date-fns';
|
||||
import localeEs from '@angular/common/locales/es-MX';
|
||||
import {registerLocaleData} from "@angular/common";
|
||||
|
||||
|
||||
// tslint:disable-next-line:class-name
|
||||
const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
|
||||
wheelSpeed: 0.5,
|
||||
swipeEasing: true,
|
||||
minScrollbarLength: 40,
|
||||
maxScrollbarLength: 300,
|
||||
};
|
||||
|
||||
export function initializeLayoutConfig(appConfig: LayoutConfigService) {
|
||||
// initialize app by loading default demo layout config
|
||||
return () => {
|
||||
if (appConfig.getConfig() === null) {
|
||||
appConfig.loadConfigs(new LayoutConfig().configs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function hljsLanguages(): HighlightLanguage[] {
|
||||
return [
|
||||
{name: 'typescript', func: typescript},
|
||||
{name: 'scss', func: scss},
|
||||
{name: 'xml', func: xml},
|
||||
{name: 'json', func: json}
|
||||
];
|
||||
}
|
||||
|
||||
registerLocaleData(localeEs);
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
environment.isMockEnabled ? HttpClientInMemoryWebApiModule.forRoot(FakeApiService, {
|
||||
passThruUnknownUrl: true,
|
||||
dataEncapsulation: false
|
||||
}) : [],
|
||||
NgxPermissionsModule.forRoot(),
|
||||
PartialsModule,
|
||||
CoreModule,
|
||||
OverlayModule,
|
||||
MatNativeDateModule,
|
||||
StoreModule.forRoot(reducers, {metaReducers}),
|
||||
EffectsModule.forRoot([]),
|
||||
StoreRouterConnectingModule.forRoot({stateKey: 'router'}),
|
||||
StoreDevtoolsModule.instrument(),
|
||||
AuthModule.forRoot(),
|
||||
TranslateModule.forRoot(),
|
||||
MatProgressSpinnerModule,
|
||||
InlineSVGModule.forRoot(),
|
||||
ThemeModule,
|
||||
MatSelectModule,
|
||||
CalendarModule.forRoot({provide: DateAdapter, useFactory: adapterFactory}),
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
TokenStorage,
|
||||
LayoutConfigService,
|
||||
LayoutRefService,
|
||||
MenuConfigService,
|
||||
PageConfigService,
|
||||
KtDialogService,
|
||||
DataTableService,
|
||||
SplashScreenService,
|
||||
{
|
||||
provide: PERFECT_SCROLLBAR_CONFIG,
|
||||
useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG
|
||||
},
|
||||
{
|
||||
provide: HAMMER_GESTURE_CONFIG,
|
||||
useClass: GestureConfig
|
||||
},
|
||||
{
|
||||
// layout config initializer
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: (initializeLayoutConfig, ps: NgxPermissionsService) => function () {
|
||||
return ps.loadPermissions([])
|
||||
},
|
||||
deps: [LayoutConfigService, NgxPermissionsService],
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
// layout config initializer
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: (initializeLayoutConfig),
|
||||
deps: [LayoutConfigService, NgxPermissionsService],
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: HIGHLIGHT_OPTIONS,
|
||||
useValue: {languages: hljsLanguages}
|
||||
},
|
||||
// template services
|
||||
SubheaderService,
|
||||
MenuHorizontalService,
|
||||
MenuAsideService,
|
||||
HttpUtilsService,
|
||||
TypesUtilsService,
|
||||
LayoutUtilsService,
|
||||
{provide: MatPaginatorIntl, useValue: PaginatorConfig()},
|
||||
{provide: MAT_DATE_LOCALE, useValue: 'es-MX'},
|
||||
{provide: LOCALE_ID, useValue: 'es-MX'}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
11
src/app/core/_base/crud/index.ts
Normal file
11
src/app/core/_base/crud/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Models
|
||||
export { BaseModel } from './models/_base.model';
|
||||
export { BaseDataSource } from './models/_base.datasource';
|
||||
export { QueryParamsModel } from './models/query-models/query-params.model';
|
||||
export { QueryResultsModel } from './models/query-models/query-results.model';
|
||||
export { HttpExtenstionsModel } from './models/http-extentsions-model';
|
||||
// Utils
|
||||
export { HttpUtilsService } from './utils/http-utils.service';
|
||||
export { TypesUtilsService } from './utils/types-utils.service';
|
||||
export { InterceptService } from './utils/intercept.service';
|
||||
export { LayoutUtilsService, MessageType } from './utils/layout-utils.service';
|
||||
67
src/app/core/_base/crud/models/_base.datasource.ts
Normal file
67
src/app/core/_base/crud/models/_base.datasource.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// Angular
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
||||
// RxJS
|
||||
import { Observable, BehaviorSubject, combineLatest, Subscription, of } from 'rxjs';
|
||||
// CRUD
|
||||
import { HttpExtenstionsModel } from './http-extentsions-model';
|
||||
import { QueryParamsModel } from './query-models/query-params.model';
|
||||
import { QueryResultsModel } from './query-models/query-results.model';
|
||||
import { BaseModel } from './_base.model';
|
||||
import { skip, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
// Why not use MatTableDataSource?
|
||||
/* In this example, we will not be using the built-in MatTableDataSource because its designed for filtering,
|
||||
sorting and pagination of a client - side data array.
|
||||
Read the article: 'https://blog.angular-university.io/angular-material-data-table/'
|
||||
**/
|
||||
export class BaseDataSource implements DataSource<BaseModel> {
|
||||
entitySubject = new BehaviorSubject<any[]>([]);
|
||||
hasItems = true; // Need to show message: 'No records found'
|
||||
|
||||
// Loading | Progress bar
|
||||
loading$: Observable<boolean>;
|
||||
isPreloadTextViewed$: Observable<boolean> = of(true);
|
||||
|
||||
// Paginator | Paginators count
|
||||
paginatorTotalSubject = new BehaviorSubject<number>(0);
|
||||
paginatorTotal$: Observable<number>;
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
constructor() {
|
||||
this.paginatorTotal$ = this.paginatorTotalSubject.asObservable();
|
||||
|
||||
// subscribe hasItems to (entitySubject.length==0)
|
||||
const hasItemsSubscription = this.paginatorTotal$.pipe(
|
||||
distinctUntilChanged(),
|
||||
skip(1)
|
||||
).subscribe(res => this.hasItems = res > 0);
|
||||
this.subscriptions.push(hasItemsSubscription);
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<any[]> {
|
||||
// Connecting data source
|
||||
return this.entitySubject.asObservable();
|
||||
}
|
||||
|
||||
disconnect(collectionViewer: CollectionViewer): void {
|
||||
// Disonnecting data source
|
||||
this.entitySubject.complete();
|
||||
this.paginatorTotalSubject.complete();
|
||||
this.subscriptions.forEach(sb => sb.unsubscribe());
|
||||
}
|
||||
|
||||
baseFilter(_entities: any[], _queryParams: QueryParamsModel, _filtrationFields: string[] = []): QueryResultsModel {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.baseFilter(_entities, _queryParams, _filtrationFields);
|
||||
}
|
||||
|
||||
sortArray(_incomingArray: any[], _sortField: string = '', _sortOrder: string = 'asc'): any[] {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.sortArray(_incomingArray, _sortField, _sortOrder);
|
||||
}
|
||||
|
||||
searchInArray(_incomingArray: any[], _queryObj: any, _filtrationFields: string[] = []): any[] {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.searchInArray(_incomingArray, _queryObj, _filtrationFields);
|
||||
}
|
||||
}
|
||||
8
src/app/core/_base/crud/models/_base.model.ts
Normal file
8
src/app/core/_base/crud/models/_base.model.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export class BaseModel {
|
||||
// Edit
|
||||
_isEditMode = false;
|
||||
// Log
|
||||
_userId = 0; // Admin
|
||||
_createdDate: string;
|
||||
_updatedDate: string;
|
||||
}
|
||||
124
src/app/core/_base/crud/models/http-extentsions-model.ts
Normal file
124
src/app/core/_base/crud/models/http-extentsions-model.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// CRUD
|
||||
import { QueryParamsModel } from './query-models/query-params.model';
|
||||
import { QueryResultsModel } from './query-models/query-results.model';
|
||||
|
||||
export class HttpExtenstionsModel {
|
||||
|
||||
/**
|
||||
* Filtration with sorting
|
||||
* First do Sort then filter
|
||||
*
|
||||
* @param _entities: any[]
|
||||
* @param _queryParams: QueryParamsModel
|
||||
* @param _filtrationFields: string[]
|
||||
*/
|
||||
baseFilter(_entities: any[], _queryParams: QueryParamsModel, _filtrationFields: string[] = []): QueryResultsModel {
|
||||
// Filtration
|
||||
let entitiesResult = this.searchInArray(_entities, _queryParams.filter, _filtrationFields);
|
||||
|
||||
// Sorting
|
||||
// start
|
||||
if (_queryParams.sortField) {
|
||||
entitiesResult = this.sortArray(entitiesResult, _queryParams.sortField, _queryParams.sortOrder);
|
||||
}
|
||||
// end
|
||||
|
||||
// Paginator
|
||||
// start
|
||||
const totalCount = entitiesResult.length;
|
||||
const initialPos = _queryParams.pageNumber * _queryParams.pageSize;
|
||||
entitiesResult = entitiesResult.slice(initialPos, initialPos + _queryParams.pageSize);
|
||||
// end
|
||||
|
||||
const queryResults = new QueryResultsModel();
|
||||
queryResults.items = entitiesResult;
|
||||
queryResults.totalCount = totalCount;
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort array by field name and order-type
|
||||
* @param _incomingArray: any[]
|
||||
* @param _sortField: string
|
||||
* @param _sortOrder: string
|
||||
*/
|
||||
sortArray(_incomingArray: any[], _sortField: string = '', _sortOrder: string = 'asc'): any[] {
|
||||
if (!_sortField) {
|
||||
return _incomingArray;
|
||||
}
|
||||
|
||||
let result: any[] = [];
|
||||
result = _incomingArray.sort((a, b) => {
|
||||
if (a[_sortField] < b[_sortField]) {
|
||||
return _sortOrder === 'asc' ? -1 : 1;
|
||||
}
|
||||
|
||||
if (a[_sortField] > b[_sortField]) {
|
||||
return _sortOrder === 'asc' ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter array by some fields
|
||||
*
|
||||
* @param _incomingArray: any[]
|
||||
* @param _queryObj: any
|
||||
* @param _filtrationFields: string[]
|
||||
*/
|
||||
searchInArray(_incomingArray: any[], _queryObj: any, _filtrationFields: string[] = []): any[] {
|
||||
const result: any[] = [];
|
||||
let resultBuffer: any[] = [];
|
||||
const indexes: number[] = [];
|
||||
let firstIndexes: number[] = [];
|
||||
let doSearch = false;
|
||||
|
||||
_filtrationFields.forEach(item => {
|
||||
if (item in _queryObj) {
|
||||
_incomingArray.forEach((element, index) => {
|
||||
if (element[item] === _queryObj[item]) {
|
||||
firstIndexes.push(index);
|
||||
}
|
||||
});
|
||||
firstIndexes.forEach(element => {
|
||||
resultBuffer.push(_incomingArray[element]);
|
||||
});
|
||||
_incomingArray = resultBuffer.slice(0);
|
||||
resultBuffer = [].slice(0);
|
||||
firstIndexes = [].slice(0);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(_queryObj).forEach(key => {
|
||||
const searchText = _queryObj[key].toString().trim().toLowerCase();
|
||||
if (key && !_filtrationFields.some(e => e === key) && searchText) {
|
||||
doSearch = true;
|
||||
try {
|
||||
_incomingArray.forEach((element, index) => {
|
||||
if (element[key]) {
|
||||
const _val = element[key].toString().trim().toLowerCase();
|
||||
if (_val.indexOf(searchText) > -1 && indexes.indexOf(index) === -1) {
|
||||
indexes.push(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
console.log(ex, key, searchText);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!doSearch) {
|
||||
return _incomingArray;
|
||||
}
|
||||
|
||||
indexes.forEach(re => {
|
||||
result.push(_incomingArray[re]);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export class QueryParamsModel {
|
||||
// fields
|
||||
filter: any;
|
||||
sortOrder: string; // asc || desc
|
||||
sortField: string;
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
|
||||
// constructor overrides
|
||||
constructor(_filter: any,
|
||||
_sortOrder: string = 'asc',
|
||||
_sortField: string = '',
|
||||
_pageNumber: number = 0,
|
||||
_pageSize: number = 10) {
|
||||
this.filter = _filter;
|
||||
this.sortOrder = _sortOrder;
|
||||
this.sortField = _sortField;
|
||||
this.pageNumber = _pageNumber;
|
||||
this.pageSize = _pageSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export class QueryResultsModel {
|
||||
// fields
|
||||
items: any[];
|
||||
totalCount: number;
|
||||
errorMessage: string;
|
||||
|
||||
constructor(_items: any[] = [], _totalCount: number = 0, _errorMessage: string = '') {
|
||||
this.items = _items;
|
||||
this.totalCount = _totalCount;
|
||||
}
|
||||
}
|
||||
49
src/app/core/_base/crud/utils/http-utils.service.ts
Normal file
49
src/app/core/_base/crud/utils/http-utils.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpParams, HttpHeaders } from '@angular/common/http';
|
||||
// CRUD
|
||||
import { QueryResultsModel } from '../models/query-models/query-results.model';
|
||||
import { QueryParamsModel } from '../models/query-models/query-params.model';
|
||||
import { HttpExtenstionsModel } from '../../crud/models/http-extentsions-model';
|
||||
|
||||
@Injectable()
|
||||
export class HttpUtilsService {
|
||||
/**
|
||||
* Prepare query http params
|
||||
* @param queryParams: QueryParamsModel
|
||||
*/
|
||||
getFindHTTPParams(queryParams): HttpParams {
|
||||
const params = new HttpParams()
|
||||
.set('lastNamefilter', queryParams.filter)
|
||||
.set('sortOrder', queryParams.sortOrder)
|
||||
.set('sortField', queryParams.sortField)
|
||||
.set('pageNumber', queryParams.pageNumber.toString())
|
||||
.set('pageSize', queryParams.pageSize.toString());
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* get standard content-type
|
||||
*/
|
||||
getHTTPHeaders(): HttpHeaders {
|
||||
const result = new HttpHeaders();
|
||||
result.set('Content-Type', 'application/json');
|
||||
return result;
|
||||
}
|
||||
|
||||
baseFilter(_entities: any[], _queryParams: QueryParamsModel, _filtrationFields: string[] = []): QueryResultsModel {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.baseFilter(_entities, _queryParams, _filtrationFields);
|
||||
}
|
||||
|
||||
sortArray(_incomingArray: any[], _sortField: string = '', _sortOrder: string = 'asc'): any[] {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.sortArray(_incomingArray, _sortField, _sortOrder);
|
||||
}
|
||||
|
||||
searchInArray(_incomingArray: any[], _queryObj: any, _filtrationFields: string[] = []): any[] {
|
||||
const httpExtention = new HttpExtenstionsModel();
|
||||
return httpExtention.searchInArray(_incomingArray, _queryObj, _filtrationFields);
|
||||
}
|
||||
}
|
||||
51
src/app/core/_base/crud/utils/intercept.service.ts
Normal file
51
src/app/core/_base/crud/utils/intercept.service.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
|
||||
// RxJS
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { debug } from 'util';
|
||||
|
||||
/**
|
||||
* More information there => https://medium.com/@MetonymyQT/angular-http-interceptors-what-are-they-and-how-to-use-them-52e060321088
|
||||
*/
|
||||
@Injectable()
|
||||
export class InterceptService implements HttpInterceptor {
|
||||
// intercept request and add token
|
||||
intercept(
|
||||
request: HttpRequest<any>,
|
||||
next: HttpHandler
|
||||
): Observable<HttpEvent<any>> {
|
||||
// tslint:disable-next-line:no-debugger
|
||||
// modify request
|
||||
// request = request.clone({
|
||||
// setHeaders: {
|
||||
// Authorization: `Bearer ${localStorage.getItem('accessToken')}`
|
||||
// }
|
||||
// });
|
||||
// console.log('----request----');
|
||||
// console.log(request);
|
||||
// console.log('--- end of request---');
|
||||
|
||||
return next.handle(request).pipe(
|
||||
tap(
|
||||
event => {
|
||||
if (event instanceof HttpResponse) {
|
||||
// console.log('all looks good');
|
||||
// http response status code
|
||||
// console.log(event.status);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
// http response status code
|
||||
// console.log('----response----');
|
||||
// console.error('status code:');
|
||||
// tslint:disable-next-line:no-debugger
|
||||
console.error(error.status);
|
||||
console.error(error.message);
|
||||
// console.log('--- end of response---');
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
122
src/app/core/_base/crud/utils/layout-utils.service.ts
Normal file
122
src/app/core/_base/crud/utils/layout-utils.service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar, MatDialog } from '@angular/material';
|
||||
// Partials for CRUD
|
||||
import { ActionNotificationComponent,
|
||||
DeleteEntityDialogComponent,
|
||||
FetchEntityDialogComponent,
|
||||
UpdateStatusDialogComponent
|
||||
} from '../../../../views/partials/content/crud';
|
||||
|
||||
export enum MessageType {
|
||||
Create,
|
||||
Read,
|
||||
Update,
|
||||
Delete
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class LayoutUtilsService {
|
||||
/**
|
||||
* Service constructor
|
||||
*
|
||||
* @param snackBar: MatSnackBar
|
||||
* @param dialog: MatDialog
|
||||
*/
|
||||
constructor(private snackBar: MatSnackBar,
|
||||
private dialog: MatDialog) { }
|
||||
|
||||
/**
|
||||
* Showing (Mat-Snackbar) Notification
|
||||
*
|
||||
* @param _message
|
||||
* @param _type
|
||||
* @param _duration
|
||||
* @param _showCloseButton
|
||||
* @param _showUndoButton
|
||||
* @param _undoButtonDuration
|
||||
* @param _verticalPosition
|
||||
*/
|
||||
showActionNotification(
|
||||
_message: string,
|
||||
_type: MessageType = MessageType.Create,
|
||||
_duration: number = 10000,
|
||||
_showCloseButton: boolean = true,
|
||||
_showUndoButton: boolean = false,
|
||||
_undoButtonDuration: number = 3000,
|
||||
_verticalPosition: 'top' | 'bottom' = 'bottom'
|
||||
) {
|
||||
const _data = {
|
||||
message: _message,
|
||||
snackBar: this.snackBar,
|
||||
showCloseButton: _showCloseButton,
|
||||
showUndoButton: _showUndoButton,
|
||||
undoButtonDuration: _undoButtonDuration,
|
||||
verticalPosition: _verticalPosition,
|
||||
type: _type,
|
||||
action: 'Undo'
|
||||
};
|
||||
return this.snackBar.openFromComponent(ActionNotificationComponent, {
|
||||
duration: _duration,
|
||||
data: _data,
|
||||
verticalPosition: _verticalPosition
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing Confirmation (Mat-Dialog) before Entity Removing
|
||||
*
|
||||
* @param title: stirng
|
||||
* @param description: stirng
|
||||
* @param waitDesciption: string
|
||||
* @param btnText
|
||||
*/
|
||||
deleteElement(title: string = '', description: string = '', waitDesciption: string = '', btnText: string = '') {
|
||||
return this.dialog.open(DeleteEntityDialogComponent, {
|
||||
data: { title, description, waitDesciption, btnText
|
||||
},
|
||||
width: '440px',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing Confirmation (Mat-Dialog) before Entity Removing
|
||||
*
|
||||
* @param title: stirng
|
||||
* @param description: stirng
|
||||
* @param waitDesciption: string
|
||||
* @param btnText
|
||||
*/
|
||||
customElement(title: string = '', description: string = '', waitDesciption: string = '', btnText: string = 'Aceptar') {
|
||||
return this.dialog.open(DeleteEntityDialogComponent, {
|
||||
data: { title, description, waitDesciption, btnText },
|
||||
width: '440px'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing Fetching Window(Mat-Dialog)
|
||||
*
|
||||
* @param _data: any
|
||||
*/
|
||||
fetchElements(_data) {
|
||||
return this.dialog.open(FetchEntityDialogComponent, {
|
||||
data: _data,
|
||||
width: '400px'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing Update Status for Entites Window
|
||||
*
|
||||
* @param title: string
|
||||
* @param statuses: string[]
|
||||
* @param messages: string[]
|
||||
*/
|
||||
updateStatusForEntities(title, statuses, messages) {
|
||||
return this.dialog.open(UpdateStatusDialogComponent, {
|
||||
data: { title, statuses, messages },
|
||||
width: '480px'
|
||||
});
|
||||
}
|
||||
}
|
||||
129
src/app/core/_base/crud/utils/types-utils.service.ts
Normal file
129
src/app/core/_base/crud/utils/types-utils.service.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/** Angular */
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class TypesUtilsService {
|
||||
/**
|
||||
* Convert number to string and addinng '0' before
|
||||
*
|
||||
* @param value: number
|
||||
*/
|
||||
padNumber(value: number) {
|
||||
if (this.isNumber(value)) {
|
||||
return `0${value}`.slice(-2);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking value type equals to Number
|
||||
*
|
||||
* @param value: any
|
||||
*/
|
||||
isNumber(value: any): boolean {
|
||||
return !isNaN(this.toInteger(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Covert value to number
|
||||
*
|
||||
* @param value: any
|
||||
*/
|
||||
toInteger(value: any): number {
|
||||
return parseInt(`${value}`, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert date to string with 'MM/dd/yyyy' format
|
||||
*
|
||||
* @param date: Date
|
||||
*/
|
||||
dateFormat(date: Date): string {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const year = date.getFullYear();
|
||||
if (date) {
|
||||
return `${month}/${day}/${year}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date to string with custom format 'MM/dd/yyyy'
|
||||
*
|
||||
* @param date: any
|
||||
*/
|
||||
dateCustomFormat(date: any): string {
|
||||
let stringDate = '';
|
||||
if (date) {
|
||||
stringDate += this.isNumber(date.month) ? this.padNumber(date.month) + '/' : '';
|
||||
stringDate += this.isNumber(date.day) ? this.padNumber(date.day) + '/' : '';
|
||||
|
||||
stringDate += date.year;
|
||||
}
|
||||
return stringDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to DateFormatter (For Reactive Forms Validators)
|
||||
*
|
||||
* @param dateInStr: string (format => 'MM/dd/yyyy')
|
||||
*/
|
||||
getDateFormatterFromString(dateInStr: string): any {
|
||||
if (dateInStr && dateInStr.length > 0) {
|
||||
const dateParts = dateInStr.trim().split('/');
|
||||
return [
|
||||
{
|
||||
year: this.toInteger(dateParts[2]),
|
||||
month: this.toInteger(dateParts[0]),
|
||||
day: this.toInteger(dateParts[1])
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const _date = new Date();
|
||||
return [
|
||||
{
|
||||
year: _date.getFullYear(),
|
||||
month: _date.getMonth() + 1,
|
||||
day: _date.getDay()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to Date
|
||||
*
|
||||
* @param dateInStr: string (format => 'MM/dd/yyyy')
|
||||
*/
|
||||
getDateFromString(dateInStr: string = ''): Date {
|
||||
if (dateInStr && dateInStr.length > 0) {
|
||||
const dateParts = dateInStr.trim().split('/');
|
||||
const year = this.toInteger(dateParts[2]);
|
||||
const month = this.toInteger(dateParts[0]);
|
||||
const day = this.toInteger(dateParts[1]);
|
||||
// tslint:disable-next-line:prefer-const
|
||||
let result = new Date();
|
||||
result.setDate(day);
|
||||
result.setMonth(month - 1);
|
||||
result.setFullYear(year);
|
||||
return result;
|
||||
}
|
||||
|
||||
return new Date();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert Date to string with format 'MM/dd/yyyy'
|
||||
* @param _date: Date?
|
||||
*/
|
||||
getDateStringFromDate(_date: Date = new Date()): string {
|
||||
const month = _date.getMonth() + 1;
|
||||
const year = _date.getFullYear();
|
||||
const date = _date.getDate();
|
||||
return `${month}/${date}/${year}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Angular
|
||||
import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||
import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
// RxJS
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Page load animation
|
||||
*
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktContentAnimate]'
|
||||
})
|
||||
export class ContentAnimateDirective implements OnInit, OnDestroy {
|
||||
// Public properties
|
||||
player: AnimationPlayer;
|
||||
// Private properties
|
||||
private events: Subscription;
|
||||
|
||||
/**
|
||||
* Directive Consturctor
|
||||
* @param el: ElementRef
|
||||
* @param router: Router
|
||||
* @param animationBuilder: AnimationBuilder
|
||||
*/
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private router: Router,
|
||||
private animationBuilder: AnimationBuilder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// animate the content
|
||||
this.initAnimate();
|
||||
// animate page load
|
||||
this.events = this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.player.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.events.unsubscribe();
|
||||
this.player.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate page load
|
||||
*/
|
||||
initAnimate() {
|
||||
this.player = this.animationBuilder
|
||||
.build([
|
||||
// style({opacity: 0, transform: 'translateY(15px)'}),
|
||||
// animate(500, style({opacity: 1, transform: 'translateY(0)'})),
|
||||
// style({transform: 'none'}),
|
||||
style({
|
||||
transform: 'translateY(-3%)',
|
||||
opacity: 0,
|
||||
position: 'static'
|
||||
}),
|
||||
animate(
|
||||
'0.5s ease-in-out',
|
||||
style({transform: 'translateY(0%)', opacity: 1})
|
||||
)
|
||||
])
|
||||
.create(this.el.nativeElement);
|
||||
}
|
||||
}
|
||||
68
src/app/core/_base/layout/directives/header.directive.ts
Normal file
68
src/app/core/_base/layout/directives/header.directive.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
// ObjectPath
|
||||
import * as objectPath from 'object-path';
|
||||
|
||||
export interface HeaderOptions {
|
||||
classic?: any;
|
||||
offset?: any;
|
||||
minimize?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Header
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktHeader]',
|
||||
exportAs: 'ktHeader',
|
||||
})
|
||||
export class HeaderDirective implements AfterViewInit {
|
||||
@Input() options: HeaderOptions = {};
|
||||
|
||||
/**
|
||||
* Directive Constructor
|
||||
* @param el: ElementRef
|
||||
*/
|
||||
constructor(private el: ElementRef) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.setupOptions();
|
||||
|
||||
const header = new KTHeader(this.el.nativeElement, this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup options to header
|
||||
*/
|
||||
private setupOptions() {
|
||||
this.options = {
|
||||
classic: {
|
||||
desktop: true,
|
||||
mobile: false
|
||||
},
|
||||
};
|
||||
|
||||
if (this.el.nativeElement.getAttribute('data-ktheader-minimize') == '1') {
|
||||
objectPath.set(this.options, 'minimize', {
|
||||
desktop: {
|
||||
on: 'kt-header--minimize'
|
||||
},
|
||||
mobile: {
|
||||
on: 'kt-header--minimize'
|
||||
}
|
||||
});
|
||||
objectPath.set(this.options, 'offset', {
|
||||
desktop: 200,
|
||||
mobile: 150
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/app/core/_base/layout/directives/menu.directive.ts
Normal file
66
src/app/core/_base/layout/directives/menu.directive.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
// Object-Path
|
||||
import * as objectPath from 'object-path';
|
||||
|
||||
export interface MenuOptions {
|
||||
scroll?: any;
|
||||
submenu?: any;
|
||||
accordion?: any;
|
||||
dropdown?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure menu
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktMenu]',
|
||||
exportAs: 'ktMenu',
|
||||
})
|
||||
export class MenuDirective implements AfterViewInit {
|
||||
// Public propeties
|
||||
@Input() options: MenuOptions;
|
||||
// Private properites
|
||||
private menu: any;
|
||||
|
||||
/**
|
||||
* Directive Constructor
|
||||
* @param el: ElementRef
|
||||
*/
|
||||
constructor(private el: ElementRef) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.setupOptions();
|
||||
this.menu = new KTMenu(this.el.nativeElement, this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the menu
|
||||
*/
|
||||
getMenu() {
|
||||
return this.menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup menu options
|
||||
*/
|
||||
private setupOptions() {
|
||||
// init aside menu
|
||||
let menuDesktopMode = 'accordion';
|
||||
if (this.el.nativeElement.getAttribute('data-ktmenu-dropdown') === '1') {
|
||||
menuDesktopMode = 'dropdown';
|
||||
}
|
||||
|
||||
if (typeof objectPath.get(this.options, 'submenu.desktop') === 'object') {
|
||||
objectPath.set(this.options, 'submenu.desktop', menuDesktopMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/app/core/_base/layout/directives/offcanvas.directive.ts
Normal file
49
src/app/core/_base/layout/directives/offcanvas.directive.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
|
||||
export interface OffcanvasOptions {
|
||||
baseClass: string;
|
||||
overlay?: boolean;
|
||||
closeBy: string;
|
||||
toggleBy?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup off Convas
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktOffcanvas]',
|
||||
exportAs: 'ktOffcanvas',
|
||||
})
|
||||
export class OffcanvasDirective implements AfterViewInit {
|
||||
// Public properties
|
||||
@Input() options: OffcanvasOptions;
|
||||
// Private properties
|
||||
private offcanvas: any;
|
||||
|
||||
/**
|
||||
* Directive Constructor
|
||||
* @param el: ElementRef
|
||||
*/
|
||||
constructor(private el: ElementRef) { }
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.offcanvas = new KTOffcanvas(this.el.nativeElement, this.options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offCanvas
|
||||
*/
|
||||
getOffcanvas() {
|
||||
return this.offcanvas;
|
||||
}
|
||||
}
|
||||
44
src/app/core/_base/layout/directives/scroll-top.directive.ts
Normal file
44
src/app/core/_base/layout/directives/scroll-top.directive.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
|
||||
export interface ScrollTopOptions {
|
||||
offset: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to top
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktScrollTop]'
|
||||
})
|
||||
export class ScrollTopDirective implements AfterViewInit {
|
||||
// Public properties
|
||||
@Input() options: ScrollTopOptions;
|
||||
// Private properites
|
||||
private scrollTop: any;
|
||||
|
||||
/**
|
||||
* Directive Conctructor
|
||||
* @param el: ElementRef
|
||||
*/
|
||||
constructor(private el: ElementRef) { }
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollTop = new KTScrolltop(this.el.nativeElement, this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ScrollTop
|
||||
*/
|
||||
getScrollTop() {
|
||||
return this.scrollTop;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
// Chart
|
||||
import { Chart } from 'chart.js/dist/Chart.min.js';
|
||||
// LayoutConfig
|
||||
import { LayoutConfigService } from '../../layout/services/layout-config.service';
|
||||
|
||||
export interface SparklineChartOptions {
|
||||
// array of numbers
|
||||
data: number[];
|
||||
// chart line color
|
||||
color: string;
|
||||
// chart line size
|
||||
border: number;
|
||||
fill?: boolean;
|
||||
tooltip?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure sparkline chart
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktSparklineChart]',
|
||||
exportAs: 'ktSparklineChart'
|
||||
})
|
||||
export class SparklineChartDirective implements AfterViewInit {
|
||||
// Public properties
|
||||
@Input() options: SparklineChartOptions;
|
||||
// Private properties
|
||||
private chart: Chart;
|
||||
|
||||
/**
|
||||
* Directive Constructor
|
||||
*
|
||||
* @param el: ElementRef
|
||||
* @param layoutConfigService: LayoutConfigService
|
||||
*/
|
||||
constructor(private el: ElementRef, private layoutConfigService: LayoutConfigService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.initChart(this.el.nativeElement, this.options.data, this.options.color, this.options.border, this.options.fill, this.options.tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init chart
|
||||
* @param src: any
|
||||
* @param data: any
|
||||
* @param color: any
|
||||
* @param border: any
|
||||
* @param fill: any
|
||||
* @param tooltip: any
|
||||
*/
|
||||
initChart(src, data, color, border, fill, tooltip) {
|
||||
if (src.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set default values
|
||||
fill = (typeof fill !== 'undefined') ? fill : false;
|
||||
tooltip = (typeof tooltip !== 'undefined') ? tooltip : false;
|
||||
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October'],
|
||||
datasets: [{
|
||||
label: '',
|
||||
borderColor: color,
|
||||
borderWidth: border,
|
||||
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 12,
|
||||
pointBackgroundColor: Chart.helpers.color('#000000').alpha(0).rgbString(),
|
||||
pointBorderColor: Chart.helpers.color('#000000').alpha(0).rgbString(),
|
||||
pointHoverBackgroundColor: this.layoutConfigService.getConfig('colors.state.danger'),
|
||||
pointHoverBorderColor: Chart.helpers.color('#000000').alpha(0.1).rgbString(),
|
||||
fill: false,
|
||||
data,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
xPadding: 10,
|
||||
yPadding: 10,
|
||||
caretPadding: 10
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
usePointStyle: false
|
||||
}
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
hover: {
|
||||
mode: 'index'
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
display: false,
|
||||
gridLines: false,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Month'
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
display: false,
|
||||
gridLines: false,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Value'
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
},
|
||||
|
||||
elements: {
|
||||
point: {
|
||||
radius: 4,
|
||||
borderWidth: 12
|
||||
},
|
||||
},
|
||||
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 10,
|
||||
top: 5,
|
||||
bottom: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.chart = new Chart(src, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the chart
|
||||
*/
|
||||
getChart() {
|
||||
return this.chart;
|
||||
}
|
||||
}
|
||||
316
src/app/core/_base/layout/directives/sticky.directive.ts
Normal file
316
src/app/core/_base/layout/directives/sticky.directive.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { AfterViewInit, Directive, ElementRef, HostBinding, HostListener, Inject, Input, isDevMode, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { animationFrame } from 'rxjs/internal/scheduler/animationFrame';
|
||||
import { filter, map, share, startWith, takeUntil, throttleTime } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* Extended version of "Sticky Directive for Angular 2+"
|
||||
* https://github.com/w11k/angular-sticky-things
|
||||
*/
|
||||
|
||||
export interface StickyPositions {
|
||||
offsetY: number;
|
||||
bottomBoundary: number | null;
|
||||
}
|
||||
|
||||
export interface StickyStatus {
|
||||
isSticky: boolean;
|
||||
reachedLowerEdge: boolean;
|
||||
marginTop: number;
|
||||
marginBottom: number;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ktSticky]'
|
||||
})
|
||||
export class StickyDirective implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
filterGate = false;
|
||||
marginTop$ = new BehaviorSubject(0);
|
||||
marginBottom$ = new BehaviorSubject(0);
|
||||
enable$ = new BehaviorSubject(true);
|
||||
|
||||
@Input() scrollContainer: string | HTMLElement;
|
||||
@Input('spacerElement') spacerElement: HTMLElement | undefined;
|
||||
@Input('boundaryElement') boundaryElement: HTMLElement | undefined;
|
||||
@HostBinding('class.is-sticky') sticky = false;
|
||||
@HostBinding('class.boundary-reached') boundaryReached = false;
|
||||
/**
|
||||
* The field represents some position values in normal (not sticky) mode.
|
||||
* If the browser size or the content of the page changes, this value must be recalculated.
|
||||
*/
|
||||
private scroll$ = new Subject<number>();
|
||||
private scrollThrottled$: Observable<number>;
|
||||
private resize$ = new Subject<void>();
|
||||
private resizeThrottled$: Observable<void>;
|
||||
private extraordinaryChange$ = new BehaviorSubject<void>(undefined);
|
||||
private status$: Observable<StickyStatus>;
|
||||
private componentDestroyed = new Subject<void>();
|
||||
|
||||
constructor(private stickyElement: ElementRef, @Inject(PLATFORM_ID) private platformId: string) {
|
||||
|
||||
/** Throttle the scroll to animation frame (around 16.67ms) */
|
||||
this.scrollThrottled$ = this.scroll$
|
||||
.pipe(
|
||||
throttleTime(0, animationFrame),
|
||||
share()
|
||||
);
|
||||
|
||||
/** Throttle the resize to animation frame (around 16.67ms) */
|
||||
this.resizeThrottled$ = this.resize$
|
||||
.pipe(
|
||||
throttleTime(0, animationFrame),
|
||||
// emit once since we are currently using combineLatest
|
||||
startWith(null),
|
||||
share()
|
||||
);
|
||||
|
||||
this.status$ = combineLatest(
|
||||
this.enable$,
|
||||
this.scrollThrottled$,
|
||||
this.marginTop$,
|
||||
this.marginBottom$,
|
||||
this.extraordinaryChange$,
|
||||
this.resizeThrottled$,
|
||||
)
|
||||
.pipe(
|
||||
filter(([enabled]) => this.checkEnabled(enabled)),
|
||||
map(([enabled, pageYOffset, marginTop, marginBottom]) => this.determineStatus(this.determineElementOffsets(), pageYOffset, marginTop, marginBottom, enabled)),
|
||||
share(),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Input() set marginTop(value: number) {
|
||||
this.marginTop$.next(value);
|
||||
}
|
||||
|
||||
@Input() set marginBottom(value: number) {
|
||||
this.marginBottom$.next(value);
|
||||
}
|
||||
|
||||
@Input() set enable(value: boolean) {
|
||||
this.enable$.next(value);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.status$
|
||||
.pipe(takeUntil(this.componentDestroyed))
|
||||
.subscribe((status) => this.setSticky(status));
|
||||
}
|
||||
|
||||
public recalculate(): void {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
// Make sure to be in the next tick by using timeout
|
||||
setTimeout(() => {
|
||||
this.extraordinaryChange$.next(undefined);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is nasty code that should be refactored at some point.
|
||||
*
|
||||
* The Problem is, we filter for enabled. So that the code doesn't run
|
||||
* if @Input enabled = false. But if the user disables, we need exactly 1
|
||||
* emit in order to reset and call removeSticky. So this method basically
|
||||
* turns the filter in "filter, but let the first pass".
|
||||
*/
|
||||
checkEnabled(enabled: boolean): boolean {
|
||||
|
||||
if (!isPlatformBrowser(this.platformId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
// reset the gate
|
||||
this.filterGate = false;
|
||||
return true;
|
||||
} else {
|
||||
if (this.filterGate) {
|
||||
// gate closed, first emit has happened
|
||||
return false;
|
||||
} else {
|
||||
// this is the first emit for enabled = false,
|
||||
// let it pass, and activate the gate
|
||||
// so the next wont pass.
|
||||
this.filterGate = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@HostListener('window:resize', [])
|
||||
onWindowResize(): void {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
this.resize$.next();
|
||||
}
|
||||
}
|
||||
|
||||
setupListener(): void {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
const target = this.getScrollTarget();
|
||||
target.addEventListener('scroll', this.listener);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener() {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
const target = this.getScrollTarget();
|
||||
target.removeEventListener('scroll', this.listener);
|
||||
}
|
||||
}
|
||||
|
||||
listener = (e: Event) => {
|
||||
const upperScreenEdgeAt = (e.target as HTMLElement).scrollTop || window.pageYOffset;
|
||||
this.scroll$.next(upperScreenEdgeAt);
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.checkSetup();
|
||||
this.setupListener();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.componentDestroyed.next();
|
||||
this.removeListener();
|
||||
}
|
||||
|
||||
getComputedStyle(el: HTMLElement): ClientRect | DOMRect {
|
||||
return el.getBoundingClientRect();
|
||||
}
|
||||
|
||||
private getScrollTarget(): Element | Window {
|
||||
|
||||
let target: Element | Window;
|
||||
|
||||
if (this.scrollContainer && typeof this.scrollContainer === 'string') {
|
||||
target = document.querySelector(this.scrollContainer);
|
||||
} else if (this.scrollContainer && this.scrollContainer instanceof HTMLElement) {
|
||||
target = this.scrollContainer;
|
||||
} else {
|
||||
target = window;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private determineStatus(originalVals: StickyPositions, pageYOffset: number, marginTop: number, marginBottom: number, enabled: boolean): StickyStatus {
|
||||
const stickyElementHeight = this.getComputedStyle(this.stickyElement.nativeElement).height;
|
||||
const reachedLowerEdge = this.boundaryElement && window.pageYOffset + stickyElementHeight + marginBottom >= (originalVals.bottomBoundary - marginTop);
|
||||
return {
|
||||
isSticky: enabled && pageYOffset > originalVals.offsetY,
|
||||
reachedLowerEdge,
|
||||
marginBottom,
|
||||
marginTop,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the offset for element. If the element
|
||||
* currently is sticky, it will get removed
|
||||
* to access the original position. Other
|
||||
* wise this would just be 0 for fixed elements.
|
||||
*/
|
||||
private determineElementOffsets(): StickyPositions {
|
||||
if (this.sticky) {
|
||||
this.removeSticky();
|
||||
}
|
||||
|
||||
let bottomBoundary: number | null = null;
|
||||
|
||||
if (this.boundaryElement) {
|
||||
const boundaryElementHeight = this.getComputedStyle(this.boundaryElement).height;
|
||||
const boundaryElementOffset = getPosition(this.boundaryElement).y;
|
||||
bottomBoundary = boundaryElementHeight + boundaryElementOffset;
|
||||
}
|
||||
|
||||
return {offsetY: (getPosition(this.stickyElement.nativeElement).y - this.marginTop$.value), bottomBoundary};
|
||||
}
|
||||
|
||||
private makeSticky(boundaryReached: boolean = false, marginTop: number, marginBottom: number): void {
|
||||
|
||||
this.boundaryReached = boundaryReached;
|
||||
|
||||
// do this before setting it to pos:fixed
|
||||
const {width, height, left} = this.getComputedStyle(this.stickyElement.nativeElement);
|
||||
const offSet = boundaryReached ? (this.getComputedStyle(this.boundaryElement).bottom - height - this.marginBottom$.value) : this.marginTop$.value;
|
||||
|
||||
this.sticky = true;
|
||||
this.stickyElement.nativeElement.style.position = 'sticky';
|
||||
this.stickyElement.nativeElement.style.backgroundColor = '#fff';
|
||||
this.stickyElement.nativeElement.style.top = this.marginTop$.value + 'px';
|
||||
// this.stickyElement.nativeElement.style.left = left + 'px';
|
||||
this.stickyElement.nativeElement.style.width = `${width}px`;
|
||||
this.stickyElement.nativeElement.style.zIndex = `2`;
|
||||
if (this.spacerElement) {
|
||||
const spacerHeight = marginBottom + height + marginTop;
|
||||
this.spacerElement.style.height = `${spacerHeight}px`;
|
||||
}
|
||||
}
|
||||
|
||||
private checkSetup() {
|
||||
if (isDevMode() && !this.spacerElement) {
|
||||
console.warn(`******There might be an issue with your sticky directive!******
|
||||
|
||||
You haven't specified a spacer element. This will cause the page to jump.
|
||||
|
||||
Best practise is to provide a spacer element (e.g. a div) right before/after the sticky element.
|
||||
Then pass the spacer element as input:
|
||||
|
||||
<div #spacer></div>
|
||||
|
||||
<div stickyThing="" [spacer]="spacer">
|
||||
I am sticky!
|
||||
</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private setSticky(status: StickyStatus): void {
|
||||
if (status.isSticky) {
|
||||
this.makeSticky(status.reachedLowerEdge, status.marginTop, status.marginBottom);
|
||||
} else {
|
||||
this.removeSticky();
|
||||
}
|
||||
}
|
||||
|
||||
private removeSticky(): void {
|
||||
|
||||
this.boundaryReached = false;
|
||||
this.sticky = false;
|
||||
|
||||
this.stickyElement.nativeElement.style.position = '';
|
||||
this.stickyElement.nativeElement.style.width = 'auto';
|
||||
this.stickyElement.nativeElement.style.left = 'auto';
|
||||
this.stickyElement.nativeElement.style.top = 'auto';
|
||||
if (this.spacerElement) {
|
||||
this.spacerElement.style.height = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thanks to https://stanko.github.io/javascript-get-element-offset/
|
||||
function getPosition(el) {
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
let element = el;
|
||||
|
||||
// Loop through the DOM tree
|
||||
// and add it's parent's offset to get page offset
|
||||
do {
|
||||
top += element.offsetTop || 0;
|
||||
left += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
} while (element);
|
||||
|
||||
return {
|
||||
y: top,
|
||||
x: left,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Listen Tab click
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktTabClickEvent]'
|
||||
})
|
||||
export class TabClickEventDirective {
|
||||
/**
|
||||
* Directive Constructor
|
||||
* @param el: ElementRef
|
||||
* @param render: Renderer2
|
||||
*/
|
||||
constructor(private el: ElementRef, private render: Renderer2) { }
|
||||
|
||||
/**
|
||||
* A directive handler the tab click event for active tab
|
||||
* @param target
|
||||
*/
|
||||
@HostListener('click', ['$event.target'])
|
||||
onClick(target: HTMLElement) {
|
||||
// remove previous active tab
|
||||
const parent = target.closest('[role="tablist"]');
|
||||
const activeLink = parent.querySelector('[role="tab"].active');
|
||||
if (activeLink) {
|
||||
this.render.removeClass(activeLink, 'active');
|
||||
}
|
||||
// set active tab
|
||||
const link = target.closest('[role="tab"]');
|
||||
if (link) {
|
||||
this.render.addClass(link, 'active');
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/app/core/_base/layout/directives/toggle.directive.ts
Normal file
38
src/app/core/_base/layout/directives/toggle.directive.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Angular
|
||||
import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';
|
||||
|
||||
export interface ToggleOptions {
|
||||
target?: string;
|
||||
targetState?: string;
|
||||
togglerState?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ktToggle]',
|
||||
exportAs: 'ktToggle'
|
||||
})
|
||||
export class ToggleDirective implements AfterViewInit {
|
||||
// Public properties
|
||||
@Input() options: ToggleOptions;
|
||||
toggle: any;
|
||||
|
||||
/**
|
||||
* Directive constructor
|
||||
* @param el: ElementRef
|
||||
*/
|
||||
constructor(private el: ElementRef) { }
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.toggle = new KTToggle(this.el.nativeElement, this.options);
|
||||
}
|
||||
}
|
||||
45
src/app/core/_base/layout/index.ts
Normal file
45
src/app/core/_base/layout/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// Directives
|
||||
export { SparklineChartOptions, SparklineChartDirective } from './directives/sparkline-chart.directive';
|
||||
export { OffcanvasDirective } from './directives/offcanvas.directive';
|
||||
export { ScrollTopDirective } from './directives/scroll-top.directive';
|
||||
export { TabClickEventDirective } from './directives/tab-click-event.directive';
|
||||
export { ToggleDirective } from './directives/toggle.directive';
|
||||
|
||||
export { ContentAnimateDirective } from './directives/content-animate.directive';
|
||||
export { HeaderDirective } from './directives/header.directive';
|
||||
export { MenuDirective } from './directives/menu.directive';
|
||||
export { StickyDirective } from './directives/sticky.directive';
|
||||
|
||||
// Models
|
||||
export { DataTableItemModel } from './models/datatable-item.model';
|
||||
export { ExternalCodeExample } from './models/external-code-example';
|
||||
export { OffcanvasOptions } from './directives/offcanvas.directive';
|
||||
export { ScrollTopOptions } from './directives/scroll-top.directive';
|
||||
export { ToggleOptions } from './directives/toggle.directive';
|
||||
|
||||
export { LayoutConfigModel } from './models/layout-config.model';
|
||||
export { MenuOptions } from './directives/menu.directive';
|
||||
|
||||
// Pipes
|
||||
export { FirstLetterPipe } from './pipes/first-letter.pipe';
|
||||
export { GetObjectPipe } from './pipes/get-object.pipe';
|
||||
export { JoinPipe } from './pipes/join.pipe';
|
||||
export { SafePipe } from './pipes/safe.pipe';
|
||||
export { TimeElapsedPipe } from './pipes/time-elapsed.pipe';
|
||||
|
||||
// Services
|
||||
export { DataTableService } from './services/datatable.service';
|
||||
export { TranslationService } from './services/translation.service';
|
||||
|
||||
export { LayoutConfigService } from './services/layout-config.service';
|
||||
export { LayoutRefService } from './services/layout-ref.service';
|
||||
export { MenuAsideService } from './services/menu-aside.service';
|
||||
export { MenuConfigService } from './services/menu-config.service';
|
||||
export { MenuHorizontalService } from './services/menu-horizontal.service';
|
||||
export { PageConfigService } from './services/page-config.service';
|
||||
export { SplashScreenService } from './services/splash-screen.service';
|
||||
export { SubheaderService } from './services/subheader.service';
|
||||
export { KtDialogService } from './services/kt-dialog.service';
|
||||
|
||||
// Server
|
||||
export { FakeApiService } from './server/fake-api/fake-api.service';
|
||||
13
src/app/core/_base/layout/models/datatable-item.model.ts
Normal file
13
src/app/core/_base/layout/models/datatable-item.model.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export class DataTableItemModel {
|
||||
id: number;
|
||||
cModel: string;
|
||||
cManufacture: string;
|
||||
cModelYear: number;
|
||||
cMileage: number;
|
||||
cDescription: string;
|
||||
cColor: string;
|
||||
cPrice: number;
|
||||
cCondition: number;
|
||||
cStatus: number;
|
||||
cVINCode: string;
|
||||
}
|
||||
15
src/app/core/_base/layout/models/external-code-example.ts
Normal file
15
src/app/core/_base/layout/models/external-code-example.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export class ExternalCodeExample {
|
||||
beforeCodeTitle: string;
|
||||
beforeCodeDescription: string;
|
||||
htmlCode: string;
|
||||
tsCode: string;
|
||||
cssCode: string;
|
||||
scssCode: string;
|
||||
viewCode: string;
|
||||
codeTitle: string;
|
||||
afterCodeTitle: string;
|
||||
afterCodeDescription: string;
|
||||
sourceLink: string;
|
||||
isCodeVisible = true;
|
||||
isExampleExpanded: boolean;
|
||||
}
|
||||
168
src/app/core/_base/layout/models/layout-config.model.ts
Normal file
168
src/app/core/_base/layout/models/layout-config.model.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
export interface LayoutConfigModel {
|
||||
demo: string;
|
||||
self: {
|
||||
layout?: string;
|
||||
body?: {
|
||||
'background-image'?: string,
|
||||
'class'?: string,
|
||||
'background-position'?: string,
|
||||
'background-size'?: string
|
||||
};
|
||||
logo: any | string;
|
||||
};
|
||||
portlet?: {
|
||||
sticky: {
|
||||
offset: number
|
||||
}
|
||||
};
|
||||
loader: {
|
||||
enabled: boolean;
|
||||
type?: string | 'default' | 'spinner-message' | 'spinner-logo';
|
||||
logo?: string;
|
||||
message?: string;
|
||||
};
|
||||
colors: {
|
||||
state?: any;
|
||||
base: {
|
||||
label: Array<string>;
|
||||
shape: Array<string>
|
||||
}
|
||||
};
|
||||
width?: string;
|
||||
header: {
|
||||
self: {
|
||||
skin?: string;
|
||||
width?: string;
|
||||
fixed: {
|
||||
desktop: any;
|
||||
mobile: boolean
|
||||
}
|
||||
};
|
||||
// not implemented yet
|
||||
topbar?: {
|
||||
self?: {
|
||||
width?: string;
|
||||
}
|
||||
search?: {
|
||||
display: boolean;
|
||||
layout: 'offcanvas' | 'dropdown';
|
||||
dropdown?: {
|
||||
style: 'light' | 'dark';
|
||||
}
|
||||
};
|
||||
notifications?: {
|
||||
display: boolean;
|
||||
layout: 'offcanvas' | 'dropdown';
|
||||
dropdown: {
|
||||
style: 'light' | 'dark';
|
||||
}
|
||||
};
|
||||
'quick-actions'?: {
|
||||
display: boolean;
|
||||
layout: 'offcanvas' | 'dropdown';
|
||||
dropdown: {
|
||||
style: 'light' | 'dark';
|
||||
}
|
||||
};
|
||||
user?: {
|
||||
display: boolean;
|
||||
layout: 'offcanvas' | 'dropdown';
|
||||
dropdown: {
|
||||
style: 'light' | 'dark';
|
||||
}
|
||||
};
|
||||
languages?: {
|
||||
display: boolean
|
||||
};
|
||||
cart?: {
|
||||
display: boolean
|
||||
};
|
||||
'my-cart'?: any
|
||||
'quick-panel'?: {
|
||||
display: boolean
|
||||
}
|
||||
};
|
||||
search?: {
|
||||
display: boolean
|
||||
};
|
||||
menu?: {
|
||||
self: {
|
||||
display: boolean;
|
||||
layout?: string;
|
||||
'root-arrow'?: boolean;
|
||||
width?: string;
|
||||
};
|
||||
desktop: {
|
||||
arrow: boolean;
|
||||
toggle: string;
|
||||
submenu: {
|
||||
skin?: string;
|
||||
arrow: boolean
|
||||
}
|
||||
};
|
||||
mobile: {
|
||||
submenu: {
|
||||
skin: string;
|
||||
accordion: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
brand?: {
|
||||
self: {
|
||||
skin: string
|
||||
}
|
||||
};
|
||||
aside?: {
|
||||
self: {
|
||||
skin?: string;
|
||||
display: boolean;
|
||||
fixed?: boolean | any;
|
||||
minimize?: {
|
||||
toggle: boolean;
|
||||
default: boolean
|
||||
}
|
||||
};
|
||||
footer?: {
|
||||
self: {
|
||||
display: boolean
|
||||
}
|
||||
};
|
||||
menu: {
|
||||
'root-arrow'?: boolean;
|
||||
dropdown: boolean;
|
||||
scroll: boolean;
|
||||
submenu: {
|
||||
accordion: boolean;
|
||||
dropdown: {
|
||||
arrow: boolean;
|
||||
'hover-timeout': number
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
'aside-secondary'?: {
|
||||
self: {
|
||||
display: boolean;
|
||||
layout?: string;
|
||||
expanded?: boolean;
|
||||
}
|
||||
};
|
||||
subheader?: {
|
||||
display: boolean;
|
||||
fixed?: boolean;
|
||||
width?: string;
|
||||
layout?: string;
|
||||
style?: 'light' | 'solid' | 'transparent';
|
||||
daterangepicker?: {
|
||||
display: boolean
|
||||
}
|
||||
};
|
||||
content?: any;
|
||||
footer?: {
|
||||
self?: any;
|
||||
};
|
||||
'quick-panel'?: {
|
||||
display?: boolean
|
||||
};
|
||||
}
|
||||
21
src/app/core/_base/layout/pipes/first-letter.pipe.ts
Normal file
21
src/app/core/_base/layout/pipes/first-letter.pipe.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// Angular
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Returns only first letter of string
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'firstLetter'
|
||||
})
|
||||
export class FirstLetterPipe implements PipeTransform {
|
||||
|
||||
/**
|
||||
* Transform
|
||||
*
|
||||
* @param value: any
|
||||
* @param args: any
|
||||
*/
|
||||
transform(value: any, args?: any): any {
|
||||
return value.split(' ').map(n => n[0]).join('');
|
||||
}
|
||||
}
|
||||
22
src/app/core/_base/layout/pipes/get-object.pipe.ts
Normal file
22
src/app/core/_base/layout/pipes/get-object.pipe.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// Angular
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
// Object-Path
|
||||
import * as objectPath from 'object-path';
|
||||
|
||||
/**
|
||||
* Returns object from parent object
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'getObject'
|
||||
})
|
||||
export class GetObjectPipe implements PipeTransform {
|
||||
/**
|
||||
* Transform
|
||||
*
|
||||
* @param value: any
|
||||
* @param args: any
|
||||
*/
|
||||
transform(value: any, args?: any): any {
|
||||
return objectPath.get(value, args);
|
||||
}
|
||||
}
|
||||
23
src/app/core/_base/layout/pipes/join.pipe.ts
Normal file
23
src/app/core/_base/layout/pipes/join.pipe.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Angular
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Returns string from Array
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'join'
|
||||
})
|
||||
export class JoinPipe implements PipeTransform {
|
||||
/**
|
||||
* Transform
|
||||
*
|
||||
* @param value: any
|
||||
* @param args: any
|
||||
*/
|
||||
transform(value: any, args?: any): any {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(' ');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
42
src/app/core/_base/layout/pipes/safe.pipe.ts
Normal file
42
src/app/core/_base/layout/pipes/safe.pipe.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// Angular
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer, SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
/**
|
||||
* Sanitize HTML
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'safe'
|
||||
})
|
||||
export class SafePipe implements PipeTransform {
|
||||
/**
|
||||
* Pipe Constructor
|
||||
*
|
||||
* @param _sanitizer: DomSanitezer
|
||||
*/
|
||||
constructor(protected _sanitizer: DomSanitizer) { }
|
||||
|
||||
/**
|
||||
* Transform
|
||||
*
|
||||
* @param value: string
|
||||
* @param type: string
|
||||
*/
|
||||
transform(value: string, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
|
||||
switch (type) {
|
||||
case 'html':
|
||||
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||
case 'style':
|
||||
return this._sanitizer.bypassSecurityTrustStyle(value);
|
||||
case 'script':
|
||||
return this._sanitizer.bypassSecurityTrustScript(value);
|
||||
case 'url':
|
||||
return this._sanitizer.bypassSecurityTrustUrl(value);
|
||||
case 'resourceUrl':
|
||||
return this._sanitizer.bypassSecurityTrustResourceUrl(value);
|
||||
default:
|
||||
return this._sanitizer.bypassSecurityTrustHtml(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
123
src/app/core/_base/layout/pipes/time-elapsed.pipe.ts
Normal file
123
src/app/core/_base/layout/pipes/time-elapsed.pipe.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
// Angular
|
||||
import { Pipe, PipeTransform, OnDestroy, ChangeDetectorRef, NgZone } from '@angular/core';
|
||||
|
||||
/**
|
||||
* https://github.com/AndrewPoyntz/time-ago-pipe
|
||||
* An Angular pipe for converting a date string into a time ago
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'kTimeElapsed'
|
||||
})
|
||||
export class TimeElapsedPipe implements PipeTransform, OnDestroy {
|
||||
// Private properties
|
||||
private timer: number;
|
||||
|
||||
/**
|
||||
* Pipe Constructor
|
||||
*
|
||||
* @param changeDetectorRef: ChangeDetectorRef
|
||||
* @param ngZone: NgZone
|
||||
*/
|
||||
constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private ngZone: NgZone
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @ Lifecycle sequences => https://angular.io/guide/lifecycle-hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.removeTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform
|
||||
*
|
||||
* @param value: string
|
||||
*/
|
||||
transform(value: string) {
|
||||
this.removeTimer();
|
||||
const d = new Date(value);
|
||||
const now = new Date();
|
||||
const seconds = Math.round(
|
||||
Math.abs((now.getTime() - d.getTime()) / 1000)
|
||||
);
|
||||
const timeToUpdate = this.getSecondsUntilUpdate(seconds) * 1000;
|
||||
this.timer = this.ngZone.runOutsideAngular(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.setTimeout(() => {
|
||||
this.ngZone.run(() =>
|
||||
this.changeDetectorRef.markForCheck()
|
||||
);
|
||||
}, timeToUpdate);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const minutes = Math.round(Math.abs(seconds / 60));
|
||||
const hours = Math.round(Math.abs(minutes / 60));
|
||||
const days = Math.round(Math.abs(hours / 24));
|
||||
const months = Math.round(Math.abs(days / 30.416));
|
||||
const years = Math.round(Math.abs(days / 365));
|
||||
if (seconds <= 45) {
|
||||
return 'just now';
|
||||
} else if (seconds <= 90) {
|
||||
return '1 min';
|
||||
} else if (minutes <= 45) {
|
||||
return minutes + ' mins';
|
||||
} else if (minutes <= 90) {
|
||||
return '1 hr';
|
||||
} else if (hours <= 22) {
|
||||
return hours + ' hrs';
|
||||
} else if (hours <= 36) {
|
||||
return '1 day';
|
||||
} else if (days <= 25) {
|
||||
return days + ' days';
|
||||
} else if (days <= 45) {
|
||||
return '1 month';
|
||||
} else if (days <= 345) {
|
||||
return months + ' months';
|
||||
} else if (days <= 545) {
|
||||
return '1 year';
|
||||
} else {
|
||||
// (days > 545)
|
||||
return years + ' years';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove timer
|
||||
*/
|
||||
private removeTimer() {
|
||||
if (this.timer) {
|
||||
window.clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Seconds Until Update
|
||||
* @param seconds: number
|
||||
*/
|
||||
private getSecondsUntilUpdate(seconds: number) {
|
||||
const min = 60;
|
||||
const hr = min * 60;
|
||||
const day = hr * 24;
|
||||
if (seconds < min) {
|
||||
// less than 1 min, update ever 2 secs
|
||||
return 2;
|
||||
} else if (seconds < hr) {
|
||||
// less than an hour, update every 30 secs
|
||||
return 30;
|
||||
} else if (seconds < day) {
|
||||
// less then a day, update every 5 mins
|
||||
return 300;
|
||||
} else {
|
||||
// update every hour
|
||||
return 3600;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// Angular in memory
|
||||
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||
// RxJS
|
||||
import { Observable } from 'rxjs';
|
||||
// Auth
|
||||
import { AuthDataContext } from '../../../../auth';
|
||||
// ECommerce
|
||||
import { ECommerceDataContext } from '../../../../e-commerce';
|
||||
// Models
|
||||
import { CarsDb } from './fake-db/cars';
|
||||
|
||||
@Injectable()
|
||||
export class FakeApiService implements InMemoryDbService {
|
||||
/**
|
||||
* Service Constructore
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Create Fake DB and API
|
||||
*/
|
||||
createDb(): {} | Observable<{}> {
|
||||
// tslint:disable-next-line:class-name
|
||||
const db = {
|
||||
// auth module
|
||||
users: AuthDataContext.users,
|
||||
roles: AuthDataContext.roles,
|
||||
permissions: AuthDataContext.permissions,
|
||||
|
||||
// e-commerce
|
||||
// customers
|
||||
customers: ECommerceDataContext.customers,
|
||||
// products
|
||||
products: ECommerceDataContext.cars,
|
||||
productRemarks: ECommerceDataContext.remarks,
|
||||
productSpecs: ECommerceDataContext.carSpecs,
|
||||
|
||||
// orders
|
||||
orders: ECommerceDataContext.orders,
|
||||
|
||||
// data-table
|
||||
cars: CarsDb.cars
|
||||
};
|
||||
return db;
|
||||
}
|
||||
}
|
||||
481
src/app/core/_base/layout/server/fake-api/fake-db/cars.ts
Normal file
481
src/app/core/_base/layout/server/fake-api/fake-db/cars.ts
Normal file
@@ -0,0 +1,481 @@
|
||||
export class CarsDb {
|
||||
public static cars: any = [
|
||||
{
|
||||
id: 1,
|
||||
cModel: 'Elise',
|
||||
cManufacture: 'Lotus',
|
||||
cModelYear: 2004,
|
||||
cMileage: 116879,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Lotus Elise first appeared in 1996 and revolutionised small sports car design with its lightweight extruded aluminium chassis and composite body. There have been many variations, but the basic principle remain the same.`,
|
||||
cColor: 'Red',
|
||||
cPrice: 18347,
|
||||
cCondition: 1,
|
||||
createdDate: '09/30/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '1FTWX3D52AE575540',
|
||||
}, {
|
||||
id: 2,
|
||||
cModel: 'Sunbird',
|
||||
cManufacture: 'Pontiac',
|
||||
cModelYear: 1984,
|
||||
cMileage: 99515,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Pontiac Sunbird is an automobile that was produced by Pontiac, initially as a subcompact for the 1976 to 1980 cModel years,and later as a compact for the 1982 to 1994 cModel years. The Sunbird badge ran for 18 years (with a hiatus during the 1981 and 1982 cModel years, as the 1982 cModel was called J2000) and was then replaced in 1995 by the Pontiac Sunfire. Through the years the Sunbird was available in notchback coupé, sedan, hatchback, station wagon, and convertible body styles.`,
|
||||
cColor: 'Khaki',
|
||||
cPrice: 165956,
|
||||
cCondition: 0,
|
||||
createdDate: '03/22/2018',
|
||||
cStatus: 1,
|
||||
cVINCode: 'JM1NC2EF8A0293556'
|
||||
}, {
|
||||
id: 3,
|
||||
cModel: 'Amigo',
|
||||
cManufacture: 'Isuzu',
|
||||
cModelYear: 1993,
|
||||
cMileage: 138027,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Isuzu MU is a mid-size SUV that was produced by the Japan-based cManufacturer Isuzu. The three-door MU was introduced in 1989, followed in 1990 by the five-door version called Isuzu MU Wizard, both of which stopped production in 1998 to be replaced by a second generation. This time, the five-door version dropped the "MU" prefix, to become the Isuzu Wizard. The acronym "MU" is short for "Mysterious Utility". Isuzu cManufactured several variations to the MU and its derivates for sale in other countries.`,
|
||||
cColor: 'Aquamarine',
|
||||
cPrice: 45684,
|
||||
cCondition: 0,
|
||||
createdDate: '03/06/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: '1G6DG8E56C0973889'
|
||||
}, {
|
||||
id: 4,
|
||||
cModel: 'LS',
|
||||
cManufacture: 'Lexus',
|
||||
cModelYear: 2004,
|
||||
cMileage: 183068,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Lexus LS (Japanese: レクサス・LS, Rekusasu LS) is a full-size luxury car (F-segment in Europe) serving as the flagship cModel of Lexus, the luxury division of Toyota. For the first four generations, all LS cModels featured V8 engines and were predominantly rear-wheel-drive, with Lexus also offering all-wheel-drive, hybrid, and long-wheelbase variants. The fifth generation changed to using a V6 engine with no V8 option, and the long wheelbase variant was removed entirely.`,
|
||||
cColor: 'Mauv',
|
||||
cPrice: 95410,
|
||||
cCondition: 1,
|
||||
createdDate: '02/03/2018',
|
||||
cStatus: 1,
|
||||
cVINCode: '2T1BU4EE6DC859114'
|
||||
}, {
|
||||
id: 5,
|
||||
cModel: 'Paseo',
|
||||
cManufacture: 'Toyota',
|
||||
cModelYear: 1997,
|
||||
cMileage: 74884,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Toyota Paseo (known as the Cynos in Japan and other regions) is a sports styled compact car sold from 1991–1999 and was loosely based on the Tercel. It was available as a coupe and in later cModels as a convertible. Toyota stopped selling the car in the United States in 1997, however the car continued to be sold in Canada, Europe and Japan until 1999, but had no direct replacement. The Paseo, like the Tercel, shares a platform with the Starlet. Several parts are interchangeable between the three`,
|
||||
cColor: 'Pink',
|
||||
cPrice: 24796,
|
||||
cCondition: 1,
|
||||
createdDate: '08/13/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '1D7RB1GP0AS597432'
|
||||
}, {
|
||||
id: 6,
|
||||
cModel: 'M',
|
||||
cManufacture: 'Infiniti',
|
||||
cModelYear: 2009,
|
||||
cMileage: 194846,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Infiniti M is a line of mid-size luxury (executive) cars from the Infiniti luxury division of Nissan.\r\nThe first iteration was the M30 Coupe/Convertible, which were rebadged JDM Nissan Leopard.\r\nAfter a long hiatus, the M nameplate was used for Infiniti's mid-luxury sedans (executive cars). First was the short-lived M45 sedan, a rebadged version of the Japanese-spec Nissan Gloria. The next generations, the M35/45 and M37/56/35h/30d, became the flagship of the Infiniti brand and are based on the JDM Nissan Fuga.`,
|
||||
cColor: 'Puce',
|
||||
cPrice: 30521,
|
||||
cCondition: 1,
|
||||
createdDate: '01/27/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: 'YV1940AS1D1542424'
|
||||
}, {
|
||||
id: 7,
|
||||
cModel: 'Phantom',
|
||||
cManufacture: 'Rolls-Royce',
|
||||
cModelYear: 2008,
|
||||
cMileage: 164124,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Rolls-Royce Phantom VIII is a luxury saloon car cManufactured by Rolls-Royce Motor Cars. It is the eighth and current generation of Rolls-Royce Phantom, and the second launched by Rolls-Royce under BMW ownership. It is offered in two wheelbase lengths`,
|
||||
cColor: 'Purple',
|
||||
cPrice: 196247,
|
||||
cCondition: 1,
|
||||
createdDate: '09/28/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '3VWML7AJ1DM234625'
|
||||
}, {
|
||||
id: 8,
|
||||
cModel: 'QX',
|
||||
cManufacture: 'Infiniti',
|
||||
cModelYear: 2002,
|
||||
cMileage: 57410,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Infiniti QX80 (called the Infiniti QX56 until 2013) is a full-size luxury SUV built by Nissan Motor Company's Infiniti division. The naming convention originally adhered to the current trend of using a numeric designation derived from the engine's displacement, thus QX56 since the car has a 5.6-liter engine. From the 2014 cModel year, the car was renamed the QX80, as part of Infiniti's cModel name rebranding. The new name carries no meaning beyond suggesting that the vehicle is larger than smaller cModels such as the QX60`,
|
||||
cColor: 'Green',
|
||||
cPrice: 185775,
|
||||
cCondition: 1,
|
||||
createdDate: '11/15/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: 'WDDHF2EB9CA161524'
|
||||
}, {
|
||||
id: 9,
|
||||
cModel: 'Daytona',
|
||||
cManufacture: 'Dodge',
|
||||
cModelYear: 1993,
|
||||
cMileage: 4444,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Dodge Daytona was an automobile which was produced by the Chrysler Corporation under their Dodge division from 1984 to 1993. It was a front-wheel drive hatchback based on the Chrysler G platform, which was derived from the Chrysler K platform. The Chrysler Laser was an upscale rebadged version of the Daytona. The Daytona was restyled for 1987, and again for 1992. It replaced the Mitsubishi Galant-based Challenger, and slotted between the Charger and the Conquest. The Daytona was replaced by the 1995 Dodge Avenger, which was built by Mitsubishi Motors. The Daytona derives its name mainly from the Dodge Charger Daytona, which itself was named after the Daytona 500 race in Daytona Beach, Florida.`,
|
||||
cColor: 'Maroon',
|
||||
cPrice: 171898,
|
||||
cCondition: 0,
|
||||
createdDate: '12/24/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: 'WBAET37422N752051'
|
||||
}, {
|
||||
id: 10,
|
||||
cModel: '1500 Silverado',
|
||||
cManufacture: 'Chevrolet',
|
||||
cModelYear: 1999,
|
||||
cMileage: 195310,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Chevrolet Silverado, and its mechanically identical cousin, the GMC Sierra, are a series of full-size and heavy-duty pickup trucks cManufactured by General Motors and introduced in 1998 as the successor to the long-running Chevrolet C/K line. The Silverado name was taken from a trim level previously used on its predecessor, the Chevrolet C/K pickup truck from 1975 through 1998. General Motors continues to offer a GMC-badged variant of the Chevrolet full-size pickup under the GMC Sierra name, first used in 1987 for its variant of the GMT400 platform trucks.`,
|
||||
cColor: 'Blue',
|
||||
cPrice: 25764,
|
||||
cCondition: 0,
|
||||
createdDate: '08/30/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '1N6AF0LX6EN590806'
|
||||
}, {
|
||||
id: 11,
|
||||
cModel: 'CTS',
|
||||
cManufacture: 'Cadillac',
|
||||
cModelYear: 2012,
|
||||
cMileage: 170862,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Cadillac CTS is a mid-size luxury car / executive car designed, engineered, cManufactured and marketed by General Motors, and now in its third generation. \r\nInitially available only as a 4-door sedan on the GM Sigma platform, GM had offered the second generation CTS in three body styles: 4-door sedan, 2-door coupe, and 5-door sport wagon also using the Sigma platform — and the third generation in coupe and sedan configurations, using a stretched version of the GM Alpha platform.\r\nWayne Cherry and Kip Wasenko designed the exterior of the first generation CTS, marking the production debut of a design language (marketed as "Art and Science") first seen on the Evoq concept car. Bob Boniface and Robin Krieg designed the exterior of the third generation CTS`,
|
||||
cColor: 'Crimson',
|
||||
cPrice: 80588,
|
||||
cCondition: 0,
|
||||
createdDate: '02/15/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: '1G4HR54KX4U506530'
|
||||
}, {
|
||||
id: 12,
|
||||
cModel: 'Astro',
|
||||
cManufacture: 'Chevrolet',
|
||||
cModelYear: 1995,
|
||||
cMileage: 142137,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Chevrolet Astro was a rear-wheel drive van/minivan cManufactured and marketed by the American automaker Chevrolet from 1985 to 2005 and over two build generations. Along with its rebadged variant, the GMC Safari, the Astro was marketed in passenger as well as cargo and livery configurations—featuring a V6 engine, unibody construction with a separate front engine/suspension sub-frame, leaf-spring rear suspension, rear bi-parting doors, and a seating capacity of up to eight passengers`,
|
||||
cColor: 'Teal',
|
||||
cPrice: 72430,
|
||||
cCondition: 1,
|
||||
createdDate: '07/31/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: 'KMHGH4JH2DU676107'
|
||||
}, {
|
||||
id: 13,
|
||||
cModel: 'XL7',
|
||||
cManufacture: 'Suzuki',
|
||||
cModelYear: 2009,
|
||||
cMileage: 165165,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Suzuki XL-7 (styled as XL7 for the second generation) is Suzuki's mid-sized SUV that was made from 1998 to 2009, over two generations. It was slotted above the Grand Vitara in Suzuki's lineup.`,
|
||||
cColor: 'Puce',
|
||||
cPrice: 118667,
|
||||
cCondition: 0,
|
||||
createdDate: '02/04/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: '1N6AF0LX9EN733005'
|
||||
}, {
|
||||
id: 14,
|
||||
cModel: 'SJ 410',
|
||||
cManufacture: 'Suzuki',
|
||||
cModelYear: 1984,
|
||||
cMileage: 176074,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The SJ-Series was introduced to the United States (Puerto Rico (SJ-410) and Canada earlier) in 1985 for the 1986 cModel year. It was cPriced at $6200 and 47,000 were sold in its first year. The Samurai had a 1.3 liter, 63 hp (47 kW), 4-cylinder engine and was available as a convertible or a hardtop, and with or without a rear seat. The Suzuki Samurai became intensely popular within the serious 4WD community for its good off-road performance and reliability compared to other 4WDs of the time. This is due to the fact that while very compact and light, it is a real 4WD vehicle equipped with a transfer case, switchable 4WD and low range. Its lightness makes it a very nimble off-roader less prone to sinking in softer ground than heavier types. It is also considered a great beginner off-roader due to its simple design and ease of engine and suspension modifications.`,
|
||||
cColor: 'Orange',
|
||||
cPrice: 84325,
|
||||
cCondition: 0,
|
||||
createdDate: '12/22/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '2C3CDYBT6DH183756'
|
||||
}, {
|
||||
id: 15,
|
||||
cModel: 'F-Series',
|
||||
cManufacture: 'Ford',
|
||||
cModelYear: 1995,
|
||||
cMileage: 53030,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Ford F-Series is a series of light-duty trucks and medium-duty trucks (Class 2-7) that have been marketed and cManufactured by Ford Motor Company since 1948. While most variants of the F-Series trucks are full-size pickup trucks, the F-Series also includes chassis cab trucks and commercial vehicles. The Ford F-Series has been the best-selling vehicle in the United States since 1986 and the best-selling pickup since 1977.[1][2] It is also the best selling vehicle in Canada.[3] As of the 2018 cModel year, the F-Series generates $41.0 billion in annual revenue for Ford, making the F-Series brand more valuable than Coca-Cola and Nike.`,
|
||||
cColor: 'Aquamarine',
|
||||
cPrice: 77108,
|
||||
cCondition: 0,
|
||||
createdDate: '01/09/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: 'WBAVB33526P873481'
|
||||
}, {
|
||||
id: 16,
|
||||
cModel: 'HS',
|
||||
cManufacture: 'Lexus',
|
||||
cModelYear: 2011,
|
||||
cMileage: 84718,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Lexus HS (Japanese: レクサス・HS, Rekusasu HS) is a dedicated hybrid vehicle introduced by Lexus as a new entry-level luxury compact sedan in 2009.[2] Built on the Toyota New MC platform,[3] it is classified as a compact under Japanese regulations concerning vehicle exterior dimensions and engine displacement. Unveiled at the North American International Auto Show in January 2009, the HS 250h went on sale in July 2009 in Japan, followed by the United States in August 2009 as a 2010 cModel. The HS 250h represented the first dedicated hybrid vehicle in the Lexus lineup, as well as the first offered with an inline-four gasoline engine.[4] Bioplastic materials are used for the vehicle interior.[5] With a total length of 184.8 inches, the Lexus HS is slightly larger than the Lexus IS, but still smaller than the mid-size Lexus ES.`,
|
||||
cColor: 'Purple',
|
||||
cPrice: 140170,
|
||||
cCondition: 0,
|
||||
createdDate: '11/14/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '1FTWF3A56AE545514'
|
||||
}, {
|
||||
id: 17,
|
||||
cModel: 'Land Cruiser',
|
||||
cManufacture: 'Toyota',
|
||||
cModelYear: 2008,
|
||||
cMileage: 157019,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `Production of the first generation Land Cruiser began in 1951 (90 units) as Toyota's version of a Jeep-like vehicle.[2][3] The Land Cruiser has been produced in convertible, hardtop, station wagon and cab chassis versions. The Land Cruiser's reliability and longevity has led to huge popularity, especially in Australia where it is the best-selling body-on-frame, four-wheel drive vehicle.[4] Toyota also extensively tests the Land Cruiser in the Australian outback – considered to be one of the toughest operating environments in both temperature and terrain. In Japan, the Land Cruiser is exclusive to Toyota Japanese dealerships called Toyota Store.`,
|
||||
cColor: 'Crimson',
|
||||
cPrice: 72638,
|
||||
cCondition: 1,
|
||||
createdDate: '08/08/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '3C3CFFDR2FT957799'
|
||||
}, {
|
||||
id: 18,
|
||||
cModel: 'Wrangler',
|
||||
cManufacture: 'Jeep',
|
||||
cModelYear: 1994,
|
||||
cMileage: 55857,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Jeep Wrangler is a series of compact and mid-size (Wrangler Unlimited and Wrangler 4-door JL) four-wheel drive off-road vehicle cModels, cManufactured by Jeep since 1986, and currently migrating from its third into its fourth generation. The Wrangler JL was unveiled in late 2017 and will be produced at Jeep's Toledo Complex.`,
|
||||
cColor: 'Red',
|
||||
cPrice: 193523,
|
||||
cCondition: 0,
|
||||
createdDate: '02/28/2018',
|
||||
cStatus: 1,
|
||||
cVINCode: '3C4PDCAB7FT652291'
|
||||
}, {
|
||||
id: 19,
|
||||
cModel: 'Sunbird',
|
||||
cManufacture: 'Pontiac',
|
||||
cModelYear: 1994,
|
||||
cMileage: 165202,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Pontiac Sunbird is an automobile that was produced by Pontiac, initially as a subcompact for the 1976 to 1980 cModel years, and later as a compact for the 1982 to 1994 cModel years. The Sunbird badge ran for 18 years (with a hiatus during the 1981 and 1982 cModel years, as the 1982 cModel was called J2000) and was then replaced in 1995 by the Pontiac Sunfire. Through the years the Sunbird was available in notchback coupé, sedan, hatchback, station wagon, and convertible body styles.`,
|
||||
cColor: 'Blue',
|
||||
cPrice: 198739,
|
||||
cCondition: 0,
|
||||
createdDate: '05/13/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '1GD22XEG9FZ103872'
|
||||
}, {
|
||||
id: 20,
|
||||
cModel: 'A4',
|
||||
cManufacture: 'Audi',
|
||||
cModelYear: 1998,
|
||||
cMileage: 117958,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The A4 has been built in five generations and is based on the Volkswagen Group B platform. The first generation A4 succeeded the Audi 80. The automaker's internal numbering treats the A4 as a continuation of the Audi 80 lineage, with the initial A4 designated as the B5-series, followed by the B6, B7, B8 and the B9. The B8 and B9 versions of the A4 are built on the Volkswagen Group MLB platform shared with many other Audi cModels and potentially one Porsche cModel within Volkswagen Group`,
|
||||
cColor: 'Yellow',
|
||||
cPrice: 159377,
|
||||
cCondition: 0,
|
||||
createdDate: '12/15/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '2C3CDXCT2FH350366'
|
||||
}, {
|
||||
id: 21,
|
||||
cModel: 'Camry Solara',
|
||||
cManufacture: 'Toyota',
|
||||
cModelYear: 2006,
|
||||
cMileage: 22436,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Toyota Camry Solara, popularly known as the Toyota Solara, is a mid-size coupe/convertible built by Toyota. The Camry Solara is mechanically based on the Toyota Camry and effectively replaced the discontinued Camry Coupe (XV10); however, in contrast with its predecessor's conservative design, the Camry Solara was designed with a greater emphasis on sportiness, with more rakish styling, and uprated suspension and engine tuning intended to provide a sportier feel.[5] The coupe was launched in late 1998 as a 1999 cModel.[1] In 2000, the convertible was introduced, effectively replacing the Celica convertible in Toyota's North American lineup`,
|
||||
cColor: 'Green',
|
||||
cPrice: 122562,
|
||||
cCondition: 0,
|
||||
createdDate: '07/11/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '3C3CFFHH6DT874066'
|
||||
}, {
|
||||
id: 22,
|
||||
cModel: 'Tribeca',
|
||||
cManufacture: 'Subaru',
|
||||
cModelYear: 2007,
|
||||
cMileage: 127958,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Subaru Tribeca is a mid-size crossover SUV made from 2005 to 2014. Released in some markets, including Canada, as the Subaru B9 Tribeca, the name "Tribeca" derives from the Tribeca neighborhood of New York City.[1] Built on the Subaru Legacy platform and sold in five- and seven-seat configurations, the Tribeca was intended to be sold alongside a slightly revised version known as the Saab 9-6. Saab, at the time a subsidiary of General Motors (GM), abandoned the 9-6 program just prior to its release subsequent to GM's 2005 divestiture of its 20 percent stake in FHI.`,
|
||||
cColor: 'Yellow',
|
||||
cPrice: 90221,
|
||||
cCondition: 1,
|
||||
createdDate: '11/12/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: 'WVWGU7AN9AE957575'
|
||||
}, {
|
||||
id: 23,
|
||||
cModel: '1500 Club Coupe',
|
||||
cManufacture: 'GMC',
|
||||
cModelYear: 1997,
|
||||
cMileage: 95783,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `GMC (General Motors Truck Company), formally the GMC Division of General Motors LLC, is a division of the American automobile cManufacturer General Motors (GM) that primarily focuses on trucks and utility vehicles. GMC sells pickup and commercial trucks, buses, vans, military vehicles, and sport utility vehicles marketed worldwide by General Motors.`,
|
||||
cColor: 'Teal',
|
||||
cPrice: 64376,
|
||||
cCondition: 1,
|
||||
createdDate: '06/28/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: 'SCFBF04BX7G920997'
|
||||
}, {
|
||||
id: 24,
|
||||
cModel: 'Firebird',
|
||||
cManufacture: 'Pontiac',
|
||||
cModelYear: 2002,
|
||||
cMileage: 74063,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Pontiac Firebird is an American automobile built by Pontiac from the 1967 to the 2002 cModel years. Designed as a pony car to compete with the Ford Mustang, it was introduced 23 February 1967, the same cModel year as GM's Chevrolet division platform-sharing Camaro.[1] This also coincided with the release of the 1967 Mercury Cougar, Ford's upscale, platform-sharing version of the Mustang. The name "Firebird" was also previously used by GM for the General Motors Firebird 1950s and early-1960s`,
|
||||
cColor: 'Puce',
|
||||
cPrice: 94178,
|
||||
cCondition: 1,
|
||||
createdDate: '09/13/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '3C63D2JL5CG563879'
|
||||
}, {
|
||||
id: 25,
|
||||
cModel: 'RAV4',
|
||||
cManufacture: 'Toyota',
|
||||
cModelYear: 1996,
|
||||
cMileage: 99461,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Toyota RAV4 (Japanese: トヨタ RAV4 Toyota Ravufō) is a compact crossover SUV (sport utility vehicle) produced by the Japanese automobile cManufacturer Toyota. This was the first compact crossover SUV;[1] it made its debut in Japan and Europe in 1994,[2] and in North America in 1995. The vehicle was designed for consumers wanting a vehicle that had most of the benefits of SUVs, such as increased cargo room, higher visibility, and the option of full-time four-wheel drive, along with the maneuverability and fuel economy of a compact car. Although not all RAV4s are four-wheel-drive, RAV4 stands for "Recreational Activity Vehicle: 4-wheel drive", because the aforementioned equipment is an option in select countries`,
|
||||
cColor: 'Goldenrod',
|
||||
cPrice: 48342,
|
||||
cCondition: 0,
|
||||
createdDate: '12/29/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '2C4RDGDG6DR836144'
|
||||
}, {
|
||||
id: 26,
|
||||
cModel: 'Amanti / Opirus',
|
||||
cManufacture: 'Kia',
|
||||
cModelYear: 2007,
|
||||
cMileage: 189651,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Kia Opirus was an executive car cManufactured and marketed by Kia Motors that was launched in April 2003 and was marketed globally under various nameplates, prominently as the Amanti. It was considered to be Kia's flagship vehicle.`,
|
||||
cColor: 'Indigo',
|
||||
cPrice: 44292,
|
||||
cCondition: 1,
|
||||
createdDate: '09/01/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '1C4SDHCT2CC055294'
|
||||
}, {
|
||||
id: 27,
|
||||
cModel: 'S60',
|
||||
cManufacture: 'Volvo',
|
||||
cModelYear: 2001,
|
||||
cMileage: 78963,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `First introduced in 2004, Volvo's S60 R used a Haldex all-wheel-drive system mated to a 300 PS (221 kW; 296 hp) / 400 N⋅m (300 lbf⋅ft) inline-5. The 2004–2005 cModels came with a 6-speed manual transmission, or an available 5-speed automatic which allowed only 258 lb⋅ft (350 N⋅m) torque in 1st and 2nd gears. The 2006–2007 cModels came with a 6-speed manual or 6-speed automatic transmission (which was no longer torque-restricted)`,
|
||||
cColor: 'Goldenrod',
|
||||
cPrice: 9440,
|
||||
cCondition: 0,
|
||||
createdDate: '11/06/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '3C6TD5CT5CG316067',
|
||||
}, {
|
||||
id: 28,
|
||||
cModel: 'Grand Marquis',
|
||||
cManufacture: 'Mercury',
|
||||
cModelYear: 1984,
|
||||
cMileage: 153027,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Mercury Grand Marquis is an automobile that was sold by the Mercury division of Ford Motor Company from 1975 to 2011. From 1975 to 1982, it was the premium cModel of the Mercury Marquis cModel line, becoming a standalone cModel line in 1983. For its entire production run, the Grand Marquis served as the flagship of the Mercury line, with the Ford (LTD) Crown Victoria serving as its Ford counterpart. In addition, from 1979 to 2011, the Grand Marquis shared the rear-wheel drive Panther platform alongside the Lincoln Town Car`,
|
||||
cColor: 'Goldenrod',
|
||||
cPrice: 76027,
|
||||
cCondition: 0,
|
||||
createdDate: '12/16/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '3C3CFFJH2DT871398'
|
||||
}, {
|
||||
id: 29,
|
||||
cModel: 'Talon',
|
||||
cManufacture: 'Eagle',
|
||||
cModelYear: 1991,
|
||||
cMileage: 111234,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `Cosmetically, differences between the three were found in wheels, availability of cColors, tail lights, front and rear bumpers, and spoilers. The Talon featured two-tone body cColor with a black 'greenhouse' (roof, pillars, door-mounted mirrors) regardless of the body cColor. The variants featured 5-speed manual or 4-speed automatic transmissions and a hood bulge on the left-hand side of the car in order for camshaft clearance on the 4G63 engine. The base cModel DL did not use this engine but still had a bulge as evident in the 1992 Talon brochure. 2nd Generation cars all had such a bulge, even with the inclusion of the 420A engine`,
|
||||
cColor: 'Teal',
|
||||
cPrice: 157216,
|
||||
cCondition: 0,
|
||||
createdDate: '05/08/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: 'YV1902FH1D2957659'
|
||||
}, {
|
||||
id: 30,
|
||||
cModel: 'Passport',
|
||||
cManufacture: 'Honda',
|
||||
cModelYear: 2002,
|
||||
cMileage: 3812,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Passport was a part of a partnership between Isuzu and Honda in the 1990s, which saw an exchange of passenger vehicles from Honda to Isuzu, such as the Isuzu Oasis, and trucks from Isuzu to Honda, such as the Passport and Acura SLX. This arrangement was convenient for both companies, as Isuzu discontinued passenger car production in 1993 after a corporate restructuring, and Honda was in desperate need a SUV, a segment that was growing in popularity in North America as well as Japan during the 1990s. The partnership ended in 2002 with the discontinuation of the Passport in favor of the Honda-engineered Pilot`,
|
||||
cColor: 'Puce',
|
||||
cPrice: 41299,
|
||||
cCondition: 1,
|
||||
createdDate: '03/08/2018',
|
||||
cStatus: 0,
|
||||
cVINCode: 'WVWEU9AN4AE524071'
|
||||
}, {
|
||||
id: 31,
|
||||
cModel: 'H3',
|
||||
cManufacture: 'Hummer',
|
||||
cModelYear: 2006,
|
||||
cMileage: 196321,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Hummer H3 is a sport utility vehicle/off-road vehicle from Hummer that was produced from 2005 to 2010. Introduced for the 2006 cModel year, it was based on a highly modified GMT355 underpinning the Chevrolet cColorado/GMC Canyon compact pickup trucks that were also built at GM's Shreveport Operations in Shreveport, Louisiana and the Port Elizabeth plant in South Africa. The H3 was actually the smallest among the Hummer cModels. It was available either as a traditional midsize SUV or as a midsize pickup known as the H3T`,
|
||||
cColor: 'Pink',
|
||||
cPrice: 186964,
|
||||
cCondition: 1,
|
||||
createdDate: '06/04/2017',
|
||||
cStatus: 1,
|
||||
cVINCode: '4T1BF1FK4FU746230',
|
||||
}, {
|
||||
id: 32,
|
||||
cModel: 'Comanche',
|
||||
cManufacture: 'Jeep',
|
||||
cModelYear: 1992,
|
||||
cMileage: 72285,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The Jeep Comanche (designated MJ) is a pickup truck variant of the Cherokee compact SUV (1984–2001)[3] cManufactured and marketed by Jeep for cModel years 1986-1992 in rear wheel (RWD) and four-wheel drive (4WD) cModels as well as two cargo bed lengths: six-feet (1.83 metres) and seven-feet (2.13 metres)`,
|
||||
cColor: 'Mauv',
|
||||
cPrice: 145971,
|
||||
cCondition: 1,
|
||||
createdDate: '09/01/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '1J4PN2GK1BW745045'
|
||||
}, {
|
||||
id: 33,
|
||||
cModel: 'Blazer',
|
||||
cManufacture: 'Chevrolet',
|
||||
cModelYear: 1993,
|
||||
cMileage: 189804,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The 2014 – 2nd generation, MY14 Duramax 2.8L diesel engines have several new parts, namely a new water-cooled variable-geometry turbocharger, a new high-pressure common-rail fuel delivery system, a new exhaust gas recirculation (EGR) system, a new intake manifold, a new cylinder head, a new cylinder block, a new balance shaft unit and a new Engine Control Module (ECM). and now produce 197 hp and 369 Ft/Lbs of torque`,
|
||||
cColor: 'Indigo',
|
||||
cPrice: 154594,
|
||||
cCondition: 0,
|
||||
createdDate: '09/13/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '1G6KD57Y43U482896'
|
||||
}, {
|
||||
id: 34,
|
||||
cModel: 'Envoy XUV',
|
||||
cManufacture: 'GMC',
|
||||
cModelYear: 2004,
|
||||
cMileage: 187960,
|
||||
// tslint:disable-next-line:max-line-length
|
||||
cDescription: `The GMC Envoy is a mid-size SUV that was produced by General Motors. It was introduced for the 1998 cModel year. After the first generation Envoy was discontinued after the 2000 cModel year, but the Envoy was reintroduced and redesigned for the 2002 cModel year, and it was available in the GMC line of vehicles from the 2002 to 2009 cModel years`,
|
||||
cColor: 'Turquoise',
|
||||
cPrice: 185103,
|
||||
cCondition: 1,
|
||||
createdDate: '12/07/2017',
|
||||
cStatus: 0,
|
||||
cVINCode: '5GAER23D09J658030'
|
||||
}
|
||||
];
|
||||
}
|
||||
26
src/app/core/_base/layout/services/datatable.service.ts
Normal file
26
src/app/core/_base/layout/services/datatable.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
// RxJS
|
||||
import { Observable } from 'rxjs';
|
||||
// Models
|
||||
import { DataTableItemModel } from '../models/datatable-item.model';
|
||||
|
||||
const API_DATATABLE_URL = 'api/cars';
|
||||
|
||||
@Injectable()
|
||||
export class DataTableService {
|
||||
/**
|
||||
* Service Constructor
|
||||
*
|
||||
* @param http: HttpClient
|
||||
*/
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
/**
|
||||
* Returns data from fake server
|
||||
*/
|
||||
getAllItems(): Observable<DataTableItemModel[]> {
|
||||
return this.http.get<DataTableItemModel[]>(API_DATATABLE_URL);
|
||||
}
|
||||
}
|
||||
29
src/app/core/_base/layout/services/kt-dialog.service.ts
Normal file
29
src/app/core/_base/layout/services/kt-dialog.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class KtDialogService {
|
||||
private ktDialog: any;
|
||||
private currentState: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
// Public properties
|
||||
constructor() {
|
||||
this.ktDialog = new KTDialog({type: 'loader', placement: 'top center', message: 'Loading ...'});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.currentState.next(true);
|
||||
this.ktDialog.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.currentState.next(false);
|
||||
this.ktDialog.hide();
|
||||
}
|
||||
|
||||
checkIsShown() {
|
||||
return this.currentState.value;
|
||||
}
|
||||
}
|
||||
144
src/app/core/_base/layout/services/layout-config.service.ts
Normal file
144
src/app/core/_base/layout/services/layout-config.service.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { Subject } from 'rxjs';
|
||||
// Object-Path
|
||||
import * as objectPath from 'object-path';
|
||||
// Lodash
|
||||
import { merge } from 'lodash';
|
||||
// Models
|
||||
import { LayoutConfigModel } from '../models/layout-config.model';
|
||||
|
||||
@Injectable()
|
||||
export class LayoutConfigService {
|
||||
// Public properties
|
||||
onConfigUpdated$: Subject<LayoutConfigModel>;
|
||||
layoutConfig: LayoutConfigModel;
|
||||
|
||||
/**
|
||||
* Servcie constructor
|
||||
*/
|
||||
constructor() {
|
||||
// register on config changed event and set default config
|
||||
this.onConfigUpdated$ = new Subject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save layout config to the local storage
|
||||
* @param layoutConfig
|
||||
*/
|
||||
saveConfig(layoutConfig: LayoutConfigModel): void {
|
||||
if (layoutConfig) {
|
||||
localStorage.setItem('layoutConfigDrenax', JSON.stringify(layoutConfig));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get layout config from local storage
|
||||
*/
|
||||
getSavedConfig(): LayoutConfigModel {
|
||||
const config = localStorage.getItem('layoutConfigDrenax');
|
||||
try {
|
||||
return JSON.parse(config);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove saved layout config and revert back to default
|
||||
*/
|
||||
resetConfig(): void {
|
||||
localStorage.removeItem('layoutConfigDrenax');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config or by object path
|
||||
* @param path | object path separated by dot
|
||||
*/
|
||||
getConfig(path?: string): LayoutConfigModel | any {
|
||||
// merge default layout config with the saved config from layout storage
|
||||
// @todo; known issue; viewing 2 or more demos at the time in different browser's tabs, can cause conflict to the layout config
|
||||
this.layoutConfig = this.getSavedConfig();
|
||||
|
||||
if (path) {
|
||||
// if path is specified, get the value within object
|
||||
return objectPath.get(this.layoutConfig, path);
|
||||
}
|
||||
|
||||
return this.layoutConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set existing config with a new value
|
||||
* @param value
|
||||
* @param save
|
||||
*/
|
||||
setConfig(value: any, save?: boolean): void {
|
||||
this.layoutConfig = merge(this.layoutConfig, value);
|
||||
|
||||
if (save) {
|
||||
this.saveConfig(this.layoutConfig);
|
||||
}
|
||||
|
||||
// fire off an event that all subscribers will listen
|
||||
this.onConfigUpdated$.next(this.layoutConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brand logo
|
||||
*/
|
||||
getLogo(): string {
|
||||
const menuAsideLeftSkin = objectPath.get(this.layoutConfig, 'brand.self.skin');
|
||||
// set brand logo
|
||||
const logoObject = objectPath.get(this.layoutConfig, 'self.logo');
|
||||
|
||||
let logo;
|
||||
if (typeof logoObject === 'string') {
|
||||
logo = logoObject;
|
||||
}
|
||||
if (typeof logoObject === 'object') {
|
||||
logo = objectPath.get(logoObject, menuAsideLeftSkin + '');
|
||||
}
|
||||
if (typeof logo === 'undefined') {
|
||||
try {
|
||||
const logos = objectPath.get(this.layoutConfig, 'self.logo');
|
||||
logo = logos[Object.keys(logos)[0]];
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return logo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sticky logo
|
||||
*/
|
||||
getStickyLogo(): string {
|
||||
let logo = objectPath.get(this.layoutConfig, 'self.logo.sticky');
|
||||
if (typeof logo === 'undefined') {
|
||||
logo = this.getLogo();
|
||||
}
|
||||
return logo + '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize layout config
|
||||
* @param config
|
||||
*/
|
||||
loadConfigs(config: LayoutConfigModel) {
|
||||
this.layoutConfig = this.getSavedConfig();
|
||||
// use saved config as priority, or load new config if demo does not matched
|
||||
if (!this.layoutConfig || objectPath.get(this.layoutConfig, 'demo') !== config.demo) {
|
||||
this.layoutConfig = config;
|
||||
}
|
||||
this.saveConfig(this.layoutConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload current layout config to the state of latest saved config
|
||||
*/
|
||||
reloadConfigs(): LayoutConfigModel {
|
||||
this.layoutConfig = this.getSavedConfig();
|
||||
this.onConfigUpdated$.next(this.layoutConfig);
|
||||
return this.layoutConfig;
|
||||
}
|
||||
}
|
||||
25
src/app/core/_base/layout/services/layout-ref.service.ts
Normal file
25
src/app/core/_base/layout/services/layout-ref.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class LayoutRefService {
|
||||
// Public properties
|
||||
layoutRefs$: BehaviorSubject<any> = new BehaviorSubject<any>({});
|
||||
layoutRefs: any = {};
|
||||
|
||||
/**
|
||||
* Add element to Ref
|
||||
*
|
||||
* @param name: any
|
||||
* @param element: any
|
||||
*/
|
||||
addElement(name, element) {
|
||||
const obj = {};
|
||||
obj[name] = element;
|
||||
|
||||
this.layoutRefs = Object.assign({}, this.layoutRefs, obj);
|
||||
this.layoutRefs$.next(this.layoutRefs);
|
||||
}
|
||||
}
|
||||
32
src/app/core/_base/layout/services/menu-aside.service.ts
Normal file
32
src/app/core/_base/layout/services/menu-aside.service.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
// Object path
|
||||
import * as objectPath from 'object-path';
|
||||
// Services
|
||||
import { MenuConfigService } from './menu-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class MenuAsideService {
|
||||
// Public properties
|
||||
menuList$: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
|
||||
|
||||
/**
|
||||
* Service constructor
|
||||
*
|
||||
* @param menuConfigService: MenuConfigService
|
||||
*/
|
||||
constructor(private menuConfigService: MenuConfigService) {
|
||||
this.loadMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load menu list
|
||||
*/
|
||||
loadMenu() {
|
||||
// get menu list
|
||||
const menuItems: any[] = objectPath.get(this.menuConfigService.getMenus(), 'aside.items');
|
||||
this.menuList$.next(menuItems);
|
||||
}
|
||||
}
|
||||
37
src/app/core/_base/layout/services/menu-config.service.ts
Normal file
37
src/app/core/_base/layout/services/menu-config.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class MenuConfigService {
|
||||
// Public properties
|
||||
onConfigUpdated$: Subject<any>;
|
||||
// Private properties
|
||||
private menuConfig: any;
|
||||
|
||||
/**
|
||||
* Service Constructor
|
||||
*/
|
||||
constructor() {
|
||||
// register on config changed event and set default config
|
||||
this.onConfigUpdated$ = new Subject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the menuConfig
|
||||
*/
|
||||
getMenus() {
|
||||
return this.menuConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load config
|
||||
*
|
||||
* @param config: any
|
||||
*/
|
||||
loadConfigs(config: any) {
|
||||
this.menuConfig = config;
|
||||
this.onConfigUpdated$.next(this.menuConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
// Object path
|
||||
import * as objectPath from 'object-path';
|
||||
// Services
|
||||
import { MenuConfigService } from './menu-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class MenuHorizontalService {
|
||||
// Public properties
|
||||
menuList$: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
|
||||
|
||||
/**
|
||||
* Service constructor
|
||||
*
|
||||
* @param menuConfigService: MenuConfigService
|
||||
*/
|
||||
constructor(private menuConfigService: MenuConfigService) {
|
||||
this.loadMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load menu list
|
||||
*/
|
||||
loadMenu() {
|
||||
// get menu list
|
||||
const menuItems: any[] = objectPath.get(this.menuConfigService.getMenus(), 'header.items');
|
||||
this.menuList$.next(menuItems);
|
||||
}
|
||||
}
|
||||
92
src/app/core/_base/layout/services/page-config.service.ts
Normal file
92
src/app/core/_base/layout/services/page-config.service.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
// RxJS
|
||||
import { Subject } from 'rxjs';
|
||||
// Object-Path
|
||||
import * as objectPath from 'object-path';
|
||||
// Lodash
|
||||
import { merge } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class PageConfigService {
|
||||
// Public properties
|
||||
onConfigUpdated$: Subject<any>;
|
||||
pageConfig: any;
|
||||
|
||||
/**
|
||||
* Service Constructor
|
||||
*
|
||||
* @param router: Router
|
||||
*/
|
||||
constructor(private router: Router) {
|
||||
// register on config changed event and set default config
|
||||
this.onConfigUpdated$ = new Subject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page config based on route
|
||||
*/
|
||||
getCurrentPageConfig(path?: string): any {
|
||||
let configPath = this.cleanUrl(this.router.url);
|
||||
|
||||
if (path) {
|
||||
configPath += '.' + path;
|
||||
}
|
||||
|
||||
// get page config by path
|
||||
return objectPath.get(this.pageConfig, configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set existing config with a new value
|
||||
* @param value: any
|
||||
* @param sav: boolean?
|
||||
*/
|
||||
setConfig(value: any, save?: boolean): void {
|
||||
this.pageConfig = merge(this.pageConfig, value);
|
||||
|
||||
if (save) {
|
||||
// not implemented
|
||||
}
|
||||
|
||||
// fire off an event that all subscribers will listen
|
||||
this.onConfigUpdated$.next(this.pageConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load confgis
|
||||
*
|
||||
* @param config: any
|
||||
*/
|
||||
loadConfigs(config: any) {
|
||||
this.pageConfig = config;
|
||||
this.onConfigUpdated$.next(this.pageConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary params from URL
|
||||
* @param url
|
||||
*/
|
||||
cleanUrl(url: string): string {
|
||||
// remove first route (demo name) from url router
|
||||
if (new RegExp(/^\/demo/).test(url)) {
|
||||
const urls = url.split('/');
|
||||
urls.splice(0, 2);
|
||||
url = urls.join('/');
|
||||
}
|
||||
|
||||
if (url.charAt(0) == '/') {
|
||||
url = url.substr(1);
|
||||
}
|
||||
|
||||
// we get the page title from config, using url path.
|
||||
// we need to remove query from url ?id=1 before we use the path to search in array config.
|
||||
let finalUrl = url.replace(/\//g, '.');
|
||||
if (finalUrl.indexOf('?') !== -1) {
|
||||
finalUrl = finalUrl.substring(0, finalUrl.indexOf('?'));
|
||||
}
|
||||
|
||||
return finalUrl;
|
||||
}
|
||||
}
|
||||
52
src/app/core/_base/layout/services/splash-screen.service.ts
Normal file
52
src/app/core/_base/layout/services/splash-screen.service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// Angular
|
||||
import { ElementRef, Injectable } from '@angular/core';
|
||||
import { animate, AnimationBuilder, style } from '@angular/animations';
|
||||
|
||||
@Injectable()
|
||||
export class SplashScreenService {
|
||||
// Private properties
|
||||
private el: ElementRef;
|
||||
private stopped: boolean;
|
||||
|
||||
/**
|
||||
* Service constctuctor
|
||||
*
|
||||
* @param animationBuilder: AnimationBuilder
|
||||
*/
|
||||
constructor(private animationBuilder: AnimationBuilder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @param element: ElementRef
|
||||
*/
|
||||
init(element: ElementRef) {
|
||||
this.el = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide
|
||||
*/
|
||||
hide() {
|
||||
if (this.stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = this.animationBuilder.build([
|
||||
style({opacity: '1'}),
|
||||
animate(800, style({opacity: '0'}))
|
||||
]).create(this.el.nativeElement);
|
||||
|
||||
player.onDone(() => {
|
||||
if (typeof this.el.nativeElement.remove === 'function') {
|
||||
this.el.nativeElement.remove();
|
||||
} else {
|
||||
this.el.nativeElement.style.display = 'none';
|
||||
}
|
||||
this.stopped = true;
|
||||
});
|
||||
|
||||
setTimeout(() => player.play(), 300);
|
||||
}
|
||||
}
|
||||
197
src/app/core/_base/layout/services/subheader.service.ts
Normal file
197
src/app/core/_base/layout/services/subheader.service.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
// RxJS
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
// Object-Path
|
||||
import * as objectPath from 'object-path';
|
||||
// Services
|
||||
import { PageConfigService } from './page-config.service';
|
||||
import { MenuConfigService } from './menu-config.service';
|
||||
|
||||
export interface Breadcrumb {
|
||||
title: string;
|
||||
page: string | any;
|
||||
}
|
||||
|
||||
export interface BreadcrumbTitle {
|
||||
title: string;
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SubheaderService {
|
||||
// Public properties
|
||||
title$: BehaviorSubject<BreadcrumbTitle> = new BehaviorSubject<BreadcrumbTitle>({title: '', desc: ''});
|
||||
breadcrumbs$: BehaviorSubject<Breadcrumb[]> = new BehaviorSubject<Breadcrumb[]>([]);
|
||||
disabled$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
// Private properties
|
||||
private manualBreadcrumbs: any = {};
|
||||
private appendingBreadcrumbs: any = {};
|
||||
private manualTitle: any = {};
|
||||
|
||||
private asideMenus: any;
|
||||
private headerMenus: any;
|
||||
private pageConfig: any;
|
||||
|
||||
/**
|
||||
* Service Constructor
|
||||
*
|
||||
* @param router: Router
|
||||
* @param pageConfigService: PageConfigServie
|
||||
* @param menuConfigService: MenuConfigService
|
||||
*/
|
||||
constructor(
|
||||
private router: Router,
|
||||
private pageConfigService: PageConfigService,
|
||||
private menuConfigService: MenuConfigService) {
|
||||
const initBreadcrumb = () => {
|
||||
// get updated title current page config
|
||||
this.pageConfig = this.pageConfigService.getCurrentPageConfig();
|
||||
|
||||
this.headerMenus = objectPath.get(this.menuConfigService.getMenus(), 'header');
|
||||
this.asideMenus = objectPath.get(this.menuConfigService.getMenus(), 'aside');
|
||||
|
||||
// update breadcrumb on initial page load
|
||||
this.updateBreadcrumbs();
|
||||
|
||||
if (objectPath.get(this.manualTitle, this.router.url)) {
|
||||
this.setTitle(this.manualTitle[this.router.url]);
|
||||
} else {
|
||||
// get updated page title on every route changed
|
||||
this.title$.next(objectPath.get(this.pageConfig, 'page'));
|
||||
|
||||
// subheader enable/disable
|
||||
const hideSubheader = objectPath.get(this.pageConfig, 'page.subheader');
|
||||
this.disabled$.next(typeof hideSubheader !== 'undefined' && !hideSubheader);
|
||||
|
||||
if (objectPath.get(this.manualBreadcrumbs, this.router.url)) {
|
||||
// breadcrumbs was set manually
|
||||
this.setBreadcrumbs(this.manualBreadcrumbs[this.router.url]);
|
||||
} else {
|
||||
// get updated breadcrumbs on every route changed
|
||||
this.updateBreadcrumbs();
|
||||
// breadcrumbs was appended before, reuse it for this page
|
||||
if (objectPath.get(this.appendingBreadcrumbs, this.router.url)) {
|
||||
this.appendBreadcrumbs(this.appendingBreadcrumbs[this.router.url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initBreadcrumb();
|
||||
|
||||
// subscribe to router events
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(initBreadcrumb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update breadCrumbs
|
||||
*/
|
||||
updateBreadcrumbs() {
|
||||
// get breadcrumbs from header menu
|
||||
let breadcrumbs = this.getBreadcrumbs(this.headerMenus);
|
||||
// if breadcrumbs empty from header menu
|
||||
if (breadcrumbs.length === 0) {
|
||||
// get breadcrumbs from aside menu
|
||||
breadcrumbs = this.getBreadcrumbs(this.asideMenus);
|
||||
}
|
||||
|
||||
if (
|
||||
// if breadcrumb has only 1 item
|
||||
breadcrumbs.length === 1 &&
|
||||
// and breadcrumb title is same as current page title
|
||||
breadcrumbs[0].title.indexOf(objectPath.get(this.pageConfig, 'page.title')) !== -1) {
|
||||
// no need to display on frontend
|
||||
breadcrumbs = [];
|
||||
}
|
||||
|
||||
this.breadcrumbs$.next(breadcrumbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set full breadcrumb paths
|
||||
*/
|
||||
setBreadcrumbs(breadcrumbs: Breadcrumb[] | any[]) {
|
||||
this.manualBreadcrumbs[this.router.url] = breadcrumbs;
|
||||
this.breadcrumbs$.next(breadcrumbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append breadcrumb to the last existing breadcrumbs
|
||||
* @param breadcrumbs
|
||||
*/
|
||||
appendBreadcrumbs(breadcrumbs: Breadcrumb[] | any[]) {
|
||||
this.appendingBreadcrumbs[this.router.url] = breadcrumbs;
|
||||
const prev = this.breadcrumbs$.getValue();
|
||||
this.breadcrumbs$.next(prev.concat(breadcrumbs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get breadcrumbs from menu items
|
||||
* @param menus
|
||||
*/
|
||||
getBreadcrumbs(menus: any) {
|
||||
let url = this.pageConfigService.cleanUrl(this.router.url);
|
||||
url = url.replace(new RegExp(/\./, 'g'), '/');
|
||||
|
||||
const breadcrumbs = [];
|
||||
const menuPath = this.getPath(menus, url) || [];
|
||||
menuPath.forEach(key => {
|
||||
menus = menus[key];
|
||||
if (typeof menus !== 'undefined' && menus.title) {
|
||||
breadcrumbs.push(menus);
|
||||
}
|
||||
});
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set title
|
||||
*
|
||||
* @param title: string
|
||||
*/
|
||||
setTitle(title: string) {
|
||||
this.manualTitle[this.router.url] = title;
|
||||
this.title$.next({title});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object path by value
|
||||
* @param obj
|
||||
* @param value
|
||||
*/
|
||||
getPath(obj, value) {
|
||||
if (typeof obj !== 'object') {
|
||||
return;
|
||||
}
|
||||
const path = [];
|
||||
let found = false;
|
||||
|
||||
const search = (haystack) => {
|
||||
// tslint:disable-next-line:forin
|
||||
for (const key in haystack) {
|
||||
path.push(key);
|
||||
if (haystack[key] === value) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (typeof haystack[key] === 'object') {
|
||||
search(haystack[key]);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
};
|
||||
|
||||
search(obj);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
71
src/app/core/_base/layout/services/translation.service.ts
Normal file
71
src/app/core/_base/layout/services/translation.service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// Tranlsation
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export interface Locale {
|
||||
lang: string;
|
||||
// tslint:disable-next-line:ban-types
|
||||
data: Object;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TranslationService {
|
||||
// Private properties
|
||||
private langIds: any = [];
|
||||
|
||||
/**
|
||||
* Service Constructor
|
||||
*
|
||||
* @param translate: TranslateService
|
||||
*/
|
||||
constructor(private translate: TranslateService) {
|
||||
// add new langIds to the list
|
||||
this.translate.addLangs(['en']);
|
||||
|
||||
// this language will be used as a fallback when a translation isn't found in the current language
|
||||
this.translate.setDefaultLang('en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Translation
|
||||
*
|
||||
* @param args: Locale[]
|
||||
*/
|
||||
loadTranslations(...args: Locale[]): void {
|
||||
const locales = [...args];
|
||||
|
||||
locales.forEach(locale => {
|
||||
// use setTranslation() with the third argument set to true
|
||||
// to append translations instead of replacing them
|
||||
this.translate.setTranslation(locale.lang, locale.data, true);
|
||||
|
||||
this.langIds.push(locale.lang);
|
||||
});
|
||||
|
||||
// add new languages to the list
|
||||
this.translate.addLangs(this.langIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup language
|
||||
*
|
||||
* @param lang: any
|
||||
*/
|
||||
setLanguage(lang) {
|
||||
if (lang) {
|
||||
this.translate.use(this.translate.getDefaultLang());
|
||||
this.translate.use(lang);
|
||||
localStorage.setItem('languageDrenax', lang);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected language
|
||||
*/
|
||||
getSelectedLanguage(): any {
|
||||
return localStorage.getItem('languageDrenax') || this.translate.getDefaultLang();
|
||||
}
|
||||
}
|
||||
105
src/app/core/_config/i18n/ch.ts
Normal file
105
src/app/core/_config/i18n/ch.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// China
|
||||
export const locale = {
|
||||
lang: 'ch',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: '选择你的语言',
|
||||
},
|
||||
MENU: {
|
||||
NEW: '新',
|
||||
ACTIONS: '行动',
|
||||
CREATE_POST: '创建新帖子',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: '特征',
|
||||
APPS: '应用',
|
||||
DASHBOARD: '仪表板',
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: '要么',
|
||||
SUBMIT_BUTTON: '提交',
|
||||
NO_ACCOUNT: '没有账号?',
|
||||
SIGNUP_BUTTON: '注册',
|
||||
FORGOT_BUTTON: '忘记密码',
|
||||
BACK_BUTTON: '背部',
|
||||
PRIVACY: '隐私',
|
||||
LEGAL: '法律',
|
||||
CONTACT: '联系',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: '创建帐号',
|
||||
BUTTON: '签到',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Forgotten Password?',
|
||||
DESC: 'Enter your email to reset your password',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Password',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: '用戶名'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} is not valid',
|
||||
REQUIRED: '{{name}} is required',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Selected records count: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'eCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: '顾客',
|
||||
CUSTOMERS_LIST: '客户名单',
|
||||
NEW_CUSTOMER: 'New Customer',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Customer Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete this customer?',
|
||||
WAIT_DESCRIPTION: 'Customer is deleting...',
|
||||
MESSAGE: 'Customer has been deleted'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Customers Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete selected customers?',
|
||||
WAIT_DESCRIPTION: 'Customers are deleting...',
|
||||
MESSAGE: 'Selected customers have been deleted'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Status has been updated for selected customers',
|
||||
MESSAGE: 'Selected customers status have successfully been updated'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Customer has been updated',
|
||||
ADD_MESSAGE: 'Customer has been created'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/app/core/_config/i18n/de.ts
Normal file
105
src/app/core/_config/i18n/de.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// Germany
|
||||
export const locale = {
|
||||
lang: 'de',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: 'Wähle deine Sprache',
|
||||
},
|
||||
MENU: {
|
||||
NEW: 'Neu',
|
||||
ACTIONS: 'Aktionen',
|
||||
CREATE_POST: 'Erstellen Sie einen neuen Beitrag',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: 'Eigenschaften',
|
||||
APPS: 'Apps',
|
||||
DASHBOARD: 'Instrumententafel'
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: 'Oder',
|
||||
SUBMIT_BUTTON: 'einreichen',
|
||||
NO_ACCOUNT: 'Hast du kein Konto?',
|
||||
SIGNUP_BUTTON: 'Anmelden',
|
||||
FORGOT_BUTTON: 'Passwort vergessen',
|
||||
BACK_BUTTON: 'Zurück',
|
||||
PRIVACY: 'Privatsphäre',
|
||||
LEGAL: 'Legal',
|
||||
CONTACT: 'Kontakt',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: 'Create Account',
|
||||
BUTTON: 'Sign In',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Forgotten Password?',
|
||||
DESC: 'Enter your email to reset your password',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Password',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: 'Nutzername'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} is not valid',
|
||||
REQUIRED: '{{name}} is required',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Selected records count: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'eCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: 'Customers',
|
||||
CUSTOMERS_LIST: 'Customers list',
|
||||
NEW_CUSTOMER: 'New Customer',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Customer Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete this customer?',
|
||||
WAIT_DESCRIPTION: 'Customer is deleting...',
|
||||
MESSAGE: 'Customer has been deleted'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Customers Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete selected customers?',
|
||||
WAIT_DESCRIPTION: 'Customers are deleting...',
|
||||
MESSAGE: 'Selected customers have been deleted'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Status has been updated for selected customers',
|
||||
MESSAGE: 'Selected customers status have successfully been updated'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Customer has been updated',
|
||||
ADD_MESSAGE: 'Customer has been created'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/app/core/_config/i18n/en.ts
Normal file
105
src/app/core/_config/i18n/en.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// USA
|
||||
export const locale = {
|
||||
lang: 'en',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: 'Select your language',
|
||||
},
|
||||
MENU: {
|
||||
NEW: 'new',
|
||||
ACTIONS: 'Actions',
|
||||
CREATE_POST: 'Create New Post',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: 'Features',
|
||||
APPS: 'Apps',
|
||||
DASHBOARD: 'Dashboard',
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: 'Or',
|
||||
SUBMIT_BUTTON: 'Submit',
|
||||
NO_ACCOUNT: 'Don\'t have an account?',
|
||||
SIGNUP_BUTTON: 'Sign Up',
|
||||
FORGOT_BUTTON: 'Forgot Password',
|
||||
BACK_BUTTON: 'Back',
|
||||
PRIVACY: 'Privacy',
|
||||
LEGAL: 'Legal',
|
||||
CONTACT: 'Contact',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: 'Login Account',
|
||||
BUTTON: 'Sign In',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Forgotten Password?',
|
||||
DESC: 'Enter your email to reset your password',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Password',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: 'Username'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} is not valid',
|
||||
REQUIRED: '{{name}} is required',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Selected records count: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'eCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: 'Customers',
|
||||
CUSTOMERS_LIST: 'Customers list',
|
||||
NEW_CUSTOMER: 'New Customer',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Customer Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete this customer?',
|
||||
WAIT_DESCRIPTION: 'Customer is deleting...',
|
||||
MESSAGE: 'Customer has been deleted'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Customers Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete selected customers?',
|
||||
WAIT_DESCRIPTION: 'Customers are deleting...',
|
||||
MESSAGE: 'Selected customers have been deleted'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Status has been updated for selected customers',
|
||||
MESSAGE: 'Selected customers status have successfully been updated'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Customer has been updated',
|
||||
ADD_MESSAGE: 'Customer has been created'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/app/core/_config/i18n/es.ts
Normal file
105
src/app/core/_config/i18n/es.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// Spain
|
||||
export const locale = {
|
||||
lang: 'es',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: 'Elige tu idioma',
|
||||
},
|
||||
MENU: {
|
||||
NEW: 'nuevo',
|
||||
ACTIONS: 'Comportamiento',
|
||||
CREATE_POST: 'Crear nueva publicación',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: 'Caracteristicas',
|
||||
APPS: 'Aplicaciones',
|
||||
DASHBOARD: 'Tablero'
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: 'O',
|
||||
SUBMIT_BUTTON: 'Enviar',
|
||||
NO_ACCOUNT: 'No tienes una cuenta?',
|
||||
SIGNUP_BUTTON: 'Regístrate',
|
||||
FORGOT_BUTTON: 'Se te olvidó tu contraseña',
|
||||
BACK_BUTTON: 'Espalda',
|
||||
PRIVACY: 'Intimidad',
|
||||
LEGAL: 'Legal',
|
||||
CONTACT: 'Contacto',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: 'Crear una cuenta',
|
||||
BUTTON: 'Registrarse',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Contraseña olvidada?',
|
||||
DESC: 'Ingrese su correo electrónico para restablecer su contraseña',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Password',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: 'Usuario'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} is not valid',
|
||||
REQUIRED: '{{name}} is required',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Selected records count: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'eCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: 'Customers',
|
||||
CUSTOMERS_LIST: 'Customers list',
|
||||
NEW_CUSTOMER: 'New Customer',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Customer Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete this customer?',
|
||||
WAIT_DESCRIPTION: 'Customer is deleting...',
|
||||
MESSAGE: 'Customer has been deleted'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Customers Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete selected customers?',
|
||||
WAIT_DESCRIPTION: 'Customers are deleting...',
|
||||
MESSAGE: 'Selected customers have been deleted'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Status has been updated for selected customers',
|
||||
MESSAGE: 'Selected customers status have successfully been updated'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Customer has been updated',
|
||||
ADD_MESSAGE: 'Customer has been created'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/app/core/_config/i18n/fr.ts
Normal file
105
src/app/core/_config/i18n/fr.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// France
|
||||
export const locale = {
|
||||
lang: 'fr',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: 'choisissez votre langue',
|
||||
},
|
||||
MENU: {
|
||||
NEW: 'Nouveau',
|
||||
ACTIONS: 'Actes',
|
||||
CREATE_POST: 'Créer un nouveau Post',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: 'Fonctionnalités',
|
||||
APPS: 'Applications',
|
||||
DASHBOARD: 'Tableau de Bord',
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: 'Ou',
|
||||
SUBMIT_BUTTON: 'Soumettre',
|
||||
NO_ACCOUNT: 'Ne pas avoir de compte?',
|
||||
SIGNUP_BUTTON: 'Registre',
|
||||
FORGOT_BUTTON: 'Mot de passe oublié',
|
||||
BACK_BUTTON: 'Back',
|
||||
PRIVACY: 'Privacy',
|
||||
LEGAL: 'Legal',
|
||||
CONTACT: 'Contact',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: 'Créer un compte',
|
||||
BUTTON: 'Sign In',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Forgotten Password?',
|
||||
DESC: 'Enter your email to reset your password',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Mot de passe',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: 'Nom d\'utilisateur'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} n\'est pas valide',
|
||||
REQUIRED: '{{name}} est requis',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Nombre d\'enregistrements sélectionnés: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'éCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: 'Les clients',
|
||||
CUSTOMERS_LIST: 'Liste des clients',
|
||||
NEW_CUSTOMER: 'Nouveau client',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Suppression du client',
|
||||
DESCRIPTION: 'Êtes-vous sûr de supprimer définitivement ce client?',
|
||||
WAIT_DESCRIPTION: 'Le client est en train de supprimer ...',
|
||||
MESSAGE: 'Le client a été supprimé'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Supprimer les clients',
|
||||
DESCRIPTION: 'Êtes-vous sûr de supprimer définitivement les clients sélectionnés?',
|
||||
WAIT_DESCRIPTION: 'Les clients suppriment ...',
|
||||
MESSAGE: 'Les clients sélectionnés ont été supprimés'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Le statut a été mis à jour pour les clients sélectionnés',
|
||||
MESSAGE: 'Le statut des clients sélectionnés a été mis à jour avec succès'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Le client a été mis à jour',
|
||||
ADD_MESSAGE: 'Le client a été créé'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
105
src/app/core/_config/i18n/jp.ts
Normal file
105
src/app/core/_config/i18n/jp.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// Japan
|
||||
export const locale = {
|
||||
lang: 'jp',
|
||||
data: {
|
||||
TRANSLATOR: {
|
||||
SELECT: 'あなたが使う言語を選んでください',
|
||||
},
|
||||
MENU: {
|
||||
NEW: '新しい',
|
||||
ACTIONS: '行動',
|
||||
CREATE_POST: '新しい投稿を作成',
|
||||
PAGES: 'Pages',
|
||||
FEATURES: '特徴',
|
||||
APPS: 'アプリ',
|
||||
DASHBOARD: 'ダッシュボード',
|
||||
},
|
||||
AUTH: {
|
||||
GENERAL: {
|
||||
OR: 'または',
|
||||
SUBMIT_BUTTON: '提出する',
|
||||
NO_ACCOUNT: 'アカウントを持っていない?',
|
||||
SIGNUP_BUTTON: 'サインアップ',
|
||||
FORGOT_BUTTON: 'パスワードをお忘れですか',
|
||||
BACK_BUTTON: 'バック',
|
||||
PRIVACY: 'プライバシー',
|
||||
LEGAL: '法的',
|
||||
CONTACT: '接触',
|
||||
},
|
||||
LOGIN: {
|
||||
TITLE: 'Create Account',
|
||||
BUTTON: 'Sign In',
|
||||
},
|
||||
FORGOT: {
|
||||
TITLE: 'Forgotten Password?',
|
||||
DESC: 'Enter your email to reset your password',
|
||||
SUCCESS: 'Your account has been successfully reset.'
|
||||
},
|
||||
REGISTER: {
|
||||
TITLE: 'Sign Up',
|
||||
DESC: 'Enter your details to create your account',
|
||||
SUCCESS: 'Your account has been successfuly registered.'
|
||||
},
|
||||
INPUT: {
|
||||
EMAIL: 'Email',
|
||||
FULLNAME: 'Fullname',
|
||||
PASSWORD: 'Password',
|
||||
CONFIRM_PASSWORD: 'Confirm Password',
|
||||
USERNAME: 'ユーザー名'
|
||||
},
|
||||
VALIDATION: {
|
||||
INVALID: '{{name}} is not valid',
|
||||
REQUIRED: '{{name}} is required',
|
||||
MIN_LENGTH: '{{name}} minimum length is {{min}}',
|
||||
AGREEMENT_REQUIRED: 'Accepting terms & conditions are required',
|
||||
NOT_FOUND: 'The requested {{name}} is not found',
|
||||
INVALID_LOGIN: 'The login detail is incorrect',
|
||||
REQUIRED_FIELD: 'Required field',
|
||||
MIN_LENGTH_FIELD: 'Minimum field length:',
|
||||
MAX_LENGTH_FIELD: 'Maximum field length:',
|
||||
INVALID_FIELD: 'Field is not valid',
|
||||
}
|
||||
},
|
||||
ECOMMERCE: {
|
||||
COMMON: {
|
||||
SELECTED_RECORDS_COUNT: 'Selected records count: ',
|
||||
ALL: 'All',
|
||||
SUSPENDED: 'Suspended',
|
||||
ACTIVE: 'Active',
|
||||
FILTER: 'Filter',
|
||||
BY_STATUS: 'by Status',
|
||||
BY_TYPE: 'by Type',
|
||||
BUSINESS: 'Business',
|
||||
INDIVIDUAL: 'Individual',
|
||||
SEARCH: 'Search',
|
||||
IN_ALL_FIELDS: 'in all fields'
|
||||
},
|
||||
ECOMMERCE: 'eCommerce',
|
||||
CUSTOMERS: {
|
||||
CUSTOMERS: 'Customers',
|
||||
CUSTOMERS_LIST: 'Customers list',
|
||||
NEW_CUSTOMER: 'New Customer',
|
||||
DELETE_CUSTOMER_SIMPLE: {
|
||||
TITLE: 'Customer Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete this customer?',
|
||||
WAIT_DESCRIPTION: 'Customer is deleting...',
|
||||
MESSAGE: 'Customer has been deleted'
|
||||
},
|
||||
DELETE_CUSTOMER_MULTY: {
|
||||
TITLE: 'Customers Delete',
|
||||
DESCRIPTION: 'Are you sure to permanently delete selected customers?',
|
||||
WAIT_DESCRIPTION: 'Customers are deleting...',
|
||||
MESSAGE: 'Selected customers have been deleted'
|
||||
},
|
||||
UPDATE_STATUS: {
|
||||
TITLE: 'Status has been updated for selected customers',
|
||||
MESSAGE: 'Selected customers status have successfully been updated'
|
||||
},
|
||||
EDIT: {
|
||||
UPDATE_MESSAGE: 'Customer has been updated',
|
||||
ADD_MESSAGE: 'Customer has been created'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
133
src/app/core/_config/layout.config.ts
Normal file
133
src/app/core/_config/layout.config.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import {LayoutConfigModel} from '../_base/layout';
|
||||
|
||||
export class LayoutConfig {
|
||||
public defaults: LayoutConfigModel = {
|
||||
demo: 'demo6',
|
||||
// == Base Layout
|
||||
self: {
|
||||
layout: 'fluid', // fluid|boxed
|
||||
body: {
|
||||
'background-image': './assets/media/misc/bg-1.jpg',
|
||||
},
|
||||
logo: './assets/media/logos/logo-6.png',
|
||||
},
|
||||
// == Page Splash Screen loading
|
||||
loader: {
|
||||
enabled: true,
|
||||
type: 'spinner-logo',
|
||||
logo: './assets/media/logos/logo-mini-md.png',
|
||||
message: 'Please wait...',
|
||||
},
|
||||
// == Colors for javascript
|
||||
colors: {
|
||||
state: {
|
||||
brand: '#22b9ff',
|
||||
light: '#19385E',
|
||||
dark: '#282a3c',
|
||||
primary: '#5867dd',
|
||||
success: '#34bfa3',
|
||||
info: '#36a3f7',
|
||||
warning: '#ffb822',
|
||||
danger: '#fd3995',
|
||||
},
|
||||
base: {
|
||||
label: [
|
||||
'#c5cbe3',
|
||||
'#a1a8c3',
|
||||
'#3d4465',
|
||||
'#3e4466',
|
||||
],
|
||||
shape: [
|
||||
'#f0f3ff',
|
||||
'#d9dffa',
|
||||
'#afb4d4',
|
||||
'#646c9a',
|
||||
],
|
||||
},
|
||||
},
|
||||
header: {
|
||||
self: {
|
||||
skin: 'light',
|
||||
fixed: {
|
||||
desktop: true,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
self: {
|
||||
display: true,
|
||||
layout: 'default',
|
||||
'root-arrow': true,
|
||||
},
|
||||
desktop: {
|
||||
arrow: true,
|
||||
toggle: 'click',
|
||||
submenu: {
|
||||
skin: 'light',
|
||||
arrow: true,
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
submenu: {
|
||||
skin: 'dark',
|
||||
accordion: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
subheader: {
|
||||
display: true,
|
||||
layout: 'subheader-v1',
|
||||
fixed: true,
|
||||
width: 'fluid',
|
||||
style: 'solid',
|
||||
},
|
||||
content: {
|
||||
width: 'fluid',
|
||||
},
|
||||
brand: {
|
||||
self: {
|
||||
skin: 'narvy',
|
||||
},
|
||||
},
|
||||
aside: {
|
||||
self: {
|
||||
display: true,
|
||||
fixed: true,
|
||||
minimize: {
|
||||
toggle: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
self: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
dropdown: false,
|
||||
scroll: false,
|
||||
submenu: {
|
||||
accordion: false,
|
||||
dropdown: {
|
||||
arrow: true,
|
||||
'hover-timeout': 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
self: {
|
||||
width: 'fluid',
|
||||
fixed: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Good place for getting the remote config
|
||||
*/
|
||||
public get configs(): LayoutConfigModel {
|
||||
return this.defaults;
|
||||
}
|
||||
}
|
||||
197
src/app/core/_config/menu.config.ts
Normal file
197
src/app/core/_config/menu.config.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
export class MenuConfig {
|
||||
public defaults: any = {
|
||||
aside: {
|
||||
self: {},
|
||||
items: [
|
||||
{
|
||||
title: 'Inicio',
|
||||
root: true,
|
||||
icon: 'flaticon2-architecture-and-city',
|
||||
page: '/inicio',
|
||||
bullet: 'dot',
|
||||
},
|
||||
{section: 'Catálogos'},
|
||||
{
|
||||
title: 'Catálogos',
|
||||
root: true,
|
||||
icon: 'fa fa-book',
|
||||
permissions: [
|
||||
'administrador.usuarios.index',
|
||||
'administrador.roles.index',
|
||||
'administrador.clientes.index',
|
||||
'administrador.estatus_servicios.index',
|
||||
'administrador.formas_pagos.index',
|
||||
'administrador.tipos_servicios.index',
|
||||
'administrador.servicios.index',
|
||||
'administrador.tipos_vehiculos.index',
|
||||
'administrador.vehiculos.index',
|
||||
'administrador.sucursales.index'
|
||||
],
|
||||
submenu: [
|
||||
{
|
||||
title: 'Usuarios',
|
||||
icon: 'fa fa-user',
|
||||
page: '/catalogos/usuarios',
|
||||
permissions: ['administrador.usuarios.index'],
|
||||
},
|
||||
{
|
||||
title: 'Roles',
|
||||
icon: 'fa fa-users',
|
||||
page: '/catalogos/roles',
|
||||
permissions: ['administrador.roles.index'],
|
||||
},
|
||||
{
|
||||
title: 'Clientes',
|
||||
icon: 'fa fa-handshake-o',
|
||||
page: '/catalogos/clientes',
|
||||
permissions: ['administrador.clientes.index'],
|
||||
},
|
||||
{
|
||||
title: 'Estados de servicio',
|
||||
icon: 'fa fa-signal',
|
||||
page: '/catalogos/estados-servicio',
|
||||
permissions: ['administrador.estatus_servicios.index'],
|
||||
},
|
||||
{
|
||||
title: 'Formas de pago',
|
||||
icon: 'fa fa-money',
|
||||
page: '/catalogos/formas-pago',
|
||||
permissions: ['administrador.formas_pagos.index'],
|
||||
},
|
||||
{
|
||||
title: 'Tipos de servicio',
|
||||
icon: 'fa fa-building',
|
||||
page: '/catalogos/tipos-servicio',
|
||||
permissions: ['administrador.tipos_servicios.index'],
|
||||
},
|
||||
{
|
||||
title: 'Servicios',
|
||||
icon: 'fa fa-cogs',
|
||||
page: '/catalogos/servicios',
|
||||
permissions: ['administrador.servicios.index'],
|
||||
},
|
||||
{
|
||||
title: 'Tipos de vehículo',
|
||||
icon: 'fa fa-truck',
|
||||
page: '/catalogos/tipos-vehiculo',
|
||||
permissions: ['administrador.tipos_vehiculos.index'],
|
||||
},
|
||||
{
|
||||
title: 'Vehículos',
|
||||
icon: 'fa fa-car',
|
||||
page: '/catalogos/vehiculos',
|
||||
permissions: ['administrador.vehiculos.index'],
|
||||
},
|
||||
{
|
||||
title: 'Sucursales',
|
||||
icon: 'fa fa-home',
|
||||
page: '/catalogos/sucursales',
|
||||
permissions: ['administrador.sucursales.index'],
|
||||
},
|
||||
]
|
||||
},
|
||||
{section: 'Servicios'},
|
||||
{
|
||||
title: 'Servicios',
|
||||
root: true,
|
||||
icon: 'fas fa-phone',
|
||||
permissions: ['atencion_clientes.solicitud_servicios.index'],
|
||||
submenu: [
|
||||
{
|
||||
title: 'Solicitudes de servicios',
|
||||
icon: 'far fa-calendar',
|
||||
page: '/servicios/solicitudes-servicio',
|
||||
permissions: ['atencion_clientes.solicitud_servicios.index']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Agenda',
|
||||
root: true,
|
||||
icon: 'flaticon2-calendar',
|
||||
page: '/agenda',
|
||||
bullet: 'dot',
|
||||
permissions: ['atencion_clientes.servicios.agenda'],
|
||||
},
|
||||
{section: 'Reportes'},
|
||||
{
|
||||
title: 'Reportes',
|
||||
root: true,
|
||||
icon: 'fa fa-line-chart',
|
||||
permissions: [
|
||||
'reportes.servicios',
|
||||
'reportes.productividad.atencionclientes',
|
||||
'reportes.servicios.asesores',
|
||||
'reportes.servicios.clientes',
|
||||
'reportes.servicios.capacidad',
|
||||
'reportes.servicios.semanal',
|
||||
'reportes.servicios.mensual'
|
||||
],
|
||||
submenu: [
|
||||
{
|
||||
title: 'Servicios',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/servicios',
|
||||
permissions: ['reportes.servicios']
|
||||
},
|
||||
{
|
||||
title: 'Productividad atención a clientes',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/productividad',
|
||||
permissions: ['reportes.productividad.atencionclientes']
|
||||
},
|
||||
{
|
||||
title: 'Servicios otorgados por asesor de operaciones',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/servicios-asesor',
|
||||
permissions: ['reportes.servicios.asesores']
|
||||
},
|
||||
{
|
||||
title: 'Servicios por cliente',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/servicios-cliente',
|
||||
permissions: ['reportes.servicios.clientes']
|
||||
},
|
||||
{
|
||||
title: 'Capacidad aprovechada',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/capacidad-aprovechada',
|
||||
permissions: ['reportes.servicios.capacidad']
|
||||
},
|
||||
{
|
||||
title: 'Semanal',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/semanal',
|
||||
permissions: ['reportes.servicios.semanal']
|
||||
},
|
||||
{
|
||||
title: 'Mensual',
|
||||
icon: 'fa fa-file-excel-o',
|
||||
page: '/reportes/mensual',
|
||||
permissions: ['reportes.servicios.mensual']
|
||||
}
|
||||
]
|
||||
},
|
||||
{section: 'Encuesta'},
|
||||
{
|
||||
title: 'Encuesta',
|
||||
root: true,
|
||||
icon: 'fa fa-bar-chart-o',
|
||||
permissions: ['reportes.servicios.encuesta'],
|
||||
submenu: [
|
||||
{
|
||||
title: 'Encuesta General',
|
||||
icon: 'glyphicon glyphicon-list-alt',
|
||||
page: '/encuesta/encuesta-general',
|
||||
permissions: ['reportes.servicios.encuesta']
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
public get configs(): any {
|
||||
return this.defaults;
|
||||
}
|
||||
}
|
||||
43
src/app/core/_config/page.config.ts
Normal file
43
src/app/core/_config/page.config.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export class PageConfig {
|
||||
public defaults: any = {
|
||||
dashboard: {
|
||||
page: {
|
||||
title: 'Dashboard',
|
||||
desc: 'Latest updates and statistic charts'
|
||||
},
|
||||
},
|
||||
catalogos: {
|
||||
'usuarios': {page: {title: 'Usuarios', desc: ''}},
|
||||
'productos': {page: {title: 'Productos', desc: ''}}
|
||||
},
|
||||
builder: {
|
||||
page: {title: 'Layout Builder', desc: ''}
|
||||
},
|
||||
header: {
|
||||
actions: {
|
||||
page: {title: 'Actions', desc: 'Actions example page'}
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
page: {title: 'User Profile', desc: ''}
|
||||
},
|
||||
error: {
|
||||
404: {
|
||||
page: {title: '404 Not Found', desc: '', subheader: false}
|
||||
},
|
||||
403: {
|
||||
page: {title: '403 Access Forbidden', desc: '', subheader: false}
|
||||
}
|
||||
},
|
||||
wizard: {
|
||||
'wizard-1': {page: {title: 'Wizard 1', desc: ''}},
|
||||
'wizard-2': {page: {title: 'Wizard 2', desc: ''}},
|
||||
'wizard-3': {page: {title: 'Wizard 3', desc: ''}},
|
||||
'wizard-4': {page: {title: 'Wizard 4', desc: ''}},
|
||||
},
|
||||
};
|
||||
|
||||
public get configs(): any {
|
||||
return this.defaults;
|
||||
}
|
||||
}
|
||||
192
src/app/core/api/CustomValidators.ts
Normal file
192
src/app/core/api/CustomValidators.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import {AbstractControl, FormControl, ValidatorFn} from '@angular/forms';
|
||||
|
||||
export class CustomValidators {
|
||||
static isNullOrUndefined<T>(obj: T | null | undefined): obj is null | undefined {
|
||||
return typeof obj === 'undefined' || obj === null;
|
||||
}
|
||||
|
||||
static email2(control: FormControl): any {
|
||||
const exp: any = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'email': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static password(control: FormControl): any {
|
||||
const exp: any = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*)[A-Za-z0-9\d$]{8,16}$/;
|
||||
if (!(typeof control.value === 'undefined' || control.value === null) && control.value !== "" && !exp.test(control.value)) {
|
||||
return {'password': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static fechamx(control: FormControl): any {
|
||||
// let exp: any = /^(d{1,2})[/](d{1,2})[/](d{2}|d{4})$/; // Basic validation
|
||||
const exp: any = /^(?:(0[1-9]|[12][0-9]|3[01])[\/.](0[1-9]|1[012])[\/.](19|20)[0-9]{2})$/;
|
||||
return exp.test(control.value) ? null : {
|
||||
fechamx: {
|
||||
valid: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static fechaus(control: FormControl): any {
|
||||
// let exp: any = /^(d{1,2})[/](d{1,2})[/](d{2}|d{4})$/; // Basic validation
|
||||
const exp: any = /^(?:(19|20)[0-9]{2}[\-.](0[1-9]|1[012])[\-.](0[1-9]|[12][0-9]|3[01]))$/;
|
||||
return exp.test(control.value) ? null : {
|
||||
fechaus: {
|
||||
valid: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static telefono2(control: FormControl): any {
|
||||
const exp: any = /^\+\d{2,3}\s\d{10}$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'telefono': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static formatoNumero(control: FormControl): any {
|
||||
const exp: any = /^[0-9]+$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'formatoNumero': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static formatoDecimal(control: FormControl): any {
|
||||
const exp: any = /^(\d+\.?\d{0,9}|\.\d{1,9})$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'formatoDecimal': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static formatoHora(control: FormControl): any {
|
||||
const exp: any = /^(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'formatoHora': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static email(control: FormControl): any {
|
||||
const exp: any = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'email': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static urlYoutube(control: FormControl): any {
|
||||
const exp: any = /^(https:\/\/(www.)?youtube.com\/embed\/)/;
|
||||
|
||||
if (control.value && !exp.test(control.value)) {
|
||||
return {'deUrl': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static urlSpotify(control: FormControl): any {
|
||||
const exp: any = /^(https:\/\/open.spotify.com\/embed\/)/;
|
||||
|
||||
if (control.value && !exp.test(control.value)) {
|
||||
return {'deUrl': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static telefono(control: FormControl): any {
|
||||
const exp: any = /^\+\d{2,3}\s\d{10}$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'telefono': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static numeric(control: FormControl): any {
|
||||
const exp: any = /^[0-9]+$/;
|
||||
if (control.value && !exp.test(control.value)) {
|
||||
return {'numeric': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static deUrl(control: FormControl): any {
|
||||
const exp: any = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'deUrl': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static deUrlImagen(control: FormControl): any {
|
||||
const exp: any = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'deUrlImagen': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// VALIDA QUE LA CONTRASEÑA NO CONTENGA ESPACIOS
|
||||
static noWhiteSpace(control: FormControl): any {
|
||||
const exp: any = /^\S+$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'nowhitespace': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// VALIDA QUE EL USUARIO NO CONTENGA ESPACIOS
|
||||
static nbUsuario(control: FormControl): any {
|
||||
const exp: any = /^\S+$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'nbUsuario': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// VALIDA QUE LA CONTRASEÑA NO CONTENGA ESPACIOS
|
||||
static dePassword(control: FormControl): any {
|
||||
const exp: any = /^\S+$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'dePassword': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static precio(control: FormControl): any {
|
||||
const exp: any = /^([0-9])*[.]?[0-9]*$/;
|
||||
if (control.value !== undefined && !exp.test(control.value)) {
|
||||
return {'precio': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static preciomayor0(control: FormControl): any {
|
||||
const exp: any = /^([0-9])*[.]?[0-9]*$/;
|
||||
if (parseInt(control.value) == 0) {
|
||||
return {'precio': true, 'currentValue': control.value};
|
||||
}
|
||||
if (control.value !== undefined && !exp.test(control.value) && parseInt(control.value) == 0) {
|
||||
return {'precio': true, 'currentValue': control.value};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static minValue(min: Number): ValidatorFn {
|
||||
return (control: AbstractControl): { [key: string]: any } => {
|
||||
const input = control.value,
|
||||
isValid = input < min;
|
||||
if (isValid) {
|
||||
return {'minValue': {min}};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
47
src/app/core/api/api-base.ts
Normal file
47
src/app/core/api/api-base.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {EventEmitter, Output} from '@angular/core';
|
||||
import {HttpErrorResponse} from '@angular/common/http';
|
||||
import {Observable, throwError} from 'rxjs';
|
||||
|
||||
export class ApiBase {
|
||||
@Output() errorEvent: EventEmitter<any> = new EventEmitter(true);
|
||||
|
||||
protected handleError<T>(operation = 'operation', result?: T) {
|
||||
return (error: HttpErrorResponse): Observable<T> => {
|
||||
console.log("HANDLE ERROR", error.status)
|
||||
// console.error(`${operation} failed henry: ${error.message}`);
|
||||
let msg: any = [];
|
||||
let status = error.status;
|
||||
//if(!error.status){
|
||||
// return throwError(null);
|
||||
//}
|
||||
if (error.status === 401) {
|
||||
msg[0] = 'Usuario y/o contraseña incorrecta, intente de nuevo';
|
||||
//status = error.status;
|
||||
} else if (error.status === 403) {
|
||||
msg[0] = 'No cuentas con los suficientes permisos.';
|
||||
//status = error.status;
|
||||
} else if (error.status === 422) {
|
||||
let i = 0;
|
||||
for (let e of error.error.errors) {
|
||||
msg[i] = e;
|
||||
i++;
|
||||
}
|
||||
//status = 'warning-snackbar';
|
||||
} else if (error.status === 404) {
|
||||
msg[0] = 'La ruta no se encuentra disponible.';
|
||||
//status = 'error-snackbar';
|
||||
} else {
|
||||
msg[0] = 'Algo salió mal, intente de nuevo';
|
||||
//status = 'error-snackbar';
|
||||
}
|
||||
let info = {msg: msg, status: status, error: error.error.error};
|
||||
console.log('MENSAJE=> ', info);
|
||||
|
||||
this.errorEvent.emit(info);
|
||||
|
||||
return throwError(msg);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
80
src/app/core/api/api.service.ts
Normal file
80
src/app/core/api/api.service.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {EventEmitter, Injectable, Output} from '@angular/core';
|
||||
import {AuthService} from '../auth/_services';
|
||||
import {TokenStorage} from '../auth/_services/token-storage.service';
|
||||
import {UsuariosService} from '../../views/pages/catalogos/usuarios/usuarios.service';
|
||||
import {RolesService} from '../../views/pages/catalogos/roles/roles.service';
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {EstadosServicioService} from '../../views/pages/catalogos/estados-servicio/estados-servicio.service';
|
||||
import {FormasPagoService} from '../../views/pages/catalogos/formas-pago/formas-pago.service';
|
||||
import {TiposServicioService} from '../../views/pages/catalogos/tipos-servicio/tipos-servicio.service';
|
||||
import {ServiciosService} from '../../views/pages/catalogos/servicios/servicios.service';
|
||||
import {TiposVehiculoService} from '../../views/pages/catalogos/tipos-vehiculo/tipos-vehiculo.service';
|
||||
import {VehiculosService} from '../../views/pages/catalogos/vehiculos/vehiculos.service';
|
||||
import {SucursalesService} from '../../views/pages/catalogos/sucursales/sucursales.service';
|
||||
import {ClientesService} from '../../views/pages/catalogos/clientes/clientes.service';
|
||||
import { SolicitudesServicioService } from '../../views/pages/servicios/solicitudes-servicio/solicitudes-servicio.service';
|
||||
import { ReportesService } from '../../views/pages/reportes/reportes.service';
|
||||
import {ClientesDomiciliosService} from '../../views/pages/catalogos/clientes/clientes-domicilios.service';
|
||||
import {AgendaService} from "../../views/pages/agenda/agenda.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
|
||||
|
||||
@Output() error: EventEmitter<any> = new EventEmitter(true);
|
||||
|
||||
constructor(
|
||||
public login: AuthService,
|
||||
public usuarios: UsuariosService,
|
||||
public roles: RolesService,
|
||||
public estadosServicio: EstadosServicioService,
|
||||
public formasPago: FormasPagoService,
|
||||
public tiposServicio: TiposServicioService,
|
||||
public servicios: ServiciosService,
|
||||
public tiposVehiculos: TiposVehiculoService,
|
||||
public vehiculos: VehiculosService,
|
||||
public sucursales: SucursalesService,
|
||||
public clientes: ClientesService,
|
||||
public clientesDomicilios: ClientesDomiciliosService,
|
||||
public solicitudesServicioService: SolicitudesServicioService,
|
||||
public reportesService: ReportesService,
|
||||
public tokenStorage: TokenStorage,
|
||||
public snackBar: MatSnackBar,
|
||||
public agenda: AgendaService
|
||||
) {
|
||||
this.login.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.usuarios.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.roles.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.estadosServicio.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.formasPago.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.tiposServicio.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.servicios.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.tiposVehiculos.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.vehiculos.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.sucursales.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.clientes.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.clientesDomicilios.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.solicitudesServicioService.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.reportesService.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
this.agenda.errorEvent.subscribe((res: any) => this.error.emit(res));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token
|
||||
* @description Should return access token in Observable from e.g. localStorage
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getAccessToken(): string {
|
||||
return this.tokenStorage.getAccessToken();
|
||||
}
|
||||
|
||||
public snackbar(msg: string, duration: number = 3000) {
|
||||
this.snackBar.open(msg, '', {
|
||||
duration: duration,
|
||||
verticalPosition: 'bottom',
|
||||
horizontalPosition: 'right',
|
||||
});
|
||||
}
|
||||
}
|
||||
37
src/app/core/api/feedback-validators-reset.component.ts
Normal file
37
src/app/core/api/feedback-validators-reset.component.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
styles : [`
|
||||
.my-mat-hint {
|
||||
line-height: 1.2em;
|
||||
transform: translateY(30px);
|
||||
font-size: 75%;
|
||||
}
|
||||
`],
|
||||
selector : 'app-feedback-validator-reset',
|
||||
template :
|
||||
`<div *ngIf="submitted && errors">
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.required">El campo {{attr}} es requerido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.minlength">El {{attr}} debe ser más grande que {{errors.minlength.requiredLength}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.nowhitespace">El campo {{attr}} tiene caracteres de espacio en blanco no válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.size">El campo {{attr}} debe tener {{errors.size.requiredSize}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.numeric">El campo {{attr}} debe ser numérico.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.email">El campo {{attr}} debe ser un correo electrónico válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncproductvalidator">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncepcvalidator">El campo seleccionado {{attr}} is inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.decimal">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.max">El número máximo de caracteres para el campo {{attr}} es {{errors.max.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.min">El número mínimo de caracteres para el campo {{attr}} es {{errors.min.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.formatoHora">La hora debe ser formato 24 horas (HH:MM:SS).</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.telefono">El campo {{attr}} debe tener 10 números.</div>
|
||||
<!--<pre>{{errors | json}}</pre>-->
|
||||
</div>`
|
||||
})
|
||||
export class FeedbackValidatorsResetComponent {
|
||||
|
||||
@Input() submitted;
|
||||
@Input() errors;
|
||||
@Input() attr = 'Field';
|
||||
constructor() {}
|
||||
|
||||
}
|
||||
37
src/app/core/api/feedback-validators.component.ts
Normal file
37
src/app/core/api/feedback-validators.component.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
styles : [`
|
||||
.my-mat-hint {
|
||||
line-height: 1.2em;
|
||||
transform: translateY(30px);
|
||||
font-size: 75%;
|
||||
}
|
||||
`],
|
||||
selector : 'app-feedback-validator',
|
||||
template :
|
||||
`<div *ngIf="submitted && errors">
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.required">El campo {{attr}} es requerido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.minlength">El {{attr}} debe ser más grande que {{errors.minlength.requiredLength}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.nowhitespace">El campo {{attr}} tiene caracteres de espacio en blanco no válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.size">El campo {{attr}} debe tener {{errors.size.requiredSize}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.numeric">El campo {{attr}} debe ser numérico.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.email">El campo {{attr}} debe ser un correo electrónico válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncproductvalidator">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncepcvalidator">El campo seleccionado {{attr}} is inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.decimal">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.max">El número máximo de caracteres para el campo {{attr}} es {{errors.max.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.min">El número mínimo de caracteres para el campo {{attr}} es {{errors.min.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.formatoHora">La hora debe ser formato 24 horas (HH:MM:SS).</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.telefono">El campo {{attr}} debe tener 10 números.</div>
|
||||
<!--<pre>{{errors | json}}</pre>-->
|
||||
</div>`
|
||||
})
|
||||
export class FeedbackValidatorsComponent {
|
||||
|
||||
@Input() submitted;
|
||||
@Input() errors;
|
||||
@Input() attr = 'Field';
|
||||
constructor() {}
|
||||
|
||||
}
|
||||
9
src/app/core/api/myErrorStateMatcher.ts
Normal file
9
src/app/core/api/myErrorStateMatcher.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/** Error when invalid control is dirty or touched*/
|
||||
import {ErrorStateMatcher} from '@angular/material';
|
||||
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
|
||||
|
||||
export class MyErrorStateMatcher implements ErrorStateMatcher {
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
return !!(control && control.invalid && (control.dirty || control.touched));
|
||||
}
|
||||
}
|
||||
66
src/app/core/api/resource.ts
Normal file
66
src/app/core/api/resource.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {EventEmitter, Output} from '@angular/core';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs/index';
|
||||
import {isNullOrUndefined} from 'util';
|
||||
import {catchError} from 'rxjs/internal/operators';
|
||||
import {ApiBase} from './api-base';
|
||||
import {environment} from '../../../environments/environment';
|
||||
|
||||
|
||||
export class ResourceBase extends ApiBase {
|
||||
|
||||
baseUrl = environment.API;
|
||||
private url = '';
|
||||
|
||||
constructor(public http: HttpClient, private urlReceive: string) {
|
||||
super();
|
||||
this.url = this.baseUrl + urlReceive;
|
||||
}
|
||||
|
||||
@Output() errorEvent: EventEmitter<any> = new EventEmitter(true);
|
||||
|
||||
public index(page?: string, searchText?: string, sortBy?: string, order?: string, perPage?: string, visible?: string, deleted_at?: string): Observable<any> {
|
||||
|
||||
const params = new HttpParams()
|
||||
.set('sortBy', isNullOrUndefined(sortBy) ? '' : sortBy)
|
||||
.set('order', isNullOrUndefined(order) ? '' : order)
|
||||
.set('page', isNullOrUndefined(page) ? '1' : page)
|
||||
.set('perPage', isNullOrUndefined(perPage) ? '10' : perPage)
|
||||
.set('visible', isNullOrUndefined(visible) ? '1' : visible)
|
||||
.set('deleted', isNullOrUndefined(deleted_at) ? '0' : deleted_at)
|
||||
.set('query', searchText ? searchText : '');
|
||||
const options = {params: params};
|
||||
|
||||
return this.http.get(this.url, options).pipe(
|
||||
catchError(this.handleError('index'))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public create(body: any): Observable<any> {
|
||||
return this.http.post(this.url, body).pipe(
|
||||
catchError(this.handleError('create'))
|
||||
);
|
||||
}
|
||||
|
||||
public delete(id: number, reactivate = false): Observable<any> {
|
||||
|
||||
let deleted = reactivate ? '?deleted=1' : '';
|
||||
|
||||
return this.http.delete(`${this.url}/${id}`+ deleted).pipe(
|
||||
catchError(this.handleError('delete'))
|
||||
);
|
||||
}
|
||||
|
||||
public show(id: number): Observable<any> {
|
||||
return this.http.get(`${this.url}/${id}`).pipe(
|
||||
catchError(this.handleError('show'))
|
||||
);
|
||||
}
|
||||
|
||||
public update(id: number, body: any): Observable<any> {
|
||||
return this.http.put(`${this.url}/${id}`, body).pipe(
|
||||
catchError(this.handleError('update'))
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/app/core/api/url-interceptor.service.ts
Normal file
25
src/app/core/api/url-interceptor.service.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs/index';
|
||||
import {ApiService} from "./api.service";
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UrlInterceptorService implements HttpInterceptor {
|
||||
|
||||
constructor(private api: ApiService) {
|
||||
}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
const authToken: string = this.api.getAccessToken();
|
||||
|
||||
const authReq = req.clone({
|
||||
headers: req.headers.set('Authorization', 'Bearer ' + authToken)
|
||||
.set('Application', 'WEB'),
|
||||
url: `${req.url}`
|
||||
});
|
||||
return next.handle(authReq);
|
||||
}
|
||||
}
|
||||
40
src/app/core/api/validators.component.ts
Normal file
40
src/app/core/api/validators.component.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
styles : [`
|
||||
.my-mat-hint {
|
||||
line-height: 1.2em;
|
||||
transform: translateY(30px);
|
||||
font-size: 75%;
|
||||
}
|
||||
`],
|
||||
selector : 'app-validator',
|
||||
template :
|
||||
`<div *ngIf="submitted && errors">
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.required">El campo {{attr}} es requerido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.minlength">El {{attr}} debe ser más grande que {{errors.minlength.requiredLength}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.nowhitespace">El campo {{attr}} tiene caracteres de espacio en blanco no válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.size">El campo {{attr}} debe tener {{errors.size.requiredSize}} caracteres.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.numeric">El campo {{attr}} debe ser numérico.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.email">El campo {{attr}} debe ser un correo electrónico válido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncproductvalidator">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.asyncepcvalidator">El campo seleccionado {{attr}} is inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.decimal">El campo seleccionado {{attr}} es inválido.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.max">El número máximo de caracteres para el campo {{attr}} es {{errors.max.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.min">El número mínimo de caracteres para el campo {{attr}} es {{errors.min.length}}.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.formatoHora">La hora debe ser formato 24 horas (HH:MM:SS).</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.telefono">El campo {{attr}} debe tener 10 números.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.deUrl">El campo {{attr}} debe ser una url correcta.</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.urlYoutube">El campo {{attr}} debe ser una url de Youtube correcta..</div>
|
||||
<div class="text-danger margin-bottom-thin my-mat-hint" *ngIf="errors.precio">El campo {{attr}} debe ser un precio correcto.</div>
|
||||
<!--<pre>{{errors | json}}</pre>-->
|
||||
</div>`
|
||||
})
|
||||
export class ValidatorsComponent {
|
||||
|
||||
@Input() submitted;
|
||||
@Input() errors;
|
||||
@Input() attr = 'Field';
|
||||
constructor() {}
|
||||
|
||||
}
|
||||
38
src/app/core/auth/_actions/auth.actions.ts
Normal file
38
src/app/core/auth/_actions/auth.actions.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { User } from '../_models/user.model';
|
||||
|
||||
export enum AuthActionTypes {
|
||||
Login = '[Login] Action',
|
||||
Logout = '[Logout] Action',
|
||||
Register = '[Register] Action',
|
||||
UserRequested = '[Request User] Action',
|
||||
UserLoaded = '[Load User] Auth API'
|
||||
}
|
||||
|
||||
export class Login implements Action {
|
||||
readonly type = AuthActionTypes.Login;
|
||||
constructor(public payload: { authToken: string }) { }
|
||||
}
|
||||
|
||||
export class Logout implements Action {
|
||||
readonly type = AuthActionTypes.Logout;
|
||||
}
|
||||
|
||||
export class Register implements Action {
|
||||
readonly type = AuthActionTypes.Register;
|
||||
constructor(public payload: { authToken: string }) { }
|
||||
}
|
||||
|
||||
|
||||
export class UserRequested implements Action {
|
||||
readonly type = AuthActionTypes.UserRequested;
|
||||
}
|
||||
|
||||
export class UserLoaded implements Action {
|
||||
readonly type = AuthActionTypes.UserLoaded;
|
||||
constructor(public payload: { user: User }) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type AuthActions = Login | Logout | Register | UserRequested | UserLoaded;
|
||||
20
src/app/core/auth/_actions/permission.actions.ts
Normal file
20
src/app/core/auth/_actions/permission.actions.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// NGRX
|
||||
import { Action } from '@ngrx/store';
|
||||
// Models
|
||||
import { Permission } from '../_models/permission.model';
|
||||
|
||||
export enum PermissionActionTypes {
|
||||
AllPermissionsRequested = '[Init] All Permissions Requested',
|
||||
AllPermissionsLoaded = '[Init] All Permissions Loaded'
|
||||
}
|
||||
|
||||
export class AllPermissionsRequested implements Action {
|
||||
readonly type = PermissionActionTypes.AllPermissionsRequested;
|
||||
}
|
||||
|
||||
export class AllPermissionsLoaded implements Action {
|
||||
readonly type = PermissionActionTypes.AllPermissionsLoaded;
|
||||
constructor(public payload: { permissions: Permission[] }) { }
|
||||
}
|
||||
|
||||
export type PermissionActions = AllPermissionsRequested | AllPermissionsLoaded;
|
||||
89
src/app/core/auth/_actions/role.actions.ts
Normal file
89
src/app/core/auth/_actions/role.actions.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// NGRX
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Update } from '@ngrx/entity';
|
||||
// CRUD
|
||||
import { QueryParamsModel } from '../../_base/crud';
|
||||
// Models
|
||||
import { Role } from '../_models/role.model';
|
||||
|
||||
export enum RoleActionTypes {
|
||||
AllRolesRequested = '[Roles Home Page] All Roles Requested',
|
||||
AllRolesLoaded = '[Roles API] All Roles Loaded',
|
||||
RoleOnServerCreated = '[Edit Role Dialog] Role On Server Created',
|
||||
RoleCreated = '[Edit Roles Dialog] Roles Created',
|
||||
RoleUpdated = '[Edit Role Dialog] Role Updated',
|
||||
RoleDeleted = '[Roles List Page] Role Deleted',
|
||||
RolesPageRequested = '[Roles List Page] Roles Page Requested',
|
||||
RolesPageLoaded = '[Roles API] Roles Page Loaded',
|
||||
RolesPageCancelled = '[Roles API] Roles Page Cancelled',
|
||||
RolesPageToggleLoading = '[Roles page] Roles Page Toggle Loading',
|
||||
RolesActionToggleLoading = '[Roles] Roles Action Toggle Loading'
|
||||
}
|
||||
|
||||
export class RoleOnServerCreated implements Action {
|
||||
readonly type = RoleActionTypes.RoleOnServerCreated;
|
||||
constructor(public payload: { role: Role }) { }
|
||||
}
|
||||
|
||||
export class RoleCreated implements Action {
|
||||
readonly type = RoleActionTypes.RoleCreated;
|
||||
constructor(public payload: { role: Role }) { }
|
||||
}
|
||||
|
||||
export class RoleUpdated implements Action {
|
||||
readonly type = RoleActionTypes.RoleUpdated;
|
||||
constructor(public payload: {
|
||||
partialrole: Update<Role>,
|
||||
role: Role
|
||||
}) { }
|
||||
}
|
||||
|
||||
export class RoleDeleted implements Action {
|
||||
readonly type = RoleActionTypes.RoleDeleted;
|
||||
constructor(public payload: { id: number }) {}
|
||||
}
|
||||
|
||||
export class RolesPageRequested implements Action {
|
||||
readonly type = RoleActionTypes.RolesPageRequested;
|
||||
constructor(public payload: { page: QueryParamsModel }) { }
|
||||
}
|
||||
|
||||
export class RolesPageLoaded implements Action {
|
||||
readonly type = RoleActionTypes.RolesPageLoaded;
|
||||
constructor(public payload: { roles: Role[], totalCount: number, page: QueryParamsModel }) { }
|
||||
}
|
||||
|
||||
export class RolesPageCancelled implements Action {
|
||||
readonly type = RoleActionTypes.RolesPageCancelled;
|
||||
}
|
||||
|
||||
export class AllRolesRequested implements Action {
|
||||
readonly type = RoleActionTypes.AllRolesRequested;
|
||||
}
|
||||
|
||||
export class AllRolesLoaded implements Action {
|
||||
readonly type = RoleActionTypes.AllRolesLoaded;
|
||||
constructor(public payload: { roles: Role[] }) { }
|
||||
}
|
||||
|
||||
export class RolesPageToggleLoading implements Action {
|
||||
readonly type = RoleActionTypes.RolesPageToggleLoading;
|
||||
constructor(public payload: { isLoading: boolean }) { }
|
||||
}
|
||||
|
||||
export class RolesActionToggleLoading implements Action {
|
||||
readonly type = RoleActionTypes.RolesActionToggleLoading;
|
||||
constructor(public payload: { isLoading: boolean }) { }
|
||||
}
|
||||
|
||||
export type RoleActions = RoleCreated
|
||||
| RoleUpdated
|
||||
| RoleDeleted
|
||||
| RolesPageRequested
|
||||
| RolesPageLoaded
|
||||
| RolesPageCancelled
|
||||
| AllRolesLoaded
|
||||
| AllRolesRequested
|
||||
| RoleOnServerCreated
|
||||
| RolesPageToggleLoading
|
||||
| RolesActionToggleLoading;
|
||||
80
src/app/core/auth/_actions/user.actions.ts
Normal file
80
src/app/core/auth/_actions/user.actions.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
// NGRX
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Update } from '@ngrx/entity';
|
||||
// CRUD
|
||||
import { User } from '../_models/user.model';
|
||||
// Models
|
||||
import { QueryParamsModel } from '../../_base/crud';
|
||||
|
||||
export enum UserActionTypes {
|
||||
AllUsersRequested = '[Users Module] All Users Requested',
|
||||
AllUsersLoaded = '[Users API] All Users Loaded',
|
||||
UserOnServerCreated = '[Edit User Component] User On Server Created',
|
||||
UserCreated = '[Edit User Dialog] User Created',
|
||||
UserUpdated = '[Edit User Dialog] User Updated',
|
||||
UserDeleted = '[Users List Page] User Deleted',
|
||||
UsersPageRequested = '[Users List Page] Users Page Requested',
|
||||
UsersPageLoaded = '[Users API] Users Page Loaded',
|
||||
UsersPageCancelled = '[Users API] Users Page Cancelled',
|
||||
UsersPageToggleLoading = '[Users] Users Page Toggle Loading',
|
||||
UsersActionToggleLoading = '[Users] Users Action Toggle Loading'
|
||||
}
|
||||
|
||||
export class UserOnServerCreated implements Action {
|
||||
readonly type = UserActionTypes.UserOnServerCreated;
|
||||
constructor(public payload: { user: User }) { }
|
||||
}
|
||||
|
||||
export class UserCreated implements Action {
|
||||
readonly type = UserActionTypes.UserCreated;
|
||||
constructor(public payload: { user: User }) { }
|
||||
}
|
||||
|
||||
|
||||
export class UserUpdated implements Action {
|
||||
readonly type = UserActionTypes.UserUpdated;
|
||||
constructor(public payload: {
|
||||
partialUser: Update<User>,
|
||||
user: User
|
||||
}) { }
|
||||
}
|
||||
|
||||
export class UserDeleted implements Action {
|
||||
readonly type = UserActionTypes.UserDeleted;
|
||||
constructor(public payload: { id: number }) {}
|
||||
}
|
||||
|
||||
export class UsersPageRequested implements Action {
|
||||
readonly type = UserActionTypes.UsersPageRequested;
|
||||
constructor(public payload: { page: QueryParamsModel }) { }
|
||||
}
|
||||
|
||||
export class UsersPageLoaded implements Action {
|
||||
readonly type = UserActionTypes.UsersPageLoaded;
|
||||
constructor(public payload: { users: User[], totalCount: number, page: QueryParamsModel }) { }
|
||||
}
|
||||
|
||||
|
||||
export class UsersPageCancelled implements Action {
|
||||
readonly type = UserActionTypes.UsersPageCancelled;
|
||||
}
|
||||
|
||||
export class UsersPageToggleLoading implements Action {
|
||||
readonly type = UserActionTypes.UsersPageToggleLoading;
|
||||
constructor(public payload: { isLoading: boolean }) { }
|
||||
}
|
||||
|
||||
export class UsersActionToggleLoading implements Action {
|
||||
readonly type = UserActionTypes.UsersActionToggleLoading;
|
||||
constructor(public payload: { isLoading: boolean }) { }
|
||||
}
|
||||
|
||||
export type UserActions = UserCreated
|
||||
| UserUpdated
|
||||
| UserDeleted
|
||||
| UserOnServerCreated
|
||||
| UsersPageLoaded
|
||||
| UsersPageCancelled
|
||||
| UsersPageToggleLoading
|
||||
| UsersPageRequested
|
||||
| UsersActionToggleLoading;
|
||||
33
src/app/core/auth/_data-sources/roles.datasource.ts
Normal file
33
src/app/core/auth/_data-sources/roles.datasource.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// RxJS
|
||||
import { of } from 'rxjs';
|
||||
import { catchError, finalize, tap, debounceTime, delay, distinctUntilChanged } from 'rxjs/operators';
|
||||
// NGRX
|
||||
import { Store, select } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { BaseDataSource, QueryResultsModel } from '../../_base/crud';
|
||||
// State
|
||||
import { AppState } from '../../../core/reducers';
|
||||
// Selectirs
|
||||
import { selectQueryResult, selectRolesPageLoading, selectRolesShowInitWaitingMessage } from '../_selectors/role.selectors';
|
||||
|
||||
export class RolesDataSource extends BaseDataSource {
|
||||
constructor(private store: Store<AppState>) {
|
||||
super();
|
||||
|
||||
this.loading$ = this.store.pipe(
|
||||
select(selectRolesPageLoading)
|
||||
);
|
||||
|
||||
this.isPreloadTextViewed$ = this.store.pipe(
|
||||
select(selectRolesShowInitWaitingMessage)
|
||||
);
|
||||
|
||||
this.store.pipe(
|
||||
select(selectQueryResult)
|
||||
).subscribe((response: QueryResultsModel) => {
|
||||
this.paginatorTotalSubject.next(response.totalCount);
|
||||
this.entitySubject.next(response.items);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
32
src/app/core/auth/_data-sources/users.datasource.ts
Normal file
32
src/app/core/auth/_data-sources/users.datasource.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// RxJS
|
||||
import { of } from 'rxjs';
|
||||
import { catchError, finalize, tap, debounceTime, delay, distinctUntilChanged } from 'rxjs/operators';
|
||||
// NGRX
|
||||
import { Store, select } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { BaseDataSource, QueryResultsModel } from '../../_base/crud';
|
||||
// State
|
||||
import { AppState } from '../../../core/reducers';
|
||||
import { selectUsersInStore, selectUsersPageLoading, selectUsersShowInitWaitingMessage } from '../_selectors/user.selectors';
|
||||
|
||||
|
||||
export class UsersDataSource extends BaseDataSource {
|
||||
constructor(private store: Store<AppState>) {
|
||||
super();
|
||||
|
||||
this.loading$ = this.store.pipe(
|
||||
select(selectUsersPageLoading)
|
||||
);
|
||||
|
||||
this.isPreloadTextViewed$ = this.store.pipe(
|
||||
select(selectUsersShowInitWaitingMessage)
|
||||
);
|
||||
|
||||
this.store.pipe(
|
||||
select(selectUsersInStore)
|
||||
).subscribe((response: QueryResultsModel) => {
|
||||
this.paginatorTotalSubject.next(response.totalCount);
|
||||
this.entitySubject.next(response.items);
|
||||
});
|
||||
}
|
||||
}
|
||||
84
src/app/core/auth/_effects/auth.effects.ts
Normal file
84
src/app/core/auth/_effects/auth.effects.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
// RxJS
|
||||
import { filter, mergeMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { defer, Observable, of } from 'rxjs';
|
||||
// NGRX
|
||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||
import { Action, select, Store } from '@ngrx/store';
|
||||
// Auth actions
|
||||
import { AuthActionTypes, Login, Logout, Register, UserLoaded, UserRequested } from '../_actions/auth.actions';
|
||||
import { AuthService } from '../_services/index';
|
||||
import { AppState } from '../../reducers';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { isUserLoaded } from '../_selectors/auth.selectors';
|
||||
|
||||
@Injectable()
|
||||
export class AuthEffects {
|
||||
@Effect({dispatch: false})
|
||||
login$ = this.actions$.pipe(
|
||||
ofType<Login>(AuthActionTypes.Login),
|
||||
tap(action => {
|
||||
localStorage.setItem(environment.authTokenKey, action.payload.authToken);
|
||||
this.store.dispatch(new UserRequested());
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect({dispatch: false})
|
||||
logout$ = this.actions$.pipe(
|
||||
ofType<Logout>(AuthActionTypes.Logout),
|
||||
tap(() => {
|
||||
localStorage.removeItem(environment.authTokenKey);
|
||||
this.router.navigate(['/auth/login'], {queryParams: {returnUrl: this.returnUrl}});
|
||||
})
|
||||
);
|
||||
|
||||
@Effect({dispatch: false})
|
||||
register$ = this.actions$.pipe(
|
||||
ofType<Register>(AuthActionTypes.Register),
|
||||
tap(action => {
|
||||
localStorage.setItem(environment.authTokenKey, action.payload.authToken);
|
||||
})
|
||||
);
|
||||
|
||||
@Effect({dispatch: false})
|
||||
loadUser$ = this.actions$
|
||||
.pipe(
|
||||
ofType<UserRequested>(AuthActionTypes.UserRequested),
|
||||
withLatestFrom(this.store.pipe(select(isUserLoaded))),
|
||||
filter(([action, _isUserLoaded]) => !_isUserLoaded),
|
||||
mergeMap(([action, _isUserLoaded]) => this.auth.getUserByToken()),
|
||||
tap(_user => {
|
||||
if (_user) {
|
||||
this.store.dispatch(new UserLoaded({ user: _user }));
|
||||
} else {
|
||||
this.store.dispatch(new Logout());
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@Effect()
|
||||
init$: Observable<Action> = defer(() => {
|
||||
const userToken = localStorage.getItem(environment.authTokenKey);
|
||||
let observableResult = of({type: 'NO_ACTION'});
|
||||
if (userToken) {
|
||||
observableResult = of(new Login({ authToken: userToken }));
|
||||
}
|
||||
return observableResult;
|
||||
});
|
||||
|
||||
private returnUrl: string;
|
||||
|
||||
constructor(private actions$: Actions,
|
||||
private router: Router,
|
||||
private auth: AuthService,
|
||||
private store: Store<AppState>) {
|
||||
|
||||
this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.returnUrl = event.url;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
40
src/app/core/auth/_effects/permission.effects.ts
Normal file
40
src/app/core/auth/_effects/permission.effects.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { mergeMap, map, tap } from 'rxjs/operators';
|
||||
import { defer, Observable, of } from 'rxjs';
|
||||
// NGRX
|
||||
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||
import { Action } from '@ngrx/store';
|
||||
// Services
|
||||
import { AuthService } from '../_services';
|
||||
// Actions
|
||||
import {
|
||||
AllPermissionsLoaded,
|
||||
AllPermissionsRequested,
|
||||
PermissionActionTypes
|
||||
} from '../_actions/permission.actions';
|
||||
// Models
|
||||
import { Permission } from '../_models/permission.model';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionEffects {
|
||||
@Effect()
|
||||
loadAllPermissions$ = this.actions$
|
||||
.pipe(
|
||||
ofType<AllPermissionsRequested>(PermissionActionTypes.AllPermissionsRequested),
|
||||
mergeMap(() => this.auth.getAllPermissions()),
|
||||
map((result: Permission[]) => {
|
||||
return new AllPermissionsLoaded({
|
||||
permissions: result
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@Effect()
|
||||
init$: Observable<Action> = defer(() => {
|
||||
return of(new AllPermissionsRequested());
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions, private auth: AuthService) { }
|
||||
}
|
||||
126
src/app/core/auth/_effects/role.effects.ts
Normal file
126
src/app/core/auth/_effects/role.effects.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { of, Observable, defer, forkJoin } from 'rxjs';
|
||||
import { mergeMap, map, withLatestFrom, filter, tap } from 'rxjs/operators';
|
||||
// NGRX
|
||||
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||
import { Store, select, Action } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { QueryResultsModel, QueryParamsModel } from '../../_base/crud';
|
||||
// Services
|
||||
import { AuthService } from '../_services';
|
||||
// State
|
||||
import { AppState } from '../../../core/reducers';
|
||||
// Selectors
|
||||
import { allRolesLoaded } from '../_selectors/role.selectors';
|
||||
// Actions
|
||||
import {
|
||||
AllRolesLoaded,
|
||||
AllRolesRequested,
|
||||
RoleActionTypes,
|
||||
RolesPageRequested,
|
||||
RolesPageLoaded,
|
||||
RoleUpdated,
|
||||
RolesPageToggleLoading,
|
||||
RoleDeleted,
|
||||
RoleOnServerCreated,
|
||||
RoleCreated,
|
||||
RolesActionToggleLoading
|
||||
} from '../_actions/role.actions';
|
||||
|
||||
@Injectable()
|
||||
export class RoleEffects {
|
||||
showPageLoadingDistpatcher = new RolesPageToggleLoading({ isLoading: true });
|
||||
hidePageLoadingDistpatcher = new RolesPageToggleLoading({ isLoading: false });
|
||||
|
||||
showActionLoadingDistpatcher = new RolesActionToggleLoading({ isLoading: true });
|
||||
hideActionLoadingDistpatcher = new RolesActionToggleLoading({ isLoading: false });
|
||||
|
||||
@Effect()
|
||||
loadAllRoles$ = this.actions$
|
||||
.pipe(
|
||||
ofType<AllRolesRequested>(RoleActionTypes.AllRolesRequested),
|
||||
withLatestFrom(this.store.pipe(select(allRolesLoaded))),
|
||||
filter(([action, isAllRolesLoaded]) => !isAllRolesLoaded),
|
||||
mergeMap(() => this.auth.getAllRoles()),
|
||||
map(roles => {
|
||||
return new AllRolesLoaded({roles});
|
||||
})
|
||||
);
|
||||
|
||||
@Effect()
|
||||
loadRolesPage$ = this.actions$
|
||||
.pipe(
|
||||
ofType<RolesPageRequested>(RoleActionTypes.RolesPageRequested),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showPageLoadingDistpatcher);
|
||||
const requestToServer = this.auth.findRoles(payload.page);
|
||||
const lastQuery = of(payload.page);
|
||||
return forkJoin(requestToServer, lastQuery);
|
||||
}),
|
||||
map(response => {
|
||||
const result: QueryResultsModel = response[0];
|
||||
const lastQuery: QueryParamsModel = response[1];
|
||||
this.store.dispatch(this.hidePageLoadingDistpatcher);
|
||||
|
||||
return new RolesPageLoaded({
|
||||
roles: result.items,
|
||||
totalCount: result.totalCount,
|
||||
page: lastQuery
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
deleteRole$ = this.actions$
|
||||
.pipe(
|
||||
ofType<RoleDeleted>(RoleActionTypes.RoleDeleted),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.deleteRole(payload.id);
|
||||
}
|
||||
),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
updateRole$ = this.actions$
|
||||
.pipe(
|
||||
ofType<RoleUpdated>(RoleActionTypes.RoleUpdated),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.updateRole(payload.role);
|
||||
}),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@Effect()
|
||||
createRole$ = this.actions$
|
||||
.pipe(
|
||||
ofType<RoleOnServerCreated>(RoleActionTypes.RoleOnServerCreated),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.createRole(payload.role).pipe(
|
||||
tap(res => {
|
||||
this.store.dispatch(new RoleCreated({ role: res }));
|
||||
})
|
||||
);
|
||||
}),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
init$: Observable<Action> = defer(() => {
|
||||
return of(new AllRolesRequested());
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions, private auth: AuthService, private store: Store<AppState>) { }
|
||||
}
|
||||
101
src/app/core/auth/_effects/user.effects.ts
Normal file
101
src/app/core/auth/_effects/user.effects.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
// RxJS
|
||||
import { mergeMap, map, tap } from 'rxjs/operators';
|
||||
import { Observable, defer, of, forkJoin } from 'rxjs';
|
||||
// NGRX
|
||||
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||
import { Store, select, Action } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { QueryResultsModel, QueryParamsModel } from '../../_base/crud';
|
||||
// Services
|
||||
import { AuthService } from '../../../core/auth/_services';
|
||||
// State
|
||||
import { AppState } from '../../../core/reducers';
|
||||
import {
|
||||
UserActionTypes,
|
||||
UsersPageRequested,
|
||||
UsersPageLoaded,
|
||||
UserCreated,
|
||||
UserDeleted,
|
||||
UserUpdated,
|
||||
UserOnServerCreated,
|
||||
UsersActionToggleLoading,
|
||||
UsersPageToggleLoading
|
||||
} from '../_actions/user.actions';
|
||||
|
||||
@Injectable()
|
||||
export class UserEffects {
|
||||
showPageLoadingDistpatcher = new UsersPageToggleLoading({ isLoading: true });
|
||||
hidePageLoadingDistpatcher = new UsersPageToggleLoading({ isLoading: false });
|
||||
|
||||
showActionLoadingDistpatcher = new UsersActionToggleLoading({ isLoading: true });
|
||||
hideActionLoadingDistpatcher = new UsersActionToggleLoading({ isLoading: false });
|
||||
|
||||
@Effect()
|
||||
loadUsersPage$ = this.actions$
|
||||
.pipe(
|
||||
ofType<UsersPageRequested>(UserActionTypes.UsersPageRequested),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showPageLoadingDistpatcher);
|
||||
const requestToServer = this.auth.findUsers(payload.page);
|
||||
const lastQuery = of(payload.page);
|
||||
return forkJoin(requestToServer, lastQuery);
|
||||
}),
|
||||
map(response => {
|
||||
const result: QueryResultsModel = response[0];
|
||||
const lastQuery: QueryParamsModel = response[1];
|
||||
return new UsersPageLoaded({
|
||||
users: result.items,
|
||||
totalCount: result.totalCount,
|
||||
page: lastQuery
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
deleteUser$ = this.actions$
|
||||
.pipe(
|
||||
ofType<UserDeleted>(UserActionTypes.UserDeleted),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.deleteUser(payload.id);
|
||||
}
|
||||
),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
updateUser$ = this.actions$
|
||||
.pipe(
|
||||
ofType<UserUpdated>(UserActionTypes.UserUpdated),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.updateUser(payload.user);
|
||||
}),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
@Effect()
|
||||
createUser$ = this.actions$
|
||||
.pipe(
|
||||
ofType<UserOnServerCreated>(UserActionTypes.UserOnServerCreated),
|
||||
mergeMap(( { payload } ) => {
|
||||
this.store.dispatch(this.showActionLoadingDistpatcher);
|
||||
return this.auth.createUser(payload.user).pipe(
|
||||
tap(res => {
|
||||
this.store.dispatch(new UserCreated({ user: res }));
|
||||
})
|
||||
);
|
||||
}),
|
||||
map(() => {
|
||||
return this.hideActionLoadingDistpatcher;
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(private actions$: Actions, private auth: AuthService, private store: Store<AppState>) { }
|
||||
}
|
||||
34
src/app/core/auth/_guards/auth.guard.ts
Normal file
34
src/app/core/auth/_guards/auth.guard.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {
|
||||
CanActivate,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot, Router,
|
||||
} from '@angular/router';
|
||||
import {NgxPermissionsService} from "ngx-permissions";
|
||||
import { AuthService } from '../_services';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private authService: AuthService, private router: Router, private pService: NgxPermissionsService) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
let user = this.authService.getUser();
|
||||
if(user){
|
||||
let permissions = this.authService.getPermission();
|
||||
let result = [];
|
||||
for(let i in permissions){
|
||||
if(permissions[i]){
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
this.pService.loadPermissions(result);
|
||||
}
|
||||
|
||||
if(!user){
|
||||
this.router.navigateByUrl('/auth/login');
|
||||
}
|
||||
|
||||
return !!user;
|
||||
}
|
||||
}
|
||||
42
src/app/core/auth/_guards/module.guard.ts
Normal file
42
src/app/core/auth/_guards/module.guard.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// Angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
// RxJS
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { tap, map } from 'rxjs/operators';
|
||||
// NGRX
|
||||
import { select, Store } from '@ngrx/store';
|
||||
// Module reducers and selectors
|
||||
import { AppState} from '../../../core/reducers/';
|
||||
import { currentUserPermissions } from '../_selectors/auth.selectors';
|
||||
import { Permission } from '../_models/permission.model';
|
||||
import { find } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class ModuleGuard implements CanActivate {
|
||||
constructor(private store: Store<AppState>, private router: Router) { }
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
|
||||
const moduleName = route.data.moduleName as string;
|
||||
if (!moduleName) {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
return this.store
|
||||
.pipe(
|
||||
select(currentUserPermissions),
|
||||
map((permissions: Permission[]) => {
|
||||
const _perm = find(permissions, (elem: Permission) => {
|
||||
return elem.title.toLocaleLowerCase() === moduleName.toLocaleLowerCase();
|
||||
});
|
||||
return _perm ? true : false;
|
||||
}),
|
||||
tap(hasAccess => {
|
||||
if (!hasAccess) {
|
||||
this.router.navigateByUrl('/error/403');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
9
src/app/core/auth/_models/access-data.ts
Normal file
9
src/app/core/auth/_models/access-data.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// Samplce access data
|
||||
// {"id":1,"username":"admin","password":"demo","email":"admin@demo.com","accessToken":"access-token-0.022563452858263444","refreshToken":"access-token-0.9348573301432961","roles":["ADMIN"],"pic":"./assets/app/media/img/users/user4.jpg","fullname":"Mark Andre"}
|
||||
export interface AccessData {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
role: any;
|
||||
user: any;
|
||||
permissions: any;
|
||||
}
|
||||
13
src/app/core/auth/_models/address.model.ts
Normal file
13
src/app/core/auth/_models/address.model.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export class Address {
|
||||
addressLine: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postCode: string;
|
||||
|
||||
clear() {
|
||||
this.addressLine = '';
|
||||
this.city = '';
|
||||
this.state = '';
|
||||
this.postCode = '';
|
||||
}
|
||||
}
|
||||
21
src/app/core/auth/_models/permission.model.ts
Normal file
21
src/app/core/auth/_models/permission.model.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { BaseModel } from '../../_base/crud';
|
||||
|
||||
export class Permission extends BaseModel {
|
||||
id: number;
|
||||
title: string;
|
||||
level: number;
|
||||
parentId: number;
|
||||
isSelected: boolean;
|
||||
name: string;
|
||||
_children: Permission[];
|
||||
|
||||
clear(): void {
|
||||
this.id = undefined;
|
||||
this.title = '';
|
||||
this.level = 1;
|
||||
this.parentId = undefined;
|
||||
this.isSelected = false;
|
||||
this.name = '';
|
||||
this._children = [];
|
||||
}
|
||||
}
|
||||
15
src/app/core/auth/_models/role.model.ts
Normal file
15
src/app/core/auth/_models/role.model.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BaseModel } from '../../_base/crud';
|
||||
|
||||
export class Role extends BaseModel {
|
||||
id: number;
|
||||
title: string;
|
||||
permissions: number[];
|
||||
isCoreRole = false;
|
||||
|
||||
clear(): void {
|
||||
this.id = undefined;
|
||||
this.title = '';
|
||||
this.permissions = [];
|
||||
this.isCoreRole = false;
|
||||
}
|
||||
}
|
||||
13
src/app/core/auth/_models/social-networks.model.ts
Normal file
13
src/app/core/auth/_models/social-networks.model.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export class SocialNetworks {
|
||||
linkedIn: string;
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
instagram: string;
|
||||
|
||||
clear() {
|
||||
this.linkedIn = '';
|
||||
this.facebook = '';
|
||||
this.twitter = '';
|
||||
this.instagram = '';
|
||||
}
|
||||
}
|
||||
39
src/app/core/auth/_models/user.model.ts
Normal file
39
src/app/core/auth/_models/user.model.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { BaseModel } from '../../_base/crud';
|
||||
import { Address } from './address.model';
|
||||
import { SocialNetworks } from './social-networks.model';
|
||||
|
||||
export class User extends BaseModel {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
roles: number[];
|
||||
pic: string;
|
||||
fullname: string;
|
||||
occupation: string;
|
||||
companyName: string;
|
||||
phone: string;
|
||||
address: Address;
|
||||
socialNetworks: SocialNetworks;
|
||||
|
||||
clear(): void {
|
||||
this.id = undefined;
|
||||
this.username = '';
|
||||
this.password = '';
|
||||
this.email = '';
|
||||
this.roles = [];
|
||||
this.fullname = '';
|
||||
this.accessToken = 'access-token-' + Math.random();
|
||||
this.refreshToken = 'access-token-' + Math.random();
|
||||
this.pic = './assets/media/users/default.jpg';
|
||||
this.occupation = '';
|
||||
this.companyName = '';
|
||||
this.phone = '';
|
||||
this.address = new Address();
|
||||
this.address.clear();
|
||||
this.socialNetworks = new SocialNetworks();
|
||||
this.socialNetworks.clear();
|
||||
}
|
||||
}
|
||||
57
src/app/core/auth/_reducers/auth.reducers.ts
Normal file
57
src/app/core/auth/_reducers/auth.reducers.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// Actions
|
||||
import { AuthActions, AuthActionTypes } from '../_actions/auth.actions';
|
||||
// Models
|
||||
import { User } from '../_models/user.model';
|
||||
|
||||
export interface AuthState {
|
||||
loggedIn: boolean;
|
||||
authToken: string;
|
||||
user: User;
|
||||
isUserLoaded: boolean;
|
||||
}
|
||||
|
||||
export const initialAuthState: AuthState = {
|
||||
loggedIn: false,
|
||||
authToken: undefined,
|
||||
user: undefined,
|
||||
isUserLoaded: false
|
||||
};
|
||||
|
||||
export function authReducer(state = initialAuthState, action: AuthActions): AuthState {
|
||||
switch (action.type) {
|
||||
case AuthActionTypes.Login: {
|
||||
const _token: string = action.payload.authToken;
|
||||
return {
|
||||
loggedIn: true,
|
||||
authToken: _token,
|
||||
user: undefined,
|
||||
isUserLoaded: false
|
||||
};
|
||||
}
|
||||
|
||||
case AuthActionTypes.Register: {
|
||||
const _token: string = action.payload.authToken;
|
||||
return {
|
||||
loggedIn: true,
|
||||
authToken: _token,
|
||||
user: undefined,
|
||||
isUserLoaded: false
|
||||
};
|
||||
}
|
||||
|
||||
case AuthActionTypes.Logout:
|
||||
return initialAuthState;
|
||||
|
||||
case AuthActionTypes.UserLoaded: {
|
||||
const _user: User = action.payload.user;
|
||||
return {
|
||||
...state,
|
||||
user: _user,
|
||||
isUserLoaded: true
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
42
src/app/core/auth/_reducers/permission.reducers.ts
Normal file
42
src/app/core/auth/_reducers/permission.reducers.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// NGRX
|
||||
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
import { createFeatureSelector } from '@ngrx/store';
|
||||
// Actions
|
||||
import { PermissionActionTypes, PermissionActions } from '../_actions/permission.actions';
|
||||
// Models
|
||||
import { Permission } from '../_models/permission.model';
|
||||
|
||||
export interface PermissionsState extends EntityState<Permission> {
|
||||
_isAllPermissionsLoaded: boolean;
|
||||
}
|
||||
|
||||
export const adapter: EntityAdapter<Permission> = createEntityAdapter<Permission>();
|
||||
|
||||
export const initialPermissionsState: PermissionsState = adapter.getInitialState({
|
||||
_isAllPermissionsLoaded: false
|
||||
});
|
||||
|
||||
export function permissionsReducer(state = initialPermissionsState, action: PermissionActions): PermissionsState {
|
||||
switch (action.type) {
|
||||
case PermissionActionTypes.AllPermissionsRequested:
|
||||
return {...state,
|
||||
_isAllPermissionsLoaded: false
|
||||
};
|
||||
case PermissionActionTypes.AllPermissionsLoaded:
|
||||
return adapter.addAll(action.payload.permissions, {
|
||||
...state,
|
||||
_isAllPermissionsLoaded: true
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const getRoleState = createFeatureSelector<PermissionsState>('permissions');
|
||||
|
||||
export const {
|
||||
selectAll,
|
||||
selectEntities,
|
||||
selectIds,
|
||||
selectTotal
|
||||
} = adapter.getSelectors();
|
||||
72
src/app/core/auth/_reducers/role.reducers.ts
Normal file
72
src/app/core/auth/_reducers/role.reducers.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// NGRX
|
||||
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
// Actions
|
||||
import { RoleActions, RoleActionTypes } from '../_actions/role.actions';
|
||||
// Models
|
||||
import { Role } from '../_models/role.model';
|
||||
import { QueryParamsModel } from '../../_base/crud';
|
||||
|
||||
export interface RolesState extends EntityState<Role> {
|
||||
isAllRolesLoaded: boolean;
|
||||
queryRowsCount: number;
|
||||
queryResult: Role[];
|
||||
lastCreatedRoleId: number;
|
||||
listLoading: boolean;
|
||||
actionsloading: boolean;
|
||||
lastQuery: QueryParamsModel;
|
||||
showInitWaitingMessage: boolean;
|
||||
}
|
||||
|
||||
export const adapter: EntityAdapter<Role> = createEntityAdapter<Role>();
|
||||
|
||||
export const initialRolesState: RolesState = adapter.getInitialState({
|
||||
isAllRolesLoaded: false,
|
||||
queryRowsCount: 0,
|
||||
queryResult: [],
|
||||
lastCreatedRoleId: undefined,
|
||||
listLoading: false,
|
||||
actionsloading: false,
|
||||
lastQuery: new QueryParamsModel({}),
|
||||
showInitWaitingMessage: true
|
||||
});
|
||||
|
||||
export function rolesReducer(state = initialRolesState, action: RoleActions): RolesState {
|
||||
switch (action.type) {
|
||||
case RoleActionTypes.RolesPageToggleLoading: return {
|
||||
...state, listLoading: action.payload.isLoading, lastCreatedRoleId: undefined
|
||||
};
|
||||
case RoleActionTypes.RolesActionToggleLoading: return {
|
||||
...state, actionsloading: action.payload.isLoading
|
||||
};
|
||||
case RoleActionTypes.RoleOnServerCreated: return {
|
||||
...state
|
||||
};
|
||||
case RoleActionTypes.RoleCreated: return adapter.addOne(action.payload.role, {
|
||||
...state, lastCreatedRoleId: action.payload.role.id
|
||||
});
|
||||
case RoleActionTypes.RoleUpdated: return adapter.updateOne(action.payload.partialrole, state);
|
||||
case RoleActionTypes.RoleDeleted: return adapter.removeOne(action.payload.id, state);
|
||||
case RoleActionTypes.AllRolesLoaded: return adapter.addAll(action.payload.roles, {
|
||||
...state, isAllRolesLoaded: true
|
||||
});
|
||||
case RoleActionTypes.RolesPageCancelled: return {
|
||||
...state, listLoading: false, queryRowsCount: 0, queryResult: [], lastQuery: new QueryParamsModel({})
|
||||
};
|
||||
case RoleActionTypes.RolesPageLoaded: return adapter.addMany(action.payload.roles, {
|
||||
...initialRolesState,
|
||||
listLoading: false,
|
||||
queryRowsCount: action.payload.totalCount,
|
||||
queryResult: action.payload.roles,
|
||||
lastQuery: action.payload.page,
|
||||
showInitWaitingMessage: false
|
||||
});
|
||||
default: return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const {
|
||||
selectAll,
|
||||
selectEntities,
|
||||
selectIds,
|
||||
selectTotal
|
||||
} = adapter.getSelectors();
|
||||
71
src/app/core/auth/_reducers/user.reducers.ts
Normal file
71
src/app/core/auth/_reducers/user.reducers.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// NGRX
|
||||
import { createFeatureSelector } from '@ngrx/store';
|
||||
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
|
||||
// Actions
|
||||
import { UserActions, UserActionTypes } from '../_actions/user.actions';
|
||||
// CRUD
|
||||
import { QueryParamsModel } from '../../_base/crud';
|
||||
// Models
|
||||
import { User } from '../_models/user.model';
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
export interface UsersState extends EntityState<User> {
|
||||
listLoading: boolean;
|
||||
actionsloading: boolean;
|
||||
totalCount: number;
|
||||
lastCreatedUserId: number;
|
||||
lastQuery: QueryParamsModel;
|
||||
showInitWaitingMessage: boolean;
|
||||
}
|
||||
|
||||
export const adapter: EntityAdapter<User> = createEntityAdapter<User>();
|
||||
|
||||
export const initialUsersState: UsersState = adapter.getInitialState({
|
||||
listLoading: false,
|
||||
actionsloading: false,
|
||||
totalCount: 0,
|
||||
lastQuery: new QueryParamsModel({}),
|
||||
lastCreatedUserId: undefined,
|
||||
showInitWaitingMessage: true
|
||||
});
|
||||
|
||||
export function usersReducer(state = initialUsersState, action: UserActions): UsersState {
|
||||
switch (action.type) {
|
||||
case UserActionTypes.UsersPageToggleLoading: return {
|
||||
...state, listLoading: action.payload.isLoading, lastCreatedUserId: undefined
|
||||
};
|
||||
case UserActionTypes.UsersActionToggleLoading: return {
|
||||
...state, actionsloading: action.payload.isLoading
|
||||
};
|
||||
case UserActionTypes.UserOnServerCreated: return {
|
||||
...state
|
||||
};
|
||||
case UserActionTypes.UserCreated: return adapter.addOne(action.payload.user, {
|
||||
...state, lastCreatedUserId: action.payload.user.id
|
||||
});
|
||||
case UserActionTypes.UserUpdated: return adapter.updateOne(action.payload.partialUser, state);
|
||||
case UserActionTypes.UserDeleted: return adapter.removeOne(action.payload.id, state);
|
||||
case UserActionTypes.UsersPageCancelled: return {
|
||||
...state, listLoading: false, lastQuery: new QueryParamsModel({})
|
||||
};
|
||||
case UserActionTypes.UsersPageLoaded: {
|
||||
return adapter.addMany(action.payload.users, {
|
||||
...initialUsersState,
|
||||
totalCount: action.payload.totalCount,
|
||||
lastQuery: action.payload.page,
|
||||
listLoading: false,
|
||||
showInitWaitingMessage: false
|
||||
});
|
||||
}
|
||||
default: return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserState = createFeatureSelector<UsersState>('users');
|
||||
|
||||
export const {
|
||||
selectAll,
|
||||
selectEntities,
|
||||
selectIds,
|
||||
selectTotal
|
||||
} = adapter.getSelectors();
|
||||
100
src/app/core/auth/_selectors/auth.selectors.ts
Normal file
100
src/app/core/auth/_selectors/auth.selectors.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
// NGRX
|
||||
import { createSelector } from '@ngrx/store';
|
||||
// Lodash
|
||||
import { each, find, some } from 'lodash';
|
||||
// Selectors
|
||||
import { selectAllRoles } from './role.selectors';
|
||||
import { selectAllPermissions } from './permission.selectors';
|
||||
// Models
|
||||
import { Role } from '../_models/role.model';
|
||||
import { Permission } from '../_models/permission.model';
|
||||
|
||||
export const selectAuthState = state => state.auth;
|
||||
|
||||
export const isLoggedIn = createSelector(
|
||||
selectAuthState,
|
||||
auth => auth.loggedIn
|
||||
);
|
||||
|
||||
export const isLoggedOut = createSelector(
|
||||
isLoggedIn,
|
||||
loggedIn => !loggedIn
|
||||
);
|
||||
|
||||
|
||||
export const currentAuthToken = createSelector(
|
||||
selectAuthState,
|
||||
auth => auth.authToken
|
||||
);
|
||||
|
||||
export const isUserLoaded = createSelector(
|
||||
selectAuthState,
|
||||
auth => auth.isUserLoaded
|
||||
);
|
||||
|
||||
export const currentUser = createSelector(
|
||||
selectAuthState,
|
||||
auth => auth.user
|
||||
);
|
||||
|
||||
export const currentUserRoleIds = createSelector(
|
||||
currentUser,
|
||||
user => {
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return user.roles;
|
||||
}
|
||||
);
|
||||
|
||||
export const currentUserPermissionsIds = createSelector(
|
||||
currentUserRoleIds,
|
||||
selectAllRoles,
|
||||
(userRoleIds: number[], allRoles: Role[]) => {
|
||||
const result = getPermissionsIdsFrom(userRoleIds, allRoles);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
export const checkHasUserPermission = (permissionId: number) => createSelector(
|
||||
currentUserPermissionsIds,
|
||||
(ids: number[]) => {
|
||||
return ids.some(id => id === permissionId);
|
||||
}
|
||||
);
|
||||
|
||||
export const currentUserPermissions = createSelector(
|
||||
currentUserPermissionsIds,
|
||||
selectAllPermissions,
|
||||
(permissionIds: number[], allPermissions: Permission[]) => {
|
||||
const result: Permission[] = [];
|
||||
each(permissionIds, id => {
|
||||
const userPermission = find(allPermissions, elem => elem.id === id);
|
||||
if (userPermission) {
|
||||
result.push(userPermission);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
function getPermissionsIdsFrom(userRolesIds: number[] = [], allRoles: Role[] = []): number[] {
|
||||
const userRoles: Role[] = [];
|
||||
each(userRolesIds, (_id: number) => {
|
||||
const userRole = find(allRoles, (_role: Role) => _role.id === _id);
|
||||
if (userRole) {
|
||||
userRoles.push(userRole);
|
||||
}
|
||||
});
|
||||
|
||||
const result: number[] = [];
|
||||
each(userRoles, (_role: Role) => {
|
||||
each(_role.permissions, id => {
|
||||
if (!some(result, _id => _id === id)) {
|
||||
result.push(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
27
src/app/core/auth/_selectors/permission.selectors.ts
Normal file
27
src/app/core/auth/_selectors/permission.selectors.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// NGRX
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
// State
|
||||
import { PermissionsState } from '../_reducers/permission.reducers';
|
||||
import * as fromPermissions from '../_reducers/permission.reducers';
|
||||
|
||||
export const selectPermissionsState = createFeatureSelector<PermissionsState>('permissions');
|
||||
|
||||
export const selectPermissionById = (permissionId: number) => createSelector(
|
||||
selectPermissionsState,
|
||||
ps => ps.entities[permissionId]
|
||||
);
|
||||
|
||||
export const selectAllPermissions = createSelector(
|
||||
selectPermissionsState,
|
||||
fromPermissions.selectAll
|
||||
);
|
||||
|
||||
export const selectAllPermissionsIds = createSelector(
|
||||
selectPermissionsState,
|
||||
fromPermissions.selectIds
|
||||
);
|
||||
|
||||
export const allPermissionsLoaded = createSelector(
|
||||
selectPermissionsState,
|
||||
ps => ps._isAllPermissionsLoaded
|
||||
);
|
||||
68
src/app/core/auth/_selectors/role.selectors.ts
Normal file
68
src/app/core/auth/_selectors/role.selectors.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Role } from './../_models/role.model';
|
||||
|
||||
// NGRX
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { QueryResultsModel, HttpExtenstionsModel } from '../../_base/crud';
|
||||
// State
|
||||
import { RolesState } from '../_reducers/role.reducers';
|
||||
import * as fromRole from '../_reducers/role.reducers';
|
||||
import { each } from 'lodash';
|
||||
|
||||
export const selectRolesState = createFeatureSelector<RolesState>('roles');
|
||||
|
||||
export const selectRoleById = (roleId: number) => createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.entities[roleId]
|
||||
);
|
||||
|
||||
export const selectAllRoles = createSelector(
|
||||
selectRolesState,
|
||||
fromRole.selectAll
|
||||
);
|
||||
|
||||
export const selectAllRolesIds = createSelector(
|
||||
selectRolesState,
|
||||
fromRole.selectIds
|
||||
);
|
||||
|
||||
export const allRolesLoaded = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.isAllRolesLoaded
|
||||
);
|
||||
|
||||
|
||||
export const selectRolesPageLoading = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.listLoading
|
||||
);
|
||||
|
||||
export const selectRolesActionLoading = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.actionsloading
|
||||
);
|
||||
|
||||
export const selectLastCreatedRoleId = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.lastCreatedRoleId
|
||||
);
|
||||
|
||||
export const selectRolesShowInitWaitingMessage = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => rolesState.showInitWaitingMessage
|
||||
);
|
||||
|
||||
|
||||
export const selectQueryResult = createSelector(
|
||||
selectRolesState,
|
||||
rolesState => {
|
||||
const items: Role[] = [];
|
||||
each(rolesState.entities, element => {
|
||||
items.push(element);
|
||||
});
|
||||
const httpExtension = new HttpExtenstionsModel();
|
||||
const result: Role[] = httpExtension.sortArray(items, rolesState.lastQuery.sortField, rolesState.lastQuery.sortOrder);
|
||||
|
||||
return new QueryResultsModel(rolesState.queryResult, rolesState.queryRowsCount);
|
||||
}
|
||||
);
|
||||
67
src/app/core/auth/_selectors/user.selectors.ts
Normal file
67
src/app/core/auth/_selectors/user.selectors.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// NGRX
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
// CRUD
|
||||
import { QueryResultsModel, HttpExtenstionsModel } from '../../_base/crud';
|
||||
// State
|
||||
import { UsersState } from '../_reducers/user.reducers';
|
||||
import { each } from 'lodash';
|
||||
import { User } from '../_models/user.model';
|
||||
|
||||
|
||||
export const selectUsersState = createFeatureSelector<UsersState>('users');
|
||||
|
||||
export const selectUserById = (userId: number) => createSelector(
|
||||
selectUsersState,
|
||||
usersState => usersState.entities[userId]
|
||||
);
|
||||
|
||||
export const selectUsersPageLoading = createSelector(
|
||||
selectUsersState,
|
||||
usersState => {
|
||||
return usersState.listLoading;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectUsersActionLoading = createSelector(
|
||||
selectUsersState,
|
||||
usersState => usersState.actionsloading
|
||||
);
|
||||
|
||||
export const selectLastCreatedUserId = createSelector(
|
||||
selectUsersState,
|
||||
usersState => usersState.lastCreatedUserId
|
||||
);
|
||||
|
||||
export const selectUsersPageLastQuery = createSelector(
|
||||
selectUsersState,
|
||||
usersState => usersState.lastQuery
|
||||
);
|
||||
|
||||
export const selectUsersInStore = createSelector(
|
||||
selectUsersState,
|
||||
usersState => {
|
||||
const items: User[] = [];
|
||||
each(usersState.entities, element => {
|
||||
items.push(element);
|
||||
});
|
||||
const httpExtension = new HttpExtenstionsModel();
|
||||
const result: User[] = httpExtension.sortArray(items, usersState.lastQuery.sortField, usersState.lastQuery.sortOrder);
|
||||
return new QueryResultsModel(result, usersState.totalCount, '');
|
||||
}
|
||||
);
|
||||
|
||||
export const selectUsersShowInitWaitingMessage = createSelector(
|
||||
selectUsersState,
|
||||
usersState => usersState.showInitWaitingMessage
|
||||
);
|
||||
|
||||
export const selectHasUsersInStore = createSelector(
|
||||
selectUsersState,
|
||||
queryResult => {
|
||||
if (!queryResult.totalCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
10
src/app/core/auth/_server/auth.data-context.ts
Normal file
10
src/app/core/auth/_server/auth.data-context.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { UsersTable } from './users.table';
|
||||
import { PermissionsTable } from './permissions.table';
|
||||
import { RolesTable } from './roles.table';
|
||||
|
||||
// Wrapper class
|
||||
export class AuthDataContext {
|
||||
public static users: any = UsersTable.users;
|
||||
public static roles: any = RolesTable.roles;
|
||||
public static permissions = PermissionsTable.permissions;
|
||||
}
|
||||
85
src/app/core/auth/_server/permissions.table.ts
Normal file
85
src/app/core/auth/_server/permissions.table.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export class PermissionsTable {
|
||||
public static permissions: any = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'accessToECommerceModule',
|
||||
level: 1,
|
||||
title: 'eCommerce module'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'accessToAuthModule',
|
||||
level: 1,
|
||||
title: 'Users Management module'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'accessToMailModule',
|
||||
level: 1,
|
||||
title: 'Mail module'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'canReadECommerceData',
|
||||
level: 2,
|
||||
parentId: 1,
|
||||
title: 'Read'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'canEditECommerceData',
|
||||
level: 2,
|
||||
parentId: 1,
|
||||
title: 'Edit'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'canDeleteECommerceData',
|
||||
level: 2,
|
||||
parentId: 1,
|
||||
title: 'Delete'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'canReadAuthData',
|
||||
level: 2,
|
||||
parentId: 2,
|
||||
title: 'Read'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'canEditAuthData',
|
||||
level: 2,
|
||||
parentId: 2,
|
||||
title: 'Edit'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'canDeleteAuthData',
|
||||
level: 2,
|
||||
parentId: 2,
|
||||
title: 'Delete'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'canReadMailData',
|
||||
level: 2,
|
||||
parentId: 3,
|
||||
title: 'Read'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'canEditMailData',
|
||||
level: 2,
|
||||
parentId: 3,
|
||||
title: 'Edit'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'canDeleteMailData',
|
||||
level: 2,
|
||||
parentId: 3,
|
||||
title: 'Delete'
|
||||
},
|
||||
];
|
||||
}
|
||||
22
src/app/core/auth/_server/roles.table.ts
Normal file
22
src/app/core/auth/_server/roles.table.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export class RolesTable {
|
||||
public static roles: any = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Administrator',
|
||||
isCoreRole: true,
|
||||
permissions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Manager',
|
||||
isCoreRole: false,
|
||||
permissions: [3, 4, 10]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Guest',
|
||||
isCoreRole: false,
|
||||
permissions: []
|
||||
}
|
||||
];
|
||||
}
|
||||
90
src/app/core/auth/_server/users.table.ts
Normal file
90
src/app/core/auth/_server/users.table.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
export class UsersTable {
|
||||
public static users: any = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
password: 'demo',
|
||||
email: 'admin@demo.com',
|
||||
accessToken: 'access-token-8f3ae836da744329a6f93bf20594b5cc',
|
||||
refreshToken: 'access-token-f8c137a2c98743f48b643e71161d90aa',
|
||||
roles: [1], // Administrator
|
||||
pic: './assets/media/users/300_25.jpg',
|
||||
fullname: 'Sean',
|
||||
occupation: 'CEO',
|
||||
companyName: 'Keenthemes',
|
||||
phone: '456669067890',
|
||||
address: {
|
||||
addressLine: 'L-12-20 Vertex, Cybersquare',
|
||||
city: 'San Francisco',
|
||||
state: 'California',
|
||||
postCode: '45000'
|
||||
},
|
||||
socialNetworks: {
|
||||
linkedIn: 'https://linkedin.com/admin',
|
||||
facebook: 'https://facebook.com/admin',
|
||||
twitter: 'https://twitter.com/admin',
|
||||
instagram: 'https://instagram.com/admin'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user',
|
||||
password: 'demo',
|
||||
email: 'user@demo.com',
|
||||
accessToken: 'access-token-6829bba69dd3421d8762-991e9e806dbf',
|
||||
refreshToken: 'access-token-f8e4c61a318e4d618b6c199ef96b9e55',
|
||||
roles: [2], // Manager
|
||||
pic: './assets/media/users/100_2.jpg',
|
||||
fullname: 'Megan',
|
||||
occupation: 'Deputy Head of Keenthemes in New York office',
|
||||
companyName: 'Keenthemes',
|
||||
phone: '456669067891',
|
||||
address: {
|
||||
addressLine: '3487 Ingram Road',
|
||||
city: 'Greensboro',
|
||||
state: 'North Carolina',
|
||||
postCode: '27409'
|
||||
},
|
||||
socialNetworks: {
|
||||
linkedIn: 'https://linkedin.com/user',
|
||||
facebook: 'https://facebook.com/user',
|
||||
twitter: 'https://twitter.com/user',
|
||||
instagram: 'https://instagram.com/user'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'guest',
|
||||
password: 'demo',
|
||||
email: 'guest@demo.com',
|
||||
accessToken: 'access-token-d2dff7b82f784de584b60964abbe45b9',
|
||||
refreshToken: 'access-token-c999ccfe74aa40d0aa1a64c5e620c1a5',
|
||||
roles: [3], // Guest
|
||||
pic: './assets/media/users/default.jpg',
|
||||
fullname: 'Ginobili Maccari',
|
||||
occupation: 'CFO',
|
||||
companyName: 'Keenthemes',
|
||||
phone: '456669067892',
|
||||
address: {
|
||||
addressLine: '1467 Griffin Street',
|
||||
city: 'Phoenix',
|
||||
state: 'Arizona',
|
||||
postCode: '85012'
|
||||
},
|
||||
socialNetworks: {
|
||||
linkedIn: 'https://linkedin.com/guest',
|
||||
facebook: 'https://facebook.com/guest',
|
||||
twitter: 'https://twitter.com/guest',
|
||||
instagram: 'https://instagram.com/guest'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
public static tokens: any = [
|
||||
{
|
||||
id: 1,
|
||||
accessToken: 'access-token-' + Math.random(),
|
||||
refreshToken: 'access-token-' + Math.random()
|
||||
}
|
||||
];
|
||||
}
|
||||
220
src/app/core/auth/_services/auth.service.ts
Normal file
220
src/app/core/auth/_services/auth.service.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import {EventEmitter, Injectable, Output} from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { User } from '../_models/user.model';
|
||||
import { Permission } from '../_models/permission.model';
|
||||
import { Role } from '../_models/role.model';
|
||||
import {catchError, map, tap} from 'rxjs/operators';
|
||||
import { QueryParamsModel, QueryResultsModel } from '../../_base/crud';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import {ApiBase} from "../../api/api-base";
|
||||
import {AccessData} from "../_models/access-data";
|
||||
import {TokenStorage} from "./token-storage.service";
|
||||
|
||||
const API_USERS_URL = 'api/users';
|
||||
const API_PERMISSION_URL = 'api/permissions';
|
||||
const API_ROLES_URL = 'api/roles';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService extends ApiBase {
|
||||
@Output() errorEvent: EventEmitter<any> = new EventEmitter(true);
|
||||
constructor(private http: HttpClient, private tokenStorage: TokenStorage) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user roles
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
public getUser(): any {
|
||||
return this.tokenStorage.getUser();
|
||||
}
|
||||
|
||||
public getPermission() : any {
|
||||
return this.tokenStorage.getUserRoles()
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
public logout(refresh?: boolean): void {
|
||||
this.tokenStorage.clear();
|
||||
if (refresh) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication/Authorization
|
||||
login(email: string, password: string): Observable<any> {
|
||||
let headers = new HttpHeaders({
|
||||
'Application': 'WEB' });
|
||||
let options = { headers: headers };
|
||||
return this.http.post(environment.API + 'login', {email: email, password: password, options}).pipe(
|
||||
map((result: any) => {
|
||||
if (result instanceof Array) {
|
||||
return result.pop();
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
tap(this.saveAccessData.bind(this)),
|
||||
catchError(this.handleError('login', []))
|
||||
);
|
||||
}
|
||||
|
||||
private saveAccessData(accessData: AccessData) {
|
||||
if (typeof accessData !== 'undefined') {
|
||||
this.tokenStorage
|
||||
.setAccessToken(accessData.token)
|
||||
.setRefreshToken(accessData.token)
|
||||
.setUserRoles(accessData.role.permissions)
|
||||
.setUser(accessData.user);
|
||||
}
|
||||
}
|
||||
|
||||
getUserByToken(): Observable<User> {
|
||||
const userToken = localStorage.getItem(environment.authTokenKey);
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Authorization', 'Bearer ' + userToken);
|
||||
return this.http.get<User>(API_USERS_URL, { headers: httpHeaders });
|
||||
}
|
||||
|
||||
refreshToken(): Observable<User> {
|
||||
const userToken = localStorage.getItem('accessTokenDrenax');
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Authorization', 'Bearer ' + userToken);
|
||||
return this.http.get(environment.API + 'refresh', { headers: httpHeaders }).pipe(
|
||||
map((result: any) => {
|
||||
if (result instanceof Array) {
|
||||
return result.pop();
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
tap(this.setToken.bind(this)),
|
||||
catchError(this.handleError('refresh', []))
|
||||
);
|
||||
}
|
||||
|
||||
setToken(accessData: AccessData){
|
||||
if (typeof accessData !== 'undefined') {
|
||||
this.tokenStorage
|
||||
.setAccessToken(accessData.token)
|
||||
.setRefreshToken(accessData.token);
|
||||
}
|
||||
}
|
||||
|
||||
register(user: User): Observable<any> {
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.post<User>(API_USERS_URL, user, { headers: httpHeaders })
|
||||
.pipe(
|
||||
map((res: User) => {
|
||||
return res;
|
||||
}),
|
||||
catchError(err => {
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Submit forgot password request
|
||||
*
|
||||
* @param {string} email
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
public requestPassword(email: string): Observable<any> {
|
||||
return this.http.get(API_USERS_URL + '/forgot?=' + email)
|
||||
.pipe(catchError(this.handleError('forgot-password', []))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
getAllUsers(): Observable<User[]> {
|
||||
return this.http.get<User[]>(API_USERS_URL);
|
||||
}
|
||||
|
||||
getUserById(userId: number): Observable<User> {
|
||||
return this.http.get<User>(API_USERS_URL + `/${userId}`);
|
||||
}
|
||||
|
||||
|
||||
// DELETE => delete the user from the server
|
||||
deleteUser(userId: number) {
|
||||
const url = `${API_USERS_URL}/${userId}`;
|
||||
return this.http.delete(url);
|
||||
}
|
||||
|
||||
// UPDATE => PUT: update the user on the server
|
||||
updateUser(_user: User): Observable<any> {
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.put(API_USERS_URL, _user, { headers: httpHeaders });
|
||||
}
|
||||
|
||||
// CREATE => POST: add a new user to the server
|
||||
createUser(user: User): Observable<User> {
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.post<User>(API_USERS_URL, user, { headers: httpHeaders});
|
||||
}
|
||||
|
||||
// Method from server should return QueryResultsModel(items: any[], totalsCount: number)
|
||||
// items => filtered/sorted result
|
||||
findUsers(queryParams: QueryParamsModel): Observable<QueryResultsModel> {
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.post<QueryResultsModel>(API_USERS_URL + '/findUsers', queryParams, { headers: httpHeaders});
|
||||
}
|
||||
|
||||
// Permission
|
||||
getAllPermissions(): Observable<Permission[]> {
|
||||
return this.http.get<Permission[]>(API_PERMISSION_URL);
|
||||
}
|
||||
|
||||
getRolePermissions(roleId: number): Observable<Permission[]> {
|
||||
return this.http.get<Permission[]>(API_PERMISSION_URL + '/getRolePermission?=' + roleId);
|
||||
}
|
||||
|
||||
// Roles
|
||||
getAllRoles(): Observable<Role[]> {
|
||||
return this.http.get<Role[]>(API_ROLES_URL);
|
||||
}
|
||||
|
||||
getRoleById(roleId: number): Observable<Role> {
|
||||
return this.http.get<Role>(API_ROLES_URL + `/${roleId}`);
|
||||
}
|
||||
|
||||
// CREATE => POST: add a new role to the server
|
||||
createRole(role: Role): Observable<Role> {
|
||||
// Note: Add headers if needed (tokens/bearer)
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.post<Role>(API_ROLES_URL, role, { headers: httpHeaders});
|
||||
}
|
||||
|
||||
// UPDATE => PUT: update the role on the server
|
||||
updateRole(role: Role): Observable<any> {
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.put(API_ROLES_URL, role, { headers: httpHeaders });
|
||||
}
|
||||
|
||||
// DELETE => delete the role from the server
|
||||
deleteRole(roleId: number): Observable<Role> {
|
||||
const url = `${API_ROLES_URL}/${roleId}`;
|
||||
return this.http.delete<Role>(url);
|
||||
}
|
||||
|
||||
// Check Role Before deletion
|
||||
isRoleAssignedToUsers(roleId: number): Observable<boolean> {
|
||||
return this.http.get<boolean>(API_ROLES_URL + '/checkIsRollAssignedToUser?roleId=' + roleId);
|
||||
}
|
||||
|
||||
findRoles(queryParams: QueryParamsModel): Observable<QueryResultsModel> {
|
||||
// This code imitates server calls
|
||||
const httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set('Content-Type', 'application/json');
|
||||
return this.http.post<QueryResultsModel>(API_ROLES_URL + '/findRoles', queryParams, { headers: httpHeaders});
|
||||
}
|
||||
|
||||
}
|
||||
2
src/app/core/auth/_services/index.ts
Normal file
2
src/app/core/auth/_services/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { AuthService } from './auth.service'; // You have to comment this, when your real back-end is done
|
||||
// export { AuthService } from './auth.service'; // You have to uncomment this, when your real back-end is done
|
||||
104
src/app/core/auth/_services/token-storage.service.ts
Normal file
104
src/app/core/auth/_services/token-storage.service.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class TokenStorage {
|
||||
/**
|
||||
* Get access token
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getAccessToken(): string {
|
||||
const token: any = localStorage.getItem('accessTokenDrenax');
|
||||
try {
|
||||
return token;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refresh token
|
||||
* @returns {Observable<string>}
|
||||
*/
|
||||
public getRefreshToken(): Observable<string> {
|
||||
const token: string = <string>localStorage.getItem('refreshTokenDrenax');
|
||||
return of(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user roles in JSON string
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
public getUserRoles(): Observable<any> {
|
||||
const roles: any = localStorage.getItem('userRolesDrenax');
|
||||
try {
|
||||
return JSON.parse(roles)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user in JSON string
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
public getUser(): any {
|
||||
const user: any = localStorage.getItem('userDrenax');
|
||||
|
||||
try {
|
||||
return JSON.parse(user);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set access token
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setAccessToken(token: string): TokenStorage {
|
||||
localStorage.setItem('accessTokenDrenax', token);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set refresh token
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setRefreshToken(token: string): TokenStorage {
|
||||
localStorage.setItem('refreshTokenDrenax', token);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user roles
|
||||
* @param roles
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setUserRoles(roles: any): any {
|
||||
if (roles != null) {
|
||||
localStorage.setItem('userRolesDrenax', JSON.stringify(roles));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user
|
||||
* @param user
|
||||
* @returns {TokenStorage}
|
||||
*/
|
||||
public setUser(user: any): any {
|
||||
if (user != null) {
|
||||
localStorage.setItem('userDrenax', JSON.stringify(user));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tokens
|
||||
*/
|
||||
public clear() {
|
||||
localStorage.removeItem('accessTokenDrenax');
|
||||
localStorage.removeItem('refreshTokenDrenax');
|
||||
localStorage.removeItem('userRolesDrenax');
|
||||
localStorage.removeItem('userDrenax');
|
||||
}
|
||||
}
|
||||
4
src/app/core/auth/auth-notice/auth-notice.interface.ts
Normal file
4
src/app/core/auth/auth-notice/auth-notice.interface.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface AuthNotice {
|
||||
type?: string;
|
||||
message: string;
|
||||
}
|
||||
22
src/app/core/auth/auth-notice/auth-notice.service.ts
Normal file
22
src/app/core/auth/auth-notice/auth-notice.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { AuthNotice } from './auth-notice.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthNoticeService {
|
||||
onNoticeChanged$: BehaviorSubject<AuthNotice>;
|
||||
|
||||
constructor() {
|
||||
this.onNoticeChanged$ = new BehaviorSubject(null);
|
||||
}
|
||||
|
||||
setNotice(message: string, type?: string) {
|
||||
const notice: AuthNotice = {
|
||||
message,
|
||||
type
|
||||
};
|
||||
this.onNoticeChanged$.next(notice);
|
||||
}
|
||||
}
|
||||
114
src/app/core/auth/index.ts
Normal file
114
src/app/core/auth/index.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// SERVICES
|
||||
export { AuthService } from './_services';
|
||||
export { AuthNoticeService } from './auth-notice/auth-notice.service';
|
||||
|
||||
// DATA SOURCERS
|
||||
export { RolesDataSource } from './_data-sources/roles.datasource';
|
||||
export { UsersDataSource } from './_data-sources/users.datasource';
|
||||
|
||||
// ACTIONS
|
||||
export {
|
||||
Login,
|
||||
Logout,
|
||||
Register,
|
||||
UserRequested,
|
||||
UserLoaded,
|
||||
AuthActionTypes,
|
||||
AuthActions
|
||||
} from './_actions/auth.actions';
|
||||
export {
|
||||
AllPermissionsRequested,
|
||||
AllPermissionsLoaded,
|
||||
PermissionActionTypes,
|
||||
PermissionActions
|
||||
} from './_actions/permission.actions';
|
||||
export {
|
||||
RoleOnServerCreated,
|
||||
RoleCreated,
|
||||
RoleUpdated,
|
||||
RoleDeleted,
|
||||
RolesPageRequested,
|
||||
RolesPageLoaded,
|
||||
RolesPageCancelled,
|
||||
AllRolesLoaded,
|
||||
AllRolesRequested,
|
||||
RoleActionTypes,
|
||||
RoleActions
|
||||
} from './_actions/role.actions';
|
||||
export {
|
||||
UserCreated,
|
||||
UserUpdated,
|
||||
UserDeleted,
|
||||
UserOnServerCreated,
|
||||
UsersPageLoaded,
|
||||
UsersPageCancelled,
|
||||
UsersPageToggleLoading,
|
||||
UsersPageRequested,
|
||||
UsersActionToggleLoading
|
||||
} from './_actions/user.actions';
|
||||
|
||||
// EFFECTS
|
||||
export { AuthEffects } from './_effects/auth.effects';
|
||||
export { PermissionEffects } from './_effects/permission.effects';
|
||||
export { RoleEffects } from './_effects/role.effects';
|
||||
export { UserEffects } from './_effects/user.effects';
|
||||
|
||||
// REDUCERS
|
||||
export { authReducer } from './_reducers/auth.reducers';
|
||||
export { permissionsReducer } from './_reducers/permission.reducers';
|
||||
export { rolesReducer } from './_reducers/role.reducers';
|
||||
export { usersReducer } from './_reducers/user.reducers';
|
||||
|
||||
// SELECTORS
|
||||
export {
|
||||
isLoggedIn,
|
||||
isLoggedOut,
|
||||
isUserLoaded,
|
||||
currentAuthToken,
|
||||
currentUser,
|
||||
currentUserRoleIds,
|
||||
currentUserPermissionsIds,
|
||||
currentUserPermissions,
|
||||
checkHasUserPermission
|
||||
} from './_selectors/auth.selectors';
|
||||
export {
|
||||
selectPermissionById,
|
||||
selectAllPermissions,
|
||||
selectAllPermissionsIds,
|
||||
allPermissionsLoaded
|
||||
} from './_selectors/permission.selectors';
|
||||
export {
|
||||
selectRoleById,
|
||||
selectAllRoles,
|
||||
selectAllRolesIds,
|
||||
allRolesLoaded,
|
||||
selectLastCreatedRoleId,
|
||||
selectRolesPageLoading,
|
||||
selectQueryResult,
|
||||
selectRolesActionLoading,
|
||||
selectRolesShowInitWaitingMessage
|
||||
} from './_selectors/role.selectors';
|
||||
export {
|
||||
selectUserById,
|
||||
selectUsersPageLoading,
|
||||
selectLastCreatedUserId,
|
||||
selectUsersInStore,
|
||||
selectHasUsersInStore,
|
||||
selectUsersPageLastQuery,
|
||||
selectUsersActionLoading,
|
||||
selectUsersShowInitWaitingMessage
|
||||
} from './_selectors/user.selectors';
|
||||
|
||||
// GUARDS
|
||||
export { AuthGuard } from './_guards/auth.guard';
|
||||
export { ModuleGuard } from './_guards/module.guard';
|
||||
|
||||
// MODELS
|
||||
export { User } from './_models/user.model';
|
||||
export { Permission } from './_models/permission.model';
|
||||
export { Role } from './_models/role.model';
|
||||
export { Address } from './_models/address.model';
|
||||
export { SocialNetworks } from './_models/social-networks.model';
|
||||
export { AuthNotice } from './auth-notice/auth-notice.interface';
|
||||
|
||||
export { AuthDataContext } from './_server/auth.data-context';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user