diff --git a/src/app/modules/recipes/components/mix-editor/mix-editor.component.html b/src/app/modules/recipes/components/mix-editor/mix-editor.component.html index f9081ac..5e294cd 100644 --- a/src/app/modules/recipes/components/mix-editor/mix-editor.component.html +++ b/src/app/modules/recipes/components/mix-editor/mix-editor.component.html @@ -71,7 +71,7 @@ - + diff --git a/src/app/modules/recipes/mix/add.html b/src/app/modules/recipes/mix/add.html new file mode 100644 index 0000000..43a2dc3 --- /dev/null +++ b/src/app/modules/recipes/mix/add.html @@ -0,0 +1,6 @@ + + diff --git a/src/app/modules/recipes/mix/form.html b/src/app/modules/recipes/mix/form.html new file mode 100644 index 0000000..0dd72c3 --- /dev/null +++ b/src/app/modules/recipes/mix/form.html @@ -0,0 +1,8 @@ + + + + + diff --git a/src/app/modules/recipes/mix/info-form.html b/src/app/modules/recipes/mix/info-form.html new file mode 100644 index 0000000..915b4e1 --- /dev/null +++ b/src/app/modules/recipes/mix/info-form.html @@ -0,0 +1,10 @@ + + + Ajouter un mélange à la couleur {{recipe.company.name}} - {{recipe.name}} + + + + + + + diff --git a/src/app/modules/recipes/mix/materials-form.html b/src/app/modules/recipes/mix/materials-form.html new file mode 100644 index 0000000..49baca8 --- /dev/null +++ b/src/app/modules/recipes/mix/materials-form.html @@ -0,0 +1,44 @@ + + + Position + {{mixMaterial.position + 1}} + + + + + + + + + + + + Produit + + + + + + + + Quantité + + + + + + + + Ajouter + + + Retirer + + + diff --git a/src/app/modules/recipes/mix/materials-form.ts b/src/app/modules/recipes/mix/materials-form.ts new file mode 100644 index 0000000..762bff8 --- /dev/null +++ b/src/app/modules/recipes/mix/materials-form.ts @@ -0,0 +1,162 @@ +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"; + +@Component({ + selector: 'cre-mix-materials-form', + templateUrl: 'materials-form.html' +}) +export class MixMaterialsForm implements AfterViewInit, OnDestroy { + @ViewChild(CreTable) table: CreTable + + @Input() materials: Observable + + mixMaterials: MixMaterialDto[] = [] + columns = ['position', 'positionButtons', 'material', 'quantity', 'endButton'] + + private _allMaterials: Material[] + private _controls: ControlsByPosition[] = [] + private _availableMaterialsEntries: MaterialEntriesByPosition[] = [] + private _destroy$ = new Subject() + + constructor( + private cdRef: ChangeDetectorRef + ) { + } + + ngAfterViewInit() { + this.materials.subscribe({ + next: materials => { + this._allMaterials = materials + + this.addRow() + this.cdRef.detectChanges() + } + }) + } + + ngOnDestroy() { + this._destroy$.next(true) + this._destroy$.complete() + } + + addRow() { + const position = this.nextPosition; + const mixMaterial = new MixMaterialDto(0, 0, false, position); + + const materialIdControl = new FormControl(null, Validators.required) + const quantityControl = 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 + this.refreshAvailableMaterials() + } + }) + + this._controls.push({ + position, + controls: { + materialId: materialIdControl, + quantity: quantityControl + } + }) + this._availableMaterialsEntries.push({ + position, + entries: this.filterMaterials(mixMaterial) + }) + this.mixMaterials.push(mixMaterial) + this.table.renderRows() + } + + removeRow(mixMaterial: MixMaterialDto) { + this.mixMaterials = this.mixMaterials.filter(x => x.position != mixMaterial.position) + + for (let position = mixMaterial.position + 1; position < this.mixMaterials.length; position++) { + this.updatePosition(this.getMixMaterialByPosition(position), position - 1, false) + } + } + + updatePosition(mixMaterial: MixMaterialDto, newPosition: number, switchPositions = true) { + const currentPosition = mixMaterial.position + + this.getControlsByPosition(currentPosition).position = newPosition + this.getMaterialEntriesByPosition(currentPosition).position = newPosition + + if (switchPositions) { + this.updatePosition(this.getMixMaterialByPosition(newPosition), currentPosition, false) + } + + mixMaterial.position = newPosition + this.sortTable() + } + + getControls(position: number): MixMaterialControls { + return this.getControlsByPosition(position).controls + } + + getAvailableMaterialEntries(position: number): CreInputEntry[] { + return this.getMaterialEntriesByPosition(position).entries + } + + get materialCount(): number { + return this._allMaterials ? this._allMaterials.length : 0; + } + + private get nextPosition(): number { + return this.mixMaterials.length + } + + private getMixMaterialByPosition(position: number): MixMaterialDto { + return this.mixMaterials.filter(x => x.position === position)[0] + } + + private getControlsByPosition(position: number): ControlsByPosition { + return this._controls.filter(control => control.position === position)[0] + } + + private getMaterialEntriesByPosition(position: number): MaterialEntriesByPosition { + return this._availableMaterialsEntries.filter(control => control.position === position)[0] + } + + private refreshAvailableMaterials() { + this.mixMaterials + .sort((a, b) => a.position - b.position) + .forEach(mixMaterial => { + this.getMaterialEntriesByPosition(mixMaterial.position).entries = this.filterMaterials(mixMaterial) + }) + } + + private sortTable() { + this.mixMaterials = sortMixMaterialsDto(this.mixMaterials) + this.table.renderRows() + } + + 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)) + } +} + +interface MixMaterialControls { + materialId: FormControl + quantity: FormControl +} + +interface ControlsByPosition { + position: number + controls: MixMaterialControls +} + +interface MaterialEntriesByPosition { + position: number + entries: CreInputEntry[] +} diff --git a/src/app/modules/recipes/mix/mix.ts b/src/app/modules/recipes/mix/mix.ts new file mode 100644 index 0000000..7bdb95b --- /dev/null +++ b/src/app/modules/recipes/mix/mix.ts @@ -0,0 +1,105 @@ +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"; + +@Component({ + selector: 'cre-mix-add', + templateUrl: 'add.html' +}) +export class MixAdd extends SubscribingComponent { + materialTypes$ = this.materialTypeService.all + materials$: Observable + + private _recipe: Recipe | null + + constructor( + private recipeService: RecipeService, + private materialTypeService: MaterialTypeService, + private materialService: MaterialService, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + } + + ngOnInit() { + this.fetchRecipe() + } + + private fetchRecipe() { + const recipeId = this.urlUtils.parseIntUrlParam('recipeId') + + this.subscribe( + this.recipeService.getById(recipeId), + recipe => this.recipe = recipe + ) + } + + set recipe(recipe: Recipe) { + this._recipe = recipe; + this.materials$ = this.materialService.getAllForMixCreation(recipe.id) + } + + get recipe(): 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 { + @Input() recipe: Recipe + @Input() materialTypes: Observable + + materialTypeEntries: Observable + controls: any + + ngOnInit() { + this.materialTypeEntries = this.materialTypes.pipe( + map(materialTypes => { + return materialTypes.map(materialType => new CreInputEntry(materialType.id, materialType.name)) + }) + ) + + this.controls = { + name: new FormControl(null, Validators.required), + materialType: new FormControl(null, Validators.required) + } + } + + get mixName(): string { + return this.controls.name.value + } + + get mixMaterialTypeId(): number { + return this.controls.materialType.value + } +} + + diff --git a/src/app/modules/recipes/recipes-routing.module.ts b/src/app/modules/recipes/recipes-routing.module.ts index f258782..75657d8 100644 --- a/src/app/modules/recipes/recipes-routing.module.ts +++ b/src/app/modules/recipes/recipes-routing.module.ts @@ -5,6 +5,7 @@ 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'; import {RecipeList} from './list'; +import {MixAdd} from "./mix/mix"; const routes: Routes = [{ path: 'list', @@ -17,7 +18,7 @@ const routes: Routes = [{ component: RecipeEdit }, { path: 'add/mix/:recipeId', - component: MixAddComponent + component: MixAdd }, { path: 'edit/mix/:recipeId/:id', component: MixEditComponent diff --git a/src/app/modules/recipes/recipes.module.ts b/src/app/modules/recipes/recipes.module.ts index 3ba16c2..2579711 100644 --- a/src/app/modules/recipes/recipes.module.ts +++ b/src/app/modules/recipes/recipes.module.ts @@ -21,6 +21,9 @@ import {CreButtonsModule} from '../shared/components/buttons/buttons.module'; import {RecipeAdd, RecipeEdit, RecipeForm} from './recipes'; import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module'; import {RecipeList} from './list'; +import {MixAdd, MixForm, MixInfoForm} from "./mix/mix"; +import {CreTablesModule} from "../shared/components/tables/tables.module"; +import {MixMaterialsForm} from "./mix/materials-form"; @NgModule({ declarations: [ @@ -38,7 +41,11 @@ import {RecipeList} from './list'; RecipeForm, RecipeAdd, RecipeEdit, - RecipeList + RecipeList, + MixAdd, + MixForm, + MixInfoForm, + MixMaterialsForm ], exports: [ UnitSelectorComponent @@ -51,7 +58,8 @@ import {RecipeList} from './list'; MatSortModule, CreInputsModule, CreButtonsModule, - CreActionBarModule + CreActionBarModule, + CreTablesModule ] }) export class RecipesModule { diff --git a/src/app/modules/shared/components/inputs/combo-box.html b/src/app/modules/shared/components/inputs/combo-box.html index 7df0d9f..96decd8 100644 --- a/src/app/modules/shared/components/inputs/combo-box.html +++ b/src/app/modules/shared/components/inputs/combo-box.html @@ -16,8 +16,8 @@ - - {{entry.display || entry.value}} + + {{entry.value}} diff --git a/src/app/modules/shared/components/inputs/inputs.ts b/src/app/modules/shared/components/inputs/inputs.ts index dd74af5..0e7811f 100644 --- a/src/app/modules/shared/components/inputs/inputs.ts +++ b/src/app/modules/shared/components/inputs/inputs.ts @@ -1,5 +1,5 @@ import { - AfterViewInit, + AfterViewInit, ChangeDetectorRef, Component, ContentChild, Directive, @@ -52,6 +52,12 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit { fieldRequired = false + constructor( + private cdRef: ChangeDetectorRef + ) { + super(); + } + ngAfterViewInit() { const element = this.input.nativeElement element.type = this.type @@ -60,6 +66,8 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit { element.autocomplete = this.autocomplete ? 'on' : 'off' this.fieldRequired = this.control ? this.control.validator && this.control.validator({} as AbstractControl)?.required : this.required + + this.cdRef.detectChanges() } } @@ -148,12 +156,11 @@ export class CreChipInputComponent implements OnInit { templateUrl: 'combo-box.html', encapsulation: ViewEncapsulation.None }) -export class CreComboBoxComponent implements OnInit { +export class CreComboBoxComponent { @Input() control: AbstractControl @Input() label: string @Input() icon: string @Input() required = true - @Input() entries: Observable @ContentChild(TemplateRef) errors: TemplateRef @@ -162,22 +169,38 @@ export class CreComboBoxComponent implements OnInit { private _destroy$ = new Subject(); private _entries: CreInputEntry[] + private _controlsInitialized = false - ngOnInit() { - this.entries.pipe(takeUntil(this._destroy$)) - .subscribe({ - next: entries => { - this._entries = entries - - if (this.control.value) { - this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) + @Input() + set entries(entries: Observable | CreInputEntry[]) { + if (isObservable(this.entries)) { + (entries as Observable).pipe(takeUntil(this._destroy$)) + .subscribe({ + next: entries => { + this._entries = entries } + }) + } else { + this._entries = (entries as CreInputEntry[]) + } - if (this.control.disabled) { - this.internalControl.disable() - } - } - }) + this.initControls() + } + + getEntries(): CreInputEntry[] { + return this._entries + } + + private initControls() { + if (this._controlsInitialized) return + + if (this.control.value) { + this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) + } + + if (this.control.disabled) { + this.internalControl.disable() + } this.internalControl = new FormControl({ value: null, @@ -193,6 +216,8 @@ export class CreComboBoxComponent implements OnInit { } } }) + + this._controlsInitialized = true } private findEntryByKey(key: any): CreInputEntry | null { @@ -399,13 +424,9 @@ export class CreSliderInputComponent { selector: 'cre-select', templateUrl: 'select.html' }) -export class CreSelectComponent extends _CreInputBase implements AfterViewInit { +export class CreSelectComponent extends _CreInputBase { @Input() entries: CreInputEntry[] | Observable - ngAfterViewInit(): void { - console.log(this.entries) - } - get entriesAreObservable(): boolean { return isObservable(this.entries) } diff --git a/src/app/modules/shared/components/tables/position-buttons.html b/src/app/modules/shared/components/tables/position-buttons.html new file mode 100644 index 0000000..cb85502 --- /dev/null +++ b/src/app/modules/shared/components/tables/position-buttons.html @@ -0,0 +1,17 @@ + + + + diff --git a/src/app/modules/shared/components/tables/tables.module.ts b/src/app/modules/shared/components/tables/tables.module.ts index 2e9576d..1b67ca8 100644 --- a/src/app/modules/shared/components/tables/tables.module.ts +++ b/src/app/modules/shared/components/tables/tables.module.ts @@ -1,20 +1,26 @@ import {NgModule} from '@angular/core' import {MatTableModule} from '@angular/material/table' import {CommonModule} from '@angular/common' -import {CreInteractiveCell, CreTable} from './tables' +import {CreInteractiveCell, CrePositionButtons, CreTable} from './tables' +import {MatButtonModule} from "@angular/material/button"; +import {MatIconModule} from "@angular/material/icon"; @NgModule({ declarations: [ CreTable, - CreInteractiveCell + CreInteractiveCell, + CrePositionButtons ], imports: [ MatTableModule, - CommonModule + CommonModule, + MatButtonModule, + MatIconModule ], exports: [ CreTable, CreInteractiveCell, + CrePositionButtons ] }) export class CreTablesModule { diff --git a/src/app/modules/shared/components/tables/tables.ts b/src/app/modules/shared/components/tables/tables.ts index 59cec5c..b98a7a3 100644 --- a/src/app/modules/shared/components/tables/tables.ts +++ b/src/app/modules/shared/components/tables/tables.ts @@ -2,9 +2,9 @@ import { AfterContentInit, Component, ContentChildren, - Directive, + Directive, EventEmitter, HostBinding, - Input, + Input, Output, QueryList, ViewChild, ViewEncapsulation @@ -80,4 +80,31 @@ export class CreTable implements AfterContentInit { this.interactiveCells.forEach(cell => cell.selectedIndex = index) } } + + renderRows() { + this.table.renderRows() + } +} + +@Component({ + selector: 'cre-table-position-buttons', + templateUrl: 'position-buttons.html' +}) +export class CrePositionButtons { + @Input() position = 0 + @Input() min = 0 + @Input() max: number + @Input() hidden = false + + @Output() positionChange = new EventEmitter(); + + increasePosition() { + this.position += 1; + this.positionChange.emit(this.position) + } + + decreasePosition() { + this.position -= 1; + this.positionChange.emit(this.position) + } }