diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 82a94ec2fec18aa5df9bd8bfa1666db3f44bc297..e774a44e711049a0fc558aa27c2e33749f214041 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,16 +2,14 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ToastrModule } from 'ngx-toastr'; - -import { AppRoutingModule } from './app-routing.module'; - import { ServiceWorkerModule } from '@angular/service-worker'; +import { ToastrModule } from 'ngx-toastr'; import { environment } from '../environments/environment'; import { AnnuaireComponent } from './annuaire/annuaire.component'; import { FilterModalComponent } from './annuaire/filter-modal/filter-modal.component'; import { ResultListComponent } from './annuaire/result-list/result-list.component'; import { SearchBarComponent } from './annuaire/search-bar/search-bar.component'; +import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { CartoModule } from './carto/carto.module'; import { CustomBreakPointsProvider } from './config/custom-breakpoint'; @@ -32,6 +30,7 @@ import { NewsletterSubscriptionComponent } from './newsletter-subscription/newsl import { PageComponent } from './page/page.component'; import { ResetEmailComponent } from './reset-email/reset-email.component'; import { ResetPasswordComponent } from './reset-password/reset-password.component'; +import { PersonalOfferResolver } from './resolvers/personal-offer.resolver'; import { StructureResolver } from './resolvers/structure.resolver'; import { TempUserResolver } from './resolvers/temp-user.resolver'; import { RouterListenerService } from './services/routerListener.service'; @@ -90,6 +89,7 @@ import { StructureJoinComponent } from './structure/structure-join/structure-joi DeactivateGuard, TempUserResolver, StructureResolver, + PersonalOfferResolver, RouterListenerService, UpdateService, ], diff --git a/src/app/form/form-view/form-view.component.ts b/src/app/form/form-view/form-view.component.ts index 206b57ad2e17bfaccd7f052405aa1b3378b56a0f..96f02b75fc6998ec2a448f9ee95ad1f87cf4c598 100644 --- a/src/app/form/form-view/form-view.component.ts +++ b/src/app/form/form-view/form-view.component.ts @@ -94,6 +94,10 @@ export class FormViewComponent implements OnInit, AfterViewInit { } async ngOnInit(): Promise<void> { + // Get structure from history (this is used to create personal offer from profile) + if (history.state.structure) { + this.structure = history.state.structure; + } this.routeParam = this.router.routerState.snapshot.url.split('/')[2]; this.initPage(); @@ -130,9 +134,9 @@ export class FormViewComponent implements OnInit, AfterViewInit { private initPage(): void { const profileFormSteps: number = Object.keys(profileFormStep).length / 2; - const personnalOfferFormSteps: number = Object.keys(personalOfferFormStep).length / 2 - 1; + const personalOfferFormSteps: number = Object.keys(personalOfferFormStep).length / 2 - 1; const structureFormSteps: number = Object.keys(structureFormStep).length / 2; - const totalFormSteps: number = profileFormSteps + personnalOfferFormSteps + structureFormSteps; + const totalFormSteps: number = profileFormSteps + personalOfferFormSteps + structureFormSteps; if (formType[this.routeParam] === formType.account) { this.nbSteps = 3; this.currentPage = accountFormStep.accountInfo; diff --git a/src/app/form/form-view/global-components/progress-bar/progress-bar.component.ts b/src/app/form/form-view/global-components/progress-bar/progress-bar.component.ts index 0f67bb3c7ca420aca74f61215bd4f9edd35025de..a7ad96e43d1f35ce52f783721d4891ac8801be45 100644 --- a/src/app/form/form-view/global-components/progress-bar/progress-bar.component.ts +++ b/src/app/form/form-view/global-components/progress-bar/progress-bar.component.ts @@ -17,7 +17,6 @@ export class ProgressBarComponent implements OnChanges { public progressStatus: number; public formTypeEnum = formType; public profileFormSteps: number = Object.keys(profileFormStep).length / 2; - public personnalOfferFormSteps: number = Object.keys(personalOfferFormStep).length / 2; public structureFormSteps: number = Object.keys(structureFormStep).length / 2; ngOnChanges(changes: SimpleChanges): void { diff --git a/src/app/form/form-view/guards/personalOffer.guard.ts b/src/app/form/form-view/guards/personalOffer.guard.ts index d7fc5a8bfa845a775496a5424447e4173ec8c8b3..67f422064b7ec0d74df9c77c5bc8b7f32a1aa58b 100644 --- a/src/app/form/form-view/guards/personalOffer.guard.ts +++ b/src/app/form/form-view/guards/personalOffer.guard.ts @@ -10,7 +10,8 @@ export class PersonalOfferGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot): UrlTree | boolean { if ( route.routeConfig.path === 'personaloffer' && - (this.router.routerState.snapshot.url === '/form/profile' || + (this.router.routerState.snapshot.url === '/profile' || + this.router.routerState.snapshot.url === '/form/profile' || this.router.routerState.snapshot.url === '/form/structure') ) { return true; diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.html b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.html index 66fa0b65bece74a2d3b1989e98c40cc1c064aaae..74bf05feca3c89ccaba0672235e045616f6eb23e 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.html +++ b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.html @@ -5,17 +5,6 @@ <p>Facultatif</p> </div> - <div fxLayout="column" fxLayoutGap="32px"> - <div *ngIf="onlineProcedures" class="btn-grid"> - <span *ngFor="let module of onlineProcedures.modules"> - <app-button - [ngClass]="{ selectedChoice: true }" - [extraClass]="isSelectedModule(module) ? 'selected' : ''" - [style]="buttonTypeEnum.CheckButton" - [text]="module.name" - (action)="toogleResult(module)" - ></app-button> - </span> - </div> - </div> + <app-accompaniment-picker [personalOfferForm]="personalOfferForm" [onlineProcedures]="onlineProcedures"> + </app-accompaniment-picker> </form> diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts index 105c54b0d7edb3886a6c945fa6c6b422107fa1e5..9ee5fc01ea1334270d185dec0b2443157d7d08c5 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts +++ b/src/app/form/form-view/personal-offer-form/personal-offer-accompaniment/personal-offer-accompaniment.component.ts @@ -1,8 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; -import { ButtonType } from '../../../../shared/components/button/buttonType.enum'; import { Category } from '../../../../structure-list/models/category.model'; -import { Module } from '../../../../structure-list/models/module.model'; @Component({ selector: 'app-personal-offer-accompaniment', @@ -15,29 +13,7 @@ export class PersonalOfferAccompanimentComponent implements OnInit { @Input() onlineProcedures: Category; @Output() validateForm = new EventEmitter<any>(); - public buttonTypeEnum = ButtonType; - public selectedModules: Module[] = []; - - ngOnInit(): void { this.validateForm.emit(); } - - public toogleResult(module: Module): void { - if (this.isSelectedModule(module)) { - const index = this.selectedModules.findIndex((_module) => _module.id === module.id); - this.selectedModules.splice(index, 1); - } else { - this.selectedModules.push(module); - } - this.personalOfferForm - .get('categories') - .get('onlineProcedures') - .patchValue(this.selectedModules.map((module) => module.id)); - } - - public isSelectedModule(module: Module): boolean { - if (this.selectedModules && this.selectedModules.includes(module)) return true; - return false; - } } diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-form.component.html b/src/app/form/form-view/personal-offer-form/personal-offer-form.component.html index b1cba7b3000b180dacfb4e471b04bd7c0c4cdf45..9f94b3b6e54ace314aafe6f46f1e03be0a2918c9 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-form.component.html +++ b/src/app/form/form-view/personal-offer-form/personal-offer-form.component.html @@ -1,6 +1,7 @@ <div class="no-max-width"> <ng-container *ngIf="currentStep === personalOfferFormStep.personalOfferAccompaniment"> <app-personal-offer-accompaniment + *ngIf="onlineProcedures" [structureName]="structureName" [personalOfferForm]="personalOfferForm" [onlineProcedures]="onlineProcedures" diff --git a/src/app/form/form-view/personal-offer-form/personal-offer-form.component.ts b/src/app/form/form-view/personal-offer-form/personal-offer-form.component.ts index 6b8c88fa4a9414dc3d77769a894e3c62a48c762c..296bd92871ec87c8d08a107969e30cb86f12e48d 100644 --- a/src/app/form/form-view/personal-offer-form/personal-offer-form.component.ts +++ b/src/app/form/form-view/personal-offer-form/personal-offer-form.component.ts @@ -30,6 +30,7 @@ export class PersonalOfferFormComponent implements OnChanges, OnInit { ngOnInit(): void { this.setCategories(); } + ngOnChanges(changes: SimpleChanges): void { if (changes.currentStep) { if ( diff --git a/src/app/guards/isPersonalOfferOwner.guard.ts b/src/app/guards/isPersonalOfferOwner.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9734ffd2c01c410bc13786ba522a1b38c546513 --- /dev/null +++ b/src/app/guards/isPersonalOfferOwner.guard.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, UrlTree } from '@angular/router'; +import { ProfileService } from '../profile/services/profile.service'; + +@Injectable({ + providedIn: 'root', +}) +export class IsPersonalOfferOwnerGuard implements CanActivate { + constructor(private router: Router, private profileService: ProfileService) {} + + async canActivate(route: ActivatedRouteSnapshot): Promise<boolean | UrlTree> { + const personalOffer = route.params.id; + const isPersonalOfferOwner = await this.profileService.isPersonalOfferOwner(personalOffer); + if (isPersonalOfferOwner) { + return true; + } + return this.router.parseUrl('/home'); + } +} diff --git a/src/app/profile/edit/edit.component.html b/src/app/profile/edit/edit.component.html index b5efaa4270ce2d06fb07a8a8323b9f6e9ab757a1..d974550d58ca515a4493244d91381c5cda408366 100644 --- a/src/app/profile/edit/edit.component.html +++ b/src/app/profile/edit/edit.component.html @@ -205,9 +205,10 @@ <!-- Footer --> <div class="footer" *ngIf="currentTab !== tabsEnum.credentials"> - <app-button [text]="'Annuler'" (action)="cancel()"></app-button> + <app-button [text]="'Annuler'" [iconBtn]="'close'" (action)="cancel()"></app-button> <app-button [text]="'Valider'" + [iconBtn]="'check'" [disabled]="!isPageValid()" (action)="confirm()" [style]="buttonTypeEnum.Primary" diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.html b/src/app/profile/personal-offer-edition/personal-offer-edition.component.html new file mode 100644 index 0000000000000000000000000000000000000000..fc4ac9d2b4642731e63e1c9d0e2b196639948a2d --- /dev/null +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.html @@ -0,0 +1,65 @@ +<div fxLayout="column" class="content-container full-screen"> + <div class="edit-personal-offer"> + <div class="header"> + <div class="title"> + <a routerLink="/profile"> + <svg aria-hidden="true"> + <use [attr.xlink:href]="'assets/ico/sprite.svg#arrowBack'"></use> + </svg> + </a> + <h1>Gérer mon offre de service</h1> + </div> + </div> + <!-- Navigation --> + <div class="navigation"> + <span + [ngClass]="{ tab: true, selected: currentTab === tabsEnum.onlineProcedures }" + (click)="navigateTo(tabsEnum.onlineProcedures)" + >Démarches en ligne</span + > + <span + [ngClass]="{ tab: true, selected: currentTab === tabsEnum.digitalSkills }" + (click)="navigateTo(tabsEnum.digitalSkills)" + >Compétences numériques</span + > + </div> + <!-- Content of tabs --> + <div class="content"> + <div *ngIf="currentTab === tabsEnum.onlineProcedures"> + <app-accompaniment-picker + *ngIf="personalOfferForm" + [personalOfferForm]="personalOfferForm" + [onlineProcedures]="onlineProcedures" + ></app-accompaniment-picker> + </div> + + <div *ngIf="currentTab === tabsEnum.digitalSkills"> + <app-training-type-picker + *ngIf="personalOfferForm" + [baseSkills]="personalOfferForm.get('categories').get('baseSkills').value" + [advancedSkills]="personalOfferForm.get('categories').get('advancedSkills').value" + [trainingCategories]="trainingCategories" + (selectedType)="setTrainingsFromCategories($event)" + ></app-training-type-picker> + </div> + </div> + + <!-- Footer --> + <div class="footer"> + <app-button + [text]="'Annuler'" + [iconBtn]="'close'" + (action)="cancel()" + [disabled]="personalOfferForm?.pristine" + ></app-button> + <app-button + [text]="'Valider'" + [iconBtn]="'check'" + (action)="confirm()" + [style]="buttonTypeEnum.Primary" + [disabled]="personalOfferForm?.pristine" + > + </app-button> + </div> + </div> +</div> diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss b/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..17b70b85a61d1ed327097aa07415faa72b0a23bf --- /dev/null +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.scss @@ -0,0 +1,105 @@ +@import '../../../assets/scss/color'; +@import '../../../assets/scss/typography'; +@import '../../../assets/scss/hyperlink'; +@import '../../../assets/scss/shapes'; +@import '../../../assets/scss/breakpoint'; + +.edit-personal-offer { + display: flex; + flex-direction: column; + flex: 1; + max-width: 980px; + width: 100%; + margin: 16px auto 8px auto; + padding: 40px; + padding-bottom: 0px; + background: $white; + border: 1px solid $grey-6; + border-radius: 8px; + + @media #{$tablet} { + margin: 0px 4px 4px 4px; + padding: 16px; + padding-bottom: 0px; + width: auto; + } + + .header, + .navigation, + .title { + display: flex; + } + + .header { + justify-content: space-between; + padding-bottom: 18px; + align-items: center; + + .title { + align-items: center; + h1 { + color: $grey-1; + font-weight: lighter; + @media #{$tablet} { + @include lato-regular-20; + } + } + svg { + stroke: $black; + height: 40px; + width: 40px; + margin-right: 14px; + cursor: pointer; + } + } + } + + .navigation { + justify-content: flex-start; + overflow-x: auto; + white-space: nowrap; + + border-bottom: 1px solid $grey-4; + + &::-webkit-scrollbar { + height: 8px; + margin-right: 4px; + } + + .tab { + color: $grey-3; + margin-right: 40px; + padding-bottom: 8px; + &:hover { + cursor: pointer; + } + &.selected { + color: $grey-1; + border-bottom: 2px solid $grey-1; + } + } + } + + .content { + padding-top: 24px; + flex: 1; + max-width: 600px; + } + + .footer { + padding: 16px; + display: flex; + gap: 24px; + justify-content: center; + flex-wrap: wrap; + border-top: 1px solid $grey-4; + //To fit border to parent div + margin: 0 -40px; + @media #{$tablet} { + margin: 0 -16px; + } + ::ng-deep div svg { + height: 22px; + } + } +} diff --git a/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts b/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..77867c21b04234caf887ad900581c9c659f8e352 --- /dev/null +++ b/src/app/profile/personal-offer-edition/personal-offer-edition.component.ts @@ -0,0 +1,108 @@ +import { Component, OnInit } from '@angular/core'; +import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { ActivatedRoute, Data } from '@angular/router'; +import { CategoriesToggle } from '../../models/categoriesToggle.model'; +import { NotificationService } from '../../services/notification.service'; +import { ButtonType } from '../../shared/components/button/buttonType.enum'; +import { CategoryEnum } from '../../shared/enum/category.enum'; +import { Category } from '../../structure-list/models/category.model'; +import { SearchService } from '../../structure-list/services/search.service'; +import { PersonalOffer } from './../../models/personalOffer.model'; +import { PersonalOfferService } from './../../services/personal-offer.service'; + +enum tabsEnum { + onlineProcedures, + digitalSkills, +} + +@Component({ + selector: 'app-personal-offer-edition', + templateUrl: './personal-offer-edition.component.html', + styleUrls: ['./personal-offer-edition.component.scss'], +}) +export class PersonalOfferEditionComponent implements OnInit { + public buttonTypeEnum = ButtonType; + public tabsEnum = tabsEnum; + public currentTab: tabsEnum = tabsEnum.onlineProcedures; + + public personalOffer: PersonalOffer; + public personalOfferForm: UntypedFormGroup = null; + private initialPersonalOffer; + public onlineProcedures: Category; + public trainingCategories: CategoriesToggle[] = []; + + constructor( + private route: ActivatedRoute, + private searchService: SearchService, + private personalOfferService: PersonalOfferService, + private notificationService: NotificationService + ) {} + + ngOnInit(): void { + this.route.data.subscribe(async (data: Data & { personalOffer: PersonalOffer }) => { + if (data.personalOffer) { + await this.setCategories(); + this.personalOffer = data.personalOffer; + this.personalOfferForm = this.createPersonalOfferForm(this.personalOffer); + this.initialPersonalOffer = this.personalOfferForm.value; + } + }); + } + + private createPersonalOfferForm(personalOfferState): UntypedFormGroup { + return new UntypedFormGroup({ + categories: new UntypedFormGroup({ + onlineProcedures: new UntypedFormControl(personalOfferState.categories.onlineProcedures), + baseSkills: new UntypedFormControl(personalOfferState.categories.baseSkills), + advancedSkills: new UntypedFormControl(personalOfferState.categories.advancedSkills), + }), + }); + } + + async setCategories(): Promise<void> { + const categories = await this.searchService.getCategories().toPromise(); + categories.forEach((categ) => { + switch (categ.id) { + case CategoryEnum.onlineProcedures: { + this.onlineProcedures = categ; + break; + } + case CategoryEnum.baseSkills: + case CategoryEnum.advancedSkills: { + this.trainingCategories.push({ category: categ, openned: false }); + break; + } + default: + break; + } + }); + } + + public navigateTo(tab: tabsEnum): void { + this.currentTab = tab; + } + + public cancel(): void { + this.personalOfferForm = this.createPersonalOfferForm(this.initialPersonalOffer); + } + + public confirm(): void { + this.personalOfferService + .updatePersonalOffer(this.personalOffer._id, this.personalOfferForm.get('categories').value) + .subscribe(() => { + this.notificationService.showSuccess('Vos informations ont bien été enregistrées'); + this.initialPersonalOffer = this.personalOfferForm.value; + this.personalOfferForm.markAsPristine(); + }); + } + + public setTrainingsFromCategories(categories: Category[]) { + for (const categorie of categories) { + const moduleIds: string[] = categorie.modules.map((module) => module.id); + if (this.personalOfferForm.get('categories').get(categorie.id)) { + this.personalOfferForm.get('categories').get(categorie.id).patchValue(moduleIds); + this.personalOfferForm.get('categories').get(categorie.id).markAsDirty(); + } + } + } +} diff --git a/src/app/profile/profile-routing.module.ts b/src/app/profile/profile-routing.module.ts index 2ddd5a0c625b8678862632bfa81f7bebd37bb6b8..0037e27099652abd7ac84d978db616ac0e5b1a67 100644 --- a/src/app/profile/profile-routing.module.ts +++ b/src/app/profile/profile-routing.module.ts @@ -1,15 +1,18 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule, Route } from '@angular/router'; +import { Route, RouterModule, Routes } from '@angular/router'; +import { FooterComponent } from '../footer/footer.component'; +import { AuthGuard } from '../guards/auth.guard'; +import { IsPersonalOfferOwnerGuard } from '../guards/isPersonalOfferOwner.guard'; import { RoleGuard } from '../guards/role.guard'; +import { PersonalOfferResolver } from '../resolvers/personal-offer.resolver'; import { StructureResolver } from '../resolvers/structure.resolver'; import { RouteRole } from '../shared/enum/routeRole.enum'; -import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component'; -import { StructuresManagementComponent } from './structures-management/structures-management.component'; +import { EditComponent } from './edit/edit.component'; +import { PersonalOfferEditionComponent } from './personal-offer-edition/personal-offer-edition.component'; import { ProfileComponent } from './profile.component'; import { StructureEditionSummaryComponent } from './structure-edition-summary/structure-edition-summary.component'; -import { AuthGuard } from '../guards/auth.guard'; -import { EditComponent } from './edit/edit.component'; -import { FooterComponent } from '../footer/footer.component'; +import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component'; +import { StructuresManagementComponent } from './structures-management/structures-management.component'; const footerOutletRoute: Route = { path: '', @@ -50,6 +53,14 @@ const routes: Routes = [ structure: StructureResolver, }, }, + { + path: 'edit-personal-offer/:id', + component: PersonalOfferEditionComponent, + canActivate: [IsPersonalOfferOwnerGuard], + resolve: { + personalOffer: PersonalOfferResolver, + }, + }, { path: 'structure-members-management/:id', component: StructureMembersManagementComponent, 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 index 394f353a5501a1f29a9c5ddd9a901b72b7bc9a92..f8e5329089aa493ca9becbb8cbe3581061b89527 100644 --- a/src/app/profile/profile-structure/personal-offer/personal-offer.component.html +++ b/src/app/profile/profile-structure/personal-offer/personal-offer.component.html @@ -8,7 +8,7 @@ [iconBtn]="'edit'" [text]="'Modifier mon offre'" [style]="buttonTypeEnum.SecondaryWide" - [disabled]="true" + routerLink="./edit-personal-offer/{{ this.personalOffer._id }}" ></app-button> <app-button *ngIf="!isPublic" @@ -16,7 +16,7 @@ [type]="'button'" [iconBtn]="'edit'" [style]="buttonTypeEnum.SecondaryOnlyIcon" - [disabled]="true" + routerLink="./edit-personal-offer/{{ this.personalOffer._id }}" ></app-button> </div> <div class="content"> diff --git a/src/app/profile/profile-structure/profile-structure.component.html b/src/app/profile/profile-structure/profile-structure.component.html index 0f1861584931296054f959812d785f319a078bac..0601de8223309548b995aff41b0b1e689bffc544 100644 --- a/src/app/profile/profile-structure/profile-structure.component.html +++ b/src/app/profile/profile-structure/profile-structure.component.html @@ -110,6 +110,7 @@ [style]="buttonTypeEnum.SecondaryOnlyIcon" routerLink="./structure-members-management/{{ structure._id }}" [routerLinkActive]="'active'" + (click)="goToOffer()" ></app-button> </div> <div class="sectionContent members"> diff --git a/src/app/profile/profile-structure/profile-structure.component.ts b/src/app/profile/profile-structure/profile-structure.component.ts index d8e4ab9328407a67e1137357da19abb7960bdd4e..c3613803ff621c3bb557c76da5f20d206754260e 100644 --- a/src/app/profile/profile-structure/profile-structure.component.ts +++ b/src/app/profile/profile-structure/profile-structure.component.ts @@ -69,7 +69,7 @@ export class ProfileStructureComponent implements OnInit { 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 + // Return personal offer if the user has one in this structure const personalOffer = this.structure.personalOffers.filter((structureOffer) => this.userProfile.personalOffers.includes(structureOffer._id) )[0]; @@ -86,6 +86,9 @@ export class ProfileStructureComponent implements OnInit { public goToEdit(step: structureFormStep): void { this.router.navigate(['/form/structure', this.structureWithOwners.structure._id, structureFormStep[step]]); } + public goToOffer(): void { + this.router.navigate(['/form/personaloffer'], { state: { structure: this.structure } }); + } public isValid(): boolean { return this.structureForm.valid; } diff --git a/src/app/profile/profile.module.ts b/src/app/profile/profile.module.ts index 35fe7ab6ae6509cbe8b2d4a5cc88f7931ae78b4a..52be3a2ed3b1c4a1c2037378fc31017c06e61df1 100644 --- a/src/app/profile/profile.module.ts +++ b/src/app/profile/profile.module.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { EditComponent } from './edit/edit.component'; +import { PersonalOfferEditionComponent } from './personal-offer-edition/personal-offer-edition.component'; import { ProfileRoutingModule } from './profile-routing.module'; import { PersonalOfferComponent } from './profile-structure/personal-offer/personal-offer.component'; import { ProfileStructureMemberComponent } from './profile-structure/profile-structure-member/profile-structure-member.component'; @@ -27,6 +28,7 @@ import { StructuresManagementComponent } from './structures-management/structure StructureMembersManagementComponent, StructuresManagementComponent, PersonalOfferComponent, + PersonalOfferEditionComponent, ], imports: [CommonModule, ProfileRoutingModule, SharedModule], }) diff --git a/src/app/profile/services/profile.service.ts b/src/app/profile/services/profile.service.ts index 2ba4e114ec6d20b1448c24fcb01d6c94a1d7de97..9582385992a5b67d0eec8ed283bc5b1d5505b94d 100644 --- a/src/app/profile/services/profile.service.ts +++ b/src/app/profile/services/profile.service.ts @@ -136,4 +136,9 @@ export class ProfileService { }) ); } + + public async isPersonalOfferOwner(personalOfferId: string): Promise<boolean> { + await this.getProfile(); + return this.currentProfile.personalOffers.includes(personalOfferId); + } } diff --git a/src/app/resolvers/personal-offer.resolver.ts b/src/app/resolvers/personal-offer.resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..55809560b02c546e50312f9cf5b1b2100ec28773 --- /dev/null +++ b/src/app/resolvers/personal-offer.resolver.ts @@ -0,0 +1,50 @@ +import { PersonalOffer } from './../models/personalOffer.model'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { PersonalOfferService } from '../services/personal-offer.service'; +import { SearchService } from '../structure-list/services/search.service'; +import { Utils } from '../utils/utils'; + +@Injectable() +export class PersonalOfferResolver implements Resolve<PersonalOffer> { + public subject: Subject<PersonalOffer> = new Subject(); + + constructor( + private personalOfferService: PersonalOfferService, + private searchService: SearchService, + private router: Router, + public utils: Utils + ) {} + + resolve(route: ActivatedRouteSnapshot): Observable<PersonalOffer> { + const personalOfferId = route.params.id; + + if (personalOfferId) { + forkJoin({ + personalOffer: this.personalOfferService.getPersonalOffer(personalOfferId).pipe( + map((res) => res), + catchError(() => { + this.router.navigate(['/home']); + return new Observable<PersonalOffer>(); + }) + ), + categories: this.searchService.getCategories(), + }).subscribe((res) => { + const personalOffer = this.utils.setServiceCategories(res.categories, res.personalOffer) as PersonalOffer; + this.setSubject(personalOffer); + }); + + return this.getSubject(); + } + } + + setSubject(personalOffer: PersonalOffer): void { + this.subject.next(personalOffer); + } + + getSubject(): Observable<PersonalOffer> { + return this.subject.asObservable(); + } +} diff --git a/src/app/services/personal-offer.service.ts b/src/app/services/personal-offer.service.ts index aa57be29be6269016ef634e2ca24a9403791c29f..93b485b6c5c96426b614f84c09074e02ff23d2da 100644 --- a/src/app/services/personal-offer.service.ts +++ b/src/app/services/personal-offer.service.ts @@ -2,14 +2,26 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { PersonalOffer } from '../models/personalOffer.model'; +import { Category } from '../structure-list/models/category.model'; @Injectable({ providedIn: 'root', }) export class PersonalOfferService { + private readonly baseUrl = 'api/personal-offers'; constructor(private http: HttpClient) {} - public createPersonalOffer(structureId: string, personalOffer: PersonalOffer): Observable<any> { - return this.http.post<any>(`api/personal-offers/`, { structureId: structureId, personalOffer: personalOffer }); + public createPersonalOffer(structureId: string, personalOffer: PersonalOffer): Observable<PersonalOffer> { + return this.http.post<PersonalOffer>(this.baseUrl, { structureId: structureId, personalOffer: personalOffer }); + } + + public getPersonalOffer(id: string): Observable<PersonalOffer> { + return this.http.get<PersonalOffer>(`${this.baseUrl}/${id}`); + } + + public updatePersonalOffer(id: string, categories: Category[]): Observable<PersonalOffer> { + return this.http.put<PersonalOffer>(`${this.baseUrl}/${id}`, { + categories: categories, + }); } } diff --git a/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.html b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.html new file mode 100644 index 0000000000000000000000000000000000000000..786ace30d9722bfc3d3e86359132601eebb510a9 --- /dev/null +++ b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.html @@ -0,0 +1,11 @@ +<div fxLayout="column" fxLayoutGap="32px"> + <div *ngIf="onlineProcedures" class="btn-grid"> + <app-button + *ngFor="let module of onlineProcedures.modules" + [extraClass]="isSelectedModule(module) ? 'selected' : ''" + [style]="buttonTypeEnum.CheckButton" + [text]="module.name" + (action)="toogleResult(module)" + ></app-button> + </div> +</div> diff --git a/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.scss b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..2ac0963fbdfd41e48d168c6acaadfd6121e53f68 --- /dev/null +++ b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.scss @@ -0,0 +1 @@ +@import '../../../../assets/scss/buttons'; diff --git a/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.ts b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbd8c77f3667dbcc7ec35e09bdf4195e8f678e70 --- /dev/null +++ b/src/app/shared/components/accompaniment-picker/accompaniment-picker.component.ts @@ -0,0 +1,52 @@ +import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; +import { Category } from '../../../structure-list/models/category.model'; +import { Module } from '../../../structure-list/models/module.model'; +import { ButtonType } from '../button/buttonType.enum'; + +@Component({ + selector: 'app-accompaniment-picker', + templateUrl: './accompaniment-picker.component.html', + styleUrls: ['./accompaniment-picker.component.scss'], +}) +export class AccompanimentPickerComponent implements OnInit, OnChanges { + @Input() personalOfferForm: UntypedFormGroup; + @Input() onlineProcedures: Category; + @Output() validateForm = new EventEmitter<any>(); + + public buttonTypeEnum = ButtonType; + public selectedModules: Module[] = []; + + ngOnInit(): void { + this.initSelectedModule(); + } + ngOnChanges(): void { + this.initSelectedModule(); + } + + private initSelectedModule() { + this.selectedModules = this.onlineProcedures.modules.filter((module) => + this.personalOfferForm.get('categories').get('onlineProcedures').value.includes(module.id) + ); + this.validateForm.emit(); + } + + public toogleResult(module: Module): void { + if (this.isSelectedModule(module)) { + const index = this.selectedModules.findIndex((_module) => _module.id === module.id); + this.selectedModules.splice(index, 1); + } else { + this.selectedModules.push(module); + } + this.personalOfferForm + .get('categories') + .get('onlineProcedures') + .patchValue(this.selectedModules.map((module) => module.id)); + this.personalOfferForm.get('categories').get('onlineProcedures').markAsDirty(); + } + + public isSelectedModule(module: Module): boolean { + if (this.selectedModules && this.selectedModules.includes(module)) return true; + return false; + } +} diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index db332878641605e660c5d1496b18a28125d2cb48..38c15b1ec4e5b9e8125750847adc048816d2db5c 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -1,5 +1,6 @@ import { InformationStepComponent } from '../../form/form-view/global-components/information-step/information-step.component'; import { ProgressBarComponent } from '../../form/form-view/global-components/progress-bar/progress-bar.component'; +import { AccompanimentPickerComponent } from './accompaniment-picker/accompaniment-picker.component'; import { AddressAutocompleteComponent } from './address-autocomplete/address-autocomplete.component'; import { ButtonComponent } from './button/button.component'; import { CheckboxFormComponent } from './checkbox-form/checkbox-form.component'; @@ -20,6 +21,7 @@ import { TrainingTypePickerComponent } from './training-type-picker/training-typ // tslint:disable-next-line: max-line-length export { + AccompanimentPickerComponent, LogoCardComponent, SvgIconComponent, ButtonComponent, @@ -42,6 +44,7 @@ export { // tslint:disable-next-line:variable-name export const SharedComponents = [ + AccompanimentPickerComponent, LogoCardComponent, SvgIconComponent, ButtonComponent, diff --git a/src/app/shared/components/training-type-picker/training-type-picker.component.html b/src/app/shared/components/training-type-picker/training-type-picker.component.html index f08373e7ce604d1c03c2c0f36a405708a601dec2..9e24b80490932a67d3ca8660478d90b194eab7c4 100644 --- a/src/app/shared/components/training-type-picker/training-type-picker.component.html +++ b/src/app/shared/components/training-type-picker/training-type-picker.component.html @@ -1,6 +1,6 @@ <div class="trainingContainer"> <div - class="boutonSection" + class="collapse" *ngFor="let categorie of categories" [ngClass]="{ notCollapsed: !isCategorieExpanded(categorie.id) }" > @@ -21,10 +21,10 @@ </div> <div class="logo"> <svg class="show" aria-hidden="true"> - <use [attr.xlink:href]="'assets/form/sprite.svg#show'"></use> + <use [attr.xlink:href]="'assets/form/sprite.svg#unfold'"></use> </svg> <svg class="hide" aria-hidden="true"> - <use [attr.xlink:href]="'assets/form/sprite.svg#hide'"></use> + <use [attr.xlink:href]="'assets/form/sprite.svg#fold'"></use> </svg> </div> </div> diff --git a/src/app/shared/components/training-type-picker/training-type-picker.component.scss b/src/app/shared/components/training-type-picker/training-type-picker.component.scss index 60e1f4e1c6e452b8047880cfb4228c87346d9d70..577cef9436dd2f05695777b0def81c631a16331a 100644 --- a/src/app/shared/components/training-type-picker/training-type-picker.component.scss +++ b/src/app/shared/components/training-type-picker/training-type-picker.component.scss @@ -10,7 +10,13 @@ flex-direction: column; gap: 0.5rem; } -.boutonSection { + +label.checkbox { + width: 2.25rem; + margin-top: 0.2rem; +} + +.collapse { border-radius: 4px; border: 1px solid $grey-5; box-sizing: border-box; diff --git a/src/app/shared/components/training-type-picker/training-type-picker.component.ts b/src/app/shared/components/training-type-picker/training-type-picker.component.ts index 77f4c990ef1baf59dafd816bed036c1f28ec6cc6..c2acf78ba497461519ba91425e2b7dcb2bcef545 100644 --- a/src/app/shared/components/training-type-picker/training-type-picker.component.ts +++ b/src/app/shared/components/training-type-picker/training-type-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { cloneDeep, remove } from 'lodash'; import { CategoriesToggle } from 'src/app/models/categoriesToggle.model'; import { Category } from '../../../structure-list/models/category.model'; @@ -11,7 +11,7 @@ import { ButtonType } from '../button/buttonType.enum'; templateUrl: './training-type-picker.component.html', styleUrls: ['./training-type-picker.component.scss'], }) -export class TrainingTypePickerComponent implements OnInit { +export class TrainingTypePickerComponent implements OnInit, OnChanges { @Input() public baseSkills: string[]; @Input() public advancedSkills: string[]; @Input() public trainingCategories: CategoriesToggle[]; @@ -26,6 +26,19 @@ export class TrainingTypePickerComponent implements OnInit { this.trainingCategories.forEach((data) => { this.categories.push(data.category); }); + this.initSelectedChoice(); + this.selectedChoices.forEach((category) => { + if (category.modules.length) { + this.categoriesExpanded.push(category.id); + } + }); + } + + ngOnChanges(): void { + this.initSelectedChoice(); + } + + private initSelectedChoice() { this.selectedChoices = cloneDeep(this.categories); this.selectedChoices.forEach((category) => { let selectedModulesId: string[] = []; @@ -39,9 +52,6 @@ export class TrainingTypePickerComponent implements OnInit { default: throw new Error(`Unimplemented training type ${category.id}`); } - if (selectedModulesId.length) { - this.categoriesExpanded.push(category.id); - } category.modules = category.modules.filter((module) => selectedModulesId.includes(module.id)); }); } @@ -83,6 +93,7 @@ export class TrainingTypePickerComponent implements OnInit { if (!this.isModulePicked(categorie, module)) this.selectedChoices[index].modules.push(module); } } + this.selectedType.emit(this.selectedChoices); } public getCategoryCheckboxStatus(c: Category): string {