develop #5
|
@ -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)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<cre-entity-add
|
||||
title="Création d'une recette"
|
||||
backButtonLink="/color/list"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)">
|
||||
</cre-entity-add>
|
|
@ -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}`)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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}`)
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<recipe-form (submitForm)="submit($event)"></recipe-form>
|
|
@ -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>
|
|
@ -53,7 +53,7 @@ export class ListComponent extends ErrorHandlingComponent {
|
|||
}
|
||||
)
|
||||
|
||||
//this.fetchCompanies()
|
||||
this.fetchCompanies()
|
||||
this.fetchRecipes()
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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}`)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -42,7 +42,7 @@ export class CreFormComponent implements OnInit {
|
|||
}
|
||||
|
||||
get hasActions(): boolean {
|
||||
return this.formActions === true
|
||||
return !!this.formActions
|
||||
}
|
||||
|
||||
get invalid(): boolean {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue