#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-content [class.no-action]="!editionMode">
<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 class="image-wrapper">

View File

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

View File

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

View File

@ -2,7 +2,13 @@ import {Component} from '@angular/core'
import {RecipeService} from '../../services/recipe.service'
import {ActivatedRoute, Router} from '@angular/router'
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 {ErrorHandler, ErrorService} from '../../../shared/service/error.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 {AccountService} from '../../../accounts/services/account.service'
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({
selector: 'cre-explore',
@ -20,8 +29,6 @@ import {Permission} from '../../../shared/model/user'
styleUrls: ['./explore.component.sass']
})
export class ExploreComponent extends ErrorHandlingComponent {
recipe: Recipe | null
groups$ = this.groupService.all
deductErrorBody = {}
units$ = new Subject<string>()
selectedGroupId: number | null
@ -33,6 +40,12 @@ export class ExploreComponent extends ErrorHandlingComponent {
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[] = [{
filter: error => error.type === 'notfound-recipe-id',
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'
}]
private _recipe: Recipe | null
private _notePlaceholder = !this.canEditRecipesPublicData ? 'N/A' : ''
constructor(
private recipeService: RecipeService,
private inventoryService: InventoryService,
@ -62,18 +78,30 @@ export class ExploreComponent extends ErrorHandlingComponent {
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.recipeService,
id,
r => {
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')
}
}
recipeId,
recipe => this.recipe = recipe
)
}
@ -128,11 +156,24 @@ export class ExploreComponent extends ErrorHandlingComponent {
subscribeDeductMix(observable: Observable<any>) {
this.subscribe(
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
)
}
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 {
return this.appState.authenticatedUser.group?.id
}
@ -141,11 +182,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
if (!this.groupsNote.has(this.selectedGroupId)) {
this.groupsNote.set(this.selectedGroupId, recipeNoteForGroupId(this.recipe, this.selectedGroupId))
}
return this.groupsNote.get(this.selectedGroupId)
}
set selectedGroupNote(value: string) {
this.groupsNote.set(this.selectedGroupId, value)
return this.groupsNote.get(this.selectedGroupId) ?? this._notePlaceholder
}
get canEditRecipesPublicData(): boolean {
@ -160,7 +197,9 @@ export class ExploreComponent extends ErrorHandlingComponent {
})
this.groupsNote.forEach((content, groupId) => {
updatedNotes.set(groupId, content)
if (content) {
updatedNotes.set(groupId, content)
}
})
return updatedNotes

View File

@ -1,6 +1,6 @@
import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component';
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 {RecipeService} from './services/recipe.service';
import {CompanyService} from '../company/service/company.service';
@ -29,7 +29,7 @@ export class RecipeForm extends SubscribingComponent {
@Output() submitForm = new EventEmitter<Recipe>();
controls: any
companyEntries$: Observable<ComboBoxEntry[]>
companyEntries$: Observable<CreInputEntry[]>
hasCompanies = true
constructor(
@ -62,7 +62,7 @@ export class RecipeForm extends SubscribingComponent {
private fetchCompanies() {
this.companyEntries$ = this.companyService.all.pipe(
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 {Recipe, RecipeStep} from '../../shared/model/recipe.model'
import {map} from 'rxjs/operators'
import {Company} from '../../shared/model/company.model';
@Injectable({
providedIn: 'root'

View File

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

View File

@ -4,7 +4,7 @@ import {
CreChipComboBoxComponent,
CreChipInputComponent,
CreComboBoxComponent, CreFileInputComponent,
CreInputComponent, CrePeriodInputComponent, CreSliderInputComponent
CreInputComponent, CrePeriodInputComponent, CreSelectComponent, CreSliderInputComponent, CreTextareaComponent
} from './inputs'
import {MatInputModule} from '@angular/material/input'
import {MatIconModule} from '@angular/material/icon'
@ -29,7 +29,9 @@ import {MatSliderModule} from '@angular/material/slider';
CreFileInputComponent,
CreCheckboxInputComponent,
CrePeriodInputComponent,
CreSliderInputComponent
CreSliderInputComponent,
CreTextareaComponent,
CreSelectComponent
],
imports: [
MatInputModule,
@ -55,7 +57,9 @@ import {MatSliderModule} from '@angular/material/slider';
CreFileInputComponent,
CreCheckboxInputComponent,
CrePeriodInputComponent,
CreSliderInputComponent
CreSliderInputComponent,
CreTextareaComponent,
CreSelectComponent
]
})
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({
selector: 'cre-autocomplete-input',
templateUrl: 'autocomplete.html',
@ -140,7 +153,7 @@ export class CreComboBoxComponent implements OnInit {
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() entries: Observable<ComboBoxEntry[]>
@Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
@ -148,7 +161,7 @@ export class CreComboBoxComponent implements OnInit {
validValue = false
private _destroy$ = new Subject<boolean>();
private _entries: ComboBoxEntry[]
private _entries: CreInputEntry[]
ngOnInit() {
this.entries.pipe(takeUntil(this._destroy$))
@ -160,7 +173,7 @@ export class CreComboBoxComponent implements OnInit {
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
}
if (this.internalControl.disabled) {
if (this.control.disabled) {
this.internalControl.disable()
}
}
@ -168,7 +181,7 @@ export class CreComboBoxComponent implements OnInit {
this.internalControl = new FormControl({
value: null,
disabled: true
disabled: false
}, Validators.compose([this.control.validator, this.valueValidator()]))
this.internalControl.valueChanges.pipe(takeUntil(this._destroy$))
.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)
if (found.length <= 0) {
return null
@ -190,7 +203,7 @@ export class CreComboBoxComponent implements OnInit {
return found[0]
}
private findEntryByValue(value: any): ComboBoxEntry | null {
private findEntryByValue(value: any): CreInputEntry | null {
const found = this._entries.filter(e => e.value === value)
if (found.length <= 0) {
return null
@ -216,15 +229,15 @@ export class CreComboBoxComponent implements OnInit {
encapsulation: ViewEncapsulation.None
})
export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy {
@Input() entries: Observable<ComboBoxEntry[]>
@Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
@ViewChild('chipInput') chipInput: ElementRef<HTMLInputElement>
@ViewChild('auto') matAutocomplete: MatAutocomplete
filteredEntries: Observable<ComboBoxEntry[]>
filteredEntries: Observable<CreInputEntry[]>
private _entries: ComboBoxEntry[]
private _entries: CreInputEntry[]
private _destroy$ = new Subject()
ngOnInit() {
@ -255,7 +268,7 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
return this.selectedValues.length <= 0
}
private _filter(query: string): ComboBoxEntry[] {
private _filter(query: string): CreInputEntry[] {
const filterValue = query.toString().toLowerCase()
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(
public key: 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 {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 {TouchUpKitProductEditor} from './product-editor'
import {FormControl, Validators} from '@angular/forms'
@ -25,7 +25,7 @@ export class TouchUpKitForm extends SubscribingComponent {
controls: any
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(
map(companies => companies.map(company => company.name))