Skip to content
Snippets Groups Projects
Commit e3257219 authored by FORESTIER Fabien's avatar FORESTIER Fabien
Browse files

Add crud for projections

parent 06558d94
No related branches found
No related tags found
1 merge request!14Version 1.3.0
Pipeline #2864 passed
Showing
with 674 additions and 8 deletions
......@@ -6,10 +6,15 @@ import { OrganizationFormComponent } from './components/organizations/edit/organ
import { ResourcesComponent } from './components/resources/list/resources.component';
import { ResourceFormComponent } from './components/resources/edit/resource-form.component';
import { ResourceDetailComponent } from './components/resources/detail/resource-detail.component';
import { LogsPreReportComponent, LogsReportComponent, LogsHomeComponent, FormatsComponent,
import {
LogsPreReportComponent, LogsReportComponent, LogsHomeComponent, FormatsComponent,
FormatDetailComponent, FormatFormComponent, ChangelogDetailComponent, ChangelogFormComponent,
CreditsComponent, CreditFormComponent, CreditDetailComponent, ReusesComponent, ReuseFormComponent,
ReuseDetailComponent } from './components';
ReuseDetailComponent,
ProjectionsComponent,
ProjectionFormComponent,
ProjectionDetailComponent
} from './components';
import { AuthenticatedGuard } from './user/guards/authenticated.guard';
import { ChangelogComponent } from './components/changelog/list/changelog.component';
......@@ -235,6 +240,38 @@ const appRoutes: Routes = [
title: "Rapport final d'indexation",
},
},
{
path: 'projections',
component: ProjectionsComponent,
canActivate: [AuthenticatedGuard],
data: {
title: 'Projections',
},
},
{
path: 'projections/new',
component: ProjectionFormComponent,
canActivate: [AuthenticatedGuard],
data: {
title: 'Nouvelle projection',
},
},
{
path: 'projections/:id/edit',
component: ProjectionFormComponent,
canActivate: [AuthenticatedGuard],
data: {
title: 'Modifier la projection',
},
},
{
path: 'projections/:id',
component: ProjectionDetailComponent,
canActivate: [AuthenticatedGuard],
data: {
title: 'Detail de la projection',
},
},
];
@NgModule({
......
......@@ -33,6 +33,9 @@ import { CreditsComponent } from './credits/list/credits.component';
import { ReusesComponent } from './reuses/list/reuses.component';
import { ReuseFormComponent } from './reuses/edit/reuse-form.component';
import { ReuseDetailComponent } from './reuses/detail/reuse-detail.component';
import { ProjectionsComponent } from './projections/list/projections.component';
import { ProjectionDetailComponent } from './projections/detail/projection-detail.component';
import { ProjectionFormComponent } from './projections/edit/projection-form.component';
export {
MenuComponent,
......@@ -70,6 +73,9 @@ export {
ReuseDetailComponent,
ReuseFormComponent,
ReusesComponent,
ProjectionsComponent,
ProjectionDetailComponent,
ProjectionFormComponent,
};
// tslint:disable-next-line:variable-name
......@@ -108,4 +114,7 @@ export const AppComponents = [
ReuseDetailComponent,
ReuseFormComponent,
ReusesComponent,
ProjectionsComponent,
ProjectionDetailComponent,
ProjectionFormComponent,
];
......@@ -41,6 +41,13 @@
<span class="label-menu">Formats</span>
</a>
</li>
<li><a [routerLink]="['/', 'projections']" routerLinkActive="active-link">
<span class="icon">
<i class="fas fa-map-marked-alt"></i>
</span>
<span class="label-menu">Projections</span>
</a>
</li>
<li><a [routerLink]="['/', 'changelog']" routerLinkActive="active-link">
<span class="icon">
<i class="fas fa-clipboard-list"></i>
......
......@@ -24,7 +24,6 @@ export class PageHeaderComponent implements OnInit {
goToPreviousPage() {
if (this.goToThisUrl) {
console.log('INSIDE GOTOTHISURL');
this._router.navigate([this.goToThisUrl]);
} else {
const index = 1; // Start to retrieve the previous element
......
<section class="section page-container" *ngIf="projection">
<app-page-header [pageInfo]="{title: title}"></app-page-header>
<div class="columns is-centered">
<div class="column is-8">
<div class="card">
<header class="card-header">
<p class="card-header-title has-text-centered">
{{projection.name}}
</p>
</header>
<div class="card-content">
<div class="content">
<p>
<span class="has-text-weight-bold">Id: </span>
<span>{{projection.id}}</span>
</p>
<p>
<span class="has-text-weight-bold">Nom commun: </span>
<span>{{projection.commonName}}</span>
</p>
<p>
<span class="has-text-weight-bold">Description: </span>
<span *ngIf="projection.description; else emptyDescription">{{projection.description}}</span>
<ng-template #emptyDescription>
<span class="empty-property">Non renseigné</span>
</ng-template>
</p>
</div>
</div>
</div>
</div>
</div>
</section>
\ No newline at end of file
.card-header-title {
justify-content: center;
}
\ No newline at end of file
import { switchMap } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Projection } from '../../../models/projection.model';
import { ProjectionService } from '../../../services';
@Component({
selector: 'app-projection-detail',
templateUrl: './projection-detail.component.html',
styleUrls: ['./projection-detail.component.scss'],
})
export class ProjectionDetailComponent implements OnInit {
projection: Projection;
title: string;
constructor(
private _route: ActivatedRoute,
private _projectionService: ProjectionService,
) {
}
ngOnInit(): void {
this.title = this._route.snapshot.data.title;
this._route.paramMap.pipe(
switchMap((params: ParamMap) => this._projectionService.findById(params.get('id'))))
.subscribe((projection: Projection) => this.projection = projection);
}
}
<section class="section page-container" *ngIf="projection">
<app-page-header [pageInfo]="{title: title}"></app-page-header>
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="columns is-centered is-marginless">
<div class="column is-7">
<input type="hidden" formControlName="id" value="{{projection.id}}">
<div class="field">
<label class="label required" for="name">Nom</label>
<div class="control">
<input class="input" type="text" [value]="projection.name" formControlName="name" id="name" required>
</div>
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger">
<p *ngIf="name.errors['required']" class="help is-danger">
Le nom de la projection est obligatoire.
</p>
</div>
</div>
<div class="field">
<label class="label required" for="mapServerType">Nom commun</label>
<div class="control">
<input class="input" type="text" [value]="projection.commonName" formControlName="commonName" id="commonName">
</div>
<div *ngIf="commonName.invalid && (commonName.dirty || commonName.touched)" class="alert alert-danger">
<p *ngIf="commonName.errors['required']" class="help is-danger">
Le nom commun est obligatoire.
</p>
</div>
</div>
<div class="field">
<label class="label" for="fileExtension">Description</label>
<div class="control">
<input class="input" type="text" [value]="projection.description" formControlName="description"
id="description">
</div>
</div>
<div class="has-text-right">
<button class="button button-gl" type="submit" [disabled]="formInvalid == true">Valider</button>
</div>
</div>
</form>
</section>
\ No newline at end of file
.full-width {
width: 100%;
}
h1 {
text-align: center
}
.icon {
cursor: pointer;
&:hover {
.fa-plus {
color: lightblue;
}
.fa-trash {
color: #d5232a;
}
}
}
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { filter, switchMap } from 'rxjs/operators';
import { NotificationService, ProjectionService } from 'src/app/services';
import { Projection } from '../../../models/projection.model';
@Component({
selector: 'app-projection-form',
templateUrl: './projection-form.component.html',
styleUrls: ['./projection-form.component.scss'],
})
export class ProjectionFormComponent implements OnInit {
projection: Projection = new Projection();
form: FormGroup;
title: string;
constructor(
private _projectionService: ProjectionService,
private _route: ActivatedRoute,
private _router: Router,
private _fb: FormBuilder,
private _notificationService: NotificationService,
) {
}
ngOnInit() {
this.title = this._route.snapshot.data.title;
this.initForm();
this._route.paramMap.pipe(
filter((paramMap: ParamMap) => (paramMap.get('id') !== null)),
switchMap((paramMap: ParamMap) => this._projectionService.findById(paramMap.get('id'))))
.subscribe((projection: Projection) => {
this.projection = projection;
this.initForm();
});
}
initForm() {
this.form = this._fb.group({
id: [this.projection.id],
name: [this.projection.name, Validators.required],
commonName: [this.projection.commonName, Validators.required],
description: [this.projection.description],
});
}
onSubmit() {
if (!this.formInvalid) {
this.projection = new Projection(this.form.value);
if (this.projection.id) {
this._projectionService.update(this.projection).subscribe(
(projectionUpdate) => {
this._notificationService.notify({
type: 'success',
message: 'Le projection a bien été mise à jour.',
});
this._router.navigate(['/projections', projectionUpdate.id]);
},
() => {
this._notificationService.notify({
type: 'error',
message: 'Une erreur est survenue lors de la mise à jour de la projection.',
});
},
);
} else {
this._projectionService.create(this.projection).subscribe(
(projectionCreated) => {
this._notificationService.notify({
type: 'success',
message: 'Le projection a bien été créé.',
});
this._router.navigate(['/projections', projectionCreated.id]);
},
() => {
this._notificationService.notify({
type: 'error',
message: 'Une erreur est survenue lors de la création de la projection.',
});
},
);
}
}
}
// Getters for each property
get name() {
return this.form.controls['name'];
}
get commonName() {
return this.form.controls['commonName'];
}
get description() {
return this.form.controls['description'];
}
get formInvalid() {
return this.form.invalid;
}
}
<section class="section page-container">
<app-page-header [pageInfo]="pageHeaderInfo" [hideBackButton]="true"></app-page-header>
<div class="add-item-link has-text-right">
<a class="button button-gl" [routerLink]="['new']">
Ajouter
</a>
</div>
<div class="table entity-list-table" *ngIf="projections">
<div class="header columns is-marginless">
<div class="column is-2">
<span (click)="sortBy('name')" class="is-sortable">
<span class="sort-icons">
<span class="icon">
<i class="fas fa-sort-up"
[ngClass]="{'icon-red': sortOptions.value === 'name' && sortOptions.order === 'desc'}"></i>
</span>
<span class="icon">
<i class="fas fa-sort-down"
[ngClass]="{'icon-red': sortOptions.value === 'name' && sortOptions.order === 'asc'}"></i>
</span>
</span>
<span class="column-title" [ngClass]="{'active': sortOptions.value === name}">Nom</span>
</span>
</div>
<div class="column is-2">
<span (click)="sortBy('commonName')" class="is-sortable">
<span class="sort-icons">
<span class="icon">
<i class="fas fa-sort-up"
[ngClass]="{'icon-red': sortOptions.value === 'commonName' && sortOptions.order === 'desc'}"></i>
</span>
<span class="icon">
<i class="fas fa-sort-down"
[ngClass]="{'icon-red': sortOptions.value === 'commonName' && sortOptions.order === 'asc'}"></i>
</span>
</span>
<span class="column-title" [ngClass]="{'active': sortOptions.value === commonName}">Nom commun</span>
</span>
</div>
<div class="column is-2">
<span (click)="sortBy('description')" class="is-sortable">
<span class="sort-icons">
<span class="icon">
<i class="fas fa-sort-up"
[ngClass]="{'icon-red': sortOptions.value === 'description' && sortOptions.order === 'desc'}"></i>
</span>
<span class="icon">
<i class="fas fa-sort-down"
[ngClass]="{'icon-red': sortOptions.value === 'description' && sortOptions.order === 'asc'}"></i>
</span>
</span>
<span class="column-title" [ngClass]="{'active': sortOptions.value === description}">Description</span>
</span>
</div>
<div class="column is-offset-5 is-1 has-text-centered">
<span class="column-title">Actions</span>
</div>
</div>
<div class="data-list">
<div class="data columns is-multiline is-vcentered is-marginless"
*ngFor="let projection of projections; let i=index; let odd=odd; let even=even;"
[ngClass]="{ odd: odd, even: even }">
<div class="column is-2">
<span>{{ projection.name}}</span>
</div>
<div class="column is-2">
<span>{{ projection.commonName}}</span>
</div>
<div class="column is-2">
<span>{{ projection.description}}</span>
</div>
<div class="column is-offset-5 is-1 has-text-centered actions">
<app-crud-buttons [id]="projection.id" (delete)="displayDeletePopup($event)"></app-crud-buttons>
</div>
</div>
</div>
<div class="columns is-marginless paginator">
<div class="column">
<app-paginator *ngIf="paginator.length > 0" [length]="paginator.length" [pageSize]="paginator.limit"
[pageSizeOptions]="paginator.pageSizeOptions" [pageIndex]="paginator.pageIndex" [pagesToShow]="5"
[showFirstLastButtons]="true" (page)="changePagination($event)" (pageSizeChanged)="changePageSize($event)">
</app-paginator>
</div>
</div>
</div>
</section>
<app-confirmation-modal (cancel)="objectToBeDeletedId=null" (continue)="deleteProjection()" [texts]="deleteModalTexts"
[isOpened]="objectToBeDeletedId !== null">
</app-confirmation-modal>
\ No newline at end of file
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { PaginatorOptions } from 'src/app/models/paginator-options.model';
import { IPageHeaderInfo } from '../../../models/page.model';
import { NotificationService } from '../../../services';
import { Projection, ProjectionRO } from '../../../models/projection.model';
import { ProjectionService } from '../../../services/projection.service';
@Component({
selector: 'app-projections',
templateUrl: './projections.component.html',
styleUrls: ['./projections.component.scss'],
})
export class ProjectionsComponent implements OnInit, OnDestroy {
pageHeaderInfo: IPageHeaderInfo = {
title: '',
};
objectToBeDeletedId = null;
deleteModalTexts = {
main: 'Si vous poursuivez, la projection sera définitivement supprimée.',
cancel: 'Annuler',
continue: 'Supprimer',
};
projections: Projection[] = [];
searchChangeSub: Subscription;
// Paginator options
paginator: PaginatorOptions;
sortValue: string;
totalElement: number;
pageSize = 10;
pageSizeOptions = [5, 10, 25, 100];
filters = {
name: '',
};
where = {};
constructor(
private _projectionService: ProjectionService,
private _notificationService: NotificationService,
) {
this.paginator = {
pageIndex: this._projectionService.pageNumber,
length: 0,
limit: this._projectionService.limit,
pageSizeOptions: [5, 10, 20],
};
}
ngOnInit(): void {
this._projectionService.sortOptions = {
value: 'name',
order: 'asc',
};
this.search();
this.searchChangeSub = this._projectionService.searchChange$.subscribe(
() => {
this.search();
},
);
}
private search() {
this._projectionService.getProjections()
.subscribe(
(items: ProjectionRO) => {
this.projections = items.projections;
this.totalElement = items.totalCount;
this.pageHeaderInfo.title = `${this.totalElement} projections trouvées`;
this.pageHeaderInfo.title = this.totalElement > 1 ?
`${this.totalElement} projections trouvées` :
`${this.totalElement} projection trouvée`;
this.paginator.limit = this._projectionService.limit;
this.paginator.pageIndex = this._projectionService.pageNumber;
this.paginator.length = items.totalCount;
},
() => {
this.pageHeaderInfo.title = '0 projection trouvée';
this._notificationService.notify({
type: 'error',
message: 'Une erreur est survenue lors du chargement des projections.',
});
},
);
}
// When pagination is changed by user, we update projections list with new pagination options
changePagination(pageIndex) {
this._projectionService.paginationChanged(this.paginator.limit, pageIndex);
}
changePageSize(pageSize) {
this._projectionService.paginationChanged(pageSize, 1);
}
sortBy(key: string) {
if (this._projectionService.sortOptions.value === key) {
this._projectionService.reverseSortOrder();
} else {
this._projectionService.sortOptions.value = key;
this._projectionService.sortOptions.order = 'asc';
}
this.search();
}
get sortOptions() {
return this._projectionService.sortOptions;
}
displayDeletePopup(id) {
this.objectToBeDeletedId = id;
}
deleteProjection() {
this._projectionService.delete(this.objectToBeDeletedId).subscribe(
() => {
this._notificationService.notify({
type: 'success',
message: 'La projection a été supprimée avec succès.',
});
this._projectionService.pageNumber = 1;
this.search();
},
() => {
this._notificationService.notify({
type: 'error',
message: 'Une erreur est survenue lors de la suppression de la projection.',
});
},
() => {
this.objectToBeDeletedId = null;
},
);
}
ngOnDestroy() {
this.searchChangeSub.unsubscribe();
}
}
.card-header-title {
justify-content: center;
}
.empty-property {
font-style: italic;
color: #818080;
}
export class Projection {
id?: number;
name: string;
commonName: string;
description: string;
constructor(projection?: IProjection) {
if (projection) {
if (projection.id) {
this.id = projection.id;
}
this.name = projection.name;
this.commonName = projection.commonName;
this.description = projection.description;
} else {
this.name = '';
this.commonName = '';
this.description = '';
}
}
}
export class ProjectionRO {
projections: Projection[];
totalCount: number;
constructor(projections, totalCount) {
this.projections = projections;
this.totalCount = totalCount;
}
}
export interface IProjection {
id: number;
name: string;
commonName: string;
description: string;
}
......@@ -9,6 +9,7 @@ import { ChangelogService } from './changelog.service';
import { CreditService } from './credit.service';
import { MediaService } from './media.service';
import { ReuseService } from './reuse.service';
import { ProjectionService } from './projection.service';
export {
AppConfigService,
......@@ -22,6 +23,7 @@ export {
CreditService,
MediaService,
ReuseService,
ProjectionService,
};
// tslint:disable-next-line:variable-name
......@@ -37,4 +39,5 @@ export const AppServices = [
CreditService,
MediaService,
ReuseService,
ProjectionService,
];
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { APP_CONFIG } from './app-config.service';
import { ProjectionRO, IProjection, Projection } from '../models/projection.model';
@Injectable()
export class ProjectionService {
projectionServiceUrl: string;
limit: number;
pageNumber: number;
sortOptions: {
value: string,
order: string,
};
private _searchChangeSubject: Subject<any>;
constructor(
private _httpClient: HttpClient) {
this.projectionServiceUrl = `${APP_CONFIG.resources.url}projections/`;
this._searchChangeSubject = new Subject<any>();
this.limit = 10;
this.pageNumber = 1;
}
getProjections(): Observable<ProjectionRO> {
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<IProjection[]>(this.projectionServiceUrl + query, { observe: 'response' }).pipe(
map((response) => {
const totalCount = response.headers.get('Content-Range');
const projections = [];
response.body.forEach((projection) => {
projections.push(new Projection(projection));
});
return new ProjectionRO(projections, parseInt(totalCount, 10));
}));
}
getAllProjections(): Observable<Projection[]> {
return this._httpClient.get<IProjection[]>(this.projectionServiceUrl).pipe(
map(body => body.map(projection => new Projection(projection))),
);
}
findById(id): Observable<Projection> {
return this._httpClient.get<IProjection>(this.projectionServiceUrl + id).pipe(
map((response) => {
return new Projection(response);
}),
);
}
delete(id) {
return this._httpClient.delete(this.projectionServiceUrl + id, { withCredentials: true });
}
create(data): Observable<Projection> {
return this._httpClient.post<IProjection>(this.projectionServiceUrl, data, { withCredentials: true }).pipe(
map((response) => {
return new Projection(response);
}),
);
}
update(data): Observable<Projection> {
return this._httpClient.put<IProjection>(this.projectionServiceUrl + data.id, data, { withCredentials: true }).pipe(
map((response) => {
return new Projection(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();
}
}
......@@ -312,3 +312,8 @@ textarea.ng-invalid:not(form).ng-touched {
}
}
}
.empty-property {
font-style: italic;
color: #818080;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment