---|qcm| Mise à jour V17 et QCM

This commit is contained in:
Rampeur
2024-05-04 02:08:04 +02:00
parent fa93c0c8f2
commit 6dece821a8
241 changed files with 27526 additions and 6520 deletions
-16
View File
@@ -1,16 +0,0 @@
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
import { HttpTokenInterceptor } from "src/app/core/interceptors";
@NgModule({
imports: [
CommonModule,
HttpClientModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: HttpTokenInterceptor, multi: true }
]
})
export class CoreModule { }
+17
View File
@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';
import { authGuard } from './auth.guard';
describe('authGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});
+18
View File
@@ -0,0 +1,18 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services';
export const adminGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const router = inject(Router);
const userService = inject(UserService);
return userService.currentUser.pipe(take(1)).pipe(map(data => {
if (data.role !== 'Admin') {
router.navigate(['/']);
return false;
} else {
return true;
}
}));
};
+17
View File
@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';
import { adminGuard } from './admin.guard';
describe('adminGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => adminGuard(...guardParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});
+18
View File
@@ -0,0 +1,18 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services';
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const router = inject(Router);
const userService = inject(UserService);
return userService.isAuthenticated.pipe(take(1)).pipe(map(data => {
if (!data) {
router.navigate(['/']);
return false;
} else {
return true;
}
}));
};
+3
View File
@@ -0,0 +1,3 @@
export * from './admin.guard';
export * from './auth.guard';
export * from './noauth.guard';
+17
View File
@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';
import { noauthGuard } from './noauth.guard';
describe('noauthGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => noauthGuard(...guardParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});
+23
View File
@@ -0,0 +1,23 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services';
export const noauthGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> => {
/*
const router = inject(Router);
const userService = inject(UserService);
return userService.isAuthenticated.pipe(take(1)).pipe(map(data => {
if (data) {
router.navigate(['/']);
return false;
} else {
return true;
}
}));
*/
const userService = inject(UserService);
return userService.isAuthenticated.pipe(take(1), map(isAuth => !isAuth));
};
+2 -1
View File
@@ -1,5 +1,6 @@
export * from './core.module';
export * from './guards';
export * from './interceptors';
//export * from './interfaces';
export * from './models';
export * from './resolvers';
export * from './services';
@@ -0,0 +1,13 @@
import { HttpInterceptorFn } from "@angular/common/http";
import { environment } from 'src/environments/environment';
export const apiInterceptor: HttpInterceptorFn = (req, next) => {
const apiReq = req.clone({
setHeaders: {
...({ 'Content-Type': 'application/json', 'Accept': 'application/json' }),
},
url: `${environment.api_url}${req.url}`
});
return next(apiReq);
};
@@ -0,0 +1,10 @@
import { HttpInterceptorFn } from "@angular/common/http";
import { throwError } from "rxjs";
import { catchError } from "rxjs/operators";
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(catchError((err) => throwError(() => {
console.log(err.error);
return err.error;
})));
};
@@ -1,52 +0,0 @@
import { Injectable } from '@angular/core';
import { environment } from '../../../environments/environment';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { JwtService } from '../services';
export interface headersConfig {
'Content-Type': string;
'Accept': string;
'Authorization'?: string;
}
@Injectable({ providedIn: 'root' })
export class HttpTokenInterceptor implements HttpInterceptor {
constructor(private jwtService: JwtService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
var config: headersConfig = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
//Basic cmFtcGV1cjpES3hwMjRQUw==
//console.log(req.headers.get('Authorization'));
const basic = req.headers.get('Authorization');
if (basic == 'Basic cmFtcGV1cjpES3hwMjRQUw==') {
config['Authorization'] = `${basic}`;
} else {
const token = this.jwtService.getToken();
if (token) {
config['Authorization'] = `Token ${token}`;
} else if (environment.use_api_key) {
config['Authorization'] = `Api-Key ${environment.api_key}`;
}
}
const request = req.clone({ setHeaders: <any>config });
return next.handle(request);
/*
const headers= new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');
const token = this.jwtService.getToken();
if (token) {
headers.set('Authorization', `Token ${token}`);
} else if (environment.use_api_key) {
headers.set('Authorization', `Api-Key ${environment.api_key}`);
}
const request = req.clone({ setHeaders: headers });
return next.handle(request);
*/
}
}
+4 -1
View File
@@ -1 +1,4 @@
export * from './http.token.interceptor';
export * from './api.interceptor';
export * from './error.interceptor';
//export * from './http.token.interceptor';
export * from './token.interceptor';
@@ -0,0 +1,13 @@
import { inject } from "@angular/core";
import { HttpInterceptorFn } from "@angular/common/http";
import { JwtService } from "src/app/core/services/jwt.service";
export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(JwtService).getToken();
const request = req.clone({
setHeaders: {
...(token ? { Authorization: `Token ${token}` } : {}),
},
});
return next(request);
};
+12 -9
View File
@@ -1,3 +1,4 @@
import { Jump } from './jump.model';
import { Profile } from './profile.model';
export interface Aeronef {
@@ -9,18 +10,20 @@ export interface Aeronef {
}
export interface AeronefByImat {
_id: {
aeronef: string,
imat: string
},
aeronef: string,
imat: string,
count: number
}
export interface AeronefByYear {
_id: {
aeronef: string,
imat: string,
year: number
},
aeronef: string,
imat: string,
year: number,
count: number
}
export interface AeronefsPageData {
aeronefsByImat: Array<AeronefByImat>;
aeronefsByImatByYear: Array<AeronefByYear>;
lastjump: Jump;
}
+6 -3
View File
@@ -24,14 +24,17 @@ export interface InputParams {
color: string;
}
export interface Range {
export interface WeightSizeRange extends Range {
num: number;
active: string;
name: string;
label: string;
}
export interface Range {
start: number;
end: number;
value: number;
value?: number;
}
export interface TableHeader {
@@ -42,7 +45,7 @@ export interface TableHeader {
export interface WeightSize {
weight: number;
active: string;
ranges: Range[];
ranges: WeightSizeRange[];
}
export interface WeightSizes {
+17 -16
View File
@@ -1,3 +1,4 @@
import { Jump } from './jump.model';
import { Profile } from './profile.model';
export interface Canopy {
@@ -9,33 +10,33 @@ export interface Canopy {
}
export interface CanopyModelBySize {
_id: {
voile: string,
taille: number
},
voile: string,
taille: number,
count: number
}
export interface CanopyModelByYear {
_id: {
voile: string,
taille: number,
year: number
},
voile: string,
taille: number,
year: number,
count: number
}
export interface CanopyBySize {
_id: {
taille: number
},
taille: number,
count: number
}
export interface CanopyByYear {
_id: {
taille: number,
year: number
},
taille: number,
year: number,
count: number
}
export interface CanopiesPageData {
canopiesBySize: Array<CanopyBySize>;
canopiesBySizeByYear: Array<CanopyByYear>;
canopiesBySizeByModel: Array<CanopyModelBySize>;
canopiesBySizeByModelByYear: Array<CanopyModelByYear>;
lastjump: Jump;
}
+8
View File
@@ -7,6 +7,14 @@ export interface BarConfig {
barChartLegend: boolean
}
export interface CircleConfig {
circleChartLabels: string[],
circleChartDatasets: ChartConfiguration<'doughnut'>['data']['datasets'],
circleChartOptions: ChartConfiguration<'doughnut'>['options'],
circleChartPlugins: ChartConfiguration<'doughnut'>['plugins'],
circleChartLegend: boolean
}
export interface DoughnutConfig {
doughnutChartLabels: string[],
doughnutChartDatasets: ChartConfiguration<'doughnut'>['data']['datasets'],
+12 -9
View File
@@ -1,3 +1,4 @@
import { Jump } from './jump.model';
import { Profile } from './profile.model';
export interface DropZone {
@@ -9,18 +10,20 @@ export interface DropZone {
}
export interface DropZoneByOaci {
_id: {
lieu: string,
oaci: string
},
lieu: string,
oaci: string,
count: number
}
export interface DropZoneByYear {
_id: {
lieu: string,
oaci: string,
year: number
},
lieu: string,
oaci: string,
year: number,
count: number
}
export interface DropZonesPageData {
dropZonesByOaci: Array<DropZoneByOaci>;
dropZonesByOaciByYear: Array<DropZoneByYear>;
lastjump: Jump;
}
@@ -23,4 +23,14 @@ export interface JumpListFilters {
author?: string;
limit?: number;
offset?: number;
numeroRangeStart?: number;
numeroRangeEnd?: number;
tailleRangeStart?: number;
tailleRangeEnd?: number;
participantsRangeStart?: number;
participantsRangeEnd?: number;
hauteurRangeStart?: number;
hauteurRangeEnd?: number;
yearRangeStart?: number;
yearRangeEnd?: number;
}
+43 -8
View File
@@ -28,7 +28,7 @@ export interface Jump {
updatedAt: string;
//isEdit?: boolean;
isSelected?: boolean;
};
}
export interface JumpForm {
date: FormControl<string>;
@@ -56,15 +56,50 @@ export interface JumpAddParams {
numeros: string;
date: string;
video: boolean;
};
}
export interface JumpByDate {
_id: {
month: number;
year: number;
};
month: number;
year: number;
count: number;
};
}
export interface JumpByCategorie {
categorie: string;
count: number;
}
export interface JumpByModule {
categorie: string;
module: string;
count: number;
}
export interface JumpByDateByModule {
categorie: string;
module: string;
year: number;
month: number;
count: number;
}
export interface JumpYears {
year: number;
count: number;
}
export interface JumpsPageData {
jumpsByCategory: Array<JumpByCategorie>;
jumpsByDate: Array<JumpByDate>;
jumpsByDateByModule: Array<JumpByDateByModule>;
jumpsByModule: Array<JumpByModule>;
jumpsByYears: Array<JumpYears>;
lastjump: Jump;
/*yearsCount: number;
jumpsByCategoryCount: number;
jumpsByDateCount: number;
jumpsByModuleCount: number;*/
}
export interface ColumnDefinition {
key: string;
@@ -74,7 +109,7 @@ export interface ColumnDefinition {
pattern?: string;
min?: number;
step?: number;
};
}
export const jumpColumns: Array<ColumnDefinition> = [
{
@@ -0,0 +1,5 @@
export enum LoadingState {
NOT_LOADED = "NOT_LOADED",
LOADING = "LOADING",
LOADED = "LOADED",
}
+7 -7
View File
@@ -1,21 +1,21 @@
export interface QCM {
export interface Qcm {
name: string;
categories: Array<QCMCategories>;
categories: Array<QcmCategory>;
}
export interface QCMCategories {
export interface QcmCategory {
num: number;
name: string;
questions: Array<Question>;
questions: Array<QcmQuestion>;
}
export interface Question {
export interface QcmQuestion {
num: number;
libelle: string;
choices: Array<Choice>;
choices: Array<QcmChoice>;
}
export interface Choice {
export interface QcmChoice {
index: string;
libelle: string;
correct: boolean;
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { AeronefsPageData } from 'src/app/core/models';
import { PagesService } from 'src/app/core/services';
export const aeronefsPageResolver: ResolveFn<AeronefsPageData> = () => {
return inject(PagesService).getAeronefsPage().pipe(take(1));
};
@@ -0,0 +1,9 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services';
export const authResolver: ResolveFn<boolean> = () => {
return inject(UserService).isAuthenticated.pipe(take(1));
};
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { CanopiesPageData } from 'src/app/core/models';
import { PagesService } from 'src/app/core/services';
export const canopiesPageResolver: ResolveFn<CanopiesPageData> = () => {
return inject(PagesService).getCanopiesPage().pipe(take(1));
};
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { DropZonesPageData } from 'src/app/core/models';
import { PagesService } from 'src/app/core/services';
export const dropZonesPageResolver: ResolveFn<DropZonesPageData> = () => {
return inject(PagesService).getDropZonesPage().pipe(take(1));
};
+10
View File
@@ -0,0 +1,10 @@
export * from './aeronefs-page-resolver.service';
export * from './auth-resolver.service';
export * from './canopies-page-resolver.service';
export * from './dropzones-page-resolver.service';
export * from './jump-resolver.service';
export * from './jumps-page-resolver.service';
export * from './jumps-resolver.service';
export * from './lastjump-resolver.service';
export * from './profile-resolver.service';
export * from './qcm-resolver.service';
@@ -0,0 +1,12 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { Jump } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services';
export const jumpResolver: ResolveFn<Jump> = (
route: ActivatedRouteSnapshot
) => {
return inject(JumpsService).get(route.params['slug']).pipe(take(1));
};
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { JumpsPageData } from 'src/app/core/models';
import { PagesService } from 'src/app/core/services';
export const jumpsPageResolver: ResolveFn<JumpsPageData> = () => {
return inject(PagesService).getJumpsPage().pipe(take(1));
};
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { Jump } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services';
export const jumpsResolver: ResolveFn<Array<Jump>> = () => {
return inject(JumpsService).getAll().pipe(take(1));
};
@@ -0,0 +1,10 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { Jump } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services';
export const lastjumpResolver: ResolveFn<Jump> = () => {
return inject(JumpsService).getLastJump().pipe(take(1));
};
@@ -0,0 +1,12 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { Profile } from 'src/app/core/models';
import { ProfilesService } from 'src/app/core/services';
export const profileResolver: ResolveFn<Profile> = (
route: ActivatedRouteSnapshot
) => {
return inject(ProfilesService).get(route.params['username']).pipe(take(1));
};
@@ -0,0 +1,12 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators';
import { Qcm } from 'src/app/core/models';
import { QcmService } from 'src/app/core/services';
export const qcmResolver: ResolveFn<Qcm> = (
route: ActivatedRouteSnapshot
) => {
return inject(QcmService).get(route.params['type']).pipe(take(1));
};
+4 -14
View File
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import { Aeronef, AeronefByImat, AeronefByYear } from 'src/app/core/models';
import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class AeronefsService {
@@ -12,22 +12,12 @@ export class AeronefsService {
) { }
getAllByImat(): Observable<Array<AeronefByImat>> {
return this.apiService.get('/aeronefs')
return this.apiService.get('/aeronefs/allByImat')
.pipe(map(data => data.aeronefs));
/*
//return this.apiService.get('/aeronefs');
return this.apiService.get('/aeronefs')
.pipe(map(data => {
return {
aeronefs: <Array<Aeronef>>data.aeronefs,
aeronefsCount: <number>data.aeronefsCount
};
}));
*/
}
getAllByDate(): Observable<Array<AeronefByYear>> {
return this.apiService.get('/aeronefs/allByDate')
getAllByImatByYear(): Observable<Array<AeronefByYear>> {
return this.apiService.get('/aeronefs/allByImatByYear')
.pipe(map(data => data.aeronefs));
}
+8 -20
View File
@@ -1,8 +1,7 @@
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
//import { environment } from 'src/environments/environment';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ApiService {
@@ -10,34 +9,23 @@ export class ApiService {
private http: HttpClient
) { }
private formatErrors(error: any) {
/*private formatErrors(error: any) {
return throwError(error.error);
}
}*/
get(path: string, params: HttpParams = new HttpParams()): Observable<any> {
return this.http.get(
`${environment.api_url}${path}`,
{ params }
).pipe(catchError(this.formatErrors));
return this.http.get(`${path}`, { params });
}
put(path: string, body: NonNullable<unknown> = {}): Observable<any> {
return this.http.put(
`${environment.api_url}${path}`,
JSON.stringify(body)
).pipe(catchError(this.formatErrors));
return this.http.put(`${path}`, JSON.stringify(body));
}
post(path: string, body: NonNullable<unknown> = {}): Observable<any> {
return this.http.post(
`${environment.api_url}${path}`,
JSON.stringify(body)
).pipe(catchError(this.formatErrors));
return this.http.post(`${path}`, JSON.stringify(body));
}
delete(path: string): Observable<any> {
return this.http.delete(
`${environment.api_url}${path}`
).pipe(catchError(this.formatErrors));
return this.http.delete(`${path}`);
}
}
@@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from 'src/app/core/services';
import { take } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private userService: UserService
) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.userService.isAuthenticated.pipe(take(1));
}
}
@@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from 'src/app/core/services';
import { take } from 'rxjs/operators';
@Injectable()
export class AuthResolver implements Resolve<boolean> {
constructor(
private userService: UserService
) { }
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.userService.isAuthenticated.pipe(take(1));
}
}
+10 -10
View File
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { CalculatorResult, Range, weightSizes } from 'src/app/core/models';
import { CalculatorResult, WeightSizeRange, weightSizes } from 'src/app/core/models';
import { UtilitiesService } from 'src/app/core/services';
@Injectable({ providedIn: 'root' })
@@ -14,7 +14,7 @@ export class CalculatorService {
private _getTableColumn(jumps: number): number {
let column: number = 0;
weightSizes[0].ranges.some((range: Range) => {
weightSizes[0].ranges.some((range: WeightSizeRange) => {
if (jumps >= range.start && jumps <= range.end) {
column = (range.num-1)
return true;
@@ -39,7 +39,7 @@ export class CalculatorService {
return line;
}
private _isInRange(nb: number, range: Range): boolean {
private _isInRange(nb: number, range: WeightSizeRange): boolean {
return (nb >= range.start && nb <= range.end);
}
@@ -60,7 +60,7 @@ export class CalculatorService {
return result;
}
let index: number = (this._getTableLine(weight) - this._getTableLine(0));
result.min = weightSizes[index].ranges[this._getTableColumn(jumps)].value;
result.min = weightSizes[index].ranges[this._getTableColumn(jumps)].value!;
result.min11 = this._reduceLimit(result.min, 11);
return result;
@@ -82,12 +82,12 @@ export class CalculatorService {
const line = (weight - this._getTableLine(0));
let data = weightSizes[line];
if (reduce) {
return data.ranges.map((range: Range): number => {
return Math.ceil(range.value * (100 - 11) / 100);
return data.ranges.map((range: WeightSizeRange): number => {
return Math.ceil(range.value! * (100 - 11) / 100);
});
} else {
return data.ranges.map((range: Range): number => {
return range.value;
return data.ranges.map((range: WeightSizeRange): number => {
return range.value!;
});
}
}
@@ -99,13 +99,13 @@ export class CalculatorService {
}
public getRangeNum(jumps: number): number {
let data: Range[] = weightSizes[0].ranges;
let data: WeightSizeRange[] = weightSizes[0].ranges;
let num: number = 1;
/* DT48 - 13 mars 2020 */
/* const maxRange = 2000; */
/* DT48 - 08 février 2024 */
const maxRange = 1600;
data.some((range: Range): boolean => {
data.some((range: WeightSizeRange): boolean => {
if (jumps >= range.start && jumps <= range.end) {
num = range.num;
return true;
+7 -7
View File
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import { Canopy, CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class CanopiesService {
@@ -16,18 +16,18 @@ export class CanopiesService {
.pipe(map(data => data.canopies));
}
getAllByYear(): Observable<Array<CanopyByYear>> {
return this.apiService.get('/canopies/allByYear')
getAllBySizeByYear(): Observable<Array<CanopyByYear>> {
return this.apiService.get('/canopies/allBySizeByYear')
.pipe(map(data => data.canopies));
}
getAllModelBySize(): Observable<Array<CanopyModelBySize>> {
return this.apiService.get('/canopies/allModelBySize')
getAllBySizeByModel(): Observable<Array<CanopyModelBySize>> {
return this.apiService.get('/canopies/allBySizeByModel')
.pipe(map(data => data.canopies));
}
getAllModelByYear(): Observable<Array<CanopyModelByYear>> {
return this.apiService.get('/canopies/allModelByYear')
getAllBySizeByModelByYear(): Observable<Array<CanopyModelByYear>> {
return this.apiService.get('/canopies/allBySizeByModelByYear')
.pipe(map(data => data.canopies));
}
+4 -5
View File
@@ -1,10 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import { DropZone, DropZoneByOaci, DropZoneByYear, JumpListConfig, JumpListFilters } from 'src/app/core/models';
import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class DropZonesService {
@@ -13,12 +12,12 @@ export class DropZonesService {
) { }
getAllByOaci(): Observable<Array<DropZoneByOaci>> {
return this.apiService.get('/dropzones')
return this.apiService.get('/dropzones/allByOaci')
.pipe(map(data => data.dropzones));
}
getAllByDate(): Observable<Array<DropZoneByYear>> {
return this.apiService.get('/dropzones/allByDate')
getAllByOaciByYear(): Observable<Array<DropZoneByYear>> {
return this.apiService.get('/dropzones/allByOaciByYear')
.pipe(map(data => data.dropzones));
}
+1 -3
View File
@@ -1,8 +1,6 @@
export * from './aeronefs.service';
export * from './api.service';
export * from './applications.service';
export * from './auth-guard.service';
export * from './auth-resolver.service';
export * from './backend.service';
export * from './calculator.service';
export * from './canopies.service';
@@ -10,7 +8,7 @@ export * from './dropzones.service';
export * from './jumps.service';
export * from './jump-table.service';
export * from './jwt.service';
export * from './no-auth-guard.service';
export * from './pages.service';
export * from './profiles.service';
export * from './qcm.service';
export * from './user.service';
+24 -2
View File
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import { Jump, JumpByDate, JumpList, JumpListConfig, JumpListFilters } from 'src/app/core/models';
import { Jump, JumpByCategorie, JumpByDate, JumpByModule, JumpList, JumpListConfig, JumpListFilters } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class JumpsService {
@@ -14,6 +14,7 @@ export class JumpsService {
query(config: JumpListConfig): Observable<JumpList> {
// Convert any filters over to Angular's URLSearchParams
/*
const params: JumpListFilters = {
date: config.filters.date,
lieu: config.filters.lieu,
@@ -30,8 +31,19 @@ export class JumpsService {
zone: config.filters.zone,
author: config.filters.author,
limit: config.filters.limit,
offset: config.filters.offset
offset: config.filters.offset,
numeroRangeStart: config.filters.numeroRangeStart!,
numeroRangeEnd: config.filters.numeroRangeEnd!,
tailleRangeStart: config.filters.tailleRangeStart!,
tailleRangeEnd: config.filters.tailleRangeEnd!,
participantsRangeStart: config.filters.participantsRangeStart!,
participantsRangeEnd: config.filters.participantsRangeEnd!,
hauteurRangeStart: config.filters.hauteurRangeStart!,
hauteurRangeEnd: config.filters.hauteurRangeEnd!
};
*/
const params: JumpListFilters = {} as JumpListFilters;
Object.assign(params, config.filters);
return this.apiService
.get(
@@ -55,6 +67,16 @@ export class JumpsService {
.pipe(map(data => data.jumps));
}
getAllByCategorie(): Observable<Array<JumpByCategorie>> {
return this.apiService.get('/jumps/allByCategorie')
.pipe(map(data => data.jumps));
}
getAllByModule(): Observable<Array<JumpByModule>> {
return this.apiService.get('/jumps/allByModule')
.pipe(map(data => data.jumps));
}
getLastJump(): Observable<Jump> {
return this.apiService.get('/jumps/last')
.pipe(map(data => data.jump));
@@ -1,23 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserService } from 'src/app/core/services';
import { map, take } from 'rxjs/operators';
@Injectable()
//@Injectable({ providedIn: 'root' })
export class NoAuthGuard implements CanActivate {
constructor(
private userService: UserService
) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.userService.isAuthenticated.pipe(take(1), map(isAuth => !isAuth));
}
}
+30
View File
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { ApiService } from './api.service';
import { AeronefsPageData, CanopiesPageData, DropZonesPageData, JumpsPageData } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class PagesService {
constructor(
private apiService: ApiService
) { }
getAeronefsPage(): Observable<AeronefsPageData> {
return this.apiService.get(`/pages/aeronefs`).pipe(take(1));
}
getCanopiesPage(): Observable<CanopiesPageData> {
return this.apiService.get(`/pages/canopies`).pipe(take(1));
}
getDropZonesPage(): Observable<DropZonesPageData> {
return this.apiService.get(`/pages/dropzones`).pipe(take(1));
}
getJumpsPage(): Observable<JumpsPageData> {
return this.apiService.get(`/pages/jumps`).pipe(take(1));
}
}
+14 -5
View File
@@ -3,17 +3,26 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
import { QCM } from 'src/app/core/models';
import { Qcm, QcmCategory, QcmQuestion } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class QCMService {
export class QcmService {
constructor(
private apiService: ApiService
) { }
get(type: string): Observable<QCM> {
return this.apiService.get(`/qcm/${type}`)
.pipe(map(data => data.qcm));
get(type: string): Observable<Qcm> {
return this.apiService.get(`/qcm/type/${type}`).pipe(map(data => data.qcm));
}
saveChoices(question: object): Observable<QcmQuestion> {
return this.apiService.post('/qcm/choices', { question: question })
.pipe(map(data => data.question));
}
saveQuestions(category: object): Observable<QcmCategory> {
return this.apiService.post('/qcm/questions', { category: category })
.pipe(map(data => data.category));
}
}
+18 -24
View File
@@ -1,9 +1,10 @@
import { Injectable, OnDestroy } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { Observable, BehaviorSubject, ReplaySubject, Subscription } from 'rxjs';
import { map, distinctUntilChanged, take } from 'rxjs/operators';
import { distinctUntilChanged, take, tap } from 'rxjs/operators';
import { User } from 'src/app/core/models';
import { ApiService, JwtService } from 'src/app/core/services';
import { JwtService } from 'src/app/core/services';
@Injectable({ providedIn: 'root' })
export class UserService implements OnDestroy {
@@ -17,8 +18,8 @@ export class UserService implements OnDestroy {
constructor(
private apiService: ApiService,
private jwtService: JwtService
private readonly http: HttpClient,
private readonly jwtService: JwtService
) { }
ngOnDestroy() {
@@ -30,7 +31,7 @@ export class UserService implements OnDestroy {
populate() {
// If JWT detected, attempt to get & store user's info
if (this.jwtService.getToken()) {
const user$: Observable<any> = this.apiService.get('/user').pipe(take(1));
const user$: Observable<{ user: User }> = this.http.get<{ user: User }>('/user').pipe(take(1));
this._user = user$.subscribe({
next: (data) => this.setAuth(data.user),
error: () => this.purgeAuth()
@@ -60,20 +61,16 @@ export class UserService implements OnDestroy {
this.isAuthenticatedSubject.next(false);
}
attemptAuth(type: string, credentials?: any): Observable<User> {
let user$: Observable<any>;
if (type === 'authenticate') {
user$ = this.apiService.get('/authenticate');
} else {
const route = (type === 'login') ? '/login' : '';
user$ = this.apiService.post(`/users${route}`, { user: credentials });
}
return user$.pipe(map(
attemptAuth(type: string, credentials?: { email: string; password: string; }): Observable<{ user: User }> {
const route = (type === 'login') ? '/login' : '';
const user$: Observable<{ user: User }> = this.http.post<{ user: User }>(`/users${route}`, { user: credentials });
/*return user$.pipe(map(
(data: any) => {
this.setAuth(data.user);
return data;
}
));;
));*/
return user$.pipe(tap(({ user }) => this.setAuth(user)));
}
getCurrentUser(): User {
@@ -84,16 +81,13 @@ export class UserService implements OnDestroy {
return (this.currentUserSubject.value.role == 'Admin') ? true : false;
}
// Update the user on the server (email, pass, etc)
update(user: any): Observable<User> {
return this.apiService
.put('/user', { user })
.pipe(map(data => {
// Update the currentUser observable
this.currentUserSubject.next(data.user);
return data.user;
}));
update(user: Partial<User>): Observable<{ user: User }> {
return this.http.put<{ user: User }>("/user", { user }).pipe(
tap(({ user }) => {
this.currentUserSubject.next(user);
}),
);
}
}
+294 -25
View File
@@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { BubbleDataPoint, Chart, ChartTypeRegistry, Point } from 'chart.js';
import { Configuration } from 'ng-chartist';
import { BarConfig, DoughnutConfig, LineConfig } from 'src/app/core/models';
import { BarConfig, CircleConfig, DoughnutConfig, LineConfig } from 'src/app/core/models';
@Injectable({ providedIn: 'root' })
export class UtilitiesService {
@@ -19,11 +20,11 @@ export class UtilitiesService {
}
getBarConfig(): Configuration {
let config: Configuration = {
const config: Configuration = {
type: 'Bar',
data: {
"labels": [],
"series": []
'labels': [],
'series': []
},
options: {},
responsiveOptions: []
@@ -32,7 +33,7 @@ export class UtilitiesService {
}
getChartColors(): string[] {
let colors: string[] = [
const colors: string[] = [
'ct-color-a',
'ct-color-b',
'ct-color-c',
@@ -50,18 +51,50 @@ export class UtilitiesService {
'ct-color-o',
'ct-color-p',
'ct-color-q',
'ct-color-r'
'ct-color-r',
'ct-color-s',
'ct-color-t',
'ct-color-u',
'ct-color-v',
'ct-color-w',
'ct-color-x',
'ct-color-y',
'ct-color-z'
];
return colors;
}
getCurrentDateFr(): string {
return new Date().toLocaleDateString("fr");
return new Date().toLocaleDateString('fr');
}
getSeriesColors(opacity: number, palette: string = 'all'): string[] {
getSeriesColors(opacity: number, palette: string = 'all', offset: number = 0): string[] {
let colors: string[] = [];
switch (palette) {
case 'pastels':
colors = [
`rgba(171, 222, 230, ${opacity})`,
`rgba(203, 170, 203, ${opacity})`,
`rgba(255, 255, 181, ${opacity})`,
`rgba(255, 204, 182, ${opacity})`,
`rgba(243, 176, 195, ${opacity})`,
`rgba(198, 219, 218, ${opacity})`,
`rgba(254, 225, 232, ${opacity})`,
`rgba(254, 215, 195, ${opacity})`,
`rgba(246, 234, 194, ${opacity})`,
`rgba(236, 213, 227, ${opacity})`,
`rgba(255, 150, 138, ${opacity})`,
`rgba(255, 174, 165, ${opacity})`,
`rgba(255, 197, 191, ${opacity})`,
`rgba(255, 216, 190, ${opacity})`,
`rgba(255, 200, 162, ${opacity})`,
`rgba(212, 240, 240, ${opacity})`,
`rgba(143, 202, 202, ${opacity})`,
`rgba(204, 226, 203, ${opacity})`,
`rgba(182, 207, 182, ${opacity})`,
`rgba(151, 193, 169, ${opacity})`
];
break;
case 'red':
colors = [
`rgba(163, 0, 0, ${opacity})`,
@@ -104,6 +137,32 @@ export class UtilitiesService {
case 'all':
default:
colors = [
`rgba(53, 162, 235, ${opacity})`,
`rgba(255, 99, 132, ${opacity})`,
`rgba(75, 192, 192, ${opacity})`,
`rgba(255, 159, 64, ${opacity})`,
`rgba(153, 102, 255, ${opacity})`,
`rgba(255, 205, 86, ${opacity})`,
`rgba(201, 203, 207, ${opacity})`,
`rgba(93, 204, 137, ${opacity})`,
`rgba(181, 17, 86, ${opacity})`,
`rgba(255, 226, 5, ${opacity})`,
`rgba(191, 101, 224, ${opacity})`,
`rgba(76, 174, 76, ${opacity})`,
`rgba(237, 80, 148, ${opacity})`,
`rgba(51, 122, 183, ${opacity})`,
`rgba(253, 24, 243, ${opacity})`,
`rgba(214, 31, 31, ${opacity})`,
`rgba(0, 137, 123, ${opacity})`,
`rgba(153, 171, 180, ${opacity})`,
`rgba(205, 186, 152, ${opacity})`,
`rgba(101, 224, 184, ${opacity})`,
`rgba(2, 191, 171, ${opacity})`,
`rgba(101, 218, 224, ${opacity})`,
`rgba(161, 199, 161, ${opacity})`,
`rgba(217, 202, 174, ${opacity})`,
`rgba(151, 136, 199, ${opacity})`,
`rgba(212, 206, 112, ${opacity})`,
`rgba(32, 182, 252, ${opacity})`,
`rgba(241, 80, 80, ${opacity})`,
`rgba(2, 191, 171, ${opacity})`,
@@ -131,13 +190,150 @@ export class UtilitiesService {
`rgba(212, 206, 112, ${opacity})`,
`rgba(255, 255, 255, ${opacity})`
];
if (offset > 0 && offset < colors.length) {
colors = colors.slice(offset);
}
break;
}
return colors;
}
getCircleChartConfig(): CircleConfig {
const config: CircleConfig = {
circleChartLabels: [],
circleChartDatasets: [
{
data: [],
label: 'Nombre total de sauts',
backgroundColor: this.getSeriesColors(0.8),
borderColor: this.getSeriesColors(1), // 'rgba(255, 255, 255, 1)'
borderWidth: 2
}
],
circleChartOptions: {
cutout: '90%',
elements: {
arc: {
//spacing: -10,
borderJoinStyle: 'round',
borderRadius: 10,
borderWidth: 0
}
},
plugins: {
legend: {
display: false,
position: 'left'
}
},
responsive: true,
maintainAspectRatio: false,
animation: {
onProgress: function (this: Chart) {
const width = this.width, height = this.height, ctx = this.ctx;
const count: number = parseInt(this.data.datasets[0].data[0]!.toString());
const diff: number = parseInt(this.data.datasets[0].data[1]!.toString());
const total: number = (count + diff);
const percent: number = Math.round((count/total*100));
const lineHeight = (height / 8);
ctx.restore();
let fontSize = (height / 120).toFixed(2);
ctx.font = `${fontSize}em sans-serif`;
//ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#dddddd';
//const text = `${percent} % (${count} ${this.data.datasets[0].label})`, textX = Math.round((width - ctx.measureText(text).width) / 2), textY = height / 2;
let text = this.data.datasets[0].label!;
let textX = Math.round((width - ctx.measureText(text).width) / 2);
let textY = height / 2;
ctx.fillText(text, textX, (textY-lineHeight));
text = `${percent}%`;
textX = Math.round((width - ctx.measureText(text).width) / 2);
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.restore();
fontSize = (height / 180).toFixed(2);
ctx.font = `${fontSize}em sans-serif`;
ctx.textBaseline = 'middle';
ctx.fillStyle = '#dddddd';
text = `${count} / ${total}`;
textX = Math.round((width - ctx.measureText(text).width) / 2);
textY = height / 2;
ctx.fillText(text, textX, (textY+lineHeight));
ctx.save();
//console.log(width, height, fontSize);
},
onComplete: function (this: Chart) {
const width = this.width, height = this.height, ctx = this.ctx;
const count: number = parseInt(this.data.datasets[0].data[0]!.toString());
const diff: number = parseInt(this.data.datasets[0].data[1]!.toString());
const total: number = (count + diff);
const percent: number = Math.round((count/total*100));
const lineHeight = (height / 8);
ctx.restore();
let fontSize = (height / 120).toFixed(2);
ctx.font = `${fontSize}em sans-serif`;
//ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#dddddd';
//ctx.fillStyle = this.data!.datasets[0].backgroundColor ? this.data.datasets[0].backgroundColor[0]!.toString() : '#dddddd';
//const text = `${percent} % (${count} ${this.data.datasets[0].label})`, textX = Math.round((width - ctx.measureText(text).width) / 2), textY = height / 2;
let text = this.data.datasets[0].label!;
let textX = Math.round((width - ctx.measureText(text).width) / 2);
let textY = height / 2;
ctx.fillText(text, textX, (textY-lineHeight));
text = `${percent}%`;
textX = Math.round((width - ctx.measureText(text).width) / 2);
textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.restore();
fontSize = (height / 180).toFixed(2);
ctx.font = `${fontSize}em sans-serif`;
ctx.textBaseline = 'middle';
ctx.fillStyle = '#dddddd';
text = `${count} / ${total}`;
textX = Math.round((width - ctx.measureText(text).width) / 2);
textY = height / 2;
ctx.fillText(text, textX, (textY+lineHeight));
ctx.save();
//console.log(width, height, fontSize);
},
},
},
circleChartPlugins: [
/*{
beforeInit: (chart: Chart<"doughnut", number[], unknown>) => {
const dataset = chart.data.datasets[0];
chart.data.labels = [dataset.label];
dataset.data = [dataset.data[0], 100 - dataset.data[0]];
},
id: 'beforeinit'
},*/
{
beforeDatasetDraw: (chart: Chart<"doughnut", number[], unknown>) => {
const width = chart.width, height = chart.height, ctx = chart.ctx;
ctx.restore();
const fontSize = (height / 150).toFixed(2);
ctx.font = fontSize + "em sans-serif";
ctx.fillStyle = "#ffffff";
ctx.textBaseline = "middle";
const text = chart.data.datasets[0].data[0] + "%", textX = Math.round((width - ctx.measureText(text).width) / 2), textY = height / 2;
ctx.fillText(text, textX, textY);
ctx.save();
console.log(width, height, fontSize);
},
id: 'doughnutlabel'
}
],
circleChartLegend: false
};
return config;
}
getDoughnutChartConfig(): DoughnutConfig {
let config: DoughnutConfig = {
const config: DoughnutConfig = {
doughnutChartLabels: [],
doughnutChartDatasets: [
{
@@ -160,7 +356,8 @@ export class UtilitiesService {
position: 'left'
}
},
responsive: true
responsive: true,
maintainAspectRatio: false
},
doughnutChartLegend: false
};
@@ -168,27 +365,52 @@ export class UtilitiesService {
}
getBarChartConfig(): BarConfig {
let config: BarConfig = {
const config: BarConfig = {
barChartData: {
labels: [''],
datasets: []
},
barChartOptions: {
plugins: {
legend: {
display: true,
position: 'top'
}
},
elements: {
bar: {
borderWidth: 1,
}
},
responsive: true
/*interaction: {
intersect: false,
axis: 'x'
},*/
scales: {
x: {
display: false,
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { display: true, color: 'rgba(255,255,255,0.1)', tickBorderDash: [1,2], tickBorderDashOffset: 2 }
},
y: {
display: true,
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { display: true, color: 'rgba(255,255,255,0.2)', tickBorderDash: [1,2], tickBorderDashOffset: 2 }
}
},
responsive: true,
maintainAspectRatio: false
},
barChartPlugins: [],
barChartLegend: false
barChartLegend: true
};
return config;
}
getHorizontalBarChartConfig(): BarConfig {
let config: BarConfig = {
const config: BarConfig = {
barChartData: {
labels: [''],
datasets: []
@@ -216,12 +438,12 @@ export class UtilitiesService {
x: {
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { color: 'rgba(255,255,255,0.2)', display: true, tickBorderDash: [1,2] }
grid: { display: true, color: 'rgba(255,255,255,0.1)', tickBorderDash: [1,2] }
},
y: {
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { color: 'rgba(255,255,255,0.3)', display: false }
grid: { display: false, color: 'rgba(255,255,255,0.2)' }
}
}
},
@@ -232,7 +454,7 @@ export class UtilitiesService {
}
getLineChartConfig(): LineConfig {
let config: LineConfig = {
const config: LineConfig = {
lineChartData: {
labels: [],
datasets: []
@@ -241,27 +463,74 @@ export class UtilitiesService {
plugins: {
legend: {
display: true,
position: 'bottom'
position: 'top'
}
},
interaction: {
/*interaction: {
intersect: false,
axis: 'x'
},
},*/
scales: {
x: {
display: false,
display: true,
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { display: true, color: 'rgba(255,255,255,0.1)', tickBorderDash: [1,2], tickBorderDashOffset: 2 }
},
y: {
display: true,
min: 60,
max: 200,
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { color: 'rgba(255,255,255,0.3)', display: true }
grid: { display: true, color: 'rgba(255,255,255,0.2)', tickBorderDash: [1,2], tickBorderDashOffset: 2 }
}
},
responsive: true,
maintainAspectRatio: true
maintainAspectRatio: false
},
lineChartLegend: true
};
return config;
}
getMonthsList(): Array<string> {
return ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
}
getStackedLineAreaChartConfig(): LineConfig {
const config: LineConfig = {
lineChartData: {
labels: [],
datasets: []
},
lineChartOptions: {
plugins: {
legend: {
display: true,
position: 'top'
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
scales: {
x: {
display: true,
beginAtZero: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { display: true, color: 'rgba(255,255,255,0.1)', tickBorderDash: [1,2] }
},
y: {
display: true,
beginAtZero: true,
stacked: true,
ticks: { color: 'rgba(255,255,255,0.3)' },
grid: { display: true, color: 'rgba(255,255,255,0.2)', tickBorderDash: [1,2] }
}
},
responsive: true,
maintainAspectRatio: false
},
lineChartLegend: true
};