From bc138ef26cd9411c917bf298fd6832e56883ccd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marl=C3=A8ne=20SIMONDANT?= <msimondant@grandlyon.com>
Date: Tue, 23 Jul 2024 14:59:33 +0000
Subject: [PATCH] change(employers&jobs) : new way of choosing an employer and
 a job (profile + onboarding)

---
 .../profile-employer-selection.component.html |  35 ++----
 .../profile-employer-selection.component.ts   |  38 +++----
 .../profile-job-selection.component.html      |  22 ++--
 .../profile-job-selection.component.ts        |  51 ++++-----
 src/app/profile/edit/edit.component.html      |  55 +++-------
 src/app/profile/edit/edit.component.scss      |   6 ++
 src/app/profile/edit/edit.component.ts        | 101 +++++-------------
 src/app/profile/services/profile.service.ts   |   4 +-
 src/app/shared/components/index.ts            |   3 +
 .../select-or-create.component.html           |  51 +++++++++
 .../select-or-create.component.scss           |  48 +++++++++
 .../select-or-create.component.ts             |  72 +++++++++++++
 .../components/yes-no/yes-no.component.html   |   8 +-
 src/app/shared/shared.module.ts               |   2 +
 14 files changed, 285 insertions(+), 211 deletions(-)
 create mode 100644 src/app/shared/components/select-or-create/select-or-create.component.html
 create mode 100644 src/app/shared/components/select-or-create/select-or-create.component.scss
 create mode 100644 src/app/shared/components/select-or-create/select-or-create.component.ts

diff --git a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.html b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.html
index 890809ea3..ec57eeb83 100644
--- a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.html
+++ b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.html
@@ -6,30 +6,13 @@
       Si vous êtes bénévole, recherchez la structure dans laquelle vous intervenez
     </p>
   </div>
-  <div>
-    <app-input
-      id="search-employer"
-      label="Employeur"
-      description="Recherchez votre employeur dans la liste suivante"
-      placeholder="Exemple : employeur"
-      size="large"
-      [wide]="true"
-      [value]="profileForm.get('employer').value.name"
-      (valueChange)="onSearchChange($event)"
-      (click)="onSearchChange(profileForm.get('employer').value.name)"
-    />
-    <div class="scroll">
-      <div *ngIf="!isAlreadySearching" class="autocomplete-items">
-        <div
-          *ngFor="let employer of employers"
-          role="button"
-          tabindex="0"
-          (click)="selectedResult(employer)"
-          (keyup.enter)="selectedResult(employer)"
-        >
-          <p>{{ employer.name }}</p>
-        </div>
-      </div>
-    </div>
-  </div>
+
+  <app-select-or-create
+    [autocompleteFunction]="profileService.getEmployers.bind(profileService)"
+    [name]="'Employeur'"
+    [value]="employerName"
+    (valueChange)="onValueChange()"
+    (selectItem)="selectedResult($event)"
+    (createValueChange)="onCreateValueChange($event)"
+  />
 </form>
diff --git a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts
index e305630b3..abc2f8548 100644
--- a/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts
+++ b/src/app/form/form-view/profile-form/profile-employer-selection/profile-employer-selection.component.ts
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 import { UntypedFormGroup } from '@angular/forms';
 import { Employer } from '../../../../models/employer.model';
 import { ProfileService } from '../../../../profile/services/profile.service';
@@ -7,16 +7,19 @@ import { ProfileService } from '../../../../profile/services/profile.service';
   selector: 'app-profile-employer-selection',
   templateUrl: './profile-employer-selection.component.html',
 })
-export class ProfileEmployerSelectionComponent {
+export class ProfileEmployerSelectionComponent implements OnInit {
   @Input() profileForm: UntypedFormGroup;
   @Output() validateForm = new EventEmitter<Employer>();
-  public employers: Employer[];
-  public isAlreadySearching = false;
 
-  constructor(private profileService: ProfileService) {}
+  public employerName: string;
 
-  public onSearchChange(searchString: string): void {
-    this.getEmployers(searchString);
+  constructor(public profileService: ProfileService) {}
+
+  ngOnInit(): void {
+    this.employerName = this.profileForm.get('employer').value.name;
+  }
+
+  public onCreateValueChange(searchString: string): void {
     this.profileForm.get('employer').patchValue({
       name: searchString,
       validated: false,
@@ -26,20 +29,19 @@ export class ProfileEmployerSelectionComponent {
 
   public selectedResult(employer: Employer): void {
     this.profileForm.get('employer').patchValue({
-      name: employer.name,
-      validated: employer.validated,
+      name: employer?.name,
+      validated: employer?.validated,
     });
-    this.employers = [];
+    this.employerName = employer?.name;
     this.validateForm.emit();
   }
 
-  private getEmployers(searchString = ''): void {
-    if (!this.isAlreadySearching) {
-      this.isAlreadySearching = true;
-      this.profileService.getEmployers(searchString).subscribe((employers) => {
-        this.employers = employers;
-        this.isAlreadySearching = false;
-      });
-    }
+  public onValueChange(): void {
+    // If the user changes the value in the autocomplete input, set an empty employer to invalidate the form
+    this.profileForm.get('employer').patchValue({
+      name: '',
+      validated: false,
+    });
+    this.validateForm.emit();
   }
 }
diff --git a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.html b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.html
index 196904711..0ad3d4696 100644
--- a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.html
+++ b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.html
@@ -4,19 +4,15 @@
     <p>Cette information sera visible dans l’annuaire des acteurs, accessible uniquement en version connectée</p>
   </div>
 
-  <div class="tagList">
-    <app-tag-item
-      *ngFor="let job of jobs"
-      [iconFolder]="'ico'"
-      [iconName]="isSelectedJob(job) ? 'tag-checked' : 'tag-unchecked'"
-      [label]="job.name"
-      [color]="isSelectedJob(job) ? 'green' : 'white'"
-      [size]="'medium'"
-      [clickable]="true"
-      (action)="selectedResult(job)"
-    />
-  </div>
-  <app-input *ngIf="isUnexistingJob()" label="Quelle fonction occupez-vous ?" (valueChange)="newJob($event)" />
+  <app-select-or-create
+    [name]="'Fonction'"
+    [isFeminineWord]="true"
+    [autocompleteFunction]="profileService.getJobs.bind(profileService)"
+    [value]="jobName"
+    (valueChange)="onValueChange()"
+    (selectItem)="selectedResult($event)"
+    (createValueChange)="onCreateValueChange($event)"
+  />
 
   <app-appointment-choice
     *ngIf="hasPersonalOffer"
diff --git a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts
index 69476c8f0..911872da3 100644
--- a/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts
+++ b/src/app/form/form-view/profile-form/profile-job-selection/profile-job-selection.component.ts
@@ -11,51 +11,40 @@ export class ProfileJobSelectionComponent implements OnInit {
   @Input() profileForm: UntypedFormGroup;
   @Output() validateForm = new EventEmitter<Job>();
 
-  public jobs: Job[];
+  public jobName: string;
   public selectedJob: Job;
   public selectedRdvChoice: boolean;
   public hasPersonalOffer = false;
 
-  constructor(private profileService: ProfileService) {}
+  constructor(public profileService: ProfileService) {}
 
   ngOnInit(): void {
-    // getJobs
-    this.profileService.getJobs().subscribe((jobs) => {
-      this.jobs = [...jobs, new Job({ name: 'Autre' })];
-    });
+    this.jobName = this.profileForm.get('job').value.name;
   }
 
-  public selectedResult(job: Job): void {
-    this.selectedJob = job;
-    if (!this.isUnexistingJob()) {
-      this.profileForm.get('job').setValue({
-        name: job.name,
-        validated: job.validated,
-        hasPersonalOffer: job.hasPersonalOffer,
-      });
-    } else {
-      this.profileForm.get('job').setValue({
-        name: '',
-        validated: false,
-        hasPersonalOffer: true,
-      });
-    }
-    this.hasPersonalOffer = job.hasPersonalOffer;
+  public onCreateValueChange(searchString: string): void {
+    this.profileForm.get('job').patchValue({
+      name: searchString,
+      validated: false,
+    });
     this.validateForm.emit();
   }
 
-  public isSelectedJob(job: Job): boolean {
-    if (this.selectedJob?.name === job.name) return true;
-    return false;
-  }
-
-  public isUnexistingJob(): boolean {
-    return this.selectedJob && this.selectedJob.name === 'Autre';
+  public selectedResult(job: Job): void {
+    this.profileForm.get('job').patchValue({
+      name: job?.name,
+      validated: job?.validated,
+      hasPersonalOffer: job?.hasPersonalOffer,
+    });
+    this.jobName = job?.name;
+    this.hasPersonalOffer = job?.hasPersonalOffer;
+    this.validateForm.emit();
   }
 
-  public newJob(value: string): void {
+  public onValueChange(): void {
+    // If the user changes the value in the autocomplete input, set an empty job to invalidate the form
     this.profileForm.get('job').patchValue({
-      name: value,
+      name: '',
       validated: false,
     });
     this.validateForm.emit();
diff --git a/src/app/profile/edit/edit.component.html b/src/app/profile/edit/edit.component.html
index 2dcc37c18..eb8bf52c3 100644
--- a/src/app/profile/edit/edit.component.html
+++ b/src/app/profile/edit/edit.component.html
@@ -124,49 +124,24 @@
       </div>
 
       <div *ngIf="currentTab === tabsEnum.employer" class="employerJob">
-        <div>
-          <app-input
-            id="search-employer"
-            label="Employeur"
-            description="Recherchez votre employeur dans la liste suivante"
-            [value]="selectedEmployer?.name"
-            (valueChange)="onSearchChange($event)"
-            (click)="onSearchChange(selectedEmployer?.name || '')"
-          />
-
-          <div class="structureResults">
-            <div *ngIf="!isAlreadySearching" class="autocomplete-items">
-              <div
-                *ngFor="let employer of employers"
-                role="button"
-                tabindex="0"
-                (click)="selectEmployer(employer)"
-                (keyup.enter)="selectEmployer(employer)"
-              >
-                <p>{{ employer.name }}</p>
-              </div>
-            </div>
-          </div>
-        </div>
+        <app-select-or-create
+          [autocompleteFunction]="profileService.getEmployers.bind(profileService)"
+          [name]="'Employeur'"
+          [(value)]="employerName"
+          (selectItem)="selectEmployer($event)"
+          (createValueChange)="onCreateValueChangeEmployer($event)"
+        />
 
         <div>
-          <p class="subTitle">Fonction</p>
-          <div class="tagList">
-            <app-tag-item
-              *ngFor="let job of jobs"
-              [iconName]="isSelectedJob(job) ? 'tag-checked' : 'tag-unchecked'"
-              [label]="job.name"
-              [color]="isSelectedJob(job) ? 'green' : 'white'"
-              [clickable]="true"
-              (action)="selectJob(job)"
-            />
-          </div>
+          <app-select-or-create
+            [name]="'Fonction'"
+            [isFeminineWord]="true"
+            [autocompleteFunction]="profileService.getJobs.bind(profileService)"
+            [(value)]="jobName"
+            (selectItem)="selectJob($event)"
+            (createValueChange)="onCreateValueChangeJob($event)"
+          />
         </div>
-        <app-input
-          *ngIf="isUnexistingJob()"
-          label="Quelle fonction occupez-vous&nbsp;?"
-          (valueChange)="updateNewJob($event)"
-        />
 
         <app-appointment-choice
           *ngIf="hasPersonalOffer"
diff --git a/src/app/profile/edit/edit.component.scss b/src/app/profile/edit/edit.component.scss
index a54db44f4..793ed01e9 100644
--- a/src/app/profile/edit/edit.component.scss
+++ b/src/app/profile/edit/edit.component.scss
@@ -102,6 +102,12 @@
       gap: 1.5rem;
     }
 
+    ::ng-deep .selectOrCreate {
+      gap: 24px;
+      padding: 24px;
+      border: 1px solid $grey-6;
+    }
+
     .credentialsTab {
       display: flex;
       flex-direction: column;
diff --git a/src/app/profile/edit/edit.component.ts b/src/app/profile/edit/edit.component.ts
index 0f0676f75..2fe8d98a2 100644
--- a/src/app/profile/edit/edit.component.ts
+++ b/src/app/profile/edit/edit.component.ts
@@ -1,5 +1,5 @@
 import { HttpErrorResponse } from '@angular/common/http';
-import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { lastValueFrom } from 'rxjs';
 import { Employer } from '../../models/employer.model';
@@ -49,14 +49,14 @@ export class EditComponent implements OnInit {
     newPasswordConfirm: false,
   };
   public ShowPassword = showPasswordEnum;
-  public jobs: Job[];
-  private newJob: Job = null;
-  private selectedJob: Job;
-  public employers: Employer[];
+  public selectedJob: Job;
+  public jobName: string;
+  public isNewJob: boolean;
   public selectedEmployer: Employer;
+  public employerName: string;
+  public isNewEmployer: boolean;
   public selectedRdvChoice: boolean;
   public isAlreadySearching = false;
-  public isNewEmployer: boolean;
   public hasPersonalOffer = false;
   public errorChangeEmail = false;
   public statusCode = 200;
@@ -70,12 +70,9 @@ export class EditComponent implements OnInit {
   private canDeactivate = false;
   public appointmentModal = false;
 
-  @ViewChild('newJobInput') newJobInput: ElementRef;
-
   constructor(
-    private profileService: ProfileService,
+    public profileService: ProfileService,
     private notificationService: NotificationService,
-    private cdr: ChangeDetectorRef,
     private authService: AuthService,
     private utils: Utils,
     private router: Router,
@@ -91,19 +88,10 @@ export class EditComponent implements OnInit {
       this.userProfile.description = this.userProfile.description || '';
       this.initialUserProfile = new User({ ...profile });
       this.selectedEmployer = { ...profile.employer };
+      this.employerName = this.selectedEmployer?.name;
+      this.selectedJob = { ...profile.job };
+      this.jobName = this.selectedJob?.name;
       this.hasPersonalOffer = profile.job?.hasPersonalOffer;
-      const otherJob = new Job({ name: 'Autre' });
-      this.profileService.getJobs().subscribe((jobs) => {
-        this.jobs = [...jobs, otherJob];
-
-        // Select "Autre" job and set the job's name
-        if (jobs.some((job) => job.name === profile.job?.name)) {
-          this.selectedJob = { ...profile.job };
-        } else {
-          this.selectedJob = otherJob;
-          this.newJob = profile.job;
-        }
-      });
     });
   }
 
@@ -194,11 +182,6 @@ export class EditComponent implements OnInit {
 
   public navigateTo(tab: tabsEnum): void {
     this.currentTab = tab;
-    if (tab === tabsEnum.employer) {
-      this.cdr.detectChanges();
-      this.selectEmployer(this.userProfile.employer);
-      if (this.newJob) this.newJobInput.nativeElement.value = this.userProfile.job.name;
-    }
   }
 
   public cancel(): void {
@@ -287,9 +270,7 @@ export class EditComponent implements OnInit {
   }
 
   public async confirmEmployer(): Promise<void> {
-    if (this.newJob) {
-      this.selectedJob = this.newJob;
-
+    if (this.isNewJob) {
       await lastValueFrom(this.profileService.createJob(this.selectedJob));
     }
     if (this.isNewEmployer) {
@@ -392,57 +373,27 @@ export class EditComponent implements OnInit {
   // Jobs
   public selectJob(job: Job): void {
     this.selectedJob = job;
-    this.newJob = null;
-    this.hasPersonalOffer = job.hasPersonalOffer;
-  }
-
-  public isSelectedJob(job: Job): boolean {
-    if (this.selectedJob?.name === job.name) this.hasPersonalOffer = job.hasPersonalOffer;
-    return this.selectedJob && this.selectedJob.name === job.name;
-  }
-
-  public isUnexistingJob(): boolean {
-    return this.selectedJob?.name === 'Autre';
+    this.jobName = this.selectedJob?.name;
+    this.isNewJob = false;
+    this.hasPersonalOffer = job?.hasPersonalOffer;
   }
 
-  public updateNewJob(value: string): void {
-    if (value) {
-      this.newJob = new Job({ name: value, validated: false, hasPersonalOffer: true });
-    } else {
-      this.newJob = null;
-    }
-    this.hasPersonalOffer = this.newJob?.hasPersonalOffer;
+  public onCreateValueChangeJob(value: string): void {
+    this.selectedJob = new Job({ name: value, validated: false, hasPersonalOffer: true });
+    this.isNewJob = true;
+    this.hasPersonalOffer = this.selectedJob?.hasPersonalOffer;
   }
 
   // Employer
-  public onSearchChange(searchString: string): void {
-    this.getEmployers(searchString);
-    if (!searchString) {
-      this.selectedEmployer = null;
-    } else {
-      this.selectedEmployer = new Employer({ name: searchString, validated: false });
-      if (!this.employers.map((employer) => employer.name).includes(this.selectedEmployer?.name)) {
-        this.isNewEmployer = true;
-      } else {
-        this.isNewEmployer = false;
-      }
-    }
-  }
-
   public selectEmployer(employer: Employer): void {
     this.selectedEmployer = employer;
-    this.employers = [];
+    this.employerName = this.selectedEmployer?.name;
     this.isNewEmployer = false;
   }
 
-  private getEmployers(searchString = ''): void {
-    if (!this.isAlreadySearching) {
-      this.isAlreadySearching = true;
-      this.profileService.getEmployers(searchString).subscribe((employers) => {
-        this.employers = employers;
-        this.isAlreadySearching = false;
-      });
-    }
+  public onCreateValueChangeEmployer(value: string): void {
+    this.selectedEmployer = new Employer({ name: value, validated: false });
+    this.isNewEmployer = true;
   }
 
   public canExit(): Promise<boolean> {
@@ -464,13 +415,10 @@ export class EditComponent implements OnInit {
   }
 
   private employerAndJobValid(): boolean {
-    // Check if mandatory fields are set (with newJob defined if needed, and rdv defined if needed)
+    // Check if mandatory fields are set (with rdv defined if needed)
     if (!this.selectedEmployer || !this.selectedJob) {
       return false;
     }
-    if (this.selectedJob.name === 'Autre' && this.newJob === null) {
-      return false;
-    }
     if (this.hasPersonalOffer && this.selectedRdvChoice === undefined) {
       return false;
     }
@@ -478,11 +426,10 @@ export class EditComponent implements OnInit {
   }
 
   private employerOrJobHasChanged(): boolean {
-    // Check if any field has changed : employer or job, or newJob or rdv choice if applicable
+    // Check if any field has changed : employer or job, or rdv choice if applicable
     return (
       this.selectedEmployer?.name !== this.initialUserProfile.employer?.name ||
       this.selectedJob?.name !== this.initialUserProfile.job?.name ||
-      (this.selectedJob.name === 'Autre' && this.newJob?.name !== this.userProfile.job.name) ||
       (this.hasPersonalOffer && this.selectedRdvChoice !== this.initialUserProfile.withAppointment)
     );
   }
diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts
index 363580320..b1baf54e7 100644
--- a/src/app/profile/services/profile.service.ts
+++ b/src/app/profile/services/profile.service.ts
@@ -94,8 +94,8 @@ export class ProfileService {
     return this.http.get<Employer[]>(`api/employer?search=${searchString}`);
   }
 
-  public getJobs(): Observable<Job[]> {
-    return this.http.get<Job[]>(`api/jobs`);
+  public getJobs(searchString = ''): Observable<Job[]> {
+    return this.http.get<Job[]>(`api/jobs?search=${searchString}`);
   }
 
   public createJob(value: Job): Observable<Job> {
diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts
index 564fa4c38..c30311533 100644
--- a/src/app/shared/components/index.ts
+++ b/src/app/shared/components/index.ts
@@ -25,6 +25,7 @@ import { PrintStructuresGridComponent } from './print-structures-grid/print-stru
 import { RadioOptionComponent } from './radio-option/radio-option.component';
 import { RadioComponent } from './radio/radio.component';
 import { SearchBarComponent } from './search-bar/search-bar.component';
+import { SelectOrCreateComponent } from './select-or-create/select-or-create.component';
 import { StructureHoursListComponent } from './structure-hours/structure-hours-list.component';
 import { StructurePmrComponent } from './structure-pmr/structure-pmr.component';
 import { SvgIconComponent } from './svg-icon/svg-icon.component';
@@ -54,6 +55,7 @@ export {
   PrintStructuresGridComponent,
   ProgressBarComponent,
   RadioOptionComponent,
+  SelectOrCreateComponent,
   StructureHoursListComponent,
   StructurePmrComponent,
   StructurePublicTargetComponent,
@@ -92,6 +94,7 @@ export const SharedComponents = [
   RadioOptionComponent,
   RadioComponent,
   SearchBarComponent,
+  SelectOrCreateComponent,
   StructurePmrComponent,
   StructurePublicTargetComponent,
   SvgIconComponent,
diff --git a/src/app/shared/components/select-or-create/select-or-create.component.html b/src/app/shared/components/select-or-create/select-or-create.component.html
new file mode 100644
index 000000000..67aad1246
--- /dev/null
+++ b/src/app/shared/components/select-or-create/select-or-create.component.html
@@ -0,0 +1,51 @@
+<div class="selectOrCreate">
+  <div class="select">
+    <app-input
+      autocomplete="off"
+      size="large"
+      [placeholder]="'exemple : ' + name.toLowerCase()"
+      [label]="name"
+      [wide]="true"
+      [disabled]="isAddingNewItem"
+      [status]="!isAddingNewItem ? (isSelectedItem ? 'success' : 'error') : null"
+      [description]="'Recherchez votre ' + name.toLowerCase() + ' dans la liste suivante'"
+      [value]="isAddingNewItem ? null : value"
+      (valueChange)="onAutocompleteValueChange($event)"
+      (click)="onAutocompleteValueChange(value || '')"
+    />
+    <div *ngIf="!isAlreadySearching && !isAddingNewItem" role="list" class="autocomplete-items">
+      <div
+        *ngFor="let hit of data"
+        role="listitem"
+        tabindex="0"
+        (click)="selectResult(hit)"
+        (keyup.enter)="selectResult(hit)"
+      >
+        <p tabindex="none" role="button">{{ hit.name }}</p>
+      </div>
+    </div>
+  </div>
+  <div class="create">
+    <p>Vous ne trouvez pas votre {{ name.toLowerCase() }} ? Créez-l{{ isFeminineWord ? 'a' : 'e' }} ici :</p>
+    <app-button
+      *ngIf="!isAddingNewItem"
+      [variant]="'primaryBlack'"
+      [label]="'Ajouter ' + (isFeminineWord ? 'la ' : 'l\'') + name.toLowerCase()"
+      [iconName]="'plus'"
+      [wide]="true"
+      (click)="toggleAddItem()"
+    />
+    <div *ngIf="isAddingNewItem" class="createForm">
+      <app-input
+        autocomplete="off"
+        size="large"
+        [label]="'Nom de ' + (isFeminineWord ? 'la ' : 'l\'') + (name | lowercase)"
+        [placeholder]="'exemple : ' + name.toLowerCase()"
+        [description]="'Renseignez le nom de votre ' + name.toLowerCase()"
+        [status]="isFieldValid() ? 'success' : 'error'"
+        (valueChange)="onCreateValueChange($event)"
+      />
+      <app-button label="Annuler la création" [variant]="'primaryBlack'" (click)="toggleAddItem()" />
+    </div>
+  </div>
+</div>
diff --git a/src/app/shared/components/select-or-create/select-or-create.component.scss b/src/app/shared/components/select-or-create/select-or-create.component.scss
new file mode 100644
index 000000000..3b28dfc46
--- /dev/null
+++ b/src/app/shared/components/select-or-create/select-or-create.component.scss
@@ -0,0 +1,48 @@
+@import 'breakpoint';
+@import 'color';
+@import 'typography';
+
+.selectOrCreate {
+  display: flex;
+  flex-direction: column;
+  gap: 40px;
+
+  .select {
+    .autocomplete-items {
+      top: -28px;
+      position: relative;
+      > div {
+        border-bottom: 1px solid $grey-7;
+      }
+      p {
+        width: auto;
+      }
+      @media #{$phone} {
+        max-width: 300px;
+      }
+    }
+  }
+
+  .create {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    .createForm {
+      display: flex;
+      flex-direction: row;
+      gap: 16px;
+      align-items: center;
+      flex-grow: 1;
+    }
+    p {
+      @include font-regular-16;
+      color: $grey-3;
+    }
+    app-input {
+      width: 300px;
+    }
+    app-button {
+      padding-top: 16px;
+    }
+  }
+}
diff --git a/src/app/shared/components/select-or-create/select-or-create.component.ts b/src/app/shared/components/select-or-create/select-or-create.component.ts
new file mode 100644
index 000000000..d5be19c9e
--- /dev/null
+++ b/src/app/shared/components/select-or-create/select-or-create.component.ts
@@ -0,0 +1,72 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+
+@Component({
+  selector: 'app-select-or-create',
+  templateUrl: './select-or-create.component.html',
+  styleUrls: ['./select-or-create.component.scss'],
+})
+export class SelectOrCreateComponent implements OnInit {
+  @Input() public name: string;
+  @Input() public value: string;
+  @Input() public isFeminineWord?: boolean;
+  @Input() private autocompleteFunction: Function;
+
+  @Output() selectItem = new EventEmitter<any>();
+  @Output() valueChange = new EventEmitter<any>();
+  @Output() createValueChange = new EventEmitter<any>();
+
+  public data = [];
+  public isAlreadySearching = false;
+  public searchString: string;
+  public isAddingNewItem = false;
+  public isSelectedItem = false;
+  public createValue: string;
+
+  ngOnInit(): void {
+    if (this.value) {
+      this.isSelectedItem = true;
+    }
+  }
+
+  public onAutocompleteValueChange(value: string): void {
+    this.isSelectedItem = false;
+    this.selectItem.emit(null);
+    this.searchString = value;
+    if (!this.isAlreadySearching) {
+      this.isAlreadySearching = true;
+      this.autocompleteFunction(value).subscribe((data) => {
+        this.data = data;
+        this.isAlreadySearching = false;
+
+        // If user typed another string meanwhile, relaunch the search for this new string
+        if (this.searchString !== value) {
+          this.onAutocompleteValueChange(this.searchString);
+        }
+      });
+    }
+    this.valueChange.emit(value);
+  }
+
+  public onCreateValueChange(value: string): void {
+    this.createValue = value;
+    this.createValueChange.emit(value);
+  }
+
+  public toggleAddItem(): void {
+    this.isAddingNewItem = !this.isAddingNewItem;
+  }
+
+  public selectResult(hit: any): void {
+    this.isSelectedItem = true;
+    // Set input value
+    this.searchString = hit.name;
+    // Reset autocomplete
+    this.data = [];
+    // Emit chosen value
+    this.selectItem.emit(hit);
+  }
+
+  public isFieldValid(): boolean {
+    return this.createValue && this.createValue !== '';
+  }
+}
diff --git a/src/app/shared/components/yes-no/yes-no.component.html b/src/app/shared/components/yes-no/yes-no.component.html
index 1db6aa362..d5291e02f 100644
--- a/src/app/shared/components/yes-no/yes-no.component.html
+++ b/src/app/shared/components/yes-no/yes-no.component.html
@@ -1,15 +1,15 @@
 <div class="content">
   <app-tag-item
-    [iconName]="selected ? 'tag-checked' : 'tag-unchecked'"
+    [iconName]="selected ? 'check' : null"
     [label]="'Oui'"
-    [color]="selected ? 'green' : 'white'"
+    [color]="selected ? 'black' : 'white'"
     [clickable]="true"
     (action)="selected = true; clicked()"
   />
   <app-tag-item
-    [iconName]="selected !== null && !selected ? 'tag-checked' : 'tag-unchecked'"
+    [iconName]="selected !== null && !selected ? 'check' : null"
     [label]="'Non'"
-    [color]="selected !== null && !selected ? 'green' : 'white'"
+    [color]="selected !== null && !selected ? 'black' : 'white'"
     [clickable]="true"
     (action)="selected = false; clicked()"
   />
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 792bd3e1e..741c7c8ef 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -10,6 +10,7 @@ import { HourPickerComponent } from './components/hour-picker/hour-picker.compon
 import { InputComponent } from './components/input/input.component';
 import { RadioOptionComponent } from './components/radio-option/radio-option.component';
 import { RadioComponent } from './components/radio/radio.component';
+import { SelectOrCreateComponent } from './components/select-or-create/select-or-create.component';
 import { TextareaComponent } from './components/textarea/textarea.component';
 import { YesNoComponent } from './components/yes-no/yes-no.component';
 import { SharedDirectives } from './directives';
@@ -28,6 +29,7 @@ import { SharedPipes } from './pipes';
     InputComponent,
     TextareaComponent,
     RadioOptionComponent,
+    SelectOrCreateComponent,
     YesNoComponent,
   ],
   exports: [
-- 
GitLab