Ajout du lustre et de la couleur des recettes

This commit is contained in:
FyloZ 2021-03-10 16:58:56 -05:00
parent 6019b9e718
commit 659508eb8f
21 changed files with 298 additions and 83 deletions

View File

@ -1,6 +1,12 @@
<div class="recipe-info-wrapper d-flex flex-column">
<div class="d-flex flex-column">
<div class="d-flex flex-row">
<h3>{{recipe.company.name}} - {{recipe.name}}</h3>
<div
class="recipe-color-circle"
[class.dark-mode]="darkColor"
[style.backgroundColor]="recipe.color">
<div>{{recipe.gloss}}%</div>
</div>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-column">

View File

@ -3,7 +3,7 @@
color: white
padding: 1rem
div
div:not(.recipe-color-circle)
margin-right: 3rem
p

View File

@ -1,5 +1,5 @@
import {AfterViewInit, Component, Input} from '@angular/core';
import {Recipe} from "../../../shared/model/recipe.model";
import {AfterViewInit, Component, Input} from '@angular/core'
import {Recipe} from '../../../shared/model/recipe.model'
@Component({
selector: 'cre-recipe-info',
@ -13,6 +13,19 @@ export class RecipeInfoComponent implements AfterViewInit {
isBPacExtensionInstalled = false
ngAfterViewInit(): void {
this.isBPacExtensionInstalled = document.querySelectorAll(".bpac-extension-installed").length > 0
this.isBPacExtensionInstalled = document.querySelectorAll('.bpac-extension-installed').length > 0
}
get darkColor(): boolean {
// https://stackoverflow.com/questions/12043187/how-to-check-if-hex-color-is-too-black
const c = this.recipe.color.substring(1) // strip #
const rgb = parseInt(c, 16) // convert rrggbb to decimal
const r = (rgb >> 16) & 0xff // extract red
const g = (rgb >> 8) & 0xff // extract green
const b = (rgb >> 0) & 0xff // extract blue
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b // per ITU-R BT.709
return luma < 100
}
}

View File

@ -35,6 +35,29 @@ export class AddComponent extends ErrorHandlingComponent {
{conditionFn: errors => errors.required, message: 'Une description est requise'}
]
},
{
name: 'color',
label: 'Couleur',
icon: 'palette',
type: 'color',
defaultValue: "#ffffff",
validator: Validators.required,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Une couleur est requise'}
]
},
{
name: 'gloss',
label: 'Lustre',
type: 'slider',
min: 0,
max: 100,
defaultValue: 10,
validator: Validators.required,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'}
]
},
{
name: 'sample',
label: 'Échantillon',
@ -85,7 +108,7 @@ export class AddComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribe(
this.recipeService.save(values.name, values.description, values.sample, values.approbationDate, values.remark, values.company),
this.recipeService.save(values.name, values.description, values.color, values.gloss, values.sample, values.approbationDate, values.remark, values.company),
recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`)
)
}

View File

@ -3,7 +3,7 @@
<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="accent" (click)="submit(editComponent)">Enregistrer</button>
<button mat-raised-button color="accent" (click)="submit(editComponent)" [disabled]="editComponent.form && editComponent.form.invalid">Enregistrer</button>
<button mat-raised-button color="warn" *ngIf="hasDeletePermission" (click)="delete()">Supprimer</button>
</div>
<mat-form-field>

View File

@ -45,6 +45,27 @@ export class EditComponent extends ErrorHandlingComponent {
{conditionFn: errors => errors.required, message: 'Une description est requise'}
]
},
{
name: 'color',
label: 'Couleur',
icon: 'palette',
type: 'color',
validator: Validators.required,
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',
@ -123,7 +144,7 @@ export class EditComponent extends ErrorHandlingComponent {
const values = editComponent.values
this.submittedValues = values
this.subscribeAndNavigate(
this.recipeService.update(this.recipe.id, values.name, values.description, values.sample, values.approbationDate, values.remark, this.recipe.steps),
this.recipeService.update(this.recipe.id, values.name, values.description, values.color, values.gloss, values.sample, values.approbationDate, values.remark, this.recipe.steps),
'/color/list'
)
}

View File

@ -37,6 +37,18 @@
<td mat-cell *matCellDef="let recipe">{{recipe.description}}</td>
</ng-container>
<ng-container matColumnDef="color">
<th mat-header-cell *matHeaderCellDef>Couleur</th>
<td mat-cell *matCellDef="let recipe">
<div class="recipe-color-circle" [class.light-mode]="isLightColor(recipe)" [style.backgroundColor]="recipe.color"></div>
</td>
</ng-container>
<ng-container matColumnDef="gloss">
<th mat-header-cell *matHeaderCellDef>Lustre</th>
<td mat-cell *matCellDef="let recipe">{{recipe.gloss}}%</td>
</ng-container>
<ng-container matColumnDef="sample">
<th mat-header-cell *matHeaderCellDef>Échantillon</th>
<td mat-cell *matCellDef="let recipe">

View File

@ -4,3 +4,6 @@ mat-expansion-panel
.button-add
margin-top: .8rem
.recipe-color-circle
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12)

View File

@ -14,7 +14,7 @@ import {ErrorModel, ErrorService} from '../../../shared/service/error.service'
})
export class ListComponent extends ErrorHandlingComponent {
recipes$ = this.recipeService.allSortedByCompany
tableCols = ['name', 'description', 'sample', 'iconNotApproved', 'buttonView', 'buttonEdit']
tableCols = ['name', 'description', 'color', 'gloss', 'sample', 'iconNotApproved', 'buttonView', 'buttonEdit']
searchQuery = ""
panelForcedExpanded = false
recipesHidden = []
@ -50,6 +50,18 @@ export class ListComponent extends ErrorHandlingComponent {
return (this.searchQuery && this.searchQuery.length > 0) && companyRecipes.map(r => this.recipesHidden[r.id]).filter(r => !r).length <= 0
}
isLightColor(recipe: Recipe): boolean {
// https://stackoverflow.com/questions/12043187/how-to-check-if-hex-color-is-too-black
const c = recipe.color.substring(1) // strip #
const rgb = parseInt(c, 16) // convert rrggbb to decimal
const r = (rgb >> 16) & 0xff // extract red
const g = (rgb >> 8) & 0xff // extract green
const b = (rgb >> 0) & 0xff // extract blue
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b // per ITU-R BT.709
return luma > 200
}
get hasEditPermission(): boolean {
return this.accountService.hasPermission(EmployeePermission.EDIT_RECIPE)
}

View File

@ -1,8 +1,8 @@
import {Injectable} from '@angular/core';
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 {Injectable} from '@angular/core'
import {ApiService} from '../../shared/service/api.service'
import {Observable} from 'rxjs'
import {Recipe, RecipeStep} from '../../shared/model/recipe.model'
import {map} from 'rxjs/operators'
@Injectable({
providedIn: 'root'
@ -34,8 +34,8 @@ export class RecipeService {
return this.api.get<Recipe>(`/recipe/${id}`)
}
save(name: string, description: string, sample: number, approbationDate: string, remark: string, companyId: number): Observable<Recipe> {
const body = {name, description, sample, remark, companyId}
save(name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, companyId: number): Observable<Recipe> {
const body = {name, description, color, gloss, sample, remark, companyId}
if (approbationDate) {
// @ts-ignore
body.approbationDate = approbationDate
@ -43,8 +43,8 @@ export class RecipeService {
return this.api.post<Recipe>('/recipe', body)
}
update(id: number, name: string, description: string, sample: number, approbationDate: string, remark: string, steps: RecipeStep[] = null) {
const body = {id, name, description, sample, remark, steps}
update(id: number, name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, steps: RecipeStep[] = null) {
const body = {id, name, description, color, gloss, sample, remark, steps}
if (approbationDate) {
// @ts-ignore
body.approbationDate = approbationDate
@ -85,7 +85,9 @@ export class RecipeService {
body.quantities[mix.id][m.material.id] = quantities.get(m.material.id)
} else {
let quantity = m.quantity
if (m.material.materialType.usePercentages) quantity = body.quantities[mix.id][firstMaterial] * (quantity / 100)
if (m.material.materialType.usePercentages) {
quantity = body.quantities[mix.id][firstMaterial] * (quantity / 100)
}
body.quantities[mix.id][m.material.id] = quantity
}
})

View File

@ -6,7 +6,7 @@
<form *ngIf="form" [formGroup]="form">
<ng-container *ngFor="let field of formFields">
<ng-container
*ngIf="field.type != 'checkbox' && field.type != 'select' && field.type != 'file'"
*ngIf="field.type != 'checkbox' && field.type != 'select' && field.type != 'file' && field.type != 'slider'"
[ngTemplateOutlet]="fieldTemplate"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
@ -25,6 +25,11 @@
[ngTemplateOutlet]="fileTemplate"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
<ng-container
*ngIf="field.type == 'slider'"
[ngTemplateOutlet]="sliderTemplate"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
</ng-container>
</form>
</mat-card-content>
@ -79,3 +84,13 @@
<ngx-mat-file-input [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
</mat-form-field>
</ng-template>
<ng-template
#sliderTemplate
let-control="control" let-field="field">
<cre-slider-field
[control]="control"
[field]="field"
[percents]="true">
</cre-slider-field>
</ng-template>

View File

@ -23,7 +23,7 @@ export class EntityAddComponent {
ngOnInit() {
const formGroup = {}
this.formFields.forEach(f => {
formGroup[f.name] = new FormControl(null, f.validator)
formGroup[f.name] = new FormControl(f.defaultValue, f.validator)
})
this.form = this.formBuilder.group(formGroup)
}
@ -52,10 +52,13 @@ export class FormField {
public valueFn?: (any) => any,
public template?: any,
public readonly?: boolean,
public defaultValue?: any,
// Specifics to some types
public step?: string,
public options$?: Observable<{ value: any, label: string }[]>,
public fileType?: string
public fileType?: string,
public min?: number,
public max?: number
) {
}
}

View File

@ -6,7 +6,7 @@
<form [formGroup]="form">
<ng-container *ngFor="let field of formFields">
<ng-container
*ngIf="!field.template && field.type != 'checkbox' && field.type != 'select' && field.type != 'file'"
*ngIf="!field.template && field.type != 'checkbox' && field.type != 'select' && field.type != 'file' && field.type != 'slider'"
[ngTemplateOutlet]="fieldTemplate"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
@ -24,6 +24,11 @@
[ngTemplateOutlet]="field.template"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
<ng-container
*ngIf="field.type == 'slider'"
[ngTemplateOutlet]="sliderTemplate"
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
</ng-container>
</ng-container>
</form>
</mat-card-content>
@ -74,5 +79,14 @@
</mat-form-field>
</ng-template>
<ng-template
#sliderTemplate
let-control="control" let-field="field">
<cre-slider-field
[control]="control"
[field]="field"
[percents]="true">
</cre-slider-field>
</ng-template>
<cre-confirm-box #confirmBoxComponent [message]="deleteConfirmMessage" (confirm)="delete.emit()"></cre-confirm-box>

View File

@ -0,0 +1,13 @@
<div>
<p class="slider-label">{{label}}</p>
<mat-slider
*ngIf="control"
[min]="min"
[max]="max"
[value]="control.value"
[disabled]="control.disabled"
[thumbLabel]="thumbLabel"
[displayWith]="formatValueForDisplay"
(valueChange)="control.setValue($event)">
</mat-slider>
</div>

View File

@ -0,0 +1,4 @@
.slider-label
color: rgba(0, 0, 0, 0.54)
font-size: 10px
margin-bottom: -14px

View File

@ -0,0 +1,37 @@
import {Component, Input, OnInit} from '@angular/core'
import {FormControl} from '@angular/forms'
import {FormField} from '../entity-add/entity-add.component'
@Component({
selector: 'cre-slider-field',
templateUrl: './slider-field.component.html',
styleUrls: ['./slider-field.component.sass']
})
export class SliderFieldComponent implements OnInit {
@Input() control: FormControl
@Input() field: FormField | null
@Input() label: string
@Input() min: number
@Input() max: number
@Input() step = 1
@Input() percents = false
@Input() thumbLabel = true
ngOnInit(): void {
if (this.field) {
if (!this.label) {
this.label = this.field.label
}
if (!this.min) {
this.min = this.field.min
}
if (!this.max) {
this.max = this.field.max
}
}
}
formatValueForDisplay(value: number): string {
return this.percents ? `${value}%` : value.toString()
}
}

View File

@ -7,6 +7,8 @@ export class Recipe {
public id: number,
public name: string,
public description: string,
public color: string,
public gloss: number,
public sample: number,
public approbationDate: LocalDate,
public remark: string,

View File

@ -5,7 +5,7 @@ import {environment} from '../../../../environments/environment'
import {AppState} from '../app-state'
import {Router} from '@angular/router'
import {map, share, takeUntil} from 'rxjs/operators'
import {valueOr} from '../utils/optional.utils'
import {valueOr} from '../utils/utils'
import {ErrorService} from './error.service'
@Injectable({

View File

@ -1,64 +1,67 @@
import {NgModule} from '@angular/core';
import {HeaderComponent} from './components/header/header.component';
import {MatTabsModule} from "@angular/material/tabs";
import {MatCardModule} from "@angular/material/card";
import {MatButtonModule} from "@angular/material/button";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatIconModule} from "@angular/material/icon";
import {ReactiveFormsModule} from "@angular/forms";
import {EmployeeInfoComponent} from './components/employee-info/employee-info.component';
import {LabeledIconComponent} from './components/labeled-icon/labeled-icon.component';
import {MatTableModule} from "@angular/material/table";
import {CommonModule} from "@angular/common";
import {HttpClientModule} from "@angular/common/http";
import {MatCheckboxModule} from "@angular/material/checkbox";
import {MatListModule} from "@angular/material/list";
import {ConfirmBoxComponent} from './components/confirm-box/confirm-box.component';
import {PermissionsListComponent} from './components/permissions-list/permissions-list.component';
import {MatChipsModule} from "@angular/material/chips";
import {PermissionsFieldComponent} from "./components/permissions-field/permissions-field.component";
import {NavComponent} from './components/nav/nav.component';
import {EntityListComponent} from './components/entity-list/entity-list.component';
import {RouterModule} from "@angular/router";
import {EntityAddComponent} from './components/entity-add/entity-add.component';
import {EntityEditComponent} from './components/entity-edit/entity-edit.component';
import {MatSelectModule} from "@angular/material/select";
import {MatOptionModule} from "@angular/material/core";
import {MaterialFileInputModule} from "ngx-material-file-input";
import { FileButtonComponent } from './file-button/file-button.component';
import { GlobalAlertHandlerComponent } from './components/global-alert-handler/global-alert-handler.component';
import {NgModule} from '@angular/core'
import {HeaderComponent} from './components/header/header.component'
import {MatTabsModule} from '@angular/material/tabs'
import {MatCardModule} from '@angular/material/card'
import {MatButtonModule} from '@angular/material/button'
import {MatFormFieldModule} from '@angular/material/form-field'
import {MatInputModule} from '@angular/material/input'
import {MatIconModule} from '@angular/material/icon'
import {FormsModule, ReactiveFormsModule} from '@angular/forms'
import {EmployeeInfoComponent} from './components/employee-info/employee-info.component'
import {LabeledIconComponent} from './components/labeled-icon/labeled-icon.component'
import {MatTableModule} from '@angular/material/table'
import {CommonModule} from '@angular/common'
import {HttpClientModule} from '@angular/common/http'
import {MatCheckboxModule} from '@angular/material/checkbox'
import {MatListModule} from '@angular/material/list'
import {ConfirmBoxComponent} from './components/confirm-box/confirm-box.component'
import {PermissionsListComponent} from './components/permissions-list/permissions-list.component'
import {MatChipsModule} from '@angular/material/chips'
import {PermissionsFieldComponent} from './components/permissions-field/permissions-field.component'
import {NavComponent} from './components/nav/nav.component'
import {EntityListComponent} from './components/entity-list/entity-list.component'
import {RouterModule} from '@angular/router'
import {EntityAddComponent} from './components/entity-add/entity-add.component'
import {EntityEditComponent} from './components/entity-edit/entity-edit.component'
import {MatSelectModule} from '@angular/material/select'
import {MatOptionModule} from '@angular/material/core'
import {MaterialFileInputModule} from 'ngx-material-file-input'
import {FileButtonComponent} from './file-button/file-button.component'
import {GlobalAlertHandlerComponent} from './components/global-alert-handler/global-alert-handler.component'
import {MatSliderModule} from '@angular/material/slider';
import { SliderFieldComponent } from './components/slider-field/slider-field.component'
@NgModule({
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent],
exports: [
CommonModule,
HttpClientModule,
HeaderComponent,
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatTableModule,
MatCheckboxModule,
MatListModule,
MatSelectModule,
MatOptionModule,
MaterialFileInputModule,
ReactiveFormsModule,
LabeledIconComponent,
ConfirmBoxComponent,
PermissionsListComponent,
PermissionsFieldComponent,
NavComponent,
EntityListComponent,
EntityAddComponent,
EntityEditComponent,
FileButtonComponent,
GlobalAlertHandlerComponent
],
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent],
exports: [
CommonModule,
HttpClientModule,
HeaderComponent,
MatCardModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatTableModule,
MatCheckboxModule,
MatListModule,
MatSelectModule,
MatOptionModule,
MatSliderModule,
MaterialFileInputModule,
ReactiveFormsModule,
LabeledIconComponent,
ConfirmBoxComponent,
PermissionsListComponent,
PermissionsFieldComponent,
NavComponent,
EntityListComponent,
EntityAddComponent,
EntityEditComponent,
FileButtonComponent,
GlobalAlertHandlerComponent
],
imports: [
MatTabsModule,
MatIconModule,
@ -72,9 +75,11 @@ import { GlobalAlertHandlerComponent } from './components/global-alert-handler/g
MatInputModule,
MatSelectModule,
MatOptionModule,
MatSliderModule,
ReactiveFormsModule,
RouterModule,
CommonModule
CommonModule,
FormsModule
]
})
export class SharedModule {

View File

@ -135,6 +135,9 @@ mat-form-field
.mat-form-field-underline, .mat-form-field-ripple
background-color: $light-primary-text !important
mat-slider
width: 100%
div.empty
color: $dark-secondary-text
margin: auto
@ -155,6 +158,33 @@ div.empty
margin-left: 0
margin-right: 1rem
.recipe-color-circle
color: black
width: 2.2rem
height: 2.2rem
border-radius: 1.1rem
margin-left: 1rem
font-size: 13px
&.dark-mode
color: white
width: 2.3rem
height: 2.3rem
border: solid 1px white
//&.light-mode
// color: black
// width: 2.3rem
// height: 2.3rem
// border: solid 1px black
div
position: absolute
width: 2rem
text-align: center
margin-top: 7px
margin-left: 1px
.alert p
margin-bottom: 0