diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7a671eee5647bfd78b407cd2fe5110eb9353aa17..75a74e9594c321fd32f10d33de582bbb7689b03b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -61,6 +61,7 @@ import { ExpertDetailModalComponent } from './component/common/expert-detail-mod import { ExpertDetailComponent } from './component/common/expert-detail/expert-detail.component'; import { ExpertListComponent } from './component/common/expert-list/expert-list.component'; import { FiltersComponent } from './component/common/expert-list/filters/filters.component'; +import { ExpertiseSelectComponent } from './component/common/expertise-select/expertise-select.component'; import { FileDownloadComponent } from './component/common/file-download/file-download.component'; import { FileUploadComponent } from './component/common/file-upload/file-upload.component'; import { GoToRequestComponent } from './component/common/go-to-request/go-to-request.component'; @@ -174,6 +175,7 @@ registerLocaleData(localeFr, 'fr'); PersonalDataComponent, GoToRequestComponent, AppealCourtSelectComponent, + ExpertiseSelectComponent, RequestFiltersComponent, InternalNoteListComponent, NoteItemComponent, diff --git a/src/app/component/admin/add-expert/add-expert.component.ts b/src/app/component/admin/add-expert/add-expert.component.ts index 325b65bfd36b193e2427e67c50d266ee4e665c3c..18ca701237343b1ddf168ef60f7e4264db85fc31 100644 --- a/src/app/component/admin/add-expert/add-expert.component.ts +++ b/src/app/component/admin/add-expert/add-expert.component.ts @@ -1,10 +1,11 @@ import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { AppealCourt } from '../../../model/appeal-court'; -import { ApiService } from '../../../service/api.service'; +import { map, Observable } from 'rxjs'; +import { ExpertiseService } from 'src/app/service/expertise.service'; import Utils from '../../../helper/utils'; +import { AppealCourt } from '../../../model/appeal-court'; import { ExpertCreationForm } from '../../../model/expert-creation-form'; import { Expertise } from '../../../model/expertise'; +import { ApiService } from '../../../service/api.service'; import { AppealCourtService } from '../../../service/appeal-court.service'; @Component({ @@ -38,15 +39,18 @@ export class AddExpertComponent implements OnInit { failure = false; constructor( - private apiService: ApiService, - private appealCourtService: AppealCourtService + private readonly apiService: ApiService, + private readonly expertiseService: ExpertiseService, + private readonly appealCourtService: AppealCourtService ) {} ngOnInit(): void { this.appealCourts$ = this.appealCourtService.getAppealCourts({ enabledForExperts: true }); - this.expertises$ = this.apiService.getExpertises(); + this.expertises$ = this.expertiseService + .getExpertises('id,asc', false) + .pipe(map(response => response.content)); } onSubmit() { diff --git a/src/app/component/common/appeal-court-select/appeal-court-select.component.html b/src/app/component/common/appeal-court-select/appeal-court-select.component.html index a0fcacc45bff122be0bc492228eeff38ee790ce2..6f077f7d4ddc22ac3d538c6b911c13e96ce767c3 100644 --- a/src/app/component/common/appeal-court-select/appeal-court-select.component.html +++ b/src/app/component/common/appeal-court-select/appeal-court-select.component.html @@ -3,5 +3,7 @@ [options]="(appealCourtOptions$ | async) || []" [(ngModel)]="courtIds" [showSearch]="true" + [showClearSearch]="true" + [clearableOptions]="true" (selectionChange)="selectionChange()" appendTo="body"></dsfr-ext-multiselect> diff --git a/src/app/component/common/expert-list/filters/filters.component.html b/src/app/component/common/expert-list/filters/filters.component.html index 23d30046b77f39d6b5cddae4df81bbbac77f6b55..a842363ea4de4602b88dd24d37c6617f64ab339b 100644 --- a/src/app/component/common/expert-list/filters/filters.component.html +++ b/src/app/component/common/expert-list/filters/filters.component.html @@ -10,31 +10,11 @@ </div> </div> <div class="fr-col-12 fr-col-md-4"> - <div class="fr-fieldset__element fr-pt-4v"> - <ng-select - #expertisesSelect - *ngIf="expertises$ | async as expertises" - (change)="updateFilters()" - (focus)="setExpertisePlaceholder('focus')" - (blur)="setExpertisePlaceholder('blur')" - (clear)="expertisesSelect.close(); expertisesSelect.blur()" - [(ngModel)]="expertise" - appendTo="body" - [placeholder]="expertisePlaceholder" - notFoundText="Aucune rubrique correspondante" - dropdownPosition="bottom" - [closeOnSelect]="true" - [multiple]="false" - [searchable]="expertiseSearchable"> - <ng-option - *ngFor="let expertise of expertises" - [value]="expertise.code"> - <div class="box-left"> - <div class="expertise-code fr-mr-2v">{{ expertise.code }}</div> - <div class="expertise-label">{{ expertise.label }}</div> - </div> - </ng-option> - </ng-select> + <div class="fr-fieldset__element fr-pt-2v"> + <app-expertise-select + (expertiseCodesChanged)=" + expertises = $event; updateFilters() + "></app-expertise-select> </div> </div> <div class="fr-col-12 fr-col-md-4 fr-pt-4v"> diff --git a/src/app/component/common/expert-list/filters/filters.component.ts b/src/app/component/common/expert-list/filters/filters.component.ts index 7cf4967ee76053e828de22c4d43df5a94b8459ea..9aa1437cb47a42a8aa17e774eb5f5efed80249f9 100644 --- a/src/app/component/common/expert-list/filters/filters.component.ts +++ b/src/app/component/common/expert-list/filters/filters.component.ts @@ -1,7 +1,5 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; -import { debounceTime, Observable, Subject } from 'rxjs'; -import { ApiService } from '../../../../service/api.service'; -import { Expertise } from '../../../../model/expertise'; +import { debounceTime, Subject } from 'rxjs'; import { UserService } from '../../../../service/user.service'; @Component({ @@ -10,32 +8,24 @@ import { UserService } from '../../../../service/user.service'; styleUrls: ['./filters.component.css'] }) export class FiltersComponent implements OnInit { - expertises$: Observable<Expertise[]>; filters: ExpertFilters = {}; courtIds: number[] = []; - expertise: string; - expertisePlaceholder: string; - expertiseSearchable = true; + expertises: string[]; nameChunk = ''; nameChunkSubject = new Subject<string>(); @Output() filtersChanged = new EventEmitter<ExpertFilters>(); - constructor( - private apiService: ApiService, - private userService: UserService - ) { + constructor(private readonly userService: UserService) { this.nameChunkSubject.pipe(debounceTime(500)).subscribe(() => { this.updateFilters(); }); } ngOnInit(): void { - this.setExpertisePlaceholder('blur'); - this.expertises$ = this.apiService.getExpertises(); if (this.userService.isCourt) { const appealCourt = this.userService.getUser()?.jurisdiction?.appealCourt; - if (appealCourt && appealCourt.enabledForExperts) { + if (appealCourt?.enabledForExperts) { this.courtIds.push(appealCourt.id); } } @@ -45,27 +35,19 @@ export class FiltersComponent implements OnInit { updateFilters(): void { this.filters = { ...(this.courtIds && { courtIds: this.courtIds }), - ...(this.expertise && { expertise: this.expertise }), + ...(this.expertises && { expertises: this.expertises }), ...(this.nameChunk && { nameChunk: this.nameChunk }) }; this.filtersChanged.emit(this.filters); - this.expertiseSearchable = !this.expertise; } nameChunkChanged(): void { this.nameChunkSubject.next(this.nameChunk); } - - setExpertisePlaceholder(event: 'focus' | 'blur') { - if (event === 'focus') - this.expertisePlaceholder = 'La rubrique contient ...'; - if (event === 'blur') - this.expertisePlaceholder = 'Toutes les rubriques de la nomenclature'; - } } export interface ExpertFilters { courtIds?: number[]; - expertise?: string; + expertises?: string[]; nameChunk?: string; } diff --git a/src/app/component/common/expertise-select/expertise-select.component.css b/src/app/component/common/expertise-select/expertise-select.component.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/component/common/expertise-select/expertise-select.component.html b/src/app/component/common/expertise-select/expertise-select.component.html new file mode 100644 index 0000000000000000000000000000000000000000..678bac30916d74919d9e77071d9d2792b23b45d9 --- /dev/null +++ b/src/app/component/common/expertise-select/expertise-select.component.html @@ -0,0 +1,10 @@ +<dsfr-ext-multiselect + placeHolder="Toutes les rubriques de la nomenclature" + [options]="(expertisesOptions$ | async) || []" + [(ngModel)]="expertiseCodes" + [showSearch]="true" + [showClearSearch]="true" + [clearableOptions]="true" + [maxDisplayOptions]="0" + (selectionChange)="selectionChange()" + appendTo="body"></dsfr-ext-multiselect> diff --git a/src/app/component/common/expertise-select/expertise-select.component.spec.ts b/src/app/component/common/expertise-select/expertise-select.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3e2b765a3c33c56ef0ec62aacd0e6999da58ee7 --- /dev/null +++ b/src/app/component/common/expertise-select/expertise-select.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExpertiseSelectComponent } from './expertise-select.component'; + +describe('ExpertiseSelectComponent', () => { + let component: ExpertiseSelectComponent; + let fixture: ComponentFixture<ExpertiseSelectComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ExpertiseSelectComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExpertiseSelectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/component/common/expertise-select/expertise-select.component.ts b/src/app/component/common/expertise-select/expertise-select.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..862a1ebb458f4f43d7a4caa0b2091fb53cbc3db3 --- /dev/null +++ b/src/app/component/common/expertise-select/expertise-select.component.ts @@ -0,0 +1,33 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { DsfrSelectOption } from '@edugouvfr/ngx-dsfr'; +import { map, Observable } from 'rxjs'; +import { ExpertiseService } from 'src/app/service/expertise.service'; + +@Component({ + selector: 'app-expertise-select', + templateUrl: './expertise-select.component.html', + styleUrl: './expertise-select.component.css' +}) +export class ExpertiseSelectComponent implements OnInit { + expertisesOptions$: Observable<DsfrSelectOption[]>; + @Input() expertiseCodes: string[]; + @Output() expertiseCodesChanged = new EventEmitter<string[]>(); + + constructor(private readonly expertiseService: ExpertiseService) {} + + ngOnInit(): void { + this.expertisesOptions$ = this.expertiseService + .getExpertises('id,asc', false) + .pipe( + map(response => + response.content.map(e => { + return { label: '[' + e.code + '] ' + e.label, value: e.code }; + }) + ) + ); + } + + selectionChange() { + this.expertiseCodesChanged.emit(this.expertiseCodes); + } +} diff --git a/src/app/component/common/file-upload/file-upload.component.html b/src/app/component/common/file-upload/file-upload.component.html index 6156ada8dcc92ec519750bb362f40bfcff7d7832..e3d845382088723740cda88af86e507911d3f98c 100644 --- a/src/app/component/common/file-upload/file-upload.component.html +++ b/src/app/component/common/file-upload/file-upload.component.html @@ -13,7 +13,8 @@ type="file" id="file-input" accept="application/pdf" - (change)="onFileChange($event)" /> + (change)="onFileChange($event)" + (drop)="preventDrop()" /> <button *ngIf="isDecision" class="fr-btn fr-icon-check-line" diff --git a/src/app/component/common/file-upload/file-upload.component.ts b/src/app/component/common/file-upload/file-upload.component.ts index 3e0e34171ff58f788ff9c0391590d2db31e6c967..652b83151a903370c794dd1866a332fe6f585a96 100644 --- a/src/app/component/common/file-upload/file-upload.component.ts +++ b/src/app/component/common/file-upload/file-upload.component.ts @@ -55,4 +55,8 @@ export class FileUploadComponent { get isDecision(): boolean { return this.fileType === FileType.DECISION; } + + preventDrop() { + return false; + } } diff --git a/src/app/component/common/request-list/request-item/request-expert/request-expert.component.ts b/src/app/component/common/request-list/request-item/request-expert/request-expert.component.ts index 62252349e3a8aac2f4a3b778c0f9922a70b8959c..51de689c33614fe33aa3a191fd694826b700a245 100644 --- a/src/app/component/common/request-list/request-item/request-expert/request-expert.component.ts +++ b/src/app/component/common/request-list/request-item/request-expert/request-expert.component.ts @@ -97,7 +97,7 @@ export class RequestExpertComponent implements OnInit { ) ) { description = - "<br/><br/>Si vous souhaitez étendre cette date d'un jour supplémentaire, vous pouvez cliquer sur le bouton ci-dessous"; + "<br/><br/>Pour allonger d'un jour supplémentaire le délai accordé à l'expert pour répondre à votre demande, vous pouvez cliquer sur le bouton ci-dessous"; actions = [1].map(nbDays => ({ label: `${nbDays} jour${nbDays > 1 ? 's' : ''}`, diff --git a/src/app/model/pagination.ts b/src/app/model/pagination.ts new file mode 100644 index 0000000000000000000000000000000000000000..50cdbcbb155cf42704c61c3db8cdf773ac8b020d --- /dev/null +++ b/src/app/model/pagination.ts @@ -0,0 +1,34 @@ +export interface Sort { + unsorted: boolean; + sorted: boolean; + empty: boolean; +} + +export interface Pageable { + paged: boolean; + pageNumber: number; + pageSize: number; + offset: number; + sort: Sort; + unpaged: boolean; +} + +export interface Content { + id: number; + code: string; + label: string; +} + +export interface PaginatedResponse<T> { + content: T[]; + pageable: Pageable; + totalPages: number; + totalElements: number; + last: boolean; + first: boolean; + size: number; + number: number; + sort: Sort; + numberOfElements: number; + empty: boolean; +} diff --git a/src/app/rest/api.rest.service.ts b/src/app/rest/api.rest.service.ts index 71eab66a959bb0bde5647993b8e4b7fa65a05313..570789160a6033a40f68921ac4bec19528deff52 100644 --- a/src/app/rest/api.rest.service.ts +++ b/src/app/rest/api.rest.service.ts @@ -13,7 +13,6 @@ import { CourtService } from '../model/court-service'; import { CourtUser, CourtUserForAdmin } from '../model/court-user'; import { Expert } from '../model/expert'; import { ExpertCreationForm } from '../model/expert-creation-form'; -import { Expertise } from '../model/expertise'; import { ImportRequest } from '../model/import-request'; import { Magistrate } from '../model/magistrate'; import { Note } from '../model/note'; @@ -30,7 +29,7 @@ import { UserInfo } from '../model/user-info'; export class ApiRestService { readonly BASE_PATH = `${environment.apiUrl}/api`; - constructor(private http: HttpClient) {} + constructor(private readonly http: HttpClient) {} getExperts(page: number, filters?: ExpertFilters): Observable<Page<Expert>> { return this.http.get<Page<Expert>>(`${this.BASE_PATH}/court/experts`, { @@ -235,10 +234,6 @@ export class ApiRestService { ); } - getExpertises(): Observable<Expertise[]> { - return this.http.get<Expertise[]>(`${this.BASE_PATH}/common/expertise`); - } - getCourtServices(): Observable<CourtService[]> { return this.http.get<CourtService[]>( `${this.BASE_PATH}/common/court-service` diff --git a/src/app/service/api.service.ts b/src/app/service/api.service.ts index 07f4983f571d89e3d1f9d520d753682ba74e648c..5bbf86690ea2d05adb6f0d2e17014bce30c59f40 100644 --- a/src/app/service/api.service.ts +++ b/src/app/service/api.service.ts @@ -165,10 +165,6 @@ export class ApiService { return this.apiRestService.importAddedAndDeleted(courtId); } - getExpertises(): Observable<Expertise[]> { - return this.apiRestService.getExpertises(); - } - getCourtServices(): Observable<CourtService[]> { return this.apiRestService.getCourtServices(); } diff --git a/src/app/service/expertise.service.spec.ts b/src/app/service/expertise.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..78d9f74312047e6df8eec4b27f1e6af0ff3c8bdc --- /dev/null +++ b/src/app/service/expertise.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ExpertiseService } from './expertise.service'; + +describe('ExpertiseService', () => { + let service: ExpertiseService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ExpertiseService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/service/expertise.service.ts b/src/app/service/expertise.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..07d955f81191bf3e104d7a3d6cd98a98dc763769 --- /dev/null +++ b/src/app/service/expertise.service.ts @@ -0,0 +1,31 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { Expertise } from '../model/expertise'; +import { PaginatedResponse } from '../model/pagination'; + +@Injectable({ + providedIn: 'root' +}) +export class ExpertiseService { + private readonly API_URL = `${environment.apiUrl}/api/v2/expertises`; + + constructor(private readonly http: HttpClient) {} + + getExpertises( + sort: string = '', + paged: boolean = true, + pageNumber: number = 0, + pageSize: number = 10 + ): Observable<PaginatedResponse<Expertise>> { + const params = new HttpParams() + .set('sort', sort) + .set('paged', paged) + .set('page', pageNumber) + .set('size', pageSize); + return this.http.get<PaginatedResponse<Expertise>>(`${this.API_URL}`, { + params + }); + } +}