From 971e3dcb3c487a10c0046c2213cccae6f1ff3be4 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 17 Nov 2021 19:23:13 -0500 Subject: [PATCH] Working mix editor --- docker-compose.yml | 2 +- .../unit-selector.component.html | 2 +- .../unit-selector/unit-selector.component.ts | 15 ++- src/app/modules/recipes/mix/add.html | 23 +++- .../modules/recipes/mix/materials-form.html | 106 +++++++++++------- src/app/modules/recipes/mix/materials-form.ts | 82 ++++++++++---- src/app/modules/recipes/mix/mix.ts | 67 ++++++----- src/app/modules/recipes/recipes.ts | 4 +- .../shared/components/inputs/combo-box.html | 6 +- .../shared/components/inputs/inputs.ts | 47 +++++--- 10 files changed, 237 insertions(+), 117 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0defb89..333e1ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: image: registry.fyloz.dev:5443/colorrecipesexplorer/backend:latest environment: spring_profiles_active: "mysql,debug" - cre_database_url: "mysql://database:3307/cre" + cre_database_url: "mysql://database/cre" cre_database_username: "root" cre_database_password: "pass" CRE_ENABLE_DB_UPDATE: 1 diff --git a/src/app/modules/recipes/components/unit-selector/unit-selector.component.html b/src/app/modules/recipes/components/unit-selector/unit-selector.component.html index 4b82721..b00e879 100644 --- a/src/app/modules/recipes/components/unit-selector/unit-selector.component.html +++ b/src/app/modules/recipes/components/unit-selector/unit-selector.component.html @@ -1,6 +1,6 @@ Unités - + Millilitres Litres diff --git a/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts b/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts index 418677f..9f8d192 100644 --- a/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts +++ b/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts @@ -1,17 +1,28 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core' import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from "../../../shared/units"; +import {FormControl} from '@angular/forms' @Component({ selector: 'cre-unit-selector', templateUrl: './unit-selector.component.html', styleUrls: ['./unit-selector.component.sass'] }) -export class UnitSelectorComponent { +export class UnitSelectorComponent implements OnInit { readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} @Input() unit = UNIT_MILLILITER @Input() showLabel = true @Input() short = false + @Input() control: FormControl | null @Output() unitChange = new EventEmitter() + + ngOnInit() { + this.control?.setValue(this.unit) + } + + onUnitChange(newUnit: string) { + this.control?.setValue(newUnit) + this.unitChange.emit(newUnit) + } } diff --git a/src/app/modules/recipes/mix/add.html b/src/app/modules/recipes/mix/add.html index 43a2dc3..26639dd 100644 --- a/src/app/modules/recipes/mix/add.html +++ b/src/app/modules/recipes/mix/add.html @@ -1,6 +1,17 @@ - - + + + + Retour + + + Enregistrer + + + + + + diff --git a/src/app/modules/recipes/mix/materials-form.html b/src/app/modules/recipes/mix/materials-form.html index 49baca8..91d95ca 100644 --- a/src/app/modules/recipes/mix/materials-form.html +++ b/src/app/modules/recipes/mix/materials-form.html @@ -1,44 +1,70 @@ - - - Position - {{mixMaterial.position + 1}} - +
+ +

Il n'y a actuellement aucun produit enregistré dans le système.

+

Vous pouvez en créer un ici. +

+
- - - - - - - + + + Position + {{mixMaterial.position + 1}} + - - Produit - - - - - + + + + + + + - - Quantité - - - - + + Produit + + + + + - - - Ajouter - - - Retirer - - - + + Quantité + + + + + + + Unités + + + + + + % + + + + + + + Ajouter + + + + Retirer + + + + +
diff --git a/src/app/modules/recipes/mix/materials-form.ts b/src/app/modules/recipes/mix/materials-form.ts index 762bff8..7ecc05c 100644 --- a/src/app/modules/recipes/mix/materials-form.ts +++ b/src/app/modules/recipes/mix/materials-form.ts @@ -1,11 +1,13 @@ -import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, ViewChild} from "@angular/core"; -import {CreTable} from "../../shared/components/tables/tables"; -import {MixMaterialDto, sortMixMaterialsDto} from "../../shared/model/recipe.model"; -import {Observable, Subject} from "rxjs"; -import {Material} from "../../shared/model/material.model"; -import {FormControl, Validators} from "@angular/forms"; -import {takeUntil} from "rxjs/operators"; -import {CreInputEntry} from "../../shared/components/inputs/inputs"; +import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, ViewChild, ViewChildren} from '@angular/core' +import {CreTable} from '../../shared/components/tables/tables' +import {MixMaterialDto, sortMixMaterialsDto} from '../../shared/model/recipe.model' +import {Observable, Subject} from 'rxjs' +import {Material, materialComparator} from '../../shared/model/material.model' +import {FormControl, Validators} from '@angular/forms' +import {takeUntil} from 'rxjs/operators' +import {CreComboBoxComponent, CreInputEntry} from '../../shared/components/inputs/inputs' +import {AccountService} from '../../accounts/services/account.service' +import {Permission} from '../../shared/model/user' @Component({ selector: 'cre-mix-materials-form', @@ -13,11 +15,12 @@ import {CreInputEntry} from "../../shared/components/inputs/inputs"; }) export class MixMaterialsForm implements AfterViewInit, OnDestroy { @ViewChild(CreTable) table: CreTable + @ViewChildren(CreComboBoxComponent) materialComboBoxes: CreComboBoxComponent[] @Input() materials: Observable + @Input() mixMaterials: MixMaterialDto[] = [] - mixMaterials: MixMaterialDto[] = [] - columns = ['position', 'positionButtons', 'material', 'quantity', 'endButton'] + columns = ['position', 'positionButtons', 'material', 'quantity', 'units', 'endButton'] private _allMaterials: Material[] private _controls: ControlsByPosition[] = [] @@ -25,6 +28,7 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { private _destroy$ = new Subject() constructor( + private accountService: AccountService, private cdRef: ChangeDetectorRef ) { } @@ -46,18 +50,21 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { } addRow() { - const position = this.nextPosition; - const mixMaterial = new MixMaterialDto(0, 0, false, position); + const position = this.nextPosition + const mixMaterial = new MixMaterialDto(null, 0, false, position) const materialIdControl = new FormControl(null, Validators.required) - const quantityControl = new FormControl(null, Validators.required) + const quantityControl = new FormControl(0, Validators.required) + const unitsControl = new FormControl(null, Validators.required) materialIdControl.valueChanges .pipe(takeUntil(this._destroy$)) .subscribe({ next: materialId => { - this.mixMaterials.filter(x => x.materialId === mixMaterial.materialId)[0].materialId = materialId + mixMaterial.materialId = materialId this.refreshAvailableMaterials() + this.cdRef.detectChanges() + this.materialComboBoxes.forEach(comboBox => comboBox.reloadEntries()) } }) @@ -65,7 +72,8 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { position, controls: { materialId: materialIdControl, - quantity: quantityControl + quantity: quantityControl, + units: unitsControl } }) this._availableMaterialsEntries.push({ @@ -86,15 +94,18 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { updatePosition(mixMaterial: MixMaterialDto, newPosition: number, switchPositions = true) { const currentPosition = mixMaterial.position + const currentControls = this.getControlsByPosition(currentPosition) + const currentMaterialEntries = this.getMaterialEntriesByPosition(currentPosition) - this.getControlsByPosition(currentPosition).position = newPosition - this.getMaterialEntriesByPosition(currentPosition).position = newPosition - + // Update before current to prevent position conflicts if (switchPositions) { this.updatePosition(this.getMixMaterialByPosition(newPosition), currentPosition, false) } mixMaterial.position = newPosition + currentControls.position = newPosition + currentMaterialEntries.position = newPosition + this.sortTable() } @@ -106,8 +117,24 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { return this.getMaterialEntriesByPosition(position).entries } + areUnitsPercents(mixMaterial: MixMaterialDto): boolean { + return mixMaterial.materialId ? this._allMaterials.filter(x => x.id === mixMaterial.materialId)[0].materialType.usePercentages : false + } + + get hasMaterialEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_MATERIALS) + } + get materialCount(): number { - return this._allMaterials ? this._allMaterials.length : 0; + return this._allMaterials ? this._allMaterials.length : 0 + } + + get valid(): boolean { + return this._controls + .map(controls => controls.controls) + .map(controls => [controls.materialId, controls.quantity]) + .flatMap(controls => controls) + .every(control => control.valid) } private get nextPosition(): number { @@ -141,14 +168,27 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy { private filterMaterials(mixMaterial: MixMaterialDto): CreInputEntry[] { return this._allMaterials - .filter(material => mixMaterial.materialId === material.id || this.mixMaterials.filter(mm => mm.materialId === material.id).length === 0) - .map(material => new CreInputEntry(material.id, material.name)) + .filter(material => { + // Prevent use of percents in first position + if (material.materialType.usePercentages && mixMaterial.position === 0) { + return false + } + + if (mixMaterial.materialId === material.id) { + return true + } + + return this.mixMaterials.filter(x => x.materialId === material.id).length <= 0 + }) + .sort(materialComparator) + .map(material => new CreInputEntry(material.id, material.name, material.materialType.prefix ? `[${material.materialType.prefix}] ${material.name}` : material.name)) } } interface MixMaterialControls { materialId: FormControl quantity: FormControl + units: FormControl } interface ControlsByPosition { diff --git a/src/app/modules/recipes/mix/mix.ts b/src/app/modules/recipes/mix/mix.ts index 7bdb95b..35ccc73 100644 --- a/src/app/modules/recipes/mix/mix.ts +++ b/src/app/modules/recipes/mix/mix.ts @@ -1,19 +1,19 @@ -import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core"; -import {SubscribingComponent} from "../../shared/components/subscribing.component"; -import {MixMaterialDto, Recipe, sortMixMaterialsDto} from "../../shared/model/recipe.model"; -import {ErrorService} from "../../shared/service/error.service"; -import {ActivatedRoute, Router} from "@angular/router"; -import {RecipeService} from "../services/recipe.service"; -import {FormControl, Validators} from "@angular/forms"; -import {Observable, Subject} from "rxjs"; -import {MaterialType} from "../../shared/model/materialtype.model"; -import {MaterialTypeService} from "../../material-type/service/material-type.service"; -import {CreInputEntry} from "../../shared/components/inputs/inputs"; -import {filter, map, takeUntil, tap} from "rxjs/operators"; -import {Material} from "../../shared/model/material.model"; -import {MaterialService} from "../../material/service/material.service"; -import {CreTable} from "../../shared/components/tables/tables"; -import {MatTable} from "@angular/material/table"; +import {Component, Input, OnInit, ViewChild} from '@angular/core' +import {SubscribingComponent} from '../../shared/components/subscribing.component' +import {Recipe} from '../../shared/model/recipe.model' +import {ErrorService} from '../../shared/service/error.service' +import {ActivatedRoute, Router} from '@angular/router' +import {RecipeService} from '../services/recipe.service' +import {FormControl, Validators} from '@angular/forms' +import {Observable} from 'rxjs' +import {MaterialType} from '../../shared/model/materialtype.model' +import {MaterialTypeService} from '../../material-type/service/material-type.service' +import {CreInputEntry} from '../../shared/components/inputs/inputs' +import {map} from 'rxjs/operators' +import {Material} from '../../shared/model/material.model' +import {MaterialService} from '../../material/service/material.service' +import {CreForm} from '../../shared/components/forms/forms' +import {MixMaterialsForm} from './materials-form' @Component({ selector: 'cre-mix-add', @@ -50,30 +50,22 @@ export class MixAdd extends SubscribingComponent { } set recipe(recipe: Recipe) { - this._recipe = recipe; + this._recipe = recipe this.materials$ = this.materialService.getAllForMixCreation(recipe.id) } get recipe(): Recipe { - return this._recipe; + return this._recipe } } -@Component({ - selector: 'cre-mix-form', - templateUrl: 'form.html' -}) -export class MixForm { - @Input() recipe: Recipe - @Input() materialTypes: Observable - @Input() materials: Observable -} - @Component({ selector: 'cre-mix-info-form', templateUrl: 'info-form.html' }) export class MixInfoForm implements OnInit { + @ViewChild(CreForm) form: CreForm + @Input() recipe: Recipe @Input() materialTypes: Observable @@ -100,6 +92,25 @@ export class MixInfoForm implements OnInit { get mixMaterialTypeId(): number { return this.controls.materialType.value } + + get valid(): boolean { + return this.form.valid + } } +@Component({ + selector: 'cre-mix-form', + templateUrl: 'form.html' +}) +export class MixForm { + @ViewChild(MixInfoForm) infoForm: MixInfoForm + @ViewChild(MixMaterialsForm) mixMaterialsForm: MixMaterialsForm + @Input() recipe: Recipe + @Input() materialTypes: Observable + @Input() materials: Observable + + get valid(): boolean { + return this.infoForm?.valid && this.mixMaterialsForm?.valid + } +} diff --git a/src/app/modules/recipes/recipes.ts b/src/app/modules/recipes/recipes.ts index fed6fe8..bdc619e 100644 --- a/src/app/modules/recipes/recipes.ts +++ b/src/app/modules/recipes/recipes.ts @@ -25,10 +25,10 @@ import {CreForm, ICreForm} from '../shared/components/forms/forms'; encapsulation: ViewEncapsulation.None }) export class RecipeForm extends SubscribingComponent { - @Input() recipe: Recipe | null - @ViewChild(CreForm) creForm: ICreForm + @Input() recipe: Recipe | null + @Output() submitForm = new EventEmitter(); controls: any diff --git a/src/app/modules/shared/components/inputs/combo-box.html b/src/app/modules/shared/components/inputs/combo-box.html index 96decd8..67e0162 100644 --- a/src/app/modules/shared/components/inputs/combo-box.html +++ b/src/app/modules/shared/components/inputs/combo-box.html @@ -1,4 +1,4 @@ - + {{label}} - - {{entry.value}} + + {{entry.display ? entry.display : entry.value}} diff --git a/src/app/modules/shared/components/inputs/inputs.ts b/src/app/modules/shared/components/inputs/inputs.ts index 0e7811f..df922cd 100644 --- a/src/app/modules/shared/components/inputs/inputs.ts +++ b/src/app/modules/shared/components/inputs/inputs.ts @@ -16,7 +16,7 @@ import { import {AbstractControl, FormControl, ValidationErrors, ValidatorFn, Validators} from '@angular/forms' import {COMMA, ENTER} from '@angular/cdk/keycodes' import {isObservable, Observable, Subject} from 'rxjs' -import {map, takeUntil} from 'rxjs/operators' +import {map, startWith, takeUntil} from 'rxjs/operators' import {MatChipInputEvent} from '@angular/material/chips' import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete' @@ -55,7 +55,7 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit { constructor( private cdRef: ChangeDetectorRef ) { - super(); + super() } ngAfterViewInit() { @@ -165,34 +165,36 @@ export class CreComboBoxComponent { @ContentChild(TemplateRef) errors: TemplateRef internalControl: FormControl + filteredEntries: CreInputEntry[] validValue = false - private _destroy$ = new Subject(); + private _destroy$ = new Subject() private _entries: CreInputEntry[] private _controlsInitialized = false @Input() set entries(entries: Observable | CreInputEntry[]) { - if (isObservable(this.entries)) { + if (isObservable(entries)) { (entries as Observable).pipe(takeUntil(this._destroy$)) .subscribe({ next: entries => { - this._entries = entries + this.initControls(entries) } }) } else { - this._entries = (entries as CreInputEntry[]) + this.initControls((entries as CreInputEntry[])) } - - this.initControls() } - getEntries(): CreInputEntry[] { - return this._entries + reloadEntries() { + this.filteredEntries = this.filterEntries(this.internalControl.value) } - private initControls() { - if (this._controlsInitialized) return + private initControls(entries) { + this._entries = entries + if (this._controlsInitialized) { + return + } if (this.control.value) { this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) @@ -206,7 +208,8 @@ export class CreComboBoxComponent { value: null, disabled: false }, Validators.compose([this.control.validator, this.valueValidator()])) - this.internalControl.valueChanges.pipe(takeUntil(this._destroy$)) + this.internalControl.valueChanges + .pipe(takeUntil(this._destroy$)) .subscribe({ next: value => { if (this.internalControl.valid) { @@ -214,12 +217,30 @@ export class CreComboBoxComponent { } else { this.control.setValue(null) } + + this.filteredEntries = this.filterEntries(value) } }) + this.reloadEntries() this._controlsInitialized = true } + private filterEntries(value: string): CreInputEntry[] { + if (!value) { + return this._entries + } + + const valueLowerCase = value.toLowerCase() + return this._entries.filter(entry => { + if (entry.display) { + return entry.display.toLowerCase().includes(valueLowerCase) + } else { + return entry.value.toLowerCase().includes(valueLowerCase) + } + }) + } + private findEntryByKey(key: any): CreInputEntry | null { const found = this._entries.filter(e => e.key === key) if (found.length <= 0) {