#14 Update recipe explorer

This commit is contained in:
William Nolin 2021-09-21 07:40:36 -04:00
parent 521db72f5e
commit a96062a91f
12 changed files with 145 additions and 96 deletions

View File

@ -4,7 +4,7 @@
</mat-card-header> </mat-card-header>
<mat-card-content [class.no-action]="!editionMode"> <mat-card-content [class.no-action]="!editionMode">
<div class="d-flex flex-row justify-content-around flex-wrap"> <div class="d-flex flex-row justify-content-around flex-wrap">
<p *ngIf="imagesUrls.length <= 0" class="light-text text-center">Aucune image n'est associée à cette couleur</p> <p *ngIf="imagesUrls.length <= 0" class="light-text text-center mb-0">Aucune image n'est associée à cette couleur</p>
<div *ngFor="let imageUrl of imagesUrls" class="d-flex flex-column align-self-center m-3"> <div *ngFor="let imageUrl of imagesUrls" class="d-flex flex-column align-self-center m-3">
<div class="image-wrapper"> <div class="image-wrapper">

View File

@ -3,7 +3,7 @@
<mat-card-title>Étapes</mat-card-title> <mat-card-title>Étapes</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content class="no-action"> <mat-card-content class="no-action">
<mat-list> <mat-list *ngIf="steps.length > 0">
<mat-list-item *ngFor="let step of steps"> <mat-list-item *ngFor="let step of steps">
{{step.position}}.<span class="space"></span>{{step.message}} {{step.position}}.<span class="space"></span>{{step.message}}
</mat-list-item> </mat-list-item>

View File

@ -1,60 +1,26 @@
<div *ngIf="recipe"> <div *ngIf="recipe">
<cre-recipe-info [recipe]="recipe" [hasModifications]="hasModifications"></cre-recipe-info> <cre-recipe-info [recipe]="recipe" [hasModifications]="hasModifications"></cre-recipe-info>
<div class="action-bar backward d-flex flex-row"> <cre-action-bar>
<div class="d-flex flex-column"> <cre-action-group>
<div class="mt-1 pb-2"> <cre-action-group>
<button <cre-primary-button routerLink="/color/list">Retour</cre-primary-button>
mat-raised-button
color="primary"
routerLink="/color/list">
Retour
</button>
<button
mat-raised-button
color="primary"
disabled
title="WIP">
Version Excel
</button>
<button
*ngIf="canEditRecipesPublicData"
mat-raised-button
color="accent"
(click)="saveModifications()"
[disabled]="!hasModifications">
Enregistrer
</button>
</div>
<div>
<cre-unit-selector (unitChange)="changeUnits($event)"></cre-unit-selector> <cre-unit-selector (unitChange)="changeUnits($event)"></cre-unit-selector>
<mat-form-field class="ml-3"> <cre-select [control]="groupControl" label="Group" [entries]="groupEntries$"></cre-select>
<mat-label>Groupe</mat-label> </cre-action-group>
<mat-select [(ngModel)]="selectedGroupId"> <cre-action-group>
<mat-option *ngFor="let group of (groups$ | async)" [value]="group.id"> <cre-textarea [control]="noteControl" [cols]="50" [rows]="canEditRecipesPublicData ? 2 : 1"></cre-textarea>
{{group.name}} </cre-action-group>
</mat-option> </cre-action-group>
</mat-select> <cre-action-group>
</mat-form-field> <cre-primary-button disabled title="WIP">Version Excel</cre-primary-button>
</div> <cre-accent-button *ngIf="canEditRecipesPublicData" [disabled]="!hasModifications" (click)="saveModifications()">
</div> Enregistrer
</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<div class="flex-grow-1"></div> <div class="recipe-content d-flex flex-row justify-content-around align-items-start flex-wrap">
<mat-form-field *ngIf="canEditRecipesPublicData" class="w-auto">
<mat-label>Note</mat-label>
<textarea
matInput
cols="40" rows="3"
[(ngModel)]="selectedGroupNote"
(keyup)="hasModifications = true">
</textarea>
</mat-form-field>
<p *ngIf="!canEditRecipesPublicData">{{selectedGroupNote}}</p>
</div>
<div class="recipe-content d-flex flex-row justify-content-around align-items-start flex-wrap mt-5">
<!-- Mixes --> <!-- Mixes -->
<div *ngIf="recipe.mixes.length > 0"> <div *ngIf="recipe.mixes.length > 0">
<cre-mixes-card <cre-mixes-card

View File

@ -2,7 +2,13 @@ import {Component} from '@angular/core'
import {RecipeService} from '../../services/recipe.service' import {RecipeService} from '../../services/recipe.service'
import {ActivatedRoute, Router} from '@angular/router' import {ActivatedRoute, Router} from '@angular/router'
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
import {MixMaterialDto, Recipe, recipeMixCount, recipeNoteForGroupId, recipeStepCount} from '../../../shared/model/recipe.model' import {
MixMaterialDto,
Recipe,
recipeMixCount,
recipeNoteForGroupId,
recipeStepCount
} from '../../../shared/model/recipe.model'
import {Observable, Subject} from 'rxjs' import {Observable, Subject} from 'rxjs'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service' import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AlertService} from '../../../shared/service/alert.service' import {AlertService} from '../../../shared/service/alert.service'
@ -13,6 +19,9 @@ import {GroupService} from '../../../groups/services/group.service'
import {AppState} from '../../../shared/app-state' import {AppState} from '../../../shared/app-state'
import {AccountService} from '../../../accounts/services/account.service' import {AccountService} from '../../../accounts/services/account.service'
import {Permission} from '../../../shared/model/user' import {Permission} from '../../../shared/model/user'
import {FormControl} from '@angular/forms';
import {map, tap} from 'rxjs/operators';
import {CreInputEntry} from '../../../shared/components/inputs/inputs';
@Component({ @Component({
selector: 'cre-explore', selector: 'cre-explore',
@ -20,8 +29,6 @@ import {Permission} from '../../../shared/model/user'
styleUrls: ['./explore.component.sass'] styleUrls: ['./explore.component.sass']
}) })
export class ExploreComponent extends ErrorHandlingComponent { export class ExploreComponent extends ErrorHandlingComponent {
recipe: Recipe | null
groups$ = this.groupService.all
deductErrorBody = {} deductErrorBody = {}
units$ = new Subject<string>() units$ = new Subject<string>()
selectedGroupId: number | null selectedGroupId: number | null
@ -33,6 +40,12 @@ export class ExploreComponent extends ErrorHandlingComponent {
deductedMixId: number | null deductedMixId: number | null
groupControl: FormControl
noteControl: FormControl
groupEntries$ = this.groupService.all.pipe(map(groups => {
return groups.map(group => new CreInputEntry(group.id, group.name))
}))
errorHandlers: ErrorHandler[] = [{ errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-recipe-id', filter: error => error.type === 'notfound-recipe-id',
consumer: error => this.urlUtils.navigateTo('/color/list') consumer: error => this.urlUtils.navigateTo('/color/list')
@ -42,6 +55,9 @@ export class ExploreComponent extends ErrorHandlingComponent {
messageProducer: () => 'Certains produit ne sont pas en quantité suffisante dans l\'inventaire' messageProducer: () => 'Certains produit ne sont pas en quantité suffisante dans l\'inventaire'
}] }]
private _recipe: Recipe | null
private _notePlaceholder = !this.canEditRecipesPublicData ? 'N/A' : ''
constructor( constructor(
private recipeService: RecipeService, private recipeService: RecipeService,
private inventoryService: InventoryService, private inventoryService: InventoryService,
@ -62,18 +78,30 @@ export class ExploreComponent extends ErrorHandlingComponent {
this.selectedGroupId = this.loggedInUserGroupId this.selectedGroupId = this.loggedInUserGroupId
const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id')) this.fetchRecipe()
this.groupControl = new FormControl(this.selectedGroupId)
this.subscribe(
this.groupControl.valueChanges,
groupId => {
this.selectedGroupId = groupId
this.noteControl.setValue(this.selectedGroupNote, {emitEvent: false})
}
)
this.noteControl = new FormControl({value: this._notePlaceholder, disabled: !this.canEditRecipesPublicData})
this.subscribe(
this.noteControl.valueChanges,
_ => this.hasModifications = true
)
}
fetchRecipe() {
const recipeId = parseInt(this.activatedRoute.snapshot.paramMap.get('id'))
this.subscribeEntityById( this.subscribeEntityById(
this.recipeService, this.recipeService,
id, recipeId,
r => { recipe => this.recipe = recipe
this.recipe = r
this.appState.title = r.name
if (recipeMixCount(this.recipe) <= 0 || recipeStepCount(this.recipe) <= 0) {
this.alertService.pushWarning('Cette recette n\'est pas complète')
}
}
) )
} }
@ -128,11 +156,24 @@ export class ExploreComponent extends ErrorHandlingComponent {
subscribeDeductMix(observable: Observable<any>) { subscribeDeductMix(observable: Observable<any>) {
this.subscribe( this.subscribe(
observable, observable,
() => this.alertService.pushSuccess('Les quantités quantités ont été déduites de l\'inventaire'), () => this.alertService.pushSuccess('Les quantités ont été déduites de l\'inventaire'),
true true
) )
} }
get recipe(): Recipe {
return this._recipe
}
set recipe(recipe: Recipe) {
this._recipe = recipe
this.appState.title = recipe.name
if (recipeMixCount(recipe) <= 0 || recipeStepCount(recipe) <= 0) {
this.alertService.pushWarning('Cette recette n\'est pas complète')
}
}
get loggedInUserGroupId(): number { get loggedInUserGroupId(): number {
return this.appState.authenticatedUser.group?.id return this.appState.authenticatedUser.group?.id
} }
@ -141,11 +182,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
if (!this.groupsNote.has(this.selectedGroupId)) { if (!this.groupsNote.has(this.selectedGroupId)) {
this.groupsNote.set(this.selectedGroupId, recipeNoteForGroupId(this.recipe, this.selectedGroupId)) this.groupsNote.set(this.selectedGroupId, recipeNoteForGroupId(this.recipe, this.selectedGroupId))
} }
return this.groupsNote.get(this.selectedGroupId) return this.groupsNote.get(this.selectedGroupId) ?? this._notePlaceholder
}
set selectedGroupNote(value: string) {
this.groupsNote.set(this.selectedGroupId, value)
} }
get canEditRecipesPublicData(): boolean { get canEditRecipesPublicData(): boolean {
@ -160,7 +197,9 @@ export class ExploreComponent extends ErrorHandlingComponent {
}) })
this.groupsNote.forEach((content, groupId) => { this.groupsNote.forEach((content, groupId) => {
updatedNotes.set(groupId, content) if (content) {
updatedNotes.set(groupId, content)
}
}) })
return updatedNotes return updatedNotes

View File

@ -1,6 +1,6 @@
import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component'; import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component';
import {Observable, Subject} from 'rxjs'; import {Observable, Subject} from 'rxjs';
import {ComboBoxEntry} from '../shared/components/inputs/inputs'; import {CreInputEntry} from '../shared/components/inputs/inputs';
import {map, tap} from 'rxjs/operators'; import {map, tap} from 'rxjs/operators';
import {RecipeService} from './services/recipe.service'; import {RecipeService} from './services/recipe.service';
import {CompanyService} from '../company/service/company.service'; import {CompanyService} from '../company/service/company.service';
@ -29,7 +29,7 @@ export class RecipeForm extends SubscribingComponent {
@Output() submitForm = new EventEmitter<Recipe>(); @Output() submitForm = new EventEmitter<Recipe>();
controls: any controls: any
companyEntries$: Observable<ComboBoxEntry[]> companyEntries$: Observable<CreInputEntry[]>
hasCompanies = true hasCompanies = true
constructor( constructor(
@ -62,7 +62,7 @@ export class RecipeForm extends SubscribingComponent {
private fetchCompanies() { private fetchCompanies() {
this.companyEntries$ = this.companyService.all.pipe( this.companyEntries$ = this.companyService.all.pipe(
tap(companies => this.hasCompanies = companies.length > 0), tap(companies => this.hasCompanies = companies.length > 0),
map(companies => companies.map(c => new ComboBoxEntry(c.id, c.name))), map(companies => companies.map(c => new CreInputEntry(c.id, c.name))),
) )
} }

View File

@ -3,7 +3,6 @@ import {ApiService} from '../../shared/service/api.service'
import {Observable} from 'rxjs' import {Observable} from 'rxjs'
import {Recipe, RecipeStep} from '../../shared/model/recipe.model' import {Recipe, RecipeStep} from '../../shared/model/recipe.model'
import {map} from 'rxjs/operators' import {map} from 'rxjs/operators'
import {Company} from '../../shared/model/company.model';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'

View File

@ -1,3 +1,6 @@
<div> <div class="d-flex flex-column">
<ng-content></ng-content> <div>
<ng-content></ng-content>
</div>
<ng-content select="cre-action-group"></ng-content>
</div> </div>

View File

@ -4,7 +4,7 @@ import {
CreChipComboBoxComponent, CreChipComboBoxComponent,
CreChipInputComponent, CreChipInputComponent,
CreComboBoxComponent, CreFileInputComponent, CreComboBoxComponent, CreFileInputComponent,
CreInputComponent, CrePeriodInputComponent, CreSliderInputComponent CreInputComponent, CrePeriodInputComponent, CreSelectComponent, CreSliderInputComponent, CreTextareaComponent
} from './inputs' } from './inputs'
import {MatInputModule} from '@angular/material/input' import {MatInputModule} from '@angular/material/input'
import {MatIconModule} from '@angular/material/icon' import {MatIconModule} from '@angular/material/icon'
@ -29,7 +29,9 @@ import {MatSliderModule} from '@angular/material/slider';
CreFileInputComponent, CreFileInputComponent,
CreCheckboxInputComponent, CreCheckboxInputComponent,
CrePeriodInputComponent, CrePeriodInputComponent,
CreSliderInputComponent CreSliderInputComponent,
CreTextareaComponent,
CreSelectComponent
], ],
imports: [ imports: [
MatInputModule, MatInputModule,
@ -55,7 +57,9 @@ import {MatSliderModule} from '@angular/material/slider';
CreFileInputComponent, CreFileInputComponent,
CreCheckboxInputComponent, CreCheckboxInputComponent,
CrePeriodInputComponent, CrePeriodInputComponent,
CreSliderInputComponent CreSliderInputComponent,
CreTextareaComponent,
CreSelectComponent
] ]
}) })
export class CreInputsModule { export class CreInputsModule {

View File

@ -63,6 +63,19 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
} }
} }
@Component({
selector: 'cre-textarea',
templateUrl: 'textarea.html',
encapsulation: ViewEncapsulation.None
})
export class CreTextareaComponent {
@Input() label: string
@Input() control: FormControl
@Input() cols = 40
@Input() rows = 3
@Input() placeholder: string | null
}
@Component({ @Component({
selector: 'cre-autocomplete-input', selector: 'cre-autocomplete-input',
templateUrl: 'autocomplete.html', templateUrl: 'autocomplete.html',
@ -140,7 +153,7 @@ export class CreComboBoxComponent implements OnInit {
@Input() label: string @Input() label: string
@Input() icon: string @Input() icon: string
@Input() required = true @Input() required = true
@Input() entries: Observable<ComboBoxEntry[]> @Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any> @ContentChild(TemplateRef) errors: TemplateRef<any>
@ -148,7 +161,7 @@ export class CreComboBoxComponent implements OnInit {
validValue = false validValue = false
private _destroy$ = new Subject<boolean>(); private _destroy$ = new Subject<boolean>();
private _entries: ComboBoxEntry[] private _entries: CreInputEntry[]
ngOnInit() { ngOnInit() {
this.entries.pipe(takeUntil(this._destroy$)) this.entries.pipe(takeUntil(this._destroy$))
@ -160,7 +173,7 @@ export class CreComboBoxComponent implements OnInit {
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
} }
if (this.internalControl.disabled) { if (this.control.disabled) {
this.internalControl.disable() this.internalControl.disable()
} }
} }
@ -168,7 +181,7 @@ export class CreComboBoxComponent implements OnInit {
this.internalControl = new FormControl({ this.internalControl = new FormControl({
value: null, value: null,
disabled: true disabled: false
}, Validators.compose([this.control.validator, this.valueValidator()])) }, Validators.compose([this.control.validator, this.valueValidator()]))
this.internalControl.valueChanges.pipe(takeUntil(this._destroy$)) this.internalControl.valueChanges.pipe(takeUntil(this._destroy$))
.subscribe({ .subscribe({
@ -182,7 +195,7 @@ export class CreComboBoxComponent implements OnInit {
}) })
} }
private findEntryByKey(key: any): ComboBoxEntry | null { private findEntryByKey(key: any): CreInputEntry | null {
const found = this._entries.filter(e => e.key === key) const found = this._entries.filter(e => e.key === key)
if (found.length <= 0) { if (found.length <= 0) {
return null return null
@ -190,7 +203,7 @@ export class CreComboBoxComponent implements OnInit {
return found[0] return found[0]
} }
private findEntryByValue(value: any): ComboBoxEntry | null { private findEntryByValue(value: any): CreInputEntry | null {
const found = this._entries.filter(e => e.value === value) const found = this._entries.filter(e => e.value === value)
if (found.length <= 0) { if (found.length <= 0) {
return null return null
@ -216,15 +229,15 @@ export class CreComboBoxComponent implements OnInit {
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy { export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy {
@Input() entries: Observable<ComboBoxEntry[]> @Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any> @ContentChild(TemplateRef) errors: TemplateRef<any>
@ViewChild('chipInput') chipInput: ElementRef<HTMLInputElement> @ViewChild('chipInput') chipInput: ElementRef<HTMLInputElement>
@ViewChild('auto') matAutocomplete: MatAutocomplete @ViewChild('auto') matAutocomplete: MatAutocomplete
filteredEntries: Observable<ComboBoxEntry[]> filteredEntries: Observable<CreInputEntry[]>
private _entries: ComboBoxEntry[] private _entries: CreInputEntry[]
private _destroy$ = new Subject() private _destroy$ = new Subject()
ngOnInit() { ngOnInit() {
@ -255,7 +268,7 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
return this.selectedValues.length <= 0 return this.selectedValues.length <= 0
} }
private _filter(query: string): ComboBoxEntry[] { private _filter(query: string): CreInputEntry[] {
const filterValue = query.toString().toLowerCase() const filterValue = query.toString().toLowerCase()
return this._entries.filter(e => e.value.toString().toLowerCase().indexOf(filterValue) === 0) return this._entries.filter(e => e.value.toString().toLowerCase().indexOf(filterValue) === 0)
} }
@ -382,7 +395,15 @@ export class CreSliderInputComponent {
} }
} }
export class ComboBoxEntry { @Component({
selector: 'cre-select',
templateUrl: 'select.html'
})
export class CreSelectComponent extends _CreInputBase {
@Input() entries: Observable<CreInputEntry[]>
}
export class CreInputEntry {
constructor( constructor(
public key: any, public key: any,
public value: any, public value: any,

View File

@ -0,0 +1,8 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<mat-select [formControl]="control">
<mat-option *ngFor="let entry of entries | async" [value]="entry.key">
{{entry.display || entry.value}}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -0,0 +1,9 @@
<mat-form-field class="w-auto">
<mat-label>{{label}}</mat-label>
<textarea
matInput
[cols]="cols" [rows]="rows"
[formControl]="control">
{{placeholder}}
</textarea>
</mat-form-field>

View File

@ -1,5 +1,5 @@
import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core' import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'
import {chipListRequired, ComboBoxEntry, CreChipComboBoxComponent} from '../../shared/components/inputs/inputs' import {chipListRequired, CreInputEntry, CreChipComboBoxComponent} from '../../shared/components/inputs/inputs'
import {CreFormComponent} from '../../shared/components/forms/forms' import {CreFormComponent} from '../../shared/components/forms/forms'
import {TouchUpKitProductEditor} from './product-editor' import {TouchUpKitProductEditor} from './product-editor'
import {FormControl, Validators} from '@angular/forms' import {FormControl, Validators} from '@angular/forms'
@ -25,7 +25,7 @@ export class TouchUpKitForm extends SubscribingComponent {
controls: any controls: any
finish$ = this.recipeService.all.pipe( finish$ = this.recipeService.all.pipe(
map(recipes => recipes.map(recipe => new ComboBoxEntry(recipe.id, recipe.name, `${recipe.name} - ${recipe.company.name}`))) map(recipes => recipes.map(recipe => new CreInputEntry(recipe.id, recipe.name, `${recipe.name} - ${recipe.company.name}`)))
) )
companies$ = this.companyService.all.pipe( companies$ = this.companyService.all.pipe(
map(companies => companies.map(company => company.name)) map(companies => companies.map(company => company.name))