From 521db72f5ec48308e9081d1dd6d390cb9c7e7742 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Tue, 14 Sep 2021 16:19:00 -0400 Subject: [PATCH] #14 Update recipe editor --- .../images-editor.component.html | 2 + .../mixes-card/mixes-card.component.html | 2 + src/app/modules/recipes/edit.html | 36 ++++ src/app/modules/recipes/form.html | 9 +- .../recipes/pages/edit/edit.component.html | 68 ------ .../recipes/pages/edit/edit.component.sass | 2 - .../recipes/pages/edit/edit.component.ts | 186 ---------------- .../modules/recipes/recipes-routing.module.ts | 13 +- src/app/modules/recipes/recipes.module.ts | 11 +- src/app/modules/recipes/recipes.sass | 6 + src/app/modules/recipes/recipes.ts | 199 +++++++++++++----- .../recipes/services/recipe.service.ts | 9 +- .../shared/components/inputs/inputs.ts | 13 +- src/app/modules/shared/utils/map.utils.ts | 4 + 14 files changed, 226 insertions(+), 334 deletions(-) create mode 100644 src/app/modules/recipes/edit.html delete mode 100644 src/app/modules/recipes/pages/edit/edit.component.html delete mode 100644 src/app/modules/recipes/pages/edit/edit.component.sass delete mode 100644 src/app/modules/recipes/pages/edit/edit.component.ts create mode 100644 src/app/modules/recipes/recipes.sass diff --git a/src/app/modules/recipes/components/images-editor/images-editor.component.html b/src/app/modules/recipes/components/images-editor/images-editor.component.html index e07f7b8..97e9424 100644 --- a/src/app/modules/recipes/components/images-editor/images-editor.component.html +++ b/src/app/modules/recipes/components/images-editor/images-editor.component.html @@ -4,6 +4,8 @@
+

Aucune image n'est associée à cette couleur

+
diff --git a/src/app/modules/recipes/components/mixes-card/mixes-card.component.html b/src/app/modules/recipes/components/mixes-card/mixes-card.component.html index b6766df..c8a093a 100644 --- a/src/app/modules/recipes/components/mixes-card/mixes-card.component.html +++ b/src/app/modules/recipes/components/mixes-card/mixes-card.component.html @@ -3,6 +3,8 @@ Mélanges +

Il n'y a aucun mélange dans cette couleur

+ + + + Retour + + + + Supprimer + Enregistrer + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + diff --git a/src/app/modules/recipes/form.html b/src/app/modules/recipes/form.html index a00dcf1..7f72041 100644 --- a/src/app/modules/recipes/form.html +++ b/src/app/modules/recipes/form.html @@ -1,12 +1,13 @@ -
- +
+

Il n'y a actuellement aucune bannière enregistrée dans le système.

Vous pouvez en créer une ici.

- Ajouter une recette + Ajouter une couleur + Modifier la couleur {{recipe.name}} @@ -17,7 +18,7 @@ - + Retour Enregistrer diff --git a/src/app/modules/recipes/pages/edit/edit.component.html b/src/app/modules/recipes/pages/edit/edit.component.html deleted file mode 100644 index 81461f8..0000000 --- a/src/app/modules/recipes/pages/edit/edit.component.html +++ /dev/null @@ -1,68 +0,0 @@ -
-
-
-
- - - -
- - Unités - - Millilitres - Litres - Gallons - - -
-
-
- -
-
- - -
- -
- -
- -
- - -
- -
- -
-
-
- - - diff --git a/src/app/modules/recipes/pages/edit/edit.component.sass b/src/app/modules/recipes/pages/edit/edit.component.sass deleted file mode 100644 index 2533b3c..0000000 --- a/src/app/modules/recipes/pages/edit/edit.component.sass +++ /dev/null @@ -1,2 +0,0 @@ -.recipe-wrapper > div - margin: 0 3rem 3rem diff --git a/src/app/modules/recipes/pages/edit/edit.component.ts b/src/app/modules/recipes/pages/edit/edit.component.ts deleted file mode 100644 index ab663dc..0000000 --- a/src/app/modules/recipes/pages/edit/edit.component.ts +++ /dev/null @@ -1,186 +0,0 @@ -import {Component, ViewChild} from '@angular/core' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {Recipe, recipeMixCount, RecipeStep, recipeStepCount} from '../../../shared/model/recipe.model' -import {RecipeService} from '../../services/recipe.service' -import {ActivatedRoute, Router} from '@angular/router' -import {Validators} from '@angular/forms' -import {Subject} from 'rxjs' -import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from '../../../shared/units' -import {AccountService} from '../../../accounts/services/account.service' -import {EntityEditComponent} from '../../../shared/components/entity-edit/entity-edit.component' -import {ImagesEditorComponent} from '../../components/images-editor/images-editor.component' -import {ErrorHandler, ErrorService} from '../../../shared/service/error.service' -import {AlertService} from '../../../shared/service/alert.service' -import {GroupService} from '../../../groups/services/group.service' -import {AppState} from '../../../shared/app-state' -import {StepTableComponent} from '../../components/step-table/step-table.component' - -@Component({ - selector: 'cre-edit', - templateUrl: './edit.component.html', - styleUrls: ['./edit.component.sass'] -}) -export class EditComponent extends ErrorHandlingComponent { - readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} - - @ViewChild('imagesEditor') imagesEditor: ImagesEditorComponent - - recipe: Recipe | null - groups$ = this.groupService.all - formFields = [ - { - name: 'name', - label: 'Nom', - icon: 'form-textbox', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un nom est requis'} - ] - }, - { - name: 'description', - label: 'Description', - icon: 'text', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une description est requise'} - ] - }, - { - name: 'color', - label: 'Couleur', - icon: 'palette', - type: 'color', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une couleur est requise'} - ] - }, - { - name: 'gloss', - label: 'Lustre', - type: 'slider', - min: 0, - max: 100, - validator: Validators.compose([Validators.required, Validators.min(0), Validators.max(100)]), - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'} - ] - }, - { - name: 'sample', - label: 'Échantillon', - icon: 'pound', - type: 'number', - validator: Validators.min(0), - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un numéro d\'échantillon est requis'}, - {conditionFn: errors => errors.min, message: 'Le numéro d\'échantillon doit être supérieur ou égal à 0'} - ] - }, - { - name: 'approbationDate', - label: 'Date d\'approbation', - icon: 'calendar', - type: 'date' - }, - { - name: 'remark', - label: 'Remarque', - icon: 'text', - type: 'text' - }, - { - name: 'company', - label: 'Bannière', - icon: 'domain', - type: 'text', - readonly: true, - valueFn: recipe => recipe.company.name, - } - ] - units$ = new Subject() - submittedValues: any | null - - errorHandlers: ErrorHandler[] = [{ - filter: error => error.type === 'notfound-recipe-id', - consumer: error => this.urlUtils.navigateTo('/color/list') - }] - - constructor( - private recipeService: RecipeService, - private groupService: GroupService, - private accountService: AccountService, - private alertService: AlertService, - private appState: AppState, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - } - - ngOnInit() { - super.ngOnInit() - - this.subscribeEntityById( - this.recipeService, - parseInt(this.activatedRoute.snapshot.paramMap.get('id')), - recipe => { - this.recipe = recipe - this.appState.title = `${recipe.name} (Modifications)` - - if (recipeMixCount(this.recipe) == 0) { - this.alertService.pushWarning('Il n\'y a aucun mélange dans cette recette') - } - if (recipeStepCount(this.recipe) == 0) { - this.alertService.pushWarning('Il n\'y a aucune étape dans cette recette') - } - } - ) - } - - changeUnits(unit: string) { - this.units$.next(unit) - } - - submit(editComponent: EntityEditComponent, stepTable: StepTableComponent) { - const values = editComponent.values - this.submittedValues = values - - const steps = stepTable.mappedUpdatedSteps - if (!this.stepsPositionsAreValid(steps)) { - this.alertService.pushError('Les étapes ne peuvent pas avoir une position inférieure à 1') - return - } - - this.subscribeAndNavigate( - this.recipeService.update(this.recipe.id, values.name, values.description, values.color, values.gloss, values.sample, values.approbationDate, values.remark, steps), - '/color/list' - ) - } - - delete() { - this.subscribeAndNavigate( - this.recipeService.delete(this.recipe.id), - '/color/list' - ) - } - - get loggedInUserGroupId(): number { - return this.appState.authenticatedUser.group?.id - } - - private stepsPositionsAreValid(steps: Map): boolean { - let valid = true - steps.forEach((steps, _) => { - if (steps.find(s => s.position === 0)) { - valid = false - return - } - }) - return valid - } -} diff --git a/src/app/modules/recipes/recipes-routing.module.ts b/src/app/modules/recipes/recipes-routing.module.ts index a29258a..95fd2a9 100644 --- a/src/app/modules/recipes/recipes-routing.module.ts +++ b/src/app/modules/recipes/recipes-routing.module.ts @@ -1,11 +1,10 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {ListComponent} from "./pages/list/list.component"; -import {EditComponent} from "./pages/edit/edit.component"; -import {ExploreComponent} from "./pages/explore/explore.component"; -import {MixEditComponent} from "./pages/mix/mix-edit/mix-edit.component"; -import {MixAddComponent} from "./pages/mix/mix-add/mix-add.component"; -import {RecipeAdd} from './recipes'; +import {ListComponent} from './pages/list/list.component'; +import {ExploreComponent} from './pages/explore/explore.component'; +import {MixEditComponent} from './pages/mix/mix-edit/mix-edit.component'; +import {MixAddComponent} from './pages/mix/mix-add/mix-add.component'; +import {RecipeAdd, RecipeEdit} from './recipes'; const routes: Routes = [{ path: 'list', @@ -15,7 +14,7 @@ const routes: Routes = [{ component: RecipeAdd }, { path: 'edit/:id', - component: EditComponent + component: RecipeEdit }, { path: 'add/mix/:recipeId', component: MixAddComponent diff --git a/src/app/modules/recipes/recipes.module.ts b/src/app/modules/recipes/recipes.module.ts index 3170cd2..71aacfe 100644 --- a/src/app/modules/recipes/recipes.module.ts +++ b/src/app/modules/recipes/recipes.module.ts @@ -3,7 +3,6 @@ import {NgModule} from '@angular/core' import {RecipesRoutingModule} from './recipes-routing.module' import {SharedModule} from '../shared/shared.module' import {ListComponent} from './pages/list/list.component' -import {EditComponent} from './pages/edit/edit.component' import {MatExpansionModule} from '@angular/material/expansion' import {FormsModule} from '@angular/forms' import {ExploreComponent} from './pages/explore/explore.component' @@ -20,13 +19,13 @@ import {MixesCardComponent} from './components/mixes-card/mixes-card.component' import {MatSortModule} from '@angular/material/sort' import {CreInputsModule} from '../shared/components/inputs/inputs.module'; import {CreButtonsModule} from '../shared/components/buttons/buttons.module'; -import {RecipeAdd, RecipeForm} from './recipes'; +import {RecipeAdd, RecipeEdit, RecipeForm} from './recipes'; +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module'; @NgModule({ declarations: [ ListComponent, - EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, @@ -39,7 +38,8 @@ import {RecipeAdd, RecipeForm} from './recipes'; ImagesEditorComponent, MixesCardComponent, RecipeForm, - RecipeAdd + RecipeAdd, + RecipeEdit ], exports: [ UnitSelectorComponent @@ -51,7 +51,8 @@ import {RecipeAdd, RecipeForm} from './recipes'; FormsModule, MatSortModule, CreInputsModule, - CreButtonsModule + CreButtonsModule, + CreActionBarModule ] }) export class RecipesModule { diff --git a/src/app/modules/recipes/recipes.sass b/src/app/modules/recipes/recipes.sass new file mode 100644 index 0000000..d19072a --- /dev/null +++ b/src/app/modules/recipes/recipes.sass @@ -0,0 +1,6 @@ +.recipe-wrapper > section + margin: 0 3rem 3rem + +cre-form + margin-top: 0 !important + diff --git a/src/app/modules/recipes/recipes.ts b/src/app/modules/recipes/recipes.ts index b6b6e75..19c0c3d 100644 --- a/src/app/modules/recipes/recipes.ts +++ b/src/app/modules/recipes/recipes.ts @@ -1,56 +1,27 @@ import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component'; -import {Observable} from 'rxjs'; +import {Observable, Subject} from 'rxjs'; import {ComboBoxEntry} from '../shared/components/inputs/inputs'; import {map, tap} from 'rxjs/operators'; import {RecipeService} from './services/recipe.service'; import {CompanyService} from '../company/service/company.service'; import {AppState} from '../shared/app-state'; -import {ErrorService} from '../shared/service/error.service'; +import {ErrorHandler, ErrorService} from '../shared/service/error.service'; import {ActivatedRoute, Router} from '@angular/router'; import {FormControl, Validators} from '@angular/forms'; -import {Component, EventEmitter, Input, Output} from '@angular/core'; -import {Recipe} from '../shared/model/recipe.model'; +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core'; +import {Recipe, recipeMixCount, RecipeStep, recipeStepCount} from '../shared/model/recipe.model'; import {AccountService} from '../accounts/services/account.service'; import {Permission} from '../shared/model/user'; - -@Component({ - selector: 'cre-recipe-add', - templateUrl: 'add.html' -}) -export class RecipeAdd extends ErrorHandlingComponent { - controls: any - companyEntries$: Observable = this.companyService.all.pipe( - map(companies => companies.map(c => new ComboBoxEntry(c.id, c.name))), - ) - - errorHandlers = [{ - filter: error => error.type === `exists-recipe-company-name`, - messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}` - }] - - constructor( - private recipeService: RecipeService, - private companyService: CompanyService, - private appState: AppState, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - this.appState.title = 'Nouvelle couleur' - } - - submit(recipe: Recipe) { - this.subscribe( - this.recipeService.save(recipe), - recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`) - ) - } -} +import {AlertService} from '../shared/service/alert.service'; +import {GroupService} from '../groups/services/group.service'; +import {StepTableComponent} from './components/step-table/step-table.component'; +import {anyMap} from '../shared/utils/map.utils'; @Component({ selector: 'recipe-form', - templateUrl: 'form.html' + templateUrl: 'form.html', + styleUrls: ['recipes.sass'], + encapsulation: ViewEncapsulation.None }) export class RecipeForm extends SubscribingComponent { @Input() recipe: Recipe | null @@ -77,14 +48,14 @@ export class RecipeForm extends SubscribingComponent { this.fetchCompanies() this.controls = { - name: new FormControl(null, Validators.required), - description: new FormControl(null, Validators.required), - color: new FormControl('#ffffff', Validators.required), - gloss: new FormControl(0, Validators.compose([Validators.required, Validators.min(0), Validators.max(100)])), - sample: new FormControl(null, Validators.compose([Validators.required, Validators.min(0)])), - approbationDate: new FormControl(null), - remark: new FormControl(null), - company: new FormControl(null, Validators.required) + name: new FormControl(this.recipe?.name, Validators.required), + description: new FormControl(this.recipe?.description, Validators.required), + color: new FormControl(this.recipe?.color ?? '#ffffff', Validators.required), + gloss: new FormControl(this.recipe?.gloss ?? 0, Validators.compose([Validators.required, Validators.min(0), Validators.max(100)])), + sample: new FormControl(this.recipe?.sample, Validators.compose([Validators.required, Validators.min(0)])), + approbationDate: new FormControl(this.recipe?.approbationDate), + remark: new FormControl(this.recipe?.remark), + company: new FormControl({value: this.recipe?.company.id, disabled: !!this.recipe}, Validators.required) } } @@ -96,8 +67,12 @@ export class RecipeForm extends SubscribingComponent { } submit() { - this.submitForm.emit({ - id: this.recipe?.id, + this.submitForm.emit(this.updatedRecipe) + } + + get updatedRecipe(): Recipe { + return { + ...this.recipe, name: this.controls.name.value, description: this.controls.description.value, color: this.controls.color.value, @@ -106,14 +81,128 @@ export class RecipeForm extends SubscribingComponent { approbationDate: this.controls.approbationDate.value, remark: this.controls.remark.value, company: this.controls.company.value, - mixes: this.recipe?.mixes, - approbationExpired: this.recipe?.approbationExpired, - groupsInformation: this.recipe?.groupsInformation, - imagesUrls: this.recipe?.imagesUrls - }) + } } get hasCompanyEditPermission(): boolean { return this.accountService.hasPermission(Permission.EDIT_COMPANIES) } } + +@Component({ + selector: 'cre-recipe-add', + templateUrl: 'add.html' +}) +export class RecipeAdd extends ErrorHandlingComponent { + errorHandlers = [{ + filter: error => error.type === `exists-recipe-company-name`, + messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}` + }] + + constructor( + private recipeService: RecipeService, + private companyService: CompanyService, + private appState: AppState, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + this.appState.title = 'Nouvelle couleur' + } + + submit(recipe: Recipe) { + this.subscribe( + this.recipeService.save(recipe), + recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`) + ) + } +} + +@Component({ + selector: 'cre-recipe-edit', + templateUrl: 'edit.html', + styleUrls: ['recipes.sass'] +}) +export class RecipeEdit extends ErrorHandlingComponent { + @ViewChild(StepTableComponent) stepTable: StepTableComponent + @ViewChild(RecipeForm) form: RecipeForm + + recipe: Recipe + groups$ = this.groupService.all + units$ = new Subject() + + errorHandlers: ErrorHandler[] = [{ + filter: error => error.type === 'notfound-recipe-id', + consumer: _ => this.urlUtils.navigateTo('/color/list') + }] + + constructor( + private recipeService: RecipeService, + private companyService: CompanyService, + private groupService: GroupService, + private appState: AppState, + private alertService: AlertService, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + + this.fetchRecipe() + } + + private fetchRecipe() { + const recipeId = this.urlUtils.parseIntUrlParam('id') + this.subscribe( + this.recipeService.getById(recipeId), + recipe => { + this.recipe = recipe + this.appState.title = `${recipe.name} (Modifications)` + + if (recipeMixCount(this.recipe) == 0) { + this.alertService.pushWarning('Il n\'y a aucun mélange dans cette recette') + } + if (recipeStepCount(this.recipe) == 0) { + this.alertService.pushWarning('Il n\'y a aucune étape dans cette recette') + } + }, + true, + 1 + ) + } + + changeUnits(unit: string) { + this.units$.next(unit) + } + + submit() { + const recipe = this.form.updatedRecipe + const steps = this.stepTable.mappedUpdatedSteps + + if (!this.stepsPositionsAreValid(steps)) { + this.alertService.pushError('Les étapes ne peuvent pas avoir une position inférieure à 1') + return + } + + this.subscribeAndNavigate( + this.recipeService.update(recipe, steps), + '/color/list' + ) + } + + delete() { + this.subscribeAndNavigate( + this.recipeService.delete(this.recipe.id), + '/color/list' + ) + } + + get loggedInUserGroupId(): number { + return this.appState.authenticatedUser.group?.id + } + + private stepsPositionsAreValid(steps: Map): boolean { + return !anyMap(steps, (groupId, steps) => !!steps.find(s => s.position === 0)) + } +} diff --git a/src/app/modules/recipes/services/recipe.service.ts b/src/app/modules/recipes/services/recipe.service.ts index b3d2670..4a99612 100644 --- a/src/app/modules/recipes/services/recipe.service.ts +++ b/src/app/modules/recipes/services/recipe.service.ts @@ -48,11 +48,10 @@ export class RecipeService { return this.api.post('/recipe', body) } - update(id: number, name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, steps: Map) { - const body = {id, name, description, color, gloss, sample, remark, steps: []} - if (approbationDate) { - // @ts-ignore - body.approbationDate = approbationDate + update(recipe: Recipe, steps: Map) { + const body = { + ...recipe, + steps: [] } steps.forEach((groupSteps, groupId) => { diff --git a/src/app/modules/shared/components/inputs/inputs.ts b/src/app/modules/shared/components/inputs/inputs.ts index e080f72..58c494a 100644 --- a/src/app/modules/shared/components/inputs/inputs.ts +++ b/src/app/modules/shared/components/inputs/inputs.ts @@ -156,11 +156,20 @@ export class CreComboBoxComponent implements OnInit { next: entries => { this._entries = entries - this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) + if (this.control.value) { + this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) + } + + if (this.internalControl.disabled) { + this.internalControl.disable() + } } }) - this.internalControl = new FormControl(null, Validators.compose([this.control.validator, this.valueValidator()])) + this.internalControl = new FormControl({ + value: null, + disabled: true + }, Validators.compose([this.control.validator, this.valueValidator()])) this.internalControl.valueChanges.pipe(takeUntil(this._destroy$)) .subscribe({ next: value => { diff --git a/src/app/modules/shared/utils/map.utils.ts b/src/app/modules/shared/utils/map.utils.ts index ba99aec..dd43b65 100644 --- a/src/app/modules/shared/utils/map.utils.ts +++ b/src/app/modules/shared/utils/map.utils.ts @@ -1,3 +1,7 @@ +export function anyMap(map: Map, predicate: (key: K, value: V) => boolean): boolean { + return filterMap(map, predicate).size > 0 +} + export function filterMap(map: Map, predicate: (key: K, value: V) => boolean): Map { const filteredMap = new Map() map.forEach((value, key) => {