diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e88db96820f572b128d6900e0b8290a631430028..1a210dec5e7022c73ccbebeb50d1a4bf7684c66a 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -44,7 +44,6 @@ .main-nav{ grid-row: 2; grid-column: 1 / span 1; - width: 50px; z-index: 200; transition: all .2s linear; background-color: #333745; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dae310cdf7818a05bfd15bbada78b3fcfc82a10e..bf6c2f06590bcb12c23137cf188f00f3f6741264 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -17,6 +17,7 @@ import { ResourcesComponent } from './components/resources/list/resources.compon import { ResourceService } from './services/resource.service'; import { ResourceFormComponent } from './components/resources/edit/resource-form.component'; import { ResourceDetailComponent } from './components/resources/detail/resource-detail.component'; +import { PaginationService } from './services/pagination.service'; @NgModule({ declarations: [ diff --git a/src/app/components/menu/menu.component.html b/src/app/components/menu/menu.component.html index 63c9727b0315c031bea447c498e166b48b913e8a..378c1b9028d21b31a9c21d956058ed4798a27e81 100644 --- a/src/app/components/menu/menu.component.html +++ b/src/app/components/menu/menu.component.html @@ -17,7 +17,7 @@ </li> <li><a [routerLink]="['/', 'resources']" routerLinkActive="active-link"> <span class="icon"> - <i class="fas fa-tint"></i> + <i class="fas fa-hat-wizard"></i> </span> <span class="label-menu">Ressources</span> </a> diff --git a/src/app/components/organizations/edit/organization-form.component.ts b/src/app/components/organizations/edit/organization-form.component.ts index 5ccb1db9212bf79e46970d4300c2a6843167f26a..30fb058e6473d86d80d4e83a9f6c1bfbdeb63484 100644 --- a/src/app/components/organizations/edit/organization-form.component.ts +++ b/src/app/components/organizations/edit/organization-form.component.ts @@ -81,9 +81,11 @@ export class OrganizationFormComponent implements OnInit { this.organization.links = []; } (this.form.controls.links as FormArray).push(this._fb.group( - { id: null, name: '', - url: ['', Validators.required], - organizationId: this.organization.id })); + { + id: null, name: '', + url: ['', Validators.required], + organizationId: this.organization.id + })); } removeLink(index) { @@ -97,7 +99,7 @@ export class OrganizationFormComponent implements OnInit { onSubmit() { if (! this.formInvalid) { this.organization = new Organization(this.form.value); - this.organizationService.replaceOrCreate(this.organization) + this.organizationService.updateOrCreate(this.organization) .subscribe( (organizationCreated) => { this.router.navigate(['/organizations', organizationCreated.id]) diff --git a/src/app/components/organizations/list/organizations.component.ts b/src/app/components/organizations/list/organizations.component.ts index a0299af9f17fdcfac3917c6d8d0358d475be4ce7..644b3cc062617d5a2dec62fdd224d7c6b7958fd1 100644 --- a/src/app/components/organizations/list/organizations.component.ts +++ b/src/app/components/organizations/list/organizations.component.ts @@ -1,13 +1,15 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { Organization, OrganizationRO } from 'src/app/models/organization.model'; +import { Organization } from 'src/app/models/organization.model'; import { OrganizationService } from 'src/app/services/organization.service'; import { BehaviorSubject, Subscription } from 'rxjs'; import { PaginatorOptions } from 'src/app/models/paginator-options.model'; +import { PaginationService } from 'src/app/services/pagination.service'; @Component({ selector: 'app-organizations', templateUrl: './organizations.component.html', styleUrls: ['./organizations.component.scss'], + providers: [PaginationService], }) export class OrganizationsComponent implements OnInit { @@ -30,23 +32,24 @@ export class OrganizationsComponent implements OnInit { constructor( private organizationsService: OrganizationService, + private paginationService: PaginationService, ) { this.paginator = { - pageIndex: this.organizationsService.pageNumber, + pageIndex: this.paginationService.pageNumber, length: 0, - limit: this.organizationsService.limit, + limit: this.paginationService.limit, pageSizeOptions: [5, 10, 20], }; } ngOnInit(): void { - this.organizationsService.sortOptions = { + this.paginationService.sortOptions = { value: 'name', order: 'asc' }; this.search(); - this.searchChangeSub = this.organizationsService.searchChange$.subscribe( + this.searchChangeSub = this.paginationService.searchChange$.subscribe( () => { this.search(); }, @@ -54,46 +57,51 @@ export class OrganizationsComponent implements OnInit { } private search() { - this.organizationsService.getOrganizations() - .subscribe((items: OrganizationRO) => { - this.organizations = items.organizations; - this.totalElement = items.totalCount; - - this.paginator.limit = this.organizationsService.limit; - this.paginator.pageIndex = this.organizationsService.pageNumber; - this.paginator.length = items.totalCount; + const options = { + limit: this.paginationService.limit, + pageNumber: this.paginationService.pageNumber, + sortOptions: this.paginationService.sortOptions, + }; + this.organizationsService.getAll(options) + .subscribe((items: Organization[]) => { + this.organizations = items; + this.totalElement = this.organizationsService.totalItems; + + this.paginator.limit = this.paginationService.limit; + this.paginator.pageIndex = this.paginationService.pageNumber; + this.paginator.length = this.organizationsService.totalItems; }); } - // When pagination is changed by user, we update datasetList with new pagination options + // When pagination is changed by user, we update pagination options changePagination(pageIndex) { - this.organizationsService.paginationChanged(this.paginator.limit, pageIndex); + this.paginationService.paginationChanged(this.paginator.limit, pageIndex); } changePageSize(pageSize) { - this.organizationsService.paginationChanged(pageSize, 1); + this.paginationService.paginationChanged(pageSize, 1); } sortBy(key: string) { - if (this.organizationsService.sortOptions.value === key) { - this.organizationsService.reverseSortOrder(); + if (this.paginationService.sortOptions.value === key) { + this.paginationService.reverseSortOrder(); } else { - this.organizationsService.sortOptions.value = key; - this.organizationsService.sortOptions.order = 'asc'; + this.paginationService.sortOptions.value = key; + this.paginationService.sortOptions.order = 'asc'; } this.search(); } get sortOptions() { - return this.organizationsService.sortOptions; + return this.paginationService.sortOptions; } displayDeletePopup(organizationId) { const pop = confirm('Etes vous sûr de vouloir supprimer cette organisation ?'); if (pop === true) { this.organizationsService.delete(organizationId).subscribe(() => { - this.organizationsService.pageNumber = 1; + this.paginationService.pageNumber = 1; this.search(); }); } diff --git a/src/app/components/resources/edit/resource-form.component.ts b/src/app/components/resources/edit/resource-form.component.ts index d8adb34a86852fa6f2edd3d83de0cfc6096410e4..1ec215ae12ccae2ea1352fcf973ab02aa57b9610 100644 --- a/src/app/components/resources/edit/resource-form.component.ts +++ b/src/app/components/resources/edit/resource-form.component.ts @@ -68,7 +68,7 @@ export class ResourceFormComponent implements OnInit { if (!this.formInvalid) { this.resource = new Resource(this.form.value); this.resource.outputFormats = this.form.value.outputFormats.split(','); - this.resourceService.replaceOrCreate(this.resource) + this.resourceService.updateOrCreate(this.resource) .subscribe( (resourceCreated) => { this.router.navigate(['/resources', resourceCreated.id]) diff --git a/src/app/components/resources/list/resources.component.ts b/src/app/components/resources/list/resources.component.ts index a8d9357aa537cee3e71e6f1b72186adf159138f8..073c0127400a2fc71b1c903b21836480ef33d478 100644 --- a/src/app/components/resources/list/resources.component.ts +++ b/src/app/components/resources/list/resources.component.ts @@ -1,13 +1,15 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { Resource, ResourceRO } from 'src/app/models/resource.model'; +import { Component, OnInit } from '@angular/core'; +import { Resource } from 'src/app/models/resource.model'; import { ResourceService } from 'src/app/services/resource.service'; import { Subscription } from 'rxjs'; import { PaginatorOptions } from 'src/app/models/paginator-options.model'; +import { PaginationService } from 'src/app/services/pagination.service'; @Component({ selector: 'app-resources', templateUrl: './resources.component.html', styleUrls: ['./resources.component.scss'], + providers: [PaginationService], }) export class ResourcesComponent implements OnInit { @@ -29,23 +31,24 @@ export class ResourcesComponent implements OnInit { constructor( private resourcesService: ResourceService, + private paginationService: PaginationService, ) { this.paginator = { - pageIndex: this.resourcesService.pageNumber, + pageIndex: this.paginationService.pageNumber, length: 0, - limit: this.resourcesService.limit, + limit: this.paginationService.limit, pageSizeOptions: [5, 10, 20], }; } ngOnInit(): void { - this.resourcesService.sortOptions = { + this.paginationService.sortOptions = { value: 'name', order: 'asc' }; this.search(); - this.searchChangeSub = this.resourcesService.searchChange$.subscribe( + this.searchChangeSub = this.paginationService.searchChange$.subscribe( () => { this.search(); }, @@ -53,46 +56,51 @@ export class ResourcesComponent implements OnInit { } private search() { - this.resourcesService.getResources() - .subscribe((items: ResourceRO) => { - this.resources = items.resources; - this.totalElement = items.totalCount; - - this.paginator.limit = this.resourcesService.limit; - this.paginator.pageIndex = this.resourcesService.pageNumber; - this.paginator.length = items.totalCount; + const options = { + limit: this.paginationService.limit, + pageNumber: this.paginationService.pageNumber, + sortOptions: this.paginationService.sortOptions, + }; + this.resourcesService.getAll(options) + .subscribe((items: Resource[]) => { + this.resources = items; + this.totalElement = this.resourcesService.totalItems; + + this.paginator.limit = this.paginationService.limit; + this.paginator.pageIndex = this.paginationService.pageNumber; + this.paginator.length = this.resourcesService.totalItems; }); } // When pagination is changed by user, we update datasetList with new pagination options changePagination(pageIndex) { - this.resourcesService.paginationChanged(this.paginator.limit, pageIndex); + this.paginationService.paginationChanged(this.paginator.limit, pageIndex); } changePageSize(pageSize) { - this.resourcesService.paginationChanged(pageSize, 1); + this.paginationService.paginationChanged(pageSize, 1); } sortBy(key: string) { - if (this.resourcesService.sortOptions.value === key) { - this.resourcesService.reverseSortOrder(); + if (this.paginationService.sortOptions.value === key) { + this.paginationService.reverseSortOrder(); } else { - this.resourcesService.sortOptions.value = key; - this.resourcesService.sortOptions.order = 'asc'; + this.paginationService.sortOptions.value = key; + this.paginationService.sortOptions.order = 'asc'; } this.search(); } get sortOptions() { - return this.resourcesService.sortOptions; + return this.paginationService.sortOptions; } displayDeletePopup(resourceId) { const pop = confirm('Etes vous sûr de vouloir supprimer cette organisation ?'); if (pop === true) { this.resourcesService.delete(resourceId).subscribe(() => { - this.resourcesService.pageNumber = 1; + this.paginationService.pageNumber = 1; this.search(); }); } diff --git a/src/app/models/organization.model.ts b/src/app/models/organization.model.ts index 1b3a7d2832ea82f2764d0d44ae12c8b44ca23382..fc1303182eb42f3155a2f776c3c15f1bcd99b93e 100644 --- a/src/app/models/organization.model.ts +++ b/src/app/models/organization.model.ts @@ -1,11 +1,14 @@ -export class Organization { - id: number; +import { RestObject } from './rest.object.model'; + +export class Organization extends RestObject { name: string; description: string; logo?: string; links?: ILink[]; constructor(organization?: IOrganization) { + super(); + if (organization) { this.id = organization.id; this.name = organization.name; @@ -21,16 +24,6 @@ export class Organization { } } -export class OrganizationRO { - organizations: Organization[]; - totalCount: number; - - constructor(organizations, totalCount) { - this.organizations = organizations; - this.totalCount = totalCount; - } -} - export interface IOrganization { id: number; name: string; diff --git a/src/app/models/resource.model.ts b/src/app/models/resource.model.ts index 7fb24fa24833f9c6553c65955df1c017b10db4c2..b0848463a20cbe3631904938b3a234607e657e61 100644 --- a/src/app/models/resource.model.ts +++ b/src/app/models/resource.model.ts @@ -1,5 +1,6 @@ -export class Resource { - id: number; +import { RestObject } from './rest.object.model'; + +export class Resource extends RestObject { name: string; type: string; description?: string; @@ -8,6 +9,8 @@ export class Resource { outputFormats: string[]; constructor(resource?: IResource) { + super(); + if (resource) { this.id = resource.id; this.name = resource.name; @@ -28,16 +31,6 @@ export class Resource { } } -export class ResourceRO { - resources: Resource[]; - totalCount: number; - - constructor(resources, totalCount) { - this.resources = resources; - this.totalCount = totalCount; - } -} - export interface IResource { id: number; name: string; @@ -47,10 +40,3 @@ export interface IResource { downloadable: number; outputFormats: string[]; } - -interface ILink { - id?: number; - name: string; - url: string; - organizationId: number; -} diff --git a/src/app/models/rest.object.model.ts b/src/app/models/rest.object.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e6845a6629224f722bb73a313641fc7351b4a84 --- /dev/null +++ b/src/app/models/rest.object.model.ts @@ -0,0 +1,3 @@ +export class RestObject { + id: number; +} diff --git a/src/app/models/serializers/organization.serializer.ts b/src/app/models/serializers/organization.serializer.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3931358ae4462d8ecd02fdb21d7ba0775c74a2e --- /dev/null +++ b/src/app/models/serializers/organization.serializer.ts @@ -0,0 +1,9 @@ +import { Serializer } from './serializer.interface'; +import { Organization } from '../organization.model'; + +export class OrganizationSerializer implements Serializer { + fromJson(data: any): Organization { + + return new Organization(data); + } +} diff --git a/src/app/models/serializers/resource.serializer.ts b/src/app/models/serializers/resource.serializer.ts new file mode 100644 index 0000000000000000000000000000000000000000..71d74344cc4fe63361848567e354a192720c1a3b --- /dev/null +++ b/src/app/models/serializers/resource.serializer.ts @@ -0,0 +1,9 @@ +import { Serializer } from './serializer.interface'; +import { Resource } from '../resource.model'; + +export class ResourceSerializer implements Serializer { + fromJson(data: any): Resource { + + return new Resource(data); + } +} diff --git a/src/app/models/serializers/serializer.interface.ts b/src/app/models/serializers/serializer.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..30041df1801aba108ec5d8c0f44c82b744c42ac6 --- /dev/null +++ b/src/app/models/serializers/serializer.interface.ts @@ -0,0 +1,5 @@ +import { RestObject } from '../rest.object.model'; + +export interface Serializer { + fromJson(json: any): RestObject; +} diff --git a/src/app/services/organization.service.ts b/src/app/services/organization.service.ts index 954ed26edfeec27a01fc19c5b8d8e1483ebbe1c2..51c6f2e4a91e5fe6b75d4ac35b73ebf843a6a7fb 100644 --- a/src/app/services/organization.service.ts +++ b/src/app/services/organization.service.ts @@ -1,90 +1,19 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import { map } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; -import { Organization, IOrganization, OrganizationRO } from '../models/organization.model'; +import { Organization } from '../models/organization.model'; import { environment } from 'src/environments/environment'; +import { RESTService } from './rest.service'; +import { OrganizationSerializer } from '../models/serializers/organization.serializer'; @Injectable() -export class OrganizationService { - - limit: number; - pageNumber: number; - sortOptions: { - value: string, - order: string, - }; - - private _searchChangeSubject: Subject<any>; - - constructor( - private _httpClient: HttpClient) { - this._searchChangeSubject = new Subject<any>(); - this.limit = 10; - this.pageNumber = 1; - } - - getOrganizations(options?): Observable<OrganizationRO> { - let query = '?'; - query += 'limit=' + (this.limit ? this.limit : 20); - query += '&offset=' + (this.pageNumber ? (this.pageNumber - 1) * this.limit : 0); - query += '&sort_by=' + this.sortOptions.value + '.' + this.sortOptions.order; - - return this._httpClient.get<IOrganization[]>(environment.organizations.url + query, { observe: 'response' }).pipe( - map((response) => { - const totalCount = response.headers.get('Content-Range'); - const organizations = []; - response.body.forEach((organization) => { - organizations.push(new Organization(organization)); - }); - return new OrganizationRO(organizations, parseInt(totalCount, 10)); - })); - } - - findById(id): Observable<Organization> { - return this._httpClient.get<IOrganization>(environment.organizations.url + id).pipe( - map((response) => { - return new Organization(response); - } - )); - } - - delete(id) { - return this._httpClient.delete(environment.organizations.url + id); +export class OrganizationService extends RESTService<Organization> { + + constructor(httpClient: HttpClient) { + super( + httpClient, + environment.organizations.url, + new OrganizationSerializer() + ); } - replaceOrCreate(data): Observable<Organization> { - if (data.id) { - return this._httpClient.put<IOrganization>(environment.organizations.url + data.id, data).pipe( - map((response) => { - return new Organization(response); - } - )); - } - return this._httpClient.post<IOrganization>(environment.organizations.url, data).pipe( - map((response) => { - return new Organization(response); - } - )); - } - - /* PAGINATION */ - paginationChanged(limit: number, pageNumber: number) { - this.limit = limit; - this.pageNumber = pageNumber; - this._searchChangeSubject.next(); - } - - reverseSortOrder(): void { - if (this.sortOptions.order === 'asc') { - this.sortOptions.order = 'desc'; - } else { - this.sortOptions.order = 'asc'; - } - } - - get searchChange$(): Observable<string> { - return this._searchChangeSubject.asObservable(); - } } diff --git a/src/app/services/pagination.service.ts b/src/app/services/pagination.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..04962d6ea6e5ae6679ad94f9437c3394152b6c5f --- /dev/null +++ b/src/app/services/pagination.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; + +@Injectable() +export class PaginationService { + + limit: number; + pageNumber: number; + sortOptions: { + value: string, + order: string, + }; + private _searchChangeSubject: Subject<any>; + + + constructor() { + this.limit = 10; + this.pageNumber = 1; + this._searchChangeSubject = new Subject<any>(); + } + + /* PAGINATION */ + paginationChanged(limit: number, pageNumber: number) { + this.limit = limit; + this.pageNumber = pageNumber; + this._searchChangeSubject.next(); + } + + reverseSortOrder(): void { + if (this.sortOptions.order === 'asc') { + this.sortOptions.order = 'desc'; + } else { + this.sortOptions.order = 'asc'; + } + } + + get searchChange$(): Observable<string> { + return this._searchChangeSubject.asObservable(); + } +} diff --git a/src/app/services/resource.service.ts b/src/app/services/resource.service.ts index c5045c1774a21ea9da0d93335e392b862926a2af..f5b4660bbc75b00b805793e19fb63bbe2fd712cc 100644 --- a/src/app/services/resource.service.ts +++ b/src/app/services/resource.service.ts @@ -1,90 +1,18 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import { map } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; -import { Resource, IResource, ResourceRO } from '../models/resource.model'; +import { Resource } from '../models/resource.model'; import { environment } from 'src/environments/environment'; +import { RESTService } from './rest.service'; +import { ResourceSerializer } from '../models/serializers/resource.serializer'; @Injectable() -export class ResourceService { - - limit: number; - pageNumber: number; - sortOptions: { - value: string, - order: string, - }; - - private _searchChangeSubject: Subject<any>; - - constructor( - private _httpClient: HttpClient) { - this._searchChangeSubject = new Subject<any>(); - this.limit = 10; - this.pageNumber = 1; - } - - getResources(options?): Observable<ResourceRO> { - let query = '?'; - query += 'limit=' + (this.limit ? this.limit : 20); - query += '&offset=' + (this.pageNumber ? (this.pageNumber - 1) * this.limit : 0); - query += '&sort_by=' + this.sortOptions.value + '.' + this.sortOptions.order; - - return this._httpClient.get<IResource[]>(environment.resources.url + query, { observe: 'response' }).pipe( - map((response) => { - const totalCount = response.headers.get('Content-Range'); - const resources = []; - response.body.forEach((resource) => { - resources.push(new Resource(resource)); - }); - return new ResourceRO(resources, parseInt(totalCount, 10)); - })); - } - - findById(id): Observable<Resource> { - return this._httpClient.get<IResource>(environment.resources.url + id).pipe( - map((response) => { - return new Resource(response); - } - )); - } - - delete(id) { - return this._httpClient.delete(environment.resources.url + id); - } - - replaceOrCreate(data): Observable<Resource> { - if (data.id) { - return this._httpClient.put<IResource>(environment.resources.url + data.id, data).pipe( - map((response) => { - return new Resource(response); - } - )); - } - return this._httpClient.post<IResource>(environment.resources.url, data).pipe( - map((response) => { - return new Resource(response); - } - )); - } - - /* PAGINATION */ - paginationChanged(limit: number, pageNumber: number) { - this.limit = limit; - this.pageNumber = pageNumber; - this._searchChangeSubject.next(); - } - - reverseSortOrder(): void { - if (this.sortOptions.order === 'asc') { - this.sortOptions.order = 'desc'; - } else { - this.sortOptions.order = 'asc'; - } - } - - get searchChange$(): Observable<string> { - return this._searchChangeSubject.asObservable(); - } +export class ResourceService extends RESTService<Resource> { + + constructor(httpClient: HttpClient) { + super( + httpClient, + environment.resources.url, + new ResourceSerializer, + ); + } } diff --git a/src/app/services/rest.service.ts b/src/app/services/rest.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..61a2aa81afdc5b12fd03da432f4b23287174c744 --- /dev/null +++ b/src/app/services/rest.service.ts @@ -0,0 +1,70 @@ +import { RestObject } from '../models/rest.object.model'; +import { Subject, Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Serializer } from '../models/serializers/serializer.interface'; +import { map } from 'rxjs/operators'; + +export class RESTService<T extends RestObject> { + + private _totalItems: number; + + constructor( + private httpClient: HttpClient, + private endpoint: string, + private serializer: Serializer) { + } + + getAll(options?): Observable<T[]> { + let query = '?'; + if (options) { + query += 'limit=' + (options.limit ? options.limit : 20); + query += '&offset=' + (options.pageNumber ? (options.pageNumber - 1) * options.limit : 0); + query += '&sort_by=' + options.sortOptions.value + '.' + options.sortOptions.order; + } + + return this.httpClient.get(this.endpoint + query, { observe: 'response' }).pipe( + map((response) => { + this._totalItems = parseInt(response.headers.get('Content-Range'), 10); + return this.convertData(response.body); + })); + } + + findById(id): Observable<T> { + return this.httpClient.get(this.endpoint + id).pipe( + map((response) => { + return this.serializer.fromJson(response) as T; + } + )); + } + + updateOrCreate(data): Observable<T> { + // Update an existing item + if (data.id) { + return this.httpClient.put<T>(this.endpoint + data.id, data).pipe( + map((response) => { + return this.serializer.fromJson(response) as T; + } + )); + } + // Create a new item + return this.httpClient.post<T>(this.endpoint, data).pipe( + map((response) => { + return this.serializer.fromJson(response) as T; + } + )); + } + + + delete(id) { + return this.httpClient.delete(this.endpoint + id); + } + + private convertData(data: any): T[] { + return data.map(item => this.serializer.fromJson(item)); + } + + get totalItems() { + return this._totalItems; + } + +}