develop #5

Merged
william merged 42 commits from develop into master 2021-12-15 00:25:11 -05:00
71 changed files with 419 additions and 284 deletions
Showing only changes of commit c35635c596 - Show all commits

View File

@ -7,7 +7,7 @@ import {CreConfigEditor} from './modules/configuration/config-editor'
const routes: Routes = [{
path: 'color',
loadChildren: () => import('./modules/colors/colors.module').then(m => m.ColorsModule)
loadChildren: () => import('./modules/recipes/recipes.module').then(m => m.RecipesModule)
}, {
path: 'account',
loadChildren: () => import('./modules/accounts/accounts.module').then(m => m.AccountsModule)

View File

@ -1,6 +0,0 @@
<cre-entity-add
title="Création d'une recette"
backButtonLink="/color/list"
[formFields]="formFields"
(submit)="submit($event)">
</cre-entity-add>

View File

@ -1,123 +0,0 @@
import {Component} from '@angular/core'
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
import {RecipeService} from '../../services/recipe.service'
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
import {Validators} from '@angular/forms'
import {CompanyService} from '../../../company/service/company.service'
import {map} from 'rxjs/operators'
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
@Component({
selector: 'cre-add',
templateUrl: './add.component.html',
styleUrls: ['./add.component.sass']
})
export class AddComponent extends ErrorHandlingComponent {
formFields: FormField[] = [
{
name: 'name',
label: 'Nom',
icon: 'form-textbox',
type: 'text',
required: true,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
]
},
{
name: 'description',
label: 'Description',
icon: 'text',
type: 'text',
required: true,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Une description est requise'}
]
},
{
name: 'color',
label: 'Couleur',
icon: 'palette',
type: 'color',
defaultValue: "#ffffff",
required: true,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Une couleur est requise'}
]
},
{
name: 'gloss',
label: 'Lustre',
type: 'slider',
min: 0,
max: 100,
defaultValue: 0,
required: true,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'}
]
},
{
name: 'sample',
label: 'Échantillon',
icon: 'pound',
type: 'number',
validator: Validators.min(0),
errorMessages: [
{conditionFn: errors => errors.required, message: 'Un numéro d\'échantillon est requis'},
{conditionFn: errors => errors.min, message: 'Le numéro d\'échantillon doit être supérieur ou égal à 0'}
]
},
{
name: 'approbationDate',
label: 'Date d\'approbation',
icon: 'calendar',
type: 'date'
},
{
name: 'remark',
label: 'Remarque',
icon: 'text',
type: 'text'
},
{
name: 'company',
label: 'Bannière',
icon: 'domain',
type: 'select',
required: true,
errorMessages: [
{conditionFn: errors => errors.required, message: 'Une bannière est requise'}
],
options$: this.companyService.all.pipe(map(companies => companies.map(c => {
return {value: c.id, label: c.name}
})))
}
]
errorHandlers = [{
filter: error => error.type === `exists-recipe-company-name`,
messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}`
}]
constructor(
private recipeService: RecipeService,
private companyService: CompanyService,
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
this.appState.title = "Nouvelle couleur"
}
submit(values) {
this.subscribe(
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

@ -1,89 +0,0 @@
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 {Company} from '../../shared/model/company.model';
@Injectable({
providedIn: 'root'
})
export class RecipeService {
constructor(
private api: ApiService
) {
}
get all(): Observable<Recipe[]> {
return this.api.get<Recipe[]>('/recipe')
}
getAllByName(name: string): Observable<Recipe[]> {
return this.api.get<Recipe[]>(`/recipe?name=${name}`)
}
get allByCompany(): Observable<Map<number, Recipe[]>> {
return this.all.pipe(map(recipes => {
const map = new Map<number, Recipe[]>()
recipes.forEach(r => {
if (!map.has(r.company.id)) {
map.set(r.company.id, [])
}
map.get(r.company.id).push(r)
})
return map
}))
}
getById(id: number): Observable<Recipe> {
return this.api.get<Recipe>(`/recipe/${id}`)
}
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
}
return this.api.post<Recipe>('/recipe', body)
}
update(id: number, name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, steps: Map<number, RecipeStep[]>) {
const body = {id, name, description, color, gloss, sample, remark, steps: []}
if (approbationDate) {
// @ts-ignore
body.approbationDate = approbationDate
}
steps.forEach((groupSteps, groupId) => {
const mappedGroupSteps = groupSteps.map(s => {
return {message: s.message, position: s.position}
})
body.steps.push({groupId, steps: mappedGroupSteps})
})
return this.api.put<Recipe>('/recipe', body)
}
updateExplorerModifications(id: number, notes: Map<number, string>, mixesLocationChange: Map<number, string>): Observable<void> {
const body = {
recipeId: id,
notes: [],
mixesLocation: []
}
notes.forEach((content, groupId) => {
body.notes.push({groupId, content})
})
mixesLocationChange.forEach((location, mixId) => {
body.mixesLocation.push({mixId, location})
})
return this.api.put<void>('/recipe/public', body)
}
delete(id: number): Observable<void> {
return this.api.delete<void>(`/recipe/${id}`)
}
}

View File

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {ApiService} from "../../shared/service/api.service";
import {Observable} from "rxjs";
import {Company} from "../../shared/model/company.model";
import {ApiService} from '../../shared/service/api.service';
import {Observable} from 'rxjs';
import {Company} from '../../shared/model/company.model';
@Injectable({
providedIn: 'root'

View File

@ -6,7 +6,7 @@ import {InventoryComponent} from './pages/inventory/inventory.component';
import {SharedModule} from "../shared/shared.module";
import {AddComponent} from './pages/add/add.component';
import {EditComponent} from './pages/edit/edit.component';
import {ColorsModule} from '../colors/colors.module'
import {RecipesModule} from '../recipes/recipes.module'
import {MatSortModule} from '@angular/material/sort'
import {FormsModule} from '@angular/forms'
@ -17,7 +17,7 @@ import {FormsModule} from '@angular/forms'
CommonModule,
MaterialRoutingModule,
SharedModule,
ColorsModule,
RecipesModule,
MatSortModule,
FormsModule
]

View File

@ -0,0 +1 @@
<recipe-form (submitForm)="submit($event)"></recipe-form>

View File

@ -0,0 +1,24 @@
<div class="mt-5">
<cre-warning-alert *ngIf="!loading && !hasCompanies">
<p>Il n'y a actuellement aucune bannière enregistrée dans le système.</p>
<p *ngIf="hasCompanyEditPermission">Vous pouvez en créer une <b><a routerLink="/catalog/company/add">ici</a></b>.</p>
</cre-warning-alert>
</div>
<cre-form *ngIf="hasCompanies" #form [formControls]="controls" class="mx-auto">
<cre-form-title>Ajouter une recette</cre-form-title>
<cre-form-content>
<cre-input [control]="controls.name" label="Name" icon="form-textbox"></cre-input>
<cre-input [control]="controls.description" label="Description" icon="text"></cre-input>
<cre-input [control]="controls.color" type="color" label="Couleur" icon="palette"></cre-input>
<cre-slider-input [control]="controls.gloss" label="Lustre"></cre-slider-input>
<cre-input [control]="controls.sample" type="number" label="Échantillon" icon="pound"></cre-input>
<cre-input [control]="controls.approbationDate" type="date" label="Date d'approbation" icon="calendar"></cre-input>
<cre-input [control]="controls.remark" label="Remarque" icon="text"></cre-input>
<cre-combo-box [control]="controls.company" label="Bannière" [entries]="companyEntries$"></cre-combo-box>
</cre-form-content>
<cre-form-actions>
<cre-primary-button routerLink="/color/list">Retour</cre-primary-button>
<cre-accent-button [disabled]="form.invalid" (click)="submit()">Enregistrer</cre-accent-button>
</cre-form-actions>
</cre-form>

View File

@ -53,7 +53,7 @@ export class ListComponent extends ErrorHandlingComponent {
}
)
//this.fetchCompanies()
this.fetchCompanies()
this.fetchRecipes()
}

View File

@ -1,18 +1,18 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ListComponent} from "./pages/list/list.component";
import {AddComponent} from "./pages/add/add.component";
import {EditComponent} from "./pages/edit/edit.component";
import {ExploreComponent} from "./pages/explore/explore.component";
import {MixEditComponent} from "./pages/mix/mix-edit/mix-edit.component";
import {MixAddComponent} from "./pages/mix/mix-add/mix-add.component";
import {RecipeAdd} from './recipes';
const routes: Routes = [{
path: 'list',
component: ListComponent
}, {
path: 'add',
component: AddComponent
component: RecipeAdd
}, {
path: 'edit/:id',
component: EditComponent
@ -35,5 +35,5 @@ const routes: Routes = [{
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ColorsRoutingModule {
export class RecipesRoutingModule {
}

View File

@ -1,9 +1,8 @@
import {NgModule} from '@angular/core'
import {ColorsRoutingModule} from './colors-routing.module'
import {RecipesRoutingModule} from './recipes-routing.module'
import {SharedModule} from '../shared/shared.module'
import {ListComponent} from './pages/list/list.component'
import {AddComponent} from './pages/add/add.component'
import {EditComponent} from './pages/edit/edit.component'
import {MatExpansionModule} from '@angular/material/expansion'
import {FormsModule} from '@angular/forms'
@ -19,20 +18,41 @@ import {MixEditComponent} from './pages/mix/mix-edit/mix-edit.component'
import {ImagesEditorComponent} from './components/images-editor/images-editor.component'
import {MixesCardComponent} from './components/mixes-card/mixes-card.component'
import {MatSortModule} from '@angular/material/sort'
import {CreInputsModule} from '../shared/components/inputs/inputs.module';
import {CreButtonsModule} from '../shared/components/buttons/buttons.module';
import {RecipeAdd, RecipeForm} from './recipes';
@NgModule({
declarations: [ListComponent, AddComponent, EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, StepListComponent, StepTableComponent, MixEditorComponent, UnitSelectorComponent, MixAddComponent, MixEditComponent, ImagesEditorComponent, MixesCardComponent],
declarations: [
ListComponent,
EditComponent,
ExploreComponent,
RecipeInfoComponent,
MixTableComponent,
StepListComponent,
StepTableComponent,
MixEditorComponent,
UnitSelectorComponent,
MixAddComponent,
MixEditComponent,
ImagesEditorComponent,
MixesCardComponent,
RecipeForm,
RecipeAdd
],
exports: [
UnitSelectorComponent
],
imports: [
ColorsRoutingModule,
RecipesRoutingModule,
SharedModule,
MatExpansionModule,
FormsModule,
MatSortModule
MatSortModule,
CreInputsModule,
CreButtonsModule
]
})
export class ColorsModule {
export class RecipesModule {
}

View File

@ -0,0 +1,119 @@
import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component';
import {Observable} from 'rxjs';
import {ComboBoxEntry} from '../shared/components/inputs/inputs';
import {map, tap} from 'rxjs/operators';
import {RecipeService} from './services/recipe.service';
import {CompanyService} from '../company/service/company.service';
import {AppState} from '../shared/app-state';
import {ErrorService} from '../shared/service/error.service';
import {ActivatedRoute, Router} from '@angular/router';
import {FormControl, Validators} from '@angular/forms';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {Recipe} from '../shared/model/recipe.model';
import {AccountService} from '../accounts/services/account.service';
import {Permission} from '../shared/model/user';
@Component({
selector: 'cre-recipe-add',
templateUrl: 'add.html'
})
export class RecipeAdd extends ErrorHandlingComponent {
controls: any
companyEntries$: Observable<ComboBoxEntry[]> = this.companyService.all.pipe(
map(companies => companies.map(c => new ComboBoxEntry(c.id, c.name))),
)
errorHandlers = [{
filter: error => error.type === `exists-recipe-company-name`,
messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}`
}]
constructor(
private recipeService: RecipeService,
private companyService: CompanyService,
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
this.appState.title = 'Nouvelle couleur'
}
submit(recipe: Recipe) {
this.subscribe(
this.recipeService.save(recipe),
recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`)
)
}
}
@Component({
selector: 'recipe-form',
templateUrl: 'form.html'
})
export class RecipeForm extends SubscribingComponent {
@Input() recipe: Recipe | null
@Output() submitForm = new EventEmitter<Recipe>();
controls: any
companyEntries$: Observable<ComboBoxEntry[]>
hasCompanies = true
constructor(
private companyService: CompanyService,
private accountService: AccountService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router,
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
super.ngOnInit();
this.fetchCompanies()
this.controls = {
name: new FormControl(null, Validators.required),
description: new FormControl(null, Validators.required),
color: new FormControl('#ffffff', Validators.required),
gloss: new FormControl(0, Validators.compose([Validators.required, Validators.min(0), Validators.max(100)])),
sample: new FormControl(null, Validators.compose([Validators.required, Validators.min(0)])),
approbationDate: new FormControl(null),
remark: new FormControl(null),
company: new FormControl(null, Validators.required)
}
}
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))),
)
}
submit() {
this.submitForm.emit({
id: this.recipe?.id,
name: this.controls.name.value,
description: this.controls.description.value,
color: this.controls.color.value,
gloss: this.controls.gloss.value,
sample: this.controls.sample.value,
approbationDate: this.controls.approbationDate.value,
remark: this.controls.remark.value,
company: this.controls.company.value,
mixes: this.recipe?.mixes,
approbationExpired: this.recipe?.approbationExpired,
groupsInformation: this.recipe?.groupsInformation,
imagesUrls: this.recipe?.imagesUrls
})
}
get hasCompanyEditPermission(): boolean {
return this.accountService.hasPermission(Permission.EDIT_COMPANIES)
}
}

View File

@ -0,0 +1,89 @@
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 {Company} from '../../shared/model/company.model';
@Injectable({
providedIn: 'root'
})
export class RecipeService {
constructor(
private api: ApiService
) {
}
get all(): Observable<Recipe[]> {
return this.api.get<Recipe[]>('/recipe')
}
getAllByName(name: string): Observable<Recipe[]> {
return this.api.get<Recipe[]>(`/recipe?name=${name}`)
}
get allByCompany(): Observable<Map<number, Recipe[]>> {
return this.all.pipe(map(recipes => {
const map = new Map<number, Recipe[]>()
recipes.forEach(r => {
if (!map.has(r.company.id)) {
map.set(r.company.id, [])
}
map.get(r.company.id).push(r)
})
return map
}))
}
getById(id: number): Observable<Recipe> {
return this.api.get<Recipe>(`/recipe/${id}`)
}
save(recipe: Recipe): Observable<Recipe> {
const body = {
...recipe,
companyId: recipe.company
}
return this.api.post<Recipe>('/recipe', body)
}
update(id: number, name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, steps: Map<number, RecipeStep[]>) {
const body = {id, name, description, color, gloss, sample, remark, steps: []}
if (approbationDate) {
// @ts-ignore
body.approbationDate = approbationDate
}
steps.forEach((groupSteps, groupId) => {
const mappedGroupSteps = groupSteps.map(s => {
return {message: s.message, position: s.position}
})
body.steps.push({groupId, steps: mappedGroupSteps})
})
return this.api.put<Recipe>('/recipe', body)
}
updateExplorerModifications(id: number, notes: Map<number, string>, mixesLocationChange: Map<number, string>): Observable<void> {
const body = {
recipeId: id,
notes: [],
mixesLocation: []
}
notes.forEach((content, groupId) => {
body.notes.push({groupId, content})
})
mixesLocationChange.forEach((location, mixId) => {
body.mixesLocation.push({mixId, location})
})
return this.api.put<void>('/recipe/public', body)
}
delete(id: number): Observable<void> {
return this.api.delete<void>(`/recipe/${id}`)
}
}

View File

@ -1,8 +1,9 @@
import {Component} from '@angular/core';
import {Component, ViewEncapsulation} from '@angular/core';
@Component({
selector: 'cre-warning-alert',
templateUrl: 'alerts.html'
templateUrl: 'alerts.html',
encapsulation: ViewEncapsulation.None
})
export class WarningAlert {

View File

@ -1,8 +1,12 @@
cre-form
display: block
width: max-content
min-width: 50rem
margin-top: 3rem
mat-card
width: inherit
min-width: inherit
cre-form-actions
display: flex

View File

@ -42,7 +42,7 @@ export class CreFormComponent implements OnInit {
}
get hasActions(): boolean {
return this.formActions === true
return !!this.formActions
}
get invalid(): boolean {

View File

@ -24,7 +24,7 @@
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
</mat-error>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options | async" [value]="option">
<mat-option *ngFor="let option of entries | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>

View File

@ -30,7 +30,7 @@
</mat-error>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.key">
<mat-option *ngFor="let option of filteredEntries | async" [value]="option.key">
{{option.display ? option.display : option.value}}
</mat-option>
</mat-autocomplete>

View File

@ -4,19 +4,20 @@
matInput
type="text"
[required]="required"
[formControl]="control"
[formControl]="internalControl"
[matAutocomplete]="auto">
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<mat-error *ngIf="internalControl && internalControl.invalid">
<span *ngIf="internalControl.errors.invalidValue">Cette valeur est invalide</span>
<span *ngIf="internalControl.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
[ngTemplateOutletContext]="{errors: internalControl.errors}"></ng-container>
</mat-error>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options | async" [value]="option.key">
{{option.value}}
<mat-option *ngFor="let entry of entries | async" [value]="entry.value">
{{entry.display || entry.value}}
</mat-option>
</mat-autocomplete>
</mat-form-field>

View File

@ -5,6 +5,7 @@
<input
#input
matInput
[required]="isFieldRequired"
[disabled]="disabled"
[(ngModel)]="value"
(change)="valueChange.emit(value)"/>
@ -13,6 +14,7 @@
<input
#input
matInput
[required]="isFieldRequired"
[formControl]="control">
</ng-container>

View File

@ -4,7 +4,7 @@ import {
CreChipComboBoxComponent,
CreChipInputComponent,
CreComboBoxComponent, CreFileInputComponent,
CreInputComponent, CrePeriodInputComponent
CreInputComponent, CrePeriodInputComponent, CreSliderInputComponent
} from './inputs'
import {MatInputModule} from '@angular/material/input'
import {MatIconModule} from '@angular/material/icon'
@ -17,6 +17,7 @@ import {MatChipsModule} from '@angular/material/chips'
import {CreButtonsModule} from '../buttons/buttons.module'
import {MatCheckboxModule} from '@angular/material/checkbox'
import {MatSelectModule} from '@angular/material/select'
import {MatSliderModule} from '@angular/material/slider';
@NgModule({
declarations: [
@ -27,7 +28,8 @@ import {MatSelectModule} from '@angular/material/select'
CreChipComboBoxComponent,
CreFileInputComponent,
CreCheckboxInputComponent,
CrePeriodInputComponent
CrePeriodInputComponent,
CreSliderInputComponent
],
imports: [
MatInputModule,
@ -42,6 +44,7 @@ import {MatSelectModule} from '@angular/material/select'
CreButtonsModule,
MatCheckboxModule,
MatSelectModule,
MatSliderModule,
],
exports: [
CreInputComponent,
@ -51,7 +54,8 @@ import {MatSelectModule} from '@angular/material/select'
CreAutocompleteInputComponent,
CreFileInputComponent,
CreCheckboxInputComponent,
CrePeriodInputComponent
CrePeriodInputComponent,
CreSliderInputComponent
]
})
export class CreInputsModule {

View File

@ -22,7 +22,7 @@ import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/a
@Directive()
abstract class _CreInputBase {
@Input() control: AbstractControl | null
@Input() control: FormControl | null
@Input() label: string
@Input() value
@Input() disabled = false
@ -55,9 +55,12 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
element.type = this.type
element.step = this.step.toString()
element.placeholder = this.placeholder
element.required = this.required
element.autocomplete = this.autocomplete ? 'on' : 'off'
}
get isFieldRequired(): boolean {
return this.control ? this.control.validator && this.control.validator({} as AbstractControl)?.required : this.required
}
}
@Component({
@ -70,7 +73,7 @@ export class CreAutocompleteInputComponent {
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() options: Observable<string[]>
@Input() entries: Observable<string[]>
@Input() value
@Output() valueChange = new EventEmitter<any>()
@ -132,14 +135,70 @@ export class CreChipInputComponent implements OnInit {
templateUrl: 'combo-box.html',
encapsulation: ViewEncapsulation.None
})
export class CreComboBoxComponent {
export class CreComboBoxComponent implements OnInit {
@Input() control: AbstractControl
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() options: Observable<ComboBoxEntry[]>
@Input() entries: Observable<ComboBoxEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
internalControl: FormControl
validValue = false
private _destroy$ = new Subject<boolean>();
private _entries: ComboBoxEntry[]
ngOnInit() {
this.entries.pipe(takeUntil(this._destroy$))
.subscribe({
next: entries => {
this._entries = entries
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
}
})
this.internalControl = new FormControl(null, Validators.compose([this.control.validator, this.valueValidator()]))
this.internalControl.valueChanges.pipe(takeUntil(this._destroy$))
.subscribe({
next: value => {
if (this.internalControl.valid) {
this.control.setValue(this.findEntryByValue(value).key)
} else {
this.control.setValue(null)
}
}
})
}
private findEntryByKey(key: any): ComboBoxEntry | null {
const found = this._entries.filter(e => e.key === key)
if (found.length <= 0) {
return null
}
return found[0]
}
private findEntryByValue(value: any): ComboBoxEntry | null {
const found = this._entries.filter(e => e.value === value)
if (found.length <= 0) {
return null
}
return found[0]
}
private existsEntryByValue(value: any): boolean {
return this._entries && this._entries.filter(o => o.value === value).length > 0
}
private valueValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const valid = this.existsEntryByValue(control.value)
return valid ? null : {invalidValue: {value: control.value}}
}
}
}
@Component({
@ -148,25 +207,27 @@ export class CreComboBoxComponent {
encapsulation: ViewEncapsulation.None
})
export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy {
@Input() options: Observable<ComboBoxEntry[]>
@Input() entries: Observable<ComboBoxEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
@ViewChild('chipInput') chipInput: ElementRef<HTMLInputElement>
@ViewChild('auto') matAutocomplete: MatAutocomplete
filteredOptions: Observable<ComboBoxEntry[]>
filteredEntries: Observable<ComboBoxEntry[]>
private _options: ComboBoxEntry[]
private _entries: ComboBoxEntry[]
private _destroy$ = new Subject()
ngOnInit() {
super.ngOnInit()
this.options.pipe(takeUntil(this._destroy$))
.subscribe({next: options => this._options = options})
this.entries.pipe(takeUntil(this._destroy$))
.subscribe({
next: entries => this._entries = entries
})
this.filteredOptions = this.inputControl.valueChanges.pipe(
map((query: string | null) => query ? this._filter(query) : this._options.slice())
this.filteredEntries = this.inputControl.valueChanges.pipe(
map((query: string | null) => query ? this._filter(query) : this._entries.slice())
)
}
@ -187,11 +248,11 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
private _filter(query: string): ComboBoxEntry[] {
const filterValue = query.toString().toLowerCase()
return this._options.filter(option => option.value.toString().toLowerCase().indexOf(filterValue) === 0)
return this._entries.filter(e => e.value.toString().toLowerCase().indexOf(filterValue) === 0)
}
private findValueByKey(key: any): any {
return this._options.filter(o => o.key === key)[0].value
return this._entries.filter(e => e.key === key)[0].value
}
}
@ -294,6 +355,24 @@ export class CrePeriodInputComponent implements OnInit {
}
}
@Component({
selector: 'cre-slider-input',
templateUrl: 'slider.html'
})
export class CreSliderInputComponent {
@Input() control: FormControl
@Input() label: string
@Input() min: number
@Input() max: number
@Input() step = 1
@Input() percents = false
@Input() thumbLabel = true
formatValueForDisplay(value: number): string {
return this.percents ? `${value}%` : value.toString()
}
}
export class ComboBoxEntry {
constructor(
public key: any,

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

@ -1,25 +1,21 @@
import {Material} from './material.model'
import {LocalDate} from 'js-joda'
import {Company} from './company.model'
import {Group} from './user'
export class Recipe {
constructor(
public id: number,
public name: string,
public description: string,
public color: string,
public gloss: number,
public sample: number,
public approbationDate: string,
public approbationExpired: boolean,
public remark: string,
public company: Company,
public mixes: Mix[],
public groupsInformation: RecipeGroupInformation[],
public imagesUrls: string[]
) {
}
public id: number
public name: string
public description: string
public color: string
public gloss: number
public sample: number
public approbationDate: string
public remark: string
public company: Company
public mixes: Mix[]
public approbationExpired: boolean
public groupsInformation: RecipeGroupInformation[]
public imagesUrls: string[]
}
export class RecipeGroupInformation {

View File

@ -1,6 +1,6 @@
import {Component, Input} from '@angular/core'
import {SubscribingComponent} from '../../shared/components/subscribing.component'
import {RecipeService} from '../../colors/services/recipe.service'
import {RecipeService} from '../../recipes/services/recipe.service'
import {ErrorService} from '../../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {Recipe} from '../../shared/model/recipe.model'

View File

@ -5,7 +5,7 @@
<cre-input [control]="controls.buggy" label="Chariot" icon="pound"></cre-input>
<cre-autocomplete-input
[control]="controls.company"
[options]="companies$"
[entries]="companies$"
label="Bannière"
icon="domain">
</cre-autocomplete-input>
@ -18,7 +18,7 @@
<cre-chip-combo-box
#finishInput
[control]="controls.finish"
[options]="finish$"
[entries]="finish$"
label="Fini"
icon="flare">
</cre-chip-combo-box>

View File

@ -3,7 +3,7 @@ import {chipListRequired, ComboBoxEntry, CreChipComboBoxComponent} from '../../s
import {CreFormComponent} from '../../shared/components/forms/forms'
import {TouchUpKitProductEditor} from './product-editor'
import {FormControl, Validators} from '@angular/forms'
import {RecipeService} from '../../colors/services/recipe.service'
import {RecipeService} from '../../recipes/services/recipe.service'
import {CompanyService} from '../../company/service/company.service'
import {ErrorService} from '../../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'

View File

@ -7,7 +7,7 @@ import {AccountService} from '../../accounts/services/account.service'
import {ErrorService} from '../../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {Permission} from '../../shared/model/user'
import {RecipeService} from '../../colors/services/recipe.service'
import {RecipeService} from '../../recipes/services/recipe.service'
import {AppState} from '../../shared/app-state'
import {map} from 'rxjs/operators'
import {LocalDate, Period} from 'js-joda'