From a2159c52384c344065d48fd5e0db824f9b3dc9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20PAILHAREY?= <rpailharey@grandlyon.com> Date: Mon, 7 Nov 2022 13:18:47 +0000 Subject: [PATCH] feat: personal offers section in profile --- src/app/models/personalOffer.model.ts | 2 + .../personal-offer.component.html | 77 +++++++++++++++++++ .../personal-offer.component.scss | 50 ++++++++++++ .../personal-offer.component.spec.ts | 23 ++++++ .../personal-offer.component.ts | 38 +++++++++ .../profile-structure.component.html | 14 +++- .../profile-structure.component.ts | 27 ++++++- src/app/profile/profile.module.ts | 2 + src/app/resolvers/structure.resolver.ts | 29 ++----- src/app/utils/utils.ts | 23 ++++++ 10 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 src/app/profile/profile-structure/personal-offer/personal-offer.component.html create mode 100644 src/app/profile/profile-structure/personal-offer/personal-offer.component.scss create mode 100644 src/app/profile/profile-structure/personal-offer/personal-offer.component.spec.ts create mode 100644 src/app/profile/profile-structure/personal-offer/personal-offer.component.ts diff --git a/src/app/models/personalOffer.model.ts b/src/app/models/personalOffer.model.ts index 1a596e5d0..3d830779c 100644 --- a/src/app/models/personalOffer.model.ts +++ b/src/app/models/personalOffer.model.ts @@ -1,5 +1,7 @@ export class PersonalOffer { + public _id: string = null; public categories: { [key: string]: string[] }; + public categoriesDisplay?: { [key: string]: string[] }; constructor(obj?: any) { Object.assign(this, obj, { diff --git a/src/app/profile/profile-structure/personal-offer/personal-offer.component.html b/src/app/profile/profile-structure/personal-offer/personal-offer.component.html new file mode 100644 index 000000000..d52fd61b2 --- /dev/null +++ b/src/app/profile/profile-structure/personal-offer/personal-offer.component.html @@ -0,0 +1,77 @@ +<div *ngIf="this.personalOffer.categoriesDisplay" class="container"> + <div class="header"> + <p>mon offre de service</p> + <app-button + *ngIf="!isPublic" + class="hide-on-mobile" + [type]="'button'" + [iconBtn]="'edit'" + [text]="'Modifier mon offre'" + [style]="buttonTypeEnum.SecondaryWide" + [disabled]="true" + ></app-button> + <app-button + *ngIf="!isPublic" + class="hide-on-desktop" + [type]="'button'" + [iconBtn]="'edit'" + [style]="buttonTypeEnum.SecondaryOnlyIcon" + [disabled]="true" + ></app-button> + </div> + <div class="content"> + <div class="dropDown" *ngIf="this.personalOffer.categoriesDisplay.onlineProcedures.length"> + <div class="collapseHeader" (click)="toggleOnlineProcedures()"> + <p>Démarches en ligne</p> + <app-svg-icon + *ngIf="!showOnlineProcedures" + [type]="'ico'" + [icon]="'unfold'" + [iconClass]="'icon-26'" + ></app-svg-icon> + <app-svg-icon + *ngIf="showOnlineProcedures" + [type]="'ico'" + [icon]="'fold'" + [iconClass]="'icon-26'" + ></app-svg-icon> + </div> + <div class="collapseContent" [@collapse]="showOnlineProcedures"> + <ul> + <li *ngFor="let onlineProcedure of this.personalOffer.categoriesDisplay.onlineProcedures"> + {{ onlineProcedure }} + </li> + </ul> + </div> + </div> + <div class="dropDown" *ngIf="this.personalOffer.categoriesDisplay.baseSkills.length"> + <div class="collapseHeader" (click)="toggleBaseSkills()"> + <p>Compétences numériques de base</p> + <app-svg-icon *ngIf="!showBaseSkills" [type]="'ico'" [icon]="'unfold'" [iconClass]="'icon-26'"></app-svg-icon> + <app-svg-icon *ngIf="showBaseSkills" [type]="'ico'" [icon]="'fold'" [iconClass]="'icon-26'"></app-svg-icon> + </div> + <div class="collapseContent" [@collapse]="showBaseSkills"> + <ul> + <li *ngFor="let baseSkill of this.personalOffer.categoriesDisplay.baseSkills">{{ baseSkill }}</li> + </ul> + </div> + </div> + <div class="dropDown" *ngIf="this.personalOffer.categoriesDisplay.advancedSkills.length"> + <div class="collapseHeader" (click)="toggleAdvancedSkills()"> + <p>Compétences numériques avancées</p> + <app-svg-icon + *ngIf="!showAdvancedSkills" + [type]="'ico'" + [icon]="'unfold'" + [iconClass]="'icon-26'" + ></app-svg-icon> + <app-svg-icon *ngIf="showAdvancedSkills" [type]="'ico'" [icon]="'fold'" [iconClass]="'icon-26'"></app-svg-icon> + </div> + <div class="collapseContent" [@collapse]="showAdvancedSkills"> + <ul> + <li *ngFor="let advancedSkill of this.personalOffer.categoriesDisplay.advancedSkills">{{ advancedSkill }}</li> + </ul> + </div> + </div> + </div> +</div> diff --git a/src/app/profile/profile-structure/personal-offer/personal-offer.component.scss b/src/app/profile/profile-structure/personal-offer/personal-offer.component.scss new file mode 100644 index 000000000..ab18eae7f --- /dev/null +++ b/src/app/profile/profile-structure/personal-offer/personal-offer.component.scss @@ -0,0 +1,50 @@ +@import '../../../../assets/scss/color'; +@import '../../../../assets/scss/typography'; + +.container { + display: flex; + flex-direction: column; + gap: 8px; + p { + margin: 0; + } + .header { + display: flex; + align-items: center; + justify-content: space-between; + p { + text-transform: uppercase; + @include lato-bold-14; + color: $grey-3; + } + } + .content { + .dropDown { + overflow: hidden; + .collapseHeader { + display: flex; + align-items: center; + justify-content: space-between; + height: 2.5rem; + cursor: pointer; + color: $grey-2; + } + .collapseContent { + background-color: $grey-7; + border-radius: 4px; + ul { + margin: 0; + padding: 0.5rem 1.5rem; + list-style-position: inside; + li { + line-height: 2rem; + @include lato-regular-15; + } + } + } + &:not(:last-child) { + border-bottom: 1px solid $grey-8; + } + } + } +} diff --git a/src/app/profile/profile-structure/personal-offer/personal-offer.component.spec.ts b/src/app/profile/profile-structure/personal-offer/personal-offer.component.spec.ts new file mode 100644 index 000000000..a3df7187f --- /dev/null +++ b/src/app/profile/profile-structure/personal-offer/personal-offer.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PersonalOfferComponent } from './personal-offer.component'; + +describe('PersonalOfferComponent', () => { + let component: PersonalOfferComponent; + let fixture: ComponentFixture<PersonalOfferComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PersonalOfferComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PersonalOfferComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/profile/profile-structure/personal-offer/personal-offer.component.ts b/src/app/profile/profile-structure/personal-offer/personal-offer.component.ts new file mode 100644 index 000000000..20ae4e006 --- /dev/null +++ b/src/app/profile/profile-structure/personal-offer/personal-offer.component.ts @@ -0,0 +1,38 @@ +import { animate, AUTO_STYLE, state, style, transition, trigger } from '@angular/animations'; +import { Component, Input, OnInit } from '@angular/core'; +import { ButtonType } from '../../../shared/components/button/buttonType.enum'; +import { PersonalOffer } from './../../../models/personalOffer.model'; + +@Component({ + selector: 'app-personal-offer', + templateUrl: './personal-offer.component.html', + styleUrls: ['./personal-offer.component.scss'], + animations: [ + trigger('collapse', [ + state('true', style({ height: AUTO_STYLE, visibility: AUTO_STYLE, margin: '8px 0' })), + state('false', style({ height: '0px', visibility: 'hidden', margin: '0' })), + transition('true => false', animate('300ms ease-out')), + transition('false => true', animate('300ms ease-out')), + ]), + ], +}) +export class PersonalOfferComponent { + @Input() public personalOffer: PersonalOffer; + @Input() public isPublic: boolean; + public buttonTypeEnum = ButtonType; + public showOnlineProcedures: boolean = false; + public showBaseSkills: boolean = false; + public showAdvancedSkills: boolean = false; + + public toggleOnlineProcedures(): void { + this.showOnlineProcedures = !this.showOnlineProcedures; + } + + public toggleBaseSkills(): void { + this.showBaseSkills = !this.showBaseSkills; + } + + public toggleAdvancedSkills(): void { + this.showAdvancedSkills = !this.showAdvancedSkills; + } +} diff --git a/src/app/profile/profile-structure/profile-structure.component.html b/src/app/profile/profile-structure/profile-structure.component.html index 322381c68..37d544242 100644 --- a/src/app/profile/profile-structure/profile-structure.component.html +++ b/src/app/profile/profile-structure/profile-structure.component.html @@ -27,7 +27,7 @@ </div> </div> <div [@collapse]="showDetails"> - <div class="collapseContent" fxLayout="column" fxLayoutAlign="center" fxLayoutGap="10px"> + <div class="collapseContent" fxLayout="column" fxLayoutAlign="center" fxLayoutGap="16px"> <div fxLayout="column"> <div fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="20px"> <p class="section-title">informations</p> @@ -90,6 +90,8 @@ </div> </div> </div> + <app-personal-offer *ngIf="this.personalOffer" [personalOffer]="personalOffer" [isPublic]="isPublic"> + </app-personal-offer> <div *ngIf="members.length > 0" fxLayout="column" fxLayoutGap="9px"> <div fxLayout="row" fxLayoutAlign="space-between center"> <p class="section-title">membres</p> @@ -127,6 +129,16 @@ (click)="addMemberModalOpenned = true" ></app-button> </div> + <div class="call-to-action" *ngIf="!isPublic && !this.personalOffer" fxLayout="row" fxLayoutAlign="center"> + <app-button + [type]="'button'" + [iconBtn]="'add'" + [text]="'Ajouter une offre'" + [style]="buttonTypeEnum.SecondaryUltraWide" + [routerLinkActive]="'active'" + [disabled]="true" + ></app-button> + </div> </div> </div> </div> diff --git a/src/app/profile/profile-structure/profile-structure.component.ts b/src/app/profile/profile-structure/profile-structure.component.ts index c7781d299..a036f6366 100644 --- a/src/app/profile/profile-structure/profile-structure.component.ts +++ b/src/app/profile/profile-structure/profile-structure.component.ts @@ -7,7 +7,10 @@ import { Structure } from '../../models/structure.model'; import { StructureWithOwners } from '../../models/structureWithOwners.model'; import { NotificationService } from '../../services/notification.service'; import { ButtonType } from '../../shared/components/button/buttonType.enum'; +import { SearchService } from '../../structure-list/services/search.service'; import { formUtils } from '../../utils/formUtils'; +import { Utils } from '../../utils/utils'; +import { PersonalOffer } from './../../models/personalOffer.model'; import { User } from './../../models/user.model'; import { UserService } from './../../services/user.service'; @@ -34,14 +37,17 @@ export class ProfileStructureComponent implements OnInit { public showDetails: boolean = false; public addMemberModalOpenned: boolean = false; public structure: Structure; + public personalOffer: PersonalOffer; constructor( private router: Router, private userService: UserService, - private notificationService: NotificationService + private notificationService: NotificationService, + private searchService: SearchService, + public utils: Utils ) {} - ngOnInit(): void { + async ngOnInit(): Promise<void> { this.structureForm = new formUtils().createStructureForm(this.structureWithOwners.structure, true); this.structureWithOwners.owners .filter((owner) => { @@ -53,6 +59,23 @@ export class ProfileStructureComponent implements OnInit { }); }); this.structure = new Structure(this.structureWithOwners.structure); + this.personalOffer = await this.getSharedPersonalOffer(); + } + + public async getSharedPersonalOffer(): Promise<PersonalOffer> { + // Check if user has personal offers + if (!this.userProfile.job.hasPersonalOffer || this.userProfile.personalOffers.length === 0) return null; + // Check if structure has personal offers + if (this.structure.personalOffers.length === 0) return null; + // Return personnal offer if the user has one in this structure + const personalOffer = this.structure.personalOffers.filter((structureOffer) => + this.userProfile.personalOffers.includes(structureOffer._id) + )[0]; + // Get categories labels + if (personalOffer) { + const categories = await this.searchService.getCategories().toPromise(); + return this.utils.setServiceCategories(categories, personalOffer); + } } public goBack(): void { diff --git a/src/app/profile/profile.module.ts b/src/app/profile/profile.module.ts index a81d170f9..2ed630b3a 100644 --- a/src/app/profile/profile.module.ts +++ b/src/app/profile/profile.module.ts @@ -12,6 +12,7 @@ import { StructureAddMemberModalComponent } from './structure-add-member-modal/s import { StructuresManagementComponent } from './structures-management/structures-management.component'; import { MissingInformationComponent } from './structure-edition-summary/missing-information/missing-information.component'; import { NoInformationComponent } from './structure-edition-summary/no-information/no-information.component'; +import { PersonalOfferComponent } from './profile-structure/personal-offer/personal-offer.component'; @NgModule({ declarations: [ @@ -25,6 +26,7 @@ import { NoInformationComponent } from './structure-edition-summary/no-informati StructureEditionSummaryComponent, StructureMembersManagementComponent, StructuresManagementComponent, + PersonalOfferComponent, ], imports: [CommonModule, ProfileRoutingModule, SharedModule], }) diff --git a/src/app/resolvers/structure.resolver.ts b/src/app/resolvers/structure.resolver.ts index 321b7a9bd..fbe2f8454 100644 --- a/src/app/resolvers/structure.resolver.ts +++ b/src/app/resolvers/structure.resolver.ts @@ -4,9 +4,9 @@ import { forkJoin, Observable, Subject } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { Structure } from '../models/structure.model'; import { StructureService } from '../services/structure.service'; -import { Category } from '../structure-list/models/category.model'; -import { Module } from '../structure-list/models/module.model'; + import { SearchService } from '../structure-list/services/search.service'; +import { Utils } from '../utils/utils'; @Injectable() export class StructureResolver implements Resolve<Structure> { @@ -15,7 +15,8 @@ export class StructureResolver implements Resolve<Structure> { constructor( private structureService: StructureService, private searchService: SearchService, - private router: Router + private router: Router, + public utils: Utils ) {} resolve(route: ActivatedRouteSnapshot): Observable<Structure> { @@ -32,7 +33,7 @@ export class StructureResolver implements Resolve<Structure> { ), categories: this.searchService.getCategories(), }).subscribe((res) => { - const structure = this.setServiceCategories(res.categories, res.structure); + const structure = this.utils.setServiceCategories(res.categories, res.structure) as Structure; // return res; this.setSubject(structure); }); @@ -48,24 +49,4 @@ export class StructureResolver implements Resolve<Structure> { getSubject(): Observable<Structure> { return this.subject.asObservable(); } - - public setServiceCategories(categories: Category[], structure: Structure): Structure { - categories.forEach((category) => { - const structureModuleIds = structure.categories[category.id]; - if (structureModuleIds) { - const moduleNames = category.modules - .map((module: Module) => { - if (structureModuleIds.includes(module.id)) { - return module.name; - } - }) - .filter((value) => value !== undefined); - structure.categoriesDisplay = { - ...structure.categoriesDisplay, - [category.id]: moduleNames, - }; - } - }); - return structure; - } } diff --git a/src/app/utils/utils.ts b/src/app/utils/utils.ts index aba1a6043..a96c73a32 100644 --- a/src/app/utils/utils.ts +++ b/src/app/utils/utils.ts @@ -1,3 +1,4 @@ +import { PersonalOffer } from './../models/personalOffer.model'; import { Injectable } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; import { Structure } from '../models/structure.model'; @@ -7,6 +8,8 @@ import { FreeWorkshop } from '../shared/enum/freeWorkshop.enum'; import { OtherServices } from '../shared/enum/otherServices.enum'; import { AccessModality } from '../structure-list/enum/access-modality.enum'; import { Equipment } from '../structure-list/enum/equipment.enum'; +import { Category } from '../structure-list/models/category.model'; +import { Module } from '../structure-list/models/module.model'; @Injectable({ providedIn: 'root', @@ -133,4 +136,24 @@ export class Utils { public getOtherServicesLabel(otherService: OtherServices) { return OtherServices[otherService]; } + + public setServiceCategories(categories: Category[], structure: Structure | PersonalOffer): Structure | PersonalOffer { + categories.forEach((category) => { + const structureModuleIds = structure.categories[category.id]; + if (structureModuleIds) { + const moduleNames = category.modules + .map((module: Module) => { + if (structureModuleIds.includes(module.id)) { + return module.name; + } + }) + .filter((value) => value !== undefined); + structure.categoriesDisplay = { + ...structure.categoriesDisplay, + [category.id]: moduleNames, + }; + } + }); + return structure; + } } -- GitLab