From 4333076c2212ceb08d73cc6d7205e0e3bc75b986 Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Tue, 19 Jan 2021 17:43:48 +0100 Subject: [PATCH 1/3] feat: add hour-picker first version from boussole project --- src/app/form/form.component.html | 5 +- src/app/form/form.component.ts | 2 + .../hour-picker/hour-picker.component.html | 176 ++++++++++++ .../hour-picker/hour-picker.component.scss | 69 +++++ .../hour-picker/hour-picker.component.spec.ts | 25 ++ .../hour-picker/hour-picker.component.ts | 259 ++++++++++++++++++ src/app/shared/components/index.ts | 3 + src/app/shared/shared.module.ts | 12 +- 8 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 src/app/shared/components/hour-picker/hour-picker.component.html create mode 100644 src/app/shared/components/hour-picker/hour-picker.component.scss create mode 100644 src/app/shared/components/hour-picker/hour-picker.component.spec.ts create mode 100644 src/app/shared/components/hour-picker/hour-picker.component.ts diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html index 494f7d019..2c73700d2 100644 --- a/src/app/form/form.component.html +++ b/src/app/form/form.component.html @@ -144,7 +144,8 @@ </div> <div formGroupName="hours"> <p>Heures</p> - <div *ngFor="let day of weekDay | keyvalue"> + <app-hour-picker></app-hour-picker> + <!-- <div *ngFor="let day of weekDay | keyvalue"> <div [formGroupName]="day.key"> <p>Ouvert le {{ day.value }} ? :</p> <input type="radio" formControlName="open" (click)="addTime(day.key)" [value]="true" />Oui <br /> @@ -167,7 +168,7 @@ </div> </div> </div> - </div> + </div> --> </div> <p>Fermetures exceptionnelles</p> <input type="text" formControlName="exceptionalClosures" /> diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts index 93ab67f33..96750559d 100644 --- a/src/app/form/form.component.ts +++ b/src/app/form/form.component.ts @@ -13,6 +13,7 @@ import { typeStructureEnum } from '../shared/enum/typeStructure.enum'; import { FonctionContactEnum } from '../shared/enum/fonctionContact.enum'; import { ProfileService } from '../profile/services/profile.service'; import { User } from '../models/user.model'; +import { Week } from '../models/week.model'; @Component({ selector: 'app-structureForm', @@ -25,6 +26,7 @@ export class FormComponent implements OnInit { @Input() public profile?: User; @Output() closeEvent = new EventEmitter<Structure>(); public structureForm: FormGroup; + public tmp = new Week(); public userAlreadyExist = false; diff --git a/src/app/shared/components/hour-picker/hour-picker.component.html b/src/app/shared/components/hour-picker/hour-picker.component.html new file mode 100644 index 000000000..206db6250 --- /dev/null +++ b/src/app/shared/components/hour-picker/hour-picker.component.html @@ -0,0 +1,176 @@ +<p> + Horaires d'ouverture :<br /> + + <span class="sub-text" #test> + Entrez les horaires généraux d'ouverture. Les horaires propres à chaque services proposés seront à remplir plus + loin. + </span> +</p> + +<div class="days"> + <p>la ?</p> + <div *ngFor="let day of structure.hours" (click)="activateDay(day)" class="day" [ngClass]="{ active: day.active }"> + <div + class="header-container sub-text" + [ngClass]="modifiedFields && modifiedFields.hours && modifiedFields.hours[day.name] ? 'modified' : ''" + > + <div class="header"> + <div> + {{ day.name | titlecase }} + </div> + + <div> + <img + *ngIf="copiedDayName === day.name" + (click)="cancelCopy()" + src="../../../../../assets/img/black/cross.svg" + matTooltip="Annuler la copie" + matTooltipClass="tooltip" + /> + <img + *ngIf="copiedDayName !== day.name && copiedDayName.length > 0" + (click)="paste(day)" + src="../../../../../assets/img/black/paste.svg" + matTooltip="Coller les heures copiées" + matTooltipClass="tooltip" + /> + <img + *ngIf="!copiedDayName" + (click)="copy(day)" + src="../../../../../assets/img/black/copy.svg" + matTooltip="Copier les heures" + matTooltipClass="tooltip" + /> + </div> + + <div> + <input + type="checkbox" + id="{{ day.name }}" + class="toggle-checkbox hidden" + (click)="toggleOpenDay(day, $event.target.checked)" + [checked]="day.open" + /> + <label for="{{ day.name }}" class="toggle-label"></label> + </div> + </div> + </div> + + <div *ngIf="!day.open"></div> + + <div *ngIf="day.open"> + <div class="active" *ngIf="day.active"> + <div class="hour" *ngFor="let hour of day.hours; let i = index"> + <div>De</div> + + <div class="input-container"> + <input type="time" [(ngModel)]="hour.start" (change)="checkHoursValid()" /> + </div> + + <div>à</div> + + <div class="input-container"> + <input type="time" [(ngModel)]="hour.end" (change)="checkHoursValid()" /> + </div> + + <div class="input-container"> + <select [(ngModel)]="hour.type"> + <option value="withoutAppointment">Présentiel (Sans rendez-vous)</option> + <option value="appointment">Présentiel (Avec rendez-vous)</option> + <option value="phone">Téléphonique</option> + </select> + </div> + + <img + src="../../../../../assets/img/black/add-full.svg" + *ngIf="day.hours.length === 1" + (click)="addHours(day)" + class="add" + /> + <img + src="../../../../../assets/img/black/cross.svg" + *ngIf="day.hours.length > 1" + (click)="removeHours(day, i)" + /> + + <div> + <div *ngIf="hour.error === 'incomplete'" class="warning-message">!</div> + <div *ngIf="hour.error === 'wrong'" class="error-message">?</div> + </div> + </div> + + <img + src="../../../../../assets/img/black/add-full.svg" + *ngIf="day.hours.length > 1 && day.hours.length < 5" + (click)="addHours(day)" + class="add" + /> + </div> + + <div class="inactive hour" *ngIf="!day.active"> + <div>De</div> + + <div> + <input type="time" [(ngModel)]="day.hours[0].start" (change)="checkHoursValid()" /> + </div> + + <div>à</div> + + <div> + <input type="time" [(ngModel)]="day.hours[0].end" (change)="checkHoursValid()" /> + </div> + + <div class="input-container"> + <select [(ngModel)]="day.hours[0].type"> + <option value="withoutAppointment">Présentiel (Sans rendez-vous)</option> + <option value="appointment">Présentiel (Avec rendez-vous)</option> + <option value="phone">Téléphonique</option> + </select> + </div> + + <img + src="../../../../../assets/img/black/add-full.svg" + *ngIf="day.hours.length === 1" + (click)="addHours(day)" + class="add" + /> + <div *ngIf="day.hours.length > 1">...</div> + + <div> + <div + *ngIf=" + day.hours[0].error === 'incomplete' || + (day.hours[1] && day.hours[1].error === 'incomplete') || + (day.hours[2] && day.hours[2].error === 'incomplete') || + (day.hours[3] && day.hours[3].error === 'incomplete') || + (day.hours[4] && day.hours[4].error === 'incomplete'); + else wrong + " + class="warning-message" + > + ! + </div> + <ng-template #wrong> + <div + *ngIf=" + day.hours[0].error === 'wrong' || + (day.hours[1] && day.hours[1].error === 'wrong') || + (day.hours[2] && day.hours[2].error === 'wrong') || + (day.hours[23] && day.hours[3].error === 'wrong') || + (day.hours[4] && day.hours[4].error === 'wrong') + " + class="error-message" + > + ? + </div> + </ng-template> + </div> + </div> + </div> + </div> +</div> + +<p class="legend"> + ! : Horaire incomplet<br /> + ? : Horaire incohérent +</p> diff --git a/src/app/shared/components/hour-picker/hour-picker.component.scss b/src/app/shared/components/hour-picker/hour-picker.component.scss new file mode 100644 index 000000000..1179c9cd8 --- /dev/null +++ b/src/app/shared/components/hour-picker/hour-picker.component.scss @@ -0,0 +1,69 @@ +.days { + display: grid; + row-gap: 15px; + + .day { + display: grid; + grid-template-columns: 175px 1fr; + column-gap: 20px; + + .header-container { + .header { + display: grid; + grid-template-columns: 1fr auto auto; + column-gap: 20px; + align-items: center; + height: 40px; + } + } + + .active { + display: grid; + row-gap: 10px; + } + + .hour { + height: 40px; + display: grid; + grid-template-columns: auto 100px auto 100px 1fr 30px 20px; + column-gap: 10px; + align-items: center; + justify-items: center; + } + } +} + +p { + margin-top: 0px; +} + +img { + cursor: pointer; + height: 15px; + width: 15px; + &.add { + height: 20px; + width: 20px; + } +} + +.modified { + border-left: 3px solid red; + padding-left: 8px; + margin-left: -11px; + border-radius: 3px; +} + +.warning-message, +.error-message { + font-weight: bold; + font-size: 1em; + display: grid; + align-items: center; +} + +.legend { + font-weight: normal; + font-style: italic; + text-align: right; +} diff --git a/src/app/shared/components/hour-picker/hour-picker.component.spec.ts b/src/app/shared/components/hour-picker/hour-picker.component.spec.ts new file mode 100644 index 000000000..e05b86064 --- /dev/null +++ b/src/app/shared/components/hour-picker/hour-picker.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HourPickerComponent } from './hour-picker.component'; + +describe('HourPickerComponent', () => { + let component: HourPickerComponent; + let fixture: ComponentFixture<HourPickerComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ HourPickerComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HourPickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/hour-picker/hour-picker.component.ts b/src/app/shared/components/hour-picker/hour-picker.component.ts new file mode 100644 index 000000000..eb50fd442 --- /dev/null +++ b/src/app/shared/components/hour-picker/hour-picker.component.ts @@ -0,0 +1,259 @@ +import { Component, Input, Output, EventEmitter, OnDestroy, ViewChild, OnChanges, OnInit } from '@angular/core'; +import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; +import * as _ from 'lodash'; +import { Structure } from '../../../models/structure.model'; +import { Time } from '../../../models/time.model'; +import { Week } from '../../../models/week.model'; + +@Component({ + selector: 'app-hour-picker', + templateUrl: './hour-picker.component.html', + styleUrls: ['./hour-picker.component.scss'], +}) +export class HourPickerComponent implements OnChanges, OnDestroy { + @ViewChild('test', { static: true }) test; + @Input() modifiedFields: any; + // @Input() structure: any; + + @Output() updateHoursError = new EventEmitter<{ badHoursFormat: boolean }>(); + + private copiedDay: any; + public copiedDayName = ''; + public structure = { + hours: [ + { + name: 'Lundi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Mardi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Mercredi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Jeudi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Vendredi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Samedi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Dimanche', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + ], + }; + public structureHoursDefault: any[] = [ + { + name: 'Lundi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Mardi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Mercredi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Jeudi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Vendredi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Samedi', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + { + name: 'Dimanche', + hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + open: false, + active: false, + }, + ]; + + ngOnChanges(): void { + this.formatHoursForEdition(); + } + + ngOnDestroy(): void { + this.formatHoursForSave(); + } + + /** + * Intégrer les horaires dans les horaires par défaut du composant + */ + formatHoursForEdition() { + console.log('formatHoursForEdition'); + if (this.structure.hours) { + for (const dayDefault of this.structureHoursDefault) { + const foundDay = this.structure.hours.filter((day) => day.name === dayDefault.name); + + // if (foundDay.length && !foundDay[0].error) { + if (foundDay.length) { + foundDay[0].open = true; + } else if (!foundDay.length) { + this.structure.hours.push(dayDefault); + } + } + } else { + this.structure.hours = this.structureHoursDefault; + } + } + + /** + * Formater les horaires pour l'enregistrement en base : + * supprimer les données inutiles + */ + formatHoursForSave(): any { + console.log('formatHoursForSave'); + if (!this.structure.hours) { + return; + } + + this.structure.hours = this.structure.hours.filter((day) => day.open === true); + + for (const day of this.structure.hours) { + delete day.open; + delete day.active; + for (const hour of day.hours) { + delete hour.error; + } + } + } + + activateDay(day: any): void { + console.log('activateDay'); + this.structure.hours.forEach((dayHours) => { + dayHours.active = false; + }); + day.active = true; + } + + toggleOpenDay(day: any, value: any): void { + day.open = value; + + this.checkHoursValid(); + } + + /** + * Ajouter une ligne d'horaires à un jour + */ + addHours(day: any): void { + if (day.hours.length >= 5) { + return; + } + + day.hours.push({ + start: '', + end: '', + type: 'withoutAppointment', + error: 'incomplete', + }); + + this.checkHoursValid(); + } + + /** + * Supprimer la dernière ligne d'horaires d'un jour + */ + removeHours(day: any, index: number): void { + if (index > -1) { + day.hours.splice(index, 1); + } + } + + /** + * Copier les horaires d'un jour pour les coller par dessus les horaires d'un autre jour + */ + copy(day): void { + this.copiedDayName = day.name; + this.copiedDay = day; + } + + /** + * Remplacer les horaires d'un jour par les horaires copiés précédemment + */ + paste(day): void { + day.hours = JSON.parse(JSON.stringify(this.copiedDay.hours)); + day.open = this.copiedDay.open; + } + + /** + * Annuler la copie des horaires + */ + cancelCopy(): void { + this.copiedDayName = ''; + this.copiedDay = null; + } + + /** + * Vérifier que le format des horaires est correct + */ + checkHoursValid() { + let error = false; + + console.log('checkHoursValid'); + for (const day of this.structure.hours) { + if (day.open) { + for (const hour of day.hours) { + if (hour.start === '' || hour.end === '') { + hour.error = 'incomplete'; + error = true; + } else if (hour.end <= hour.start) { + hour.error = 'wrong'; + error = true; + } else { + hour.error = null; + } + } + } + } + + // Émettre l'erreur à ajouter au formulaire pour autoriser + // ou empêcher de passer à l'étape suivante + if (error) { + this.updateHoursError.emit({ badHoursFormat: true }); + } else { + this.updateHoursError.emit(null); + } + } +} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index e4d55982f..2b6cc7e46 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -7,6 +7,7 @@ import { SvgIconComponent } from './svg-icon/svg-icon.component'; import { ValidatorFormComponent } from './validator-form/validator-form.component'; import { CreateAccountFormComponent } from './create-account-form/create-account-form.component'; import { AddressAutocompleteComponent } from './address-autocomplete/address-autocomplete.component'; +import { HourPickerComponent } from './hour-picker/hour-picker.component'; // tslint:disable-next-line: max-line-length export { @@ -19,6 +20,7 @@ export { SignInModalComponent, CreateAccountFormComponent, AddressAutocompleteComponent, + HourPickerComponent, }; // tslint:disable-next-line:variable-name @@ -32,4 +34,5 @@ export const SharedComponents = [ SignInModalComponent, CreateAccountFormComponent, AddressAutocompleteComponent, + HourPickerComponent, ]; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2709454bd..c0829cf6b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -8,9 +8,17 @@ import { SharedPipes } from './pipes'; import { SharedDirectives } from './directives'; import { SvgIconComponent } from './components/svg-icon/svg-icon.component'; import { AddressAutocompleteComponent } from './components/address-autocomplete/address-autocomplete.component'; +import { HourPickerComponent } from './components/hour-picker/hour-picker.component'; @NgModule({ - imports: [CommonModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], - declarations: [...SharedPipes, ...SharedComponents, ...SharedDirectives, SvgIconComponent, AddressAutocompleteComponent], + imports: [CommonModule, FormsModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], + declarations: [ + ...SharedPipes, + ...SharedComponents, + ...SharedDirectives, + SvgIconComponent, + AddressAutocompleteComponent, + HourPickerComponent, + ], exports: [ ...SharedPipes, ...SharedComponents, -- GitLab From 36904c785c075d64c68fffe669e3e9b33c9d119f Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Wed, 20 Jan 2021 14:25:08 +0100 Subject: [PATCH 2/3] feat: update date-picker style --- .../copy-paste/copy-paste.component.html | 13 ++ .../copy-paste/copy-paste.component.scss | 13 ++ .../copy-paste/copy-paste.component.spec.ts | 25 +++ .../copy-paste/copy-paste.component.ts | 30 ++++ .../hour-picker/hour-picker.component.html | 162 +++++++++--------- .../hour-picker/hour-picker.component.scss | 57 ++++-- .../hour-picker/hour-picker.component.ts | 59 ++++--- src/app/shared/components/index.ts | 3 + src/app/shared/shared.module.ts | 2 + src/assets/ico/sprite.svg | 30 ++++ src/assets/scss/_inputs.scss | 51 ++++++ src/assets/scss/_typography.scss | 17 +- 12 files changed, 337 insertions(+), 125 deletions(-) create mode 100644 src/app/shared/components/hour-picker/copy-paste/copy-paste.component.html create mode 100644 src/app/shared/components/hour-picker/copy-paste/copy-paste.component.scss create mode 100644 src/app/shared/components/hour-picker/copy-paste/copy-paste.component.spec.ts create mode 100644 src/app/shared/components/hour-picker/copy-paste/copy-paste.component.ts diff --git a/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.html b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.html new file mode 100644 index 000000000..58741d87a --- /dev/null +++ b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.html @@ -0,0 +1,13 @@ +<div> + <div *ngIf="copiedDayName === day.name" class="grey-rounded-border"> + <app-svg-icon [type]="'ico'" [icon]="'cancel'" [iconColor]="'currentColor'" (click)="cancel()"></app-svg-icon> + </div> + + <div *ngIf="copiedDayName !== day.name && copiedDayName.length > 0" class="grey-rounded-border"> + <app-svg-icon [type]="'ico'" [icon]="'paste'" [iconColor]="'currentColor'" (click)="paste(day)"></app-svg-icon> + </div> + + <div *ngIf="!copiedDayName" class="grey-rounded-border"> + <app-svg-icon [type]="'ico'" [icon]="'copy'" [iconColor]="'currentColor'" (click)="copy(day)"></app-svg-icon> + </div> +</div> diff --git a/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.scss b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.scss new file mode 100644 index 000000000..537a69933 --- /dev/null +++ b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.scss @@ -0,0 +1,13 @@ +@import '../../../../../assets/scss/color'; +@import '../../../../../assets/scss/typography'; + +.grey-rounded-border { + border: 1px solid $grey-4; + box-sizing: border-box; + border-radius: 22px; + @include cn-regular-14; + color: $grey-2; + display: flex; + justify-content: center; + width: 40px; +} diff --git a/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.spec.ts b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.spec.ts new file mode 100644 index 000000000..c904bb2ad --- /dev/null +++ b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CopyPasteComponent } from './copy-paste.component'; + +describe('CopyPasteComponent', () => { + let component: CopyPasteComponent; + let fixture: ComponentFixture<CopyPasteComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CopyPasteComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CopyPasteComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.ts b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.ts new file mode 100644 index 000000000..6a63223ca --- /dev/null +++ b/src/app/shared/components/hour-picker/copy-paste/copy-paste.component.ts @@ -0,0 +1,30 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-copy-paste', + templateUrl: './copy-paste.component.html', + styleUrls: ['./copy-paste.component.scss'], +}) +export class CopyPasteComponent implements OnInit { + @Input() copiedDayName = ''; + @Input() day = null; + + @Output() copyEvent = new EventEmitter<any>(); + @Output() cancelEvent = new EventEmitter<any>(); + @Output() pasteEvent = new EventEmitter<any>(); + constructor() {} + + ngOnInit(): void {} + + public copy() { + this.copyEvent.emit(this.day); + } + + public paste() { + this.pasteEvent.emit(this.day); + } + + public cancel() { + this.cancelEvent.emit(); + } +} diff --git a/src/app/shared/components/hour-picker/hour-picker.component.html b/src/app/shared/components/hour-picker/hour-picker.component.html index 206db6250..7d8082936 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.html +++ b/src/app/shared/components/hour-picker/hour-picker.component.html @@ -1,67 +1,56 @@ -<p> - Horaires d'ouverture :<br /> - - <span class="sub-text" #test> - Entrez les horaires généraux d'ouverture. Les horaires propres à chaque services proposés seront à remplir plus - loin. - </span> -</p> +<h1>Quels sont les horaires d'ouverture ?<br /></h1> <div class="days"> - <p>la ?</p> <div *ngFor="let day of structure.hours" (click)="activateDay(day)" class="day" [ngClass]="{ active: day.active }"> <div class="header-container sub-text" [ngClass]="modifiedFields && modifiedFields.hours && modifiedFields.hours[day.name] ? 'modified' : ''" > <div class="header"> - <div> - {{ day.name | titlecase }} - </div> - - <div> - <img - *ngIf="copiedDayName === day.name" - (click)="cancelCopy()" - src="../../../../../assets/img/black/cross.svg" - matTooltip="Annuler la copie" - matTooltipClass="tooltip" - /> - <img - *ngIf="copiedDayName !== day.name && copiedDayName.length > 0" - (click)="paste(day)" - src="../../../../../assets/img/black/paste.svg" - matTooltip="Coller les heures copiées" - matTooltipClass="tooltip" - /> - <img - *ngIf="!copiedDayName" - (click)="copy(day)" - src="../../../../../assets/img/black/copy.svg" - matTooltip="Copier les heures" - matTooltipClass="tooltip" - /> - </div> - - <div> - <input + <div class="grid-center"> + <!-- <input type="checkbox" id="{{ day.name }}" class="toggle-checkbox hidden" (click)="toggleOpenDay(day, $event.target.checked)" [checked]="day.open" - /> + /> --> + <label class="switch"> + <input + type="checkbox" + id="{{ day.name }}" + (click)="toggleOpenDay(day, $event.target.checked)" + [checked]="day.open" + /> + <span class="slider"></span> + </label> <label for="{{ day.name }}" class="toggle-label"></label> </div> + + <div> + {{ day.name | titlecase }} + </div> </div> </div> - <div *ngIf="!day.open"></div> + <div *ngIf="!day.open"> + <div class="active"> + <div class="grid-center"> + <app-copy-paste + [day]="day" + [copiedDayName]="copiedDayName" + (copyEvent)="copy($event)" + (pasteEvent)="paste($event)" + (cancelEvent)="cancelCopy()" + ></app-copy-paste> + </div> + </div> + </div> - <div *ngIf="day.open"> + <div *ngIf="day.open" class="row-container"> <div class="active" *ngIf="day.active"> <div class="hour" *ngFor="let hour of day.hours; let i = index"> - <div>De</div> + <div>de</div> <div class="input-container"> <input type="time" [(ngModel)]="hour.start" (change)="checkHoursValid()" /> @@ -73,38 +62,58 @@ <input type="time" [(ngModel)]="hour.end" (change)="checkHoursValid()" /> </div> - <div class="input-container"> - <select [(ngModel)]="hour.type"> - <option value="withoutAppointment">Présentiel (Sans rendez-vous)</option> - <option value="appointment">Présentiel (Avec rendez-vous)</option> - <option value="phone">Téléphonique</option> - </select> + <div> + <div *ngIf="hour.error === 'wrong' || hour.error === 'incomplete'" class="error-message"> + <app-svg-icon [type]="'ico'" [icon]="'nok'"></app-svg-icon> + </div> + <div *ngIf="hour.error === null" class="error-message"> + <app-svg-icon [type]="'ico'" [icon]="'ok'"></app-svg-icon> + </div> </div> - - <img - src="../../../../../assets/img/black/add-full.svg" - *ngIf="day.hours.length === 1" + </div> + <div class="add" *ngIf="day.hours.length === 1"> + <div (click)="addHours(day)" - class="add" - /> - <img - src="../../../../../assets/img/black/cross.svg" - *ngIf="day.hours.length > 1" - (click)="removeHours(day, i)" - /> - - <div> - <div *ngIf="hour.error === 'incomplete'" class="warning-message">!</div> - <div *ngIf="hour.error === 'wrong'" class="error-message">?</div> + fxLayout="row" + fxLayoutAlign="center center" + fxLayoutGap="3px" + class="grey-rounded-border" + > + <app-svg-icon + [type]="'ico'" + [icon]="'add'" + [iconColor]="'currentColor'" + (click)="cancelCopy()" + ></app-svg-icon + >Ajouter + </div> + <div class="grid-center"> + <app-copy-paste + [day]="day" + [copiedDayName]="copiedDayName" + (copyEvent)="copy($event)" + (pasteEvent)="paste($event)" + (cancelEvent)="cancelCopy()" + ></app-copy-paste> </div> </div> - <img + <div *ngIf="day.hours.length === 2" class="grid-center"> + <app-copy-paste + [day]="day" + [copiedDayName]="copiedDayName" + (copyEvent)="copy($event)" + (pasteEvent)="paste($event)" + (cancelEvent)="cancelCopy()" + ></app-copy-paste> + </div> + + <!-- <img src="../../../../../assets/img/black/add-full.svg" *ngIf="day.hours.length > 1 && day.hours.length < 5" (click)="addHours(day)" class="add" - /> + /> --> </div> <div class="inactive hour" *ngIf="!day.active"> @@ -120,20 +129,14 @@ <input type="time" [(ngModel)]="day.hours[0].end" (change)="checkHoursValid()" /> </div> - <div class="input-container"> - <select [(ngModel)]="day.hours[0].type"> - <option value="withoutAppointment">Présentiel (Sans rendez-vous)</option> - <option value="appointment">Présentiel (Avec rendez-vous)</option> - <option value="phone">Téléphonique</option> - </select> - </div> + <div *ngIf="day.hours.length > 1 && day.hours.length < 2" (click)="addHours(day)">+ Ajouter 3</div> - <img + <!-- <img src="../../../../../assets/img/black/add-full.svg" *ngIf="day.hours.length === 1" (click)="addHours(day)" class="add" - /> + /> --> <div *ngIf="day.hours.length > 1">...</div> <div> @@ -148,7 +151,7 @@ " class="warning-message" > - ! + <app-svg-icon [type]="'ico'" [icon]="'nok'"></app-svg-icon> </div> <ng-template #wrong> <div @@ -161,7 +164,7 @@ " class="error-message" > - ? + <app-svg-icon [type]="'ico'" [icon]="'nok'"></app-svg-icon> </div> </ng-template> </div> @@ -169,8 +172,3 @@ </div> </div> </div> - -<p class="legend"> - ! : Horaire incomplet<br /> - ? : Horaire incohérent -</p> diff --git a/src/app/shared/components/hour-picker/hour-picker.component.scss b/src/app/shared/components/hour-picker/hour-picker.component.scss index 1179c9cd8..01f1ff6e5 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.scss +++ b/src/app/shared/components/hour-picker/hour-picker.component.scss @@ -1,31 +1,50 @@ +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; + +h1 { + @include cn-bold-22; +} + .days { display: grid; row-gap: 15px; .day { display: grid; - grid-template-columns: 175px 1fr; + grid-template-columns: 130px 1fr; column-gap: 20px; .header-container { .header { display: grid; - grid-template-columns: 1fr auto auto; + grid-template-columns: 35px auto; column-gap: 20px; align-items: center; height: 40px; } } + .row-container { + display: grid; + grid-template-columns: auto 1fr; + } .active { display: grid; - row-gap: 10px; + grid-template-columns: 1fr 250px 40px; + } + .add { + display: grid; + grid-template-columns: 96px 40px; + column-gap: 10px; + // grid-template-columns: 80px 100px; + align-items: center; } .hour { height: 40px; display: grid; - grid-template-columns: auto 100px auto 100px 1fr 30px 20px; + // grid-template-columns: auto 70px auto 70px 30px 80px 1fr; + grid-template-columns: auto 70px auto 70px 30px 30px; column-gap: 10px; align-items: center; justify-items: center; @@ -33,6 +52,30 @@ } } +.grey-rounded-border { + border: 1px solid $grey-4; + box-sizing: border-box; + border-radius: 22px; + @include cn-regular-14; + color: $grey-2; + display: flex; + justify-content: center; +} + +.grid-center { + display: grid; + align-items: center; +} + +input { + background: $grey-6; + border: 1px solid $grey-4; + box-sizing: border-box; + border-radius: 4px; + height: 40px; + @include cn-regular-14; +} + p { margin-top: 0px; } @@ -61,9 +104,3 @@ img { display: grid; align-items: center; } - -.legend { - font-weight: normal; - font-style: italic; - text-align: right; -} diff --git a/src/app/shared/components/hour-picker/hour-picker.component.ts b/src/app/shared/components/hour-picker/hour-picker.component.ts index eb50fd442..3f3a43d7b 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.ts +++ b/src/app/shared/components/hour-picker/hour-picker.component.ts @@ -11,7 +11,6 @@ import { Week } from '../../../models/week.model'; styleUrls: ['./hour-picker.component.scss'], }) export class HourPickerComponent implements OnChanges, OnDestroy { - @ViewChild('test', { static: true }) test; @Input() modifiedFields: any; // @Input() structure: any; @@ -23,43 +22,43 @@ export class HourPickerComponent implements OnChanges, OnDestroy { hours: [ { name: 'Lundi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Mardi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Mercredi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Jeudi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Vendredi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Samedi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Dimanche', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, @@ -68,43 +67,43 @@ export class HourPickerComponent implements OnChanges, OnDestroy { public structureHoursDefault: any[] = [ { name: 'Lundi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Mardi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Mercredi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Jeudi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Vendredi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Samedi', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, { name: 'Dimanche', - hours: [{ start: '', end: '', type: 'withoutAppointment', error: 'incomplete' }], + hours: [{ start: '', end: '', error: 'incomplete' }], open: false, active: false, }, @@ -121,7 +120,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Intégrer les horaires dans les horaires par défaut du composant */ - formatHoursForEdition() { + public formatHoursForEdition(): void { console.log('formatHoursForEdition'); if (this.structure.hours) { for (const dayDefault of this.structureHoursDefault) { @@ -143,7 +142,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { * Formater les horaires pour l'enregistrement en base : * supprimer les données inutiles */ - formatHoursForSave(): any { + public formatHoursForSave(): void { console.log('formatHoursForSave'); if (!this.structure.hours) { return; @@ -160,16 +159,19 @@ export class HourPickerComponent implements OnChanges, OnDestroy { } } - activateDay(day: any): void { + public activateDay(day: any): void { console.log('activateDay'); - this.structure.hours.forEach((dayHours) => { - dayHours.active = false; - }); + // this.structure.hours.forEach((dayHours) => { + // dayHours.active = false; + // }); day.active = true; } - toggleOpenDay(day: any, value: any): void { + public toggleOpenDay(day: any, value: any): void { day.open = value; + if (!value) { + day.hours = [{ start: '', end: '', error: 'incomplete' }]; + } this.checkHoursValid(); } @@ -177,7 +179,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Ajouter une ligne d'horaires à un jour */ - addHours(day: any): void { + public addHours(day: any): void { if (day.hours.length >= 5) { return; } @@ -195,7 +197,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Supprimer la dernière ligne d'horaires d'un jour */ - removeHours(day: any, index: number): void { + public removeHours(day: any, index: number): void { if (index > -1) { day.hours.splice(index, 1); } @@ -204,7 +206,8 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Copier les horaires d'un jour pour les coller par dessus les horaires d'un autre jour */ - copy(day): void { + public copy(day): void { + console.log('copy', day); this.copiedDayName = day.name; this.copiedDay = day; } @@ -212,7 +215,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Remplacer les horaires d'un jour par les horaires copiés précédemment */ - paste(day): void { + public paste(day): void { day.hours = JSON.parse(JSON.stringify(this.copiedDay.hours)); day.open = this.copiedDay.open; } @@ -220,7 +223,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Annuler la copie des horaires */ - cancelCopy(): void { + public cancelCopy(): void { this.copiedDayName = ''; this.copiedDay = null; } @@ -228,7 +231,7 @@ export class HourPickerComponent implements OnChanges, OnDestroy { /** * Vérifier que le format des horaires est correct */ - checkHoursValid() { + public checkHoursValid(): void { let error = false; console.log('checkHoursValid'); diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 2b6cc7e46..2916701f9 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -8,6 +8,7 @@ import { ValidatorFormComponent } from './validator-form/validator-form.componen import { CreateAccountFormComponent } from './create-account-form/create-account-form.component'; import { AddressAutocompleteComponent } from './address-autocomplete/address-autocomplete.component'; import { HourPickerComponent } from './hour-picker/hour-picker.component'; +import { CopyPasteComponent } from './hour-picker/copy-paste/copy-paste.component'; // tslint:disable-next-line: max-line-length export { @@ -21,6 +22,7 @@ export { CreateAccountFormComponent, AddressAutocompleteComponent, HourPickerComponent, + CopyPasteComponent, }; // tslint:disable-next-line:variable-name @@ -35,4 +37,5 @@ export const SharedComponents = [ CreateAccountFormComponent, AddressAutocompleteComponent, HourPickerComponent, + CopyPasteComponent, ]; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c0829cf6b..d3f315bc5 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -9,6 +9,7 @@ import { SharedDirectives } from './directives'; import { SvgIconComponent } from './components/svg-icon/svg-icon.component'; import { AddressAutocompleteComponent } from './components/address-autocomplete/address-autocomplete.component'; import { HourPickerComponent } from './components/hour-picker/hour-picker.component'; +import { CopyPasteComponent } from './components/hour-picker/copy-paste/copy-paste.component'; @NgModule({ imports: [CommonModule, FormsModule, RouterModule, FlexLayoutModule, ReactiveFormsModule], declarations: [ @@ -18,6 +19,7 @@ import { HourPickerComponent } from './components/hour-picker/hour-picker.compon SvgIconComponent, AddressAutocompleteComponent, HourPickerComponent, + CopyPasteComponent, ], exports: [ ...SharedPipes, diff --git a/src/assets/ico/sprite.svg b/src/assets/ico/sprite.svg index 342b08c58..54b2b4e7e 100644 --- a/src/assets/ico/sprite.svg +++ b/src/assets/ico/sprite.svg @@ -14,6 +14,36 @@ <path fill-rule="evenodd" clip-rule="evenodd" d="M7 4C5.89543 4 5 4.89543 5 6V23C5 24.1046 5.89543 25 7 25L7 26.5C7 27.3284 7.67157 28 8.5 28C9.32843 28 10 27.3284 10 26.5V25H21V26.5C21 27.3284 21.6716 28 22.5 28C23.3284 28 24 27.3284 24 26.5V25C25.1046 25 26 24.1046 26 23V6C26 4.89543 25.1046 4 24 4H7ZM24 9H7V18H24V9ZM12 22H19L18.125 23H12.875L12 22ZM10 6C9.44772 6 9 6.44772 9 7C9 7.55228 9.44772 8 10 8H21C21.5523 8 22 7.55228 22 7C22 6.44772 21.5523 6 21 6H10ZM10.6668 21.8754C10.4609 21.1805 9.89524 20.6514 9.18821 20.4923L7 20V23H11L10.6668 21.8754ZM21.8118 20.4923C21.1048 20.6514 20.5391 21.1805 20.3332 21.8754L20 23H24V20L21.8118 20.4923Z" fill="black"/> </symbol> +<symbol id="paste" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M14 16L5 16V4H14L14 16Z" stroke="#333333" stroke-width="2"/> +<path d="M19 21C19.5523 21 20 20.5523 20 20V7C20 6.44772 19.5523 6 19 6H16V17C16 17.5523 15.5523 18 15 18H9V20C9 20.5523 9.44772 21 10 21H19Z" fill="#32383D"/> +</symbol> + + +<symbol id="copy" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C4 3.44771 4.44772 3 5 3H14C14.5523 3 15 3.44772 15 4V6H9C8.44772 6 8 6.44772 8 7V18H5C4.44772 18 4 17.5523 4 17V4ZM10 7C9.44772 7 9 7.44772 9 8V20C9 20.5523 9.44771 21 10 21H19C19.5523 21 20 20.5523 20 20V8C20 7.44772 19.5523 7 19 7H10Z" fill="#32383D"/> +</symbol> + +<symbol id="cancel" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M16.9498 5.36385C17.3403 4.97332 17.9734 4.97332 18.364 5.36385C18.7545 5.75437 18.7545 6.38753 18.364 6.77806L7.05026 18.0918C6.65973 18.4823 6.02657 18.4823 5.63605 18.0918C5.24552 17.7012 5.24552 17.0681 5.63605 16.6776L16.9498 5.36385Z" fill="black"/> +<path d="M18.364 16.6777C18.7545 17.0682 18.7545 17.7013 18.364 18.0919C17.9734 18.4824 17.3403 18.4824 16.9498 18.0919L5.63605 6.77816C5.24552 6.38764 5.24552 5.75447 5.63605 5.36395C6.02657 4.97343 6.65974 4.97343 7.05026 5.36395L18.364 16.6777Z" fill="black"/> +</symbol> + +<symbol id="nok" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="16" cy="16" r="13" fill="#ED3939"/> +<path d="M12.5 20L20 12.5" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M20 20L12.5 12.5" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +</symbol> + +<symbol id="ok" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="16" cy="16" r="13" fill="#47C562"/> +<path d="M11 16.8182L14.8889 20L21 13" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +</symbol> + +<symbol id="add" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M12 5C11.4477 5 11 5.44772 11 6V11H6C5.44772 11 5 11.4477 5 12C5 12.5523 5.44772 13 6 13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V13H18C18.5523 13 19 12.5523 19 12C19 11.4477 18.5523 11 18 11H13V6C13 5.44772 12.5523 5 12 5Z" fill="#333333"/> +</symbol> + <symbol id="liste" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> <rect x="10" y="9" width="16" height="2" rx="1" /> diff --git a/src/assets/scss/_inputs.scss b/src/assets/scss/_inputs.scss index e672692f6..ce1e0b7ab 100644 --- a/src/assets/scss/_inputs.scss +++ b/src/assets/scss/_inputs.scss @@ -23,3 +23,54 @@ border: 1px solid $blue; outline: none !important; } + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 10px; + background-color: $white; + border-radius: 7px; + width: 34px; + height: 14px; + border: 1px solid $grey-4; +} + +.slider:before { + position: absolute; + content: ''; + height: 20px; + width: 20px; + left: -6px; + bottom: -3px; + background-color: $grey-4; + -webkit-transition: 0.4s; + transition: 0.4s; + border-radius: 50%; +} + +input:checked + .slider { + border: 1px solid $secondary-color; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); + background-color: $secondary-color; +} diff --git a/src/assets/scss/_typography.scss b/src/assets/scss/_typography.scss index e7469954d..0d6744ebf 100644 --- a/src/assets/scss/_typography.scss +++ b/src/assets/scss/_typography.scss @@ -4,7 +4,8 @@ $title-font: 'Trebuchet MS', 'Helvetica', sans-serif; $font-size-xsmall: 0.875em; // 14px $font-size-small: 1em; // 16px -$font-size-medium: 1.25em; // 20px +$font-size-smedium: 1.25em; // 20px +$font-size-medium: 1.375em; // 22px $font-size-xmedium: 1.5em; // 24px $font-size-large: 1.75em; // 28px $font-size-xlarge: 2em; // 32px @@ -96,29 +97,35 @@ h6, font-weight: bold; font-size: $font-size-xmedium; } -@mixin cn-bold-20 { +@mixin cn-bold-22 { font-family: $text-font; font-style: normal; font-weight: bold; font-size: $font-size-medium; } +@mixin cn-bold-20 { + font-family: $text-font; + font-style: normal; + font-weight: bold; + font-size: $font-size-smedium; +} @mixin cn-regular-20 { font-family: $text-font; font-style: normal; font-weight: normal; - font-size: $font-size-medium; + font-size: $font-size-smedium; } @mixin cn-bold-20 { font-family: $title-font; font-style: normal; font-weight: bold; - font-size: $font-size-medium; + font-size: $font-size-smedium; } @mixin cn-regular-20 { font-family: $title-font; font-style: normal; font-weight: normal; - font-size: $font-size-medium; + font-size: $font-size-smedium; } @mixin cn-regular-18 { font-family: $title-font; -- GitLab From c61a63e391636645af45b26c174dd57d86d17e80 Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Wed, 20 Jan 2021 19:24:24 +0100 Subject: [PATCH 3/3] feat: add logic to hout-picker + remove copy/paste feature --- src/app/form/form.component.html | 2 +- src/app/form/form.component.ts | 3 +- .../hour-picker/hour-picker.component.html | 48 ++-- .../hour-picker/hour-picker.component.scss | 8 +- .../hour-picker/hour-picker.component.ts | 207 +++++++++++------- src/assets/ico/sprite.svg | 6 +- 6 files changed, 153 insertions(+), 121 deletions(-) diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html index 2c73700d2..37bdaf585 100644 --- a/src/app/form/form.component.html +++ b/src/app/form/form.component.html @@ -144,7 +144,7 @@ </div> <div formGroupName="hours"> <p>Heures</p> - <app-hour-picker></app-hour-picker> + <app-hour-picker [structureInput]="getStructureControl('hours')" [isEditMode]="!isEditMode"></app-hour-picker> <!-- <div *ngFor="let day of weekDay | keyvalue"> <div [formGroupName]="day.key"> <p>Ouvert le {{ day.value }} ? :</p> diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts index 96750559d..43b46c377 100644 --- a/src/app/form/form.component.ts +++ b/src/app/form/form.component.ts @@ -22,11 +22,10 @@ import { Week } from '../models/week.model'; }) export class FormComponent implements OnInit { @Input() public idStructure?: string; - @Input() public isEditMode: boolean; + @Input() public isEditMode: boolean = true; @Input() public profile?: User; @Output() closeEvent = new EventEmitter<Structure>(); public structureForm: FormGroup; - public tmp = new Week(); public userAlreadyExist = false; diff --git a/src/app/shared/components/hour-picker/hour-picker.component.html b/src/app/shared/components/hour-picker/hour-picker.component.html index 7d8082936..5b3e72402 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.html +++ b/src/app/shared/components/hour-picker/hour-picker.component.html @@ -21,6 +21,7 @@ id="{{ day.name }}" (click)="toggleOpenDay(day, $event.target.checked)" [checked]="day.open" + [disabled]="isEditMode" /> <span class="slider"></span> </label> @@ -33,7 +34,7 @@ </div> </div> - <div *ngIf="!day.open"> + <!-- <div *ngIf="!day.open"> <div class="active"> <div class="grid-center"> <app-copy-paste @@ -45,7 +46,7 @@ ></app-copy-paste> </div> </div> - </div> + </div> --> <div *ngIf="day.open" class="row-container"> <div class="active" *ngIf="day.active"> @@ -53,13 +54,13 @@ <div>de</div> <div class="input-container"> - <input type="time" [(ngModel)]="hour.start" (change)="checkHoursValid()" /> + <input type="time" [(ngModel)]="hour.start" (change)="checkHoursValid()" [disabled]="isEditMode" /> </div> <div>à</div> <div class="input-container"> - <input type="time" [(ngModel)]="hour.end" (change)="checkHoursValid()" /> + <input type="time" [(ngModel)]="hour.end" (change)="checkHoursValid()" [disabled]="isEditMode" /> </div> <div> @@ -71,7 +72,7 @@ </div> </div> </div> - <div class="add" *ngIf="day.hours.length === 1"> + <div class="add" *ngIf="day.hours.length === 1 && !isEditMode"> <div (click)="addHours(day)" fxLayout="row" @@ -79,15 +80,9 @@ fxLayoutGap="3px" class="grey-rounded-border" > - <app-svg-icon - [type]="'ico'" - [icon]="'add'" - [iconColor]="'currentColor'" - (click)="cancelCopy()" - ></app-svg-icon - >Ajouter + <app-svg-icon [type]="'ico'" [icon]="'add'" [iconColor]="'currentColor'"></app-svg-icon>Ajouter </div> - <div class="grid-center"> + <!-- <div class="grid-center"> <app-copy-paste [day]="day" [copiedDayName]="copiedDayName" @@ -95,10 +90,10 @@ (pasteEvent)="paste($event)" (cancelEvent)="cancelCopy()" ></app-copy-paste> - </div> + </div> --> </div> - <div *ngIf="day.hours.length === 2" class="grid-center"> + <!-- <div *ngIf="day.hours.length === 2" class="grid-center"> <app-copy-paste [day]="day" [copiedDayName]="copiedDayName" @@ -106,17 +101,10 @@ (pasteEvent)="paste($event)" (cancelEvent)="cancelCopy()" ></app-copy-paste> - </div> - - <!-- <img - src="../../../../../assets/img/black/add-full.svg" - *ngIf="day.hours.length > 1 && day.hours.length < 5" - (click)="addHours(day)" - class="add" - /> --> + </div> --> </div> - <div class="inactive hour" *ngIf="!day.active"> + <!-- <div class="inactive hour" *ngIf="!day.active"> <div>De</div> <div> @@ -129,16 +117,6 @@ <input type="time" [(ngModel)]="day.hours[0].end" (change)="checkHoursValid()" /> </div> - <div *ngIf="day.hours.length > 1 && day.hours.length < 2" (click)="addHours(day)">+ Ajouter 3</div> - - <!-- <img - src="../../../../../assets/img/black/add-full.svg" - *ngIf="day.hours.length === 1" - (click)="addHours(day)" - class="add" - /> --> - <div *ngIf="day.hours.length > 1">...</div> - <div> <div *ngIf=" @@ -168,7 +146,7 @@ </div> </ng-template> </div> - </div> + </div> --> </div> </div> </div> diff --git a/src/app/shared/components/hour-picker/hour-picker.component.scss b/src/app/shared/components/hour-picker/hour-picker.component.scss index 01f1ff6e5..d685087b0 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.scss +++ b/src/app/shared/components/hour-picker/hour-picker.component.scss @@ -1,5 +1,6 @@ @import '../../../../assets/scss/color'; @import '../../../../assets/scss/typography'; +@import '../../../../assets/scss/breakpoint'; h1 { @include cn-bold-22; @@ -12,7 +13,7 @@ h1 { .day { display: grid; grid-template-columns: 130px 1fr; - column-gap: 20px; + column-gap: 10px; .header-container { .header { @@ -31,6 +32,11 @@ h1 { .active { display: grid; grid-template-columns: 1fr 250px 40px; + @media #{$large-phone} { + grid-template-columns: unset; + grid-template-rows: 1fr 1fr; + grid-row-gap: 20px; + } } .add { display: grid; diff --git a/src/app/shared/components/hour-picker/hour-picker.component.ts b/src/app/shared/components/hour-picker/hour-picker.component.ts index 3f3a43d7b..03fefa234 100644 --- a/src/app/shared/components/hour-picker/hour-picker.component.ts +++ b/src/app/shared/components/hour-picker/hour-picker.component.ts @@ -1,9 +1,9 @@ -import { Component, Input, Output, EventEmitter, OnDestroy, ViewChild, OnChanges, OnInit } from '@angular/core'; -import { AbstractControl, FormControl, FormGroup } from '@angular/forms'; +import { Component, Input, Output, EventEmitter, OnDestroy, OnChanges } from '@angular/core'; +import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; import * as _ from 'lodash'; -import { Structure } from '../../../models/structure.model'; +import { Day } from '../../../models/day.model'; import { Time } from '../../../models/time.model'; -import { Week } from '../../../models/week.model'; +import { WeekDayEnum } from '../../enum/weekDay.enum'; @Component({ selector: 'app-hour-picker', @@ -12,14 +12,33 @@ import { Week } from '../../../models/week.model'; }) export class HourPickerComponent implements OnChanges, OnDestroy { @Input() modifiedFields: any; - // @Input() structure: any; + @Input() structureInput: FormGroup; + @Input() isEditMode: boolean; @Output() updateHoursError = new EventEmitter<{ badHoursFormat: boolean }>(); + @Output() updateForm = new EventEmitter<FormGroup>(); private copiedDay: any; public copiedDayName = ''; public structure = { - hours: [ + hours: this.initHoursDefault(), + }; + public structureHoursDefault: any[] = this.initHoursDefault(); + + ngOnChanges(): void { + this.formatHoursForEdition(); + } + + ngOnDestroy(): void { + this.formatHoursForSave(); + } + + public getStructureControl(nameControl: string): AbstractControl { + return this.structureInput.get(nameControl); + } + + private initHoursDefault(): any { + return [ { name: 'Lundi', hours: [{ start: '', end: '', error: 'incomplete' }], @@ -62,80 +81,98 @@ export class HourPickerComponent implements OnChanges, OnDestroy { open: false, active: false, }, - ], - }; - public structureHoursDefault: any[] = [ - { - name: 'Lundi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Mardi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Mercredi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Jeudi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Vendredi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Samedi', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - { - name: 'Dimanche', - hours: [{ start: '', end: '', error: 'incomplete' }], - open: false, - active: false, - }, - ]; + ]; + } - ngOnChanges(): void { - this.formatHoursForEdition(); + /** + * Convert data from form to component structure + */ + private parseFormToHours(day: Day, key: string): void { + this.structureHoursDefault.forEach((element) => { + if (element.name.toLowerCase() === key) { + element.open = day.open; + element.active = day.open; + element.hours = day.time + .map((hour: Time) => { + if (hour.openning) { + return { + start: this.formatNumericalHours(hour.openning), + end: this.formatNumericalHours(hour.closing), + error: null, + }; + } + }) + .filter((item) => item); + } + }); + this.structure.hours = this.structureHoursDefault; } - ngOnDestroy(): void { - this.formatHoursForSave(); + private parseToDay(data: { + name: string; + hours: { start: string; end: string }[]; + open: boolean; + active: boolean; + }): Day { + return new Day({ + open: data.open, + time: data.hours.map( + (hour) => + new Time({ + openning: this.formatStringHours(hour.start), + closing: this.formatStringHours(hour.end), + }) + ), + }); + } + + private parseHoursToForm(): FormGroup { + return new FormGroup({ + monday: this.createDay(this.parseToDay(this.structure.hours[0])), + tuesday: this.createDay(this.parseToDay(this.structure.hours[1])), + wednesday: this.createDay(this.parseToDay(this.structure.hours[2])), + thursday: this.createDay(this.parseToDay(this.structure.hours[3])), + friday: this.createDay(this.parseToDay(this.structure.hours[4])), + saturday: this.createDay(this.parseToDay(this.structure.hours[5])), + sunday: this.createDay(this.parseToDay(this.structure.hours[6])), + }); + } + + /** + * convert 1300 to '13:00' + */ + private formatNumericalHours(hour: number): string { + const numberStr = hour.toString(); + if (numberStr.length === 3) { + return `0${numberStr[0]}:${numberStr[1]}${numberStr[2]}`; + } else { + const splitStr = numberStr.match(/.{1,2}/g); + return `${splitStr[0]}:${splitStr[1]}`; + } + } + + /** + * convert '13:00' to 1300 + */ + private formatStringHours(hour: string): number { + const numberStr = hour.split(':')[0] + hour.split(':')[1]; + return parseInt(numberStr); } /** * Intégrer les horaires dans les horaires par défaut du composant */ public formatHoursForEdition(): void { - console.log('formatHoursForEdition'); - if (this.structure.hours) { - for (const dayDefault of this.structureHoursDefault) { - const foundDay = this.structure.hours.filter((day) => day.name === dayDefault.name); - - // if (foundDay.length && !foundDay[0].error) { - if (foundDay.length) { - foundDay[0].open = true; - } else if (!foundDay.length) { - this.structure.hours.push(dayDefault); - } - } - } else { - this.structure.hours = this.structureHoursDefault; + if (this.structureInput) { + this.parseFormToHours(this.getStructureControl('monday').value, WeekDayEnum.monday); + this.parseFormToHours(this.getStructureControl('tuesday').value, WeekDayEnum.tuesday); + this.parseFormToHours(this.getStructureControl('wednesday').value, WeekDayEnum.wednesday); + this.parseFormToHours(this.getStructureControl('thursday').value, WeekDayEnum.thursday); + this.parseFormToHours(this.getStructureControl('friday').value, WeekDayEnum.friday); + this.parseFormToHours(this.getStructureControl('saturday').value, WeekDayEnum.saturday); + this.parseFormToHours(this.getStructureControl('sunday').value, WeekDayEnum.sunday); } + // this.structure.hours = this.structureHoursDefault; } /** @@ -143,7 +180,6 @@ export class HourPickerComponent implements OnChanges, OnDestroy { * supprimer les données inutiles */ public formatHoursForSave(): void { - console.log('formatHoursForSave'); if (!this.structure.hours) { return; } @@ -160,10 +196,6 @@ export class HourPickerComponent implements OnChanges, OnDestroy { } public activateDay(day: any): void { - console.log('activateDay'); - // this.structure.hours.forEach((dayHours) => { - // dayHours.active = false; - // }); day.active = true; } @@ -187,7 +219,6 @@ export class HourPickerComponent implements OnChanges, OnDestroy { day.hours.push({ start: '', end: '', - type: 'withoutAppointment', error: 'incomplete', }); @@ -207,7 +238,6 @@ export class HourPickerComponent implements OnChanges, OnDestroy { * Copier les horaires d'un jour pour les coller par dessus les horaires d'un autre jour */ public copy(day): void { - console.log('copy', day); this.copiedDayName = day.name; this.copiedDay = day; } @@ -233,10 +263,12 @@ export class HourPickerComponent implements OnChanges, OnDestroy { */ public checkHoursValid(): void { let error = false; - - console.log('checkHoursValid'); for (const day of this.structure.hours) { if (day.open) { + // Init if no data + if (day.hours.length === 0) { + this.addHours(day); + } for (const hour of day.hours) { if (hour.start === '' || hour.end === '') { hour.error = 'incomplete'; @@ -257,6 +289,23 @@ export class HourPickerComponent implements OnChanges, OnDestroy { this.updateHoursError.emit({ badHoursFormat: true }); } else { this.updateHoursError.emit(null); + // Emit new form value + this.parseHoursToForm(); + this.updateForm.emit(this.parseHoursToForm()); } } + + private createDay(day: Day): FormGroup { + return new FormGroup({ + open: new FormControl(day.open, Validators.required), + time: new FormArray(day.time.map((oneTime) => this.createTime(oneTime))) as FormArray, + }); + } + + private createTime(time: Time): FormGroup { + return new FormGroup({ + openning: new FormControl(time.openning), + closing: new FormControl(time.closing), + }); + } } diff --git a/src/assets/ico/sprite.svg b/src/assets/ico/sprite.svg index 54b2b4e7e..6fa070156 100644 --- a/src/assets/ico/sprite.svg +++ b/src/assets/ico/sprite.svg @@ -30,9 +30,9 @@ </symbol> <symbol id="nok" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> -<circle cx="16" cy="16" r="13" fill="#ED3939"/> -<path d="M12.5 20L20 12.5" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> -<path d="M20 20L12.5 12.5" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +<circle cx="16" cy="16" r="13" fill="#DA6C2E"/> +<path d="M16.25 17.5L16.25 9.00001" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M16.25 23.6065L16.25 22.9999" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> </symbol> <symbol id="ok" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> -- GitLab