diff --git a/src/main/frontend/package-lock.json b/src/main/frontend/package-lock.json index 1afe657..6c9c4f8 100644 --- a/src/main/frontend/package-lock.json +++ b/src/main/frontend/package-lock.json @@ -6596,6 +6596,11 @@ } } }, + "js-joda": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/js-joda/-/js-joda-1.11.0.tgz", + "integrity": "sha512-/HJpRhwP2fPyuSsCaZuoVJuaSIt8tUXykV4wOMRXrFk7RP9h9VWaFdS9YHKdMepxb/3TdXpL6IhfC9L0sqYVBw==" + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -7121,6 +7126,11 @@ "object-visit": "^1.0.0" } }, + "material-design-icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", + "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index fa044c2..b65b272 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -24,6 +24,8 @@ "@mdi/angular-material": "^5.7.55", "bootstrap": "^4.5.2", "copy-webpack-plugin": "^6.2.1", + "js-joda": "^1.11.0", + "material-design-icons": "^3.0.1", "ngx-material-file-input": "^2.1.1", "rxjs": "~6.5.4", "tslib": "^1.10.0", diff --git a/src/main/frontend/src/app/app.component.ts b/src/main/frontend/src/app/app.component.ts index a3a66dc..69b3e57 100644 --- a/src/main/frontend/src/app/app.component.ts +++ b/src/main/frontend/src/app/app.component.ts @@ -3,6 +3,7 @@ import {isPlatformBrowser} from "@angular/common"; import {AppState} from "./modules/shared/app-state"; import {Observable} from "rxjs"; import {SubscribingComponent} from "./modules/shared/components/subscribing.component"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-root', @@ -15,9 +16,11 @@ export class AppComponent extends SubscribingComponent { constructor( @Inject(PLATFORM_ID) private platformId: object, - private appState: AppState + private appState: AppState, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { diff --git a/src/main/frontend/src/app/app.module.ts b/src/main/frontend/src/app/app.module.ts index eb260be..323f08e 100644 --- a/src/main/frontend/src/app/app.module.ts +++ b/src/main/frontend/src/app/app.module.ts @@ -6,8 +6,8 @@ import {AppComponent} from './app.component'; import {MatIconRegistry} from "@angular/material/icon"; import {SharedModule} from "./modules/shared/shared.module"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import { CatalogComponent } from './pages/catalog/catalog.component'; -import { CompanyModule } from './modules/company/company.module'; +import {CatalogComponent} from './pages/catalog/catalog.component'; +import {CompanyModule} from './modules/company/company.module'; @NgModule({ declarations: [ diff --git a/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts index 024e06c..9f7b3ba 100644 --- a/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts +++ b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts @@ -24,7 +24,7 @@ export class LoginComponent implements OnInit { ngOnInit(): void { if (this.accountService.isLoggedIn()) { - this.router.navigate(['/']) + this.router.navigate(['/color']) } this.idFormControl = this.formBuilder.control(null, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$'))])) diff --git a/src/main/frontend/src/app/modules/colors/colors-routing.module.ts b/src/main/frontend/src/app/modules/colors/colors-routing.module.ts index b248eee..879d0a1 100644 --- a/src/main/frontend/src/app/modules/colors/colors-routing.module.ts +++ b/src/main/frontend/src/app/modules/colors/colors-routing.module.ts @@ -1,12 +1,39 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +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 { ColorsComponent } from './colors.component'; - -const routes: Routes = [{ path: '', component: ColorsComponent }]; +const routes: Routes = [{ + path: 'list', + component: ListComponent +}, { + path: 'add', + component: AddComponent +}, { + path: 'edit/:id', + component: EditComponent +}, { + path: 'add/mix/:recipeId', + component: MixAddComponent +}, { + path: 'edit/mix/:recipeId/:id', + component: MixEditComponent +}, { + path: 'explore/:id', + component: ExploreComponent +}, { + path: '', + pathMatch: 'full', + redirectTo: 'list' +}] @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class ColorsRoutingModule { } +export class ColorsRoutingModule { +} diff --git a/src/main/frontend/src/app/modules/colors/colors.component.html b/src/main/frontend/src/app/modules/colors/colors.component.html deleted file mode 100644 index 8a12e74..0000000 --- a/src/main/frontend/src/app/modules/colors/colors.component.html +++ /dev/null @@ -1 +0,0 @@ -

colors works!

diff --git a/src/main/frontend/src/app/modules/colors/colors.component.spec.ts b/src/main/frontend/src/app/modules/colors/colors.component.spec.ts deleted file mode 100644 index 65dda2b..0000000 --- a/src/main/frontend/src/app/modules/colors/colors.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ColorsComponent } from './colors.component'; - -describe('ColorsComponent', () => { - let component: ColorsComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ ColorsComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ColorsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/main/frontend/src/app/modules/colors/colors.component.ts b/src/main/frontend/src/app/modules/colors/colors.component.ts deleted file mode 100644 index c8d9021..0000000 --- a/src/main/frontend/src/app/modules/colors/colors.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {Component, OnInit} from '@angular/core'; - -@Component({ - selector: 'cre-colors', - templateUrl: './colors.component.html', - styleUrls: ['./colors.component.sass'] -}) -export class ColorsComponent implements OnInit { - - constructor() { - } - - ngOnInit(): void { - } -} diff --git a/src/main/frontend/src/app/modules/colors/colors.module.ts b/src/main/frontend/src/app/modules/colors/colors.module.ts index 20e8b4e..d8aa665 100644 --- a/src/main/frontend/src/app/modules/colors/colors.module.ts +++ b/src/main/frontend/src/app/modules/colors/colors.module.ts @@ -1,15 +1,31 @@ import {NgModule} from '@angular/core'; import {ColorsRoutingModule} from './colors-routing.module'; -import {ColorsComponent} from './colors.component'; 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"; +import {ExploreComponent} from './pages/explore/explore.component'; +import {RecipeInfoComponent} from './components/recipe-info/recipe-info.component'; +import {MixTableComponent} from './components/mix-table/mix-table.component'; +import {StepListComponent} from './components/step-list/step-list.component'; +import {StepTableComponent} from './components/step-table/step-table.component'; +import {MixEditorComponent} from './components/mix-editor/mix-editor.component'; +import {UnitSelectorComponent} from './components/unit-selector/unit-selector.component'; +import {MixAddComponent} from './pages/mix/mix-add/mix-add.component'; +import {MixEditComponent} from './pages/mix/mix-edit/mix-edit.component'; @NgModule({ - declarations: [ColorsComponent], + declarations: [ListComponent, AddComponent, EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, StepListComponent, StepTableComponent, MixEditorComponent, UnitSelectorComponent, MixAddComponent, MixEditComponent], imports: [ ColorsRoutingModule, - SharedModule + SharedModule, + MatExpansionModule, + FormsModule ] }) -export class ColorsModule { } +export class ColorsModule { +} diff --git a/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.html b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.html new file mode 100644 index 0000000..59bfaad --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.html @@ -0,0 +1,96 @@ + + + Création d'un mélange pour la recette {{recipe.company.name}} + - {{recipe.name}} + Modification du mélange {{mix.mixType.name}} de la + recette {{recipe.company.name}} - {{recipe.name}} + + + + Nom + + + + + Type de produit + + + {{materialType.name}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Position + {{i + 1}} + Produit + + + {{material.name}} + + + Quantité + + + + Unités + + % + + + {{units}} + + + + + + + + + + + + +
+
+ diff --git a/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.sass b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.sass new file mode 100644 index 0000000..3d15487 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.sass @@ -0,0 +1,2 @@ +td.units-wrapper + width: 3rem diff --git a/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.ts b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.ts new file mode 100644 index 0000000..8839e3b --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-editor/mix-editor.component.ts @@ -0,0 +1,132 @@ +import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'; +import {Mix, MixMaterial, Recipe} from "../../../shared/model/recipe.model"; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {MixService} from "../../services/mix.service"; +import {Observable} from "rxjs"; +import {RecipeService} from "../../services/recipe.service"; +import {Material} from "../../../shared/model/material.model"; +import {MaterialService} from "../../../material/service/material.service"; +import {MaterialType} from "../../../shared/model/materialtype.model"; +import {MaterialTypeService} from "../../../material-type/service/material-type.service"; +import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; +import {UNIT_MILLILITER} from "../../../shared/units"; +import {MatTable} from "@angular/material/table"; +import {ActivatedRoute, Router} from "@angular/router"; + +@Component({ + selector: 'cre-mix-editor', + templateUrl: './mix-editor.component.html', + styleUrls: ['./mix-editor.component.sass'] +}) +export class MixEditorComponent extends SubscribingComponent { + @ViewChild('mixTable') mixTable: MatTable + + @Input() mixId: number | null + @Input() recipeId: number | null + @Input() materials: Material[] + + @Output() save = new EventEmitter(); + + mix: Mix | null + recipe: Recipe | null + materialTypes$: Observable + + form: FormGroup + nameControl: FormControl + materialTypeControl: FormControl + + mixMaterials = [] + editionMode = false + units = UNIT_MILLILITER + hoveredMixMaterial: MixMaterial | null + columns = ['position', 'material', 'quantity', 'units', 'buttonRemove'] + + constructor( + private mixService: MixService, + private recipeService: RecipeService, + private materialService: MaterialService, + private materialTypeService: MaterialTypeService, + private formBuilder: FormBuilder, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit(); + + this.mixId = this.urlUtils.parseIntUrlParam('id') + + this.subscribe( + this.recipeService.getById(this.recipeId), + { + next: r => { + this.recipe = r + if (this.mixId) { + this.editionMode = true + this.subscribe( + this.mixService.getById(this.mixId), + { + next: m => { + this.mix = m + this.mixMaterials = this.mixService.extractMixMaterials(this.mix) + this.generateForm() + }, error: err => this.handleNotFoundError(err, '/color/list') + } + ) + } else { + this.mixMaterials.push({}) + this.generateForm() + } + }, + error: err => this.handleNotFoundError(err, '/color/list') + } + ) + this.materialTypes$ = this.materialTypeService.all + } + + addRow() { + this.mixMaterials.push({materialId: null, quantity: null, percents: false}) + this.mixTable.renderRows() + } + + removeRow(position: number) { + this.mixMaterials.splice(position, 1) + this.mixTable.renderRows() + } + + submit() { + this.save.emit({ + name: this.nameControl.value, + recipeId: this.recipeId, + materialTypeId: this.materialTypeControl.value, + mixMaterials: this.mixMaterials, + units: this.units + }) + } + + delete() { + + } + + materialUsePercentages(mixMaterial: any) { + if (!mixMaterial.materialId) return null + const material = this.getMaterialFromId(mixMaterial.materialId); + mixMaterial.percents = material && material.materialType.usePercentages + return mixMaterial.percents + } + + getMaterialFromId(id: number): Material { + return id ? this.materials.filter(m => m.id === id)[0] : null + } + + private generateForm() { + this.nameControl = new FormControl(this.mix ? this.mix.mixType.name : null, Validators.required) + this.materialTypeControl = new FormControl(this.mix ? this.mix.mixType.material.materialType.id : null, Validators.required) + this.form = this.formBuilder.group({ + name: this.nameControl, + materialType: this.materialTypeControl + }) + } +} diff --git a/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.html b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.html new file mode 100644 index 0000000..1b540bb --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.html @@ -0,0 +1,100 @@ + + + {{mix.mixType.name}} + + +
+ +
+ + Casier + + +
+
+ +
+
+ +
+
+ +
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Produit{{mixMaterial.material.name}}Type{{mixMaterial.material.materialType.name}}Quantité + + + + + + Total + + + Quantité{{getComputedQuantityRounded(mixMaterial)}}CalculUnités + % + {{units}} + {{units}}
+
diff --git a/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.sass b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.sass new file mode 100644 index 0000000..0601391 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.sass @@ -0,0 +1,22 @@ +@import '../../../../../custom-theme' + +mat-expansion-panel + width: 40rem + margin: 2rem 0 + +.mix-actions + background-color: $color-primary + padding: 0 1rem + + div:last-child + margin-left: 1rem + +.low-quantity + background-color: #ffb3b3 + +::ng-deep span.mix-calculated-quantity + &:first-child + color: green + + &:last-child + color: dimgrey diff --git a/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.ts b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.ts new file mode 100644 index 0000000..a8e8374 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/mix-table/mix-table.component.ts @@ -0,0 +1,131 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Mix, MixMaterial, Recipe} from "../../../shared/model/recipe.model"; +import {Subject} from "rxjs"; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {convertMixMaterialQuantity, UNIT_MILLILITER, UNIT_RATIOS} from "../../../shared/units"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; + +@Component({ + selector: 'cre-mix-table', + templateUrl: './mix-table.component.html', + styleUrls: ['./mix-table.component.sass'] +}) +export class MixTableComponent extends SubscribingComponent { + private readonly COLUMNS = ['material', 'materialType', 'quantity', 'quantityCalculated', 'quantityUnits'] + private readonly COLUMNS_STATIC = ['material', 'materialType', 'quantityStatic', 'quantityUnits'] + + @Input() mix: Mix + @Input() recipe: Recipe + @Input() units$: Subject + @Input() deductErrorBody + @Input() editing: boolean + @Output() locationChange = new EventEmitter<{ id: number, location: string }>() + @Output() quantityChange = new EventEmitter<{ id: number, materialId: number, quantity: number }>() + @Output() deduct = new EventEmitter() + + mixColumns = this.COLUMNS + units = UNIT_MILLILITER + computedQuantities: { id: number, percents: boolean, quantity: number }[] = [] + + constructor( + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit(); + + if (this.editing) { + this.mixColumns = this.COLUMNS_STATIC + } + + this.mix.mixMaterials.forEach(m => this.computedQuantities.push({ + id: m.id, + percents: m.material.materialType.usePercentages, + quantity: m.quantity + })) + + this.subscribe( + this.units$, + { + next: u => this.convertQuantities(u) + } + ) + } + + changeLocation(event: any) { + this.locationChange.emit({id: this.mix.id, location: event.target.value}) + } + + changeQuantity(event: any, mixMaterial: MixMaterial, isTotal = false) { + const newQuantity = parseInt(event.target.value) + let ratio = 1 + if (!isTotal) { + const originalQuantity = this.getComputedQuantity(mixMaterial.id) + ratio = newQuantity / originalQuantity.quantity + } else { + ratio = newQuantity / this.getTotalQuantity() + } + this.computedQuantities.forEach((q, i) => { + if (!q.percents) { + q.quantity *= ratio + } + this.emitQuantityChangeEvent(i) + }) + } + + getComputedQuantityRounded(mixMaterial: MixMaterial): number { + return this.round(this.getComputedQuantity(mixMaterial.id).quantity) + } + + getTotalQuantity(index: number = -1): number { + if (index === -1) index = this.computedQuantities.length - 1 + let totalQuantity = 0 + for (let i = 0; i <= index; i++) { + totalQuantity += this.calculateQuantity(i) + } + return totalQuantity + } + + getCalculatedQuantity(mixMaterial: MixMaterial, index: number): string { + const totalQuantity = this.round(this.getTotalQuantity(index)) + const addedQuantity = this.round(this.calculateQuantity(index)) + return `+${addedQuantity} (${totalQuantity})` + } + + isInLowQuantity(materialId: number): boolean { + return this.deductErrorBody[this.mix.id] && this.deductErrorBody[this.mix.id].indexOf(materialId) >= 0 + } + + round(quantity: number): number { + return Math.round(quantity * 1000) / 1000 + } + + private emitQuantityChangeEvent(index: number) { + this.quantityChange.emit({ + id: this.mix.id, + materialId: this.computedQuantities[index].id, + quantity: this.calculateQuantity(index) + }) + } + + private convertQuantities(newUnit: string) { + this.computedQuantities.forEach(q => convertMixMaterialQuantity(q, this.units, newUnit)) + this.units = newUnit + } + + private getComputedQuantity(id: number): any { + return this.computedQuantities.filter(q => q.id == id)[0] + } + + private calculateQuantity(index: number): number { + const computedQuantity = this.computedQuantities[index] + if (!computedQuantity.percents) { + return computedQuantity.quantity + } + return this.computedQuantities[0].quantity * (computedQuantity.quantity / 100) + } +} diff --git a/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.html b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.html new file mode 100644 index 0000000..ac847fc --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.html @@ -0,0 +1,29 @@ +
+
+

{{recipe.company.name}} - {{recipe.name}}

+
+
+
+

Échantillon #{{recipe.sample}}

+

Approuvée le {{recipe.approbationDate}}

+
+

Non approuvée

+ +
+

{{recipe.remark}}

+
+
+

{{recipe.description}}

+
+ +
+ + + +
+
diff --git a/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.sass b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.sass new file mode 100644 index 0000000..36d3085 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.sass @@ -0,0 +1,46 @@ +.recipe-info-wrapper + background-color: black + color: white + padding: 1rem + + div + margin-right: 3rem + + p + margin-bottom: 0 + + h3 + font-weight: bold + text-decoration: underline + text-transform: uppercase + + &.recipe-not-approved-wrapper + p + margin-top: 1px + margin-right: .4em + + &.recipe-description + max-width: 30rem + + &.recipe-note + margin-right: 0 + + mat-form-field + width: 100% + + ::ng-deep .mat-form-field-wrapper + padding-bottom: 0 + + ::ng-deep .mat-form-field-underline, ::ng-deep .mat-form-field-ripple + opacity: 0 + + mat-label + color: white + + textarea:focus + background-color: white + color: black + +.has-modification-icon + width: 2em !important + diff --git a/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.ts b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.ts new file mode 100644 index 0000000..119133d --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/recipe-info/recipe-info.component.ts @@ -0,0 +1,14 @@ +import {Component, Input} from '@angular/core'; +import {Recipe} from "../../../shared/model/recipe.model"; + +@Component({ + selector: 'cre-recipe-info', + templateUrl: './recipe-info.component.html', + styleUrls: ['./recipe-info.component.sass'] +}) +export class RecipeInfoComponent { + @Input() recipe: Recipe + @Input() hasModifications: boolean + + constructor() { } +} diff --git a/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.html b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.html new file mode 100644 index 0000000..0951d5d --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.html @@ -0,0 +1,12 @@ + + + Étapes + + + + + {{i + 1}}.{{step.message}} + + + + diff --git a/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.sass b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.sass new file mode 100644 index 0000000..f2760a1 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.sass @@ -0,0 +1,2 @@ +.space + width: 1em diff --git a/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.ts b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.ts new file mode 100644 index 0000000..79666f6 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-list/step-list.component.ts @@ -0,0 +1,11 @@ +import {Component, Input} from '@angular/core'; +import {RecipeStep} from "../../../shared/model/recipe.model"; + +@Component({ + selector: 'cre-step-list', + templateUrl: './step-list.component.html', + styleUrls: ['./step-list.component.sass'] +}) +export class StepListComponent { + @Input() steps: RecipeStep[] +} diff --git a/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.html b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.html new file mode 100644 index 0000000..9eb22d4 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.html @@ -0,0 +1,33 @@ + + + Étapes + + + + + + + + + + + + + + + + + + + + +
Position{{i + 1}}Message + + + + + + + +
+
diff --git a/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.sass b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.sass new file mode 100644 index 0000000..44d6821 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.sass @@ -0,0 +1,5 @@ +mat-expansion-panel + min-width: 560px + +mat-form-field + width: 20rem diff --git a/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.ts b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.ts new file mode 100644 index 0000000..4038674 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/step-table/step-table.component.ts @@ -0,0 +1,25 @@ +import {Component, Input, ViewChild} from '@angular/core'; +import {RecipeStep} from "../../../shared/model/recipe.model"; +import {MatTable} from "@angular/material/table"; + +@Component({ + selector: 'cre-step-table', + templateUrl: './step-table.component.html', + styleUrls: ['./step-table.component.sass'] +}) +export class StepTableComponent { + @ViewChild('stepTable', {static: true}) stepTable: MatTable + readonly columns = ['position', 'message', 'buttonRemove'] + + @Input() steps: RecipeStep[] + + addStep() { + this.steps.push({id: null, message: ""}) + this.stepTable.renderRows() + } + + removeStep(position: number) { + this.steps.splice(position, 1) + this.stepTable.renderRows() + } +} diff --git a/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.html b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.html new file mode 100644 index 0000000..4b82721 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.html @@ -0,0 +1,15 @@ + + Unités + + + Millilitres + Litres + Gallons + + + mL + L + gal + + + diff --git a/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.sass b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.sass new file mode 100644 index 0000000..146f5c5 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.sass @@ -0,0 +1,2 @@ +mat-form-field.short + width: 3rem diff --git a/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.ts b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.ts new file mode 100644 index 0000000..418677f --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/components/unit-selector/unit-selector.component.ts @@ -0,0 +1,17 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from "../../../shared/units"; + +@Component({ + selector: 'cre-unit-selector', + templateUrl: './unit-selector.component.html', + styleUrls: ['./unit-selector.component.sass'] +}) +export class UnitSelectorComponent { + readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} + + @Input() unit = UNIT_MILLILITER + @Input() showLabel = true + @Input() short = false + + @Output() unitChange = new EventEmitter() +} diff --git a/src/main/frontend/src/app/modules/colors/pages/add/add.component.html b/src/main/frontend/src/app/modules/colors/pages/add/add.component.html new file mode 100644 index 0000000..f9d4e9d --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/add/add.component.html @@ -0,0 +1,8 @@ + + diff --git a/src/main/frontend/src/app/modules/colors/pages/add/add.component.sass b/src/main/frontend/src/app/modules/colors/pages/add/add.component.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/main/frontend/src/app/modules/colors/pages/add/add.component.ts b/src/main/frontend/src/app/modules/colors/pages/add/add.component.ts new file mode 100644 index 0000000..3fc5e33 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/add/add.component.ts @@ -0,0 +1,98 @@ +import {Component} from '@angular/core'; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {RecipeService} from "../../services/recipe.service"; +import {FormField} from "../../../shared/components/entity-add/entity-add.component"; +import {FormBuilder, Validators} from "@angular/forms"; +import {CompanyService} from "../../../company/service/company.service"; +import {map} from "rxjs/operators"; +import {ActivatedRoute, Router} from "@angular/router"; + +@Component({ + selector: 'cre-add', + templateUrl: './add.component.html', + styleUrls: ['./add.component.sass'] +}) +export class AddComponent extends SubscribingComponent { + formFields: FormField[] = [ + { + name: 'name', + label: 'Nom', + icon: 'form-textbox', + type: 'text', + validator: Validators.required, + errorMessages: [ + {conditionFn: errors => errors.required, message: 'Un nom est requis'} + ] + }, + { + name: 'description', + label: 'Description', + icon: 'text', + type: 'text', + validator: Validators.required, + errorMessages: [ + {conditionFn: errors => errors.required, message: 'Une description est requise'} + ] + }, + { + name: 'sample', + label: 'Échantillon', + icon: 'pound', + type: 'number', + validator: Validators.compose([Validators.required, 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', + validator: Validators.required, + 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} + }))) + } + ] + unknownError = false + errorMessage: string | null + + constructor( + private recipeService: RecipeService, + private companyService: CompanyService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + submit(values) { + this.subscribe( + this.recipeService.save(values.name, values.description, values.sample, values.approbationDate, values.remark, values.company), + { + next: recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`), + error: err => { + this.unknownError = true + console.error(err) + } + } + ) + } +} diff --git a/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.html b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.html new file mode 100644 index 0000000..7d0ab6b --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.html @@ -0,0 +1,68 @@ +
+
+

Il n'y a aucun mélange dans cette recette

+
+
+

Il n'y a aucune étape dans cette recette

+
+ +
+
+
+ + + +
+ + Unités + + Millilitres + Litres + Gallons + + +
+
+
+ +
+
+ + +
+ +
+ + + Mélanges + + + + + + + + + + + +
+ +
+ +
+
+
diff --git a/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.sass b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.sass new file mode 100644 index 0000000..a474d96 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.sass @@ -0,0 +1,17 @@ +.recipe-wrapper > div + margin: 0 3rem 3rem + + mat-card + margin-top: 3rem + +.recipe-mixes-wrapper mat-card + min-width: 20rem + +mat-card + mat-card-content + margin-bottom: 0 + padding-top: 16px !important + padding-bottom: 0 !important + + mat-card-actions + margin-top: 0 diff --git a/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.ts new file mode 100644 index 0000000..59f8923 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/edit/edit.component.ts @@ -0,0 +1,142 @@ +import {Component} from '@angular/core'; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {Recipe} from "../../../shared/model/recipe.model"; +import {RecipeService} from "../../services/recipe.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {FormBuilder, Validators} from "@angular/forms"; +import {Subject} from "rxjs"; +import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from "../../../shared/units"; +import {AccountService} from "../../../accounts/services/account.service"; +import {EmployeePermission} from "../../../shared/model/employee"; +import {EntityEditComponent} from "../../../shared/components/entity-edit/entity-edit.component"; + +@Component({ + selector: 'cre-edit', + templateUrl: './edit.component.html', + styleUrls: ['./edit.component.sass'] +}) +export class EditComponent extends SubscribingComponent { + readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} + + recipe: Recipe | null + formFields = [ + { + name: 'name', + label: 'Nom', + icon: 'form-textbox', + type: 'text', + validator: Validators.required, + errorMessages: [ + {conditionFn: errors => errors.required, message: 'Un nom est requis'} + ] + }, + { + name: 'description', + label: 'Description', + icon: 'text', + type: 'text', + validator: Validators.required, + errorMessages: [ + {conditionFn: errors => errors.required, message: 'Une description est requise'} + ] + }, + { + name: 'sample', + label: 'Échantillon', + icon: 'pound', + type: 'number', + validator: Validators.compose([Validators.required, 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: 'text', + readonly: true, + valueFn: recipe => recipe.company.name, + } + ] + unknownError = false + errorMessage: string | null + units$ = new Subject() + + constructor( + private recipeService: RecipeService, + private accountService: AccountService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit(); + + const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id')) + this.subscribe( + this.recipeService.getById(id), + { + next: recipe => this.recipe = recipe, + error: err => { + if (err.status === 404) { + this.router.navigate(['/color/list']) + } else { + this.unknownError = true + } + } + }, + 1 + ) + } + + changeUnits(unit: string) { + this.units$.next(unit) + } + + submit(editComponent: EntityEditComponent) { + const values = editComponent.values + this.subscribe( + this.recipeService.update(this.recipe.id, values.name, values.description, values.sample, values.approbationDate, values.remark, this.recipe.steps), + { + next: () => this.router.navigate(['/color/list']), + error: err => { + if (err.status === 409) { + this.errorMessage = `Une couleur avec le nom '${values.name}' et la bannière '${this.recipe.company.name}' existe déjà` + } else { + this.unknownError = true + console.error(err) + } + } + } + ) + } + + delete() { + this.subscribe( + this.recipeService.delete(this.recipe.id), + { + next: () => this.router.navigate(['/color/list']) + } + ) + } + + get hasDeletePermission(): boolean { + return this.accountService.hasPermission(EmployeePermission.REMOVE_RECIPE) + } +} diff --git a/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.html b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.html new file mode 100644 index 0000000..961922f --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.html @@ -0,0 +1,59 @@ +
+ + +
+

Une erreur est survenue

+

Certains produit ne sont pas en quantité suffisante dans l'inventaire

+
+
+

Les modifications ont été enregistrées

+

Les quantités des produits utilisés ont été déduites de l'inventaire

+
+ +
+
+
+ + + + +
+ +
+ +
+ + + Note + + +
+ +
+ +
+ + + + +
+ + +
+ +
+
+ + +
+ Images +
+
diff --git a/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.sass b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.sass new file mode 100644 index 0000000..24c7079 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.sass @@ -0,0 +1,21 @@ +.recipe-content + display: flex + flex-direction: column + align-items: center + + div + margin-bottom: 1rem + +.recipe-images + margin-top: 5rem + padding: 2rem + background-color: grey + +@media screen and (min-width: 1920px) + .action-bar + padding-bottom: 5rem + + .recipe-content + flex-direction: row + justify-content: space-around + align-items: start diff --git a/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.ts b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.ts new file mode 100644 index 0000000..cac50da --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/explore/explore.component.ts @@ -0,0 +1,135 @@ +import {Component} from '@angular/core'; +import {RecipeService} from "../../services/recipe.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {Recipe} from "../../../shared/model/recipe.model"; +import {Observable, Subject} from "rxjs"; +import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from "../../../shared/units"; +import {FormBuilder} from "@angular/forms"; + +@Component({ + selector: 'cre-explore', + templateUrl: './explore.component.html', + styleUrls: ['./explore.component.sass'] +}) +export class ExploreComponent extends SubscribingComponent { + readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} + + recipe: Recipe | null + error: string + deductErrorBody = {} + success: string + units$ = new Subject() + + hasModifications = false + note: string | null + quantitiesChanges = new Map>() + mixesLocationChanges = new Map() + + // Errors + readonly ERROR_UNKNOWN = 'unknown' + readonly ERROR_DEDUCT = 'deduct' + // Success + readonly SUCCESS_SAVE = 'save' + readonly SUCCESS_DEDUCT = 'deduct' + + constructor( + private recipeService: RecipeService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit(): void { + super.ngOnInit() + + const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id')) + this.subscribe( + this.recipeService.getById(id), + { + next: r => { + this.recipe = r + this.note = r.note + }, + error: err => { + if (err.status == 404) { + this.router.navigate(['/colors/list']) + } else { + this.error = this.ERROR_UNKNOWN + } + } + }, + 1 + ) + } + + changeUnits(unit: string) { + this.units$.next(unit) + } + + changeNote(event: any) { + this.hasModifications = true + this.note = event.target.value + } + + changeQuantity(event: { id: number, materialId: number, quantity: number }) { + if (!this.quantitiesChanges.has(event.id)) this.quantitiesChanges.set(event.id, new Map()) + this.quantitiesChanges.get(event.id).set(event.materialId, event.quantity) + } + + changeMixLocation(event: { id: number, location: string }) { + this.hasModifications = true + this.mixesLocationChanges.set(event.id, event.location) + } + + saveModifications() { + this.subscribe( + this.recipeService.saveExplorerModifications(this.recipe.id, this.note, this.mixesLocationChanges), + { + next: () => { + this.hasModifications = false + this.error = null + this.success = this.SUCCESS_SAVE + }, + error: err => { + this.success = null + this.error = this.ERROR_UNKNOWN + console.error(err) + } + }, + 1 + ) + } + + deductQuantities() { + this.performDeductQuantities(this.recipeService.deductQuantities(this.recipe, this.quantitiesChanges)) + } + + deductMixQuantities(mixId: number) { + this.performDeductQuantities(this.recipeService.deductMixQuantities(this.recipe, mixId, this.quantitiesChanges.get(mixId))) + } + + performDeductQuantities(observable: Observable) { + this.subscribe( + observable, + { + next: () => { + this.error = null + this.success = this.SUCCESS_DEDUCT + }, + error: err => { + this.success = null + if (err.status === 409) { // There is not enough of one or more materials in the inventory + this.error = this.ERROR_DEDUCT + this.deductErrorBody = err.error + } else { + this.error = this.ERROR_UNKNOWN + console.error(err) + } + } + }, + 1 + ) + } +} diff --git a/src/main/frontend/src/app/modules/colors/pages/list/list.component.html b/src/main/frontend/src/app/modules/colors/pages/list/list.component.html new file mode 100644 index 0000000..c6a02ab --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/list/list.component.html @@ -0,0 +1,71 @@ +
+ + Recherche + + + +
+ +
+
+ + + + + {{companyRecipes.company}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nom{{recipe.name}}Description{{recipe.description}}Échantillon#{{recipe.sample}} + + + + + +
+
diff --git a/src/main/frontend/src/app/modules/colors/pages/list/list.component.sass b/src/main/frontend/src/app/modules/colors/pages/list/list.component.sass new file mode 100644 index 0000000..61985de --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/list/list.component.sass @@ -0,0 +1,6 @@ +mat-expansion-panel + width: 60rem + margin: 20px auto + +.button-add + margin-top: .8rem diff --git a/src/main/frontend/src/app/modules/colors/pages/list/list.component.ts b/src/main/frontend/src/app/modules/colors/pages/list/list.component.ts new file mode 100644 index 0000000..1107b7e --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/list/list.component.ts @@ -0,0 +1,56 @@ +import {Component} from '@angular/core'; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {RecipeService} from "../../services/recipe.service"; +import {EmployeePermission} from "../../../shared/model/employee"; +import {AccountService} from "../../../accounts/services/account.service"; +import {Recipe} from "../../../shared/model/recipe.model"; +import {ActivatedRoute, Router} from "@angular/router"; + +@Component({ + selector: 'cre-list', + templateUrl: './list.component.html', + styleUrls: ['./list.component.sass'] +}) +export class ListComponent extends SubscribingComponent { + recipes$ = this.recipeService.allSortedByCompany + tableCols = ['name', 'description', 'sample', 'iconNotApproved', 'buttonView', 'buttonEdit'] + searchQuery = "" + panelForcedExpanded = false + recipesHidden = [] + + constructor( + private recipeService: RecipeService, + private accountService: AccountService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit(); + } + + searchRecipe(recipe: Recipe) { + if (this.searchQuery.length > 0) { + this.panelForcedExpanded = true + } + const positive = this.searchString(recipe.name) || + this.searchString(recipe.description) || + this.searchString(recipe.sample.toString()) + this.recipesHidden[recipe.id] = !positive + return positive + } + + isCompanyHidden(companyRecipes: Recipe[]): boolean { + return (this.searchQuery && this.searchQuery.length > 0) && companyRecipes.map(r => this.recipesHidden[r.id]).filter(r => !r).length <= 0 + } + + get hasEditPermission(): boolean { + return this.accountService.hasPermission(EmployeePermission.EDIT_RECIPE) + } + + private searchString(value: string): boolean { + return value.toLowerCase().indexOf(this.searchQuery.toLowerCase()) >= 0 + } +} diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html b/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html new file mode 100644 index 0000000..128e7d2 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html @@ -0,0 +1,5 @@ + + diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.sass b/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts b/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts new file mode 100644 index 0000000..ca267d1 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts @@ -0,0 +1,43 @@ +import {Component} from '@angular/core'; +import {Material} from "../../../../shared/model/material.model"; +import {MaterialService} from "../../../../material/service/material.service"; +import {ActivatedRoute, Router} from "@angular/router"; +import {SubscribingComponent} from "../../../../shared/components/subscribing.component"; +import {MixService} from "../../../services/mix.service"; + +@Component({ + selector: 'cre-mix-add', + templateUrl: './mix-add.component.html', + styleUrls: ['./mix-add.component.sass'] +}) +export class MixAddComponent extends SubscribingComponent { + recipeId: number | null + materials: Material[] | null + + constructor( + private materialService: MaterialService, + private mixService: MixService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit(): void { + super.ngOnInit() + + this.recipeId = this.urlUtils.parseIntUrlParam('recipeId') + + this.subscribe( + this.materialService.getAllForMixCreation(this.recipeId), + {next: m => this.materials = m} + ) + } + + submit(values) { + this.subscribe( + this.mixService.saveWithUnits(values.name, values.recipeId, values.materialTypeId, values.mixMaterials, values.units), + {next: () => this.urlUtils.navigateTo(`/color/edit/${this.recipeId}`)} + ) + } +} diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html b/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html new file mode 100644 index 0000000..762bae6 --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html @@ -0,0 +1,6 @@ + + diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.sass b/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts b/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts new file mode 100644 index 0000000..981998d --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts @@ -0,0 +1,45 @@ +import {Component} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {SubscribingComponent} from "../../../../shared/components/subscribing.component"; +import {Material} from "../../../../shared/model/material.model"; +import {MaterialService} from "../../../../material/service/material.service"; +import {MixService} from "../../../services/mix.service"; + +@Component({ + selector: 'cre-mix-edit', + templateUrl: './mix-edit.component.html', + styleUrls: ['./mix-edit.component.sass'] +}) +export class MixEditComponent extends SubscribingComponent { + mixId: number | null + recipeId: number | null + materials: Material[] | null + + constructor( + private materialService: MaterialService, + private mixService: MixService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(activatedRoute, router) + } + + ngOnInit(): void { + super.ngOnInit() + + this.mixId = this.urlUtils.parseIntUrlParam('id') + this.recipeId = this.urlUtils.parseIntUrlParam('recipeId') + + this.subscribe( + this.materialService.getAllForMixUpdate(this.mixId), + {next: m => this.materials = m} + ) + } + + submit(values) { + this.subscribe( + this.mixService.updateWithUnits(this.mixId, values.name, values.materialTypeId, values.mixMaterials, values.units), + {next: () => this.urlUtils.navigateTo(`/color/edit/${this.recipeId}`)} + ) + } +} diff --git a/src/main/frontend/src/app/modules/colors/services/mix.service.ts b/src/main/frontend/src/app/modules/colors/services/mix.service.ts new file mode 100644 index 0000000..e33db7e --- /dev/null +++ b/src/main/frontend/src/app/modules/colors/services/mix.service.ts @@ -0,0 +1,75 @@ +import {Injectable} from '@angular/core'; +import {ApiService} from "../../shared/service/api.service"; +import {convertMixMaterialQuantity, UNIT_MILLILITER} from "../../shared/units"; +import {Observable} from "rxjs"; +import {Mix} from "../../shared/model/recipe.model"; + +@Injectable({ + providedIn: 'root' +}) +export class MixService { + constructor( + private api: ApiService + ) { + } + + get all(): Observable { + return this.api.get('/recipe/mix') + } + + getById(id: number): Observable { + return this.api.get(`/recipe/mix/${id}`) + } + + saveWithUnits(name: string, recipeId: number, materialTypeId: number, mixMaterials: { materialId: number, quantity: number, percents: boolean }[], units: string): Observable { + return this.save(name, recipeId, materialTypeId, this.convertMixMaterialsToMl(mixMaterials, units)) + } + + save(name: string, recipeId: number, materialTypeId: number, mixMaterials: { materialId: number, quantity: number }[]): Observable { + const body = { + name, + recipeId, + materialTypeId, + mixMaterials: {} + } + this.appendMixMaterialsToBody(mixMaterials, body) + return this.api.post('/recipe/mix', body) + } + + updateWithUnits(id: number, name: string, materialTypeId: number, mixMaterials: { materialId: number, quantity: number, percents: boolean }[], units: string): Observable { + return this.update(id, name, materialTypeId, this.convertMixMaterialsToMl(mixMaterials, units)) + } + + update(id: number, name: string, materialTypeId: number, mixMaterials: { materialId: number, quantity: number }[]): Observable { + const body = { + id, + name, + materialTypeId, + mixMaterials: {} + } + this.appendMixMaterialsToBody(mixMaterials, body) + return this.api.put('/recipe/mix', body) + } + + extractMixMaterials(mix: Mix): { materialId: number, quantity: number, percents: boolean }[] { + return mix.mixMaterials.map(m => { + return {materialId: m.material.id, quantity: m.quantity, percents: m.material.materialType.usePercentages} + }) + } + + private convertMixMaterialsToMl(mixMaterials: { materialId: number, quantity: number, percents: boolean }[], units: string): { materialId: number, quantity: number }[] { + return mixMaterials.map(m => { + return { + materialId: m.materialId, + quantity: convertMixMaterialQuantity(m, units, UNIT_MILLILITER) + } + }) + } + + private appendMixMaterialsToBody(mixMaterials: { materialId: number, quantity: number }[], body: any) { + mixMaterials + .filter(m => m.materialId != null && m.quantity != null) + .forEach(m => body.mixMaterials[m.materialId] = m.quantity) + } +} + diff --git a/src/main/frontend/src/app/modules/colors/services/recipe.service.ts b/src/main/frontend/src/app/modules/colors/services/recipe.service.ts index c55207a..e8ce843 100644 --- a/src/main/frontend/src/app/modules/colors/services/recipe.service.ts +++ b/src/main/frontend/src/app/modules/colors/services/recipe.service.ts @@ -1,9 +1,106 @@ -import { Injectable } from '@angular/core'; +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' }) export class RecipeService { + constructor( + private api: ApiService + ) { + } - constructor() { } + get all(): Observable { + return this.api.get('/recipe') + } + + get allSortedByCompany(): Observable<{ company: string, recipes: Recipe[] }[]> { + return this.all.pipe(map(recipes => { + const mapped = [] + recipes.forEach(r => { + if (!mapped[r.company.id]) { + mapped[r.company.id] = {company: r.company.name, recipes: []} + } + mapped[r.company.id].recipes.push(r) + }) + return mapped.filter(e => e != null) // Filter to remove empty elements in the array that appears for some reason + })) + } + + getById(id: number): Observable { + return this.api.get(`/recipe/${id}`) + } + + save(name: string, description: string, sample: number, approbationDate: string, remark: string, companyId: number): Observable { + const body = {name, description, sample, remark, companyId} + if (approbationDate) { + // @ts-ignore + body.approbationDate = approbationDate + } + return this.api.post('/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} + if (approbationDate) { + // @ts-ignore + body.approbationDate = approbationDate + } + return this.api.put('/recipe', body) + } + + saveExplorerModifications(id: number, note: string, mixesLocationChange: Map): Observable { + const body = { + id, + note, + mixesLocation: {} + } + mixesLocationChange.forEach((l, i) => body.mixesLocation[i] = l) + + return this.api.put('/recipe/public', body) + } + + deductMixQuantities(recipe: Recipe, mixId: number, quantities: Map): Observable { + return this.sendDeductBody(this.buildDeductMixBody(recipe, mixId, quantities)) + } + + deductQuantities(recipe: Recipe, quantities: Map>): Observable { + return this.sendDeductBody(this.buildDeductBody(recipe, quantities)) + } + + delete(id: number): Observable { + return this.api.delete(`/recipe/${id}`) + } + + private buildDeductMixBody(recipe: Recipe, mixId: number, quantities: Map): any { + const mix = recipe.mixes.filter(m => m.id === mixId)[0] + const body = {id: recipe.id, quantities: {}} + body.quantities[mixId] = {} + const firstMaterial = mix.mixMaterials[0].material.id + mix.mixMaterials.forEach(m => { + if (quantities && quantities.has(m.material.id)) { + 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) + body.quantities[mix.id][m.material.id] = quantity + } + }) + return body + } + + private buildDeductBody(recipe: Recipe, quantities: Map>): any { + const body = {id: recipe.id, quantities: {}} + recipe.mixes.forEach(mix => { + body.quantities[mix.id] = this.buildDeductMixBody(recipe, mix.id, quantities.get(mix.id)).quantities[mix.id] + }) + return body + } + + private sendDeductBody(body: any): Observable { + return this.api.put('/recipe/deduct', body) + } } diff --git a/src/main/frontend/src/app/modules/company/pages/add/add.component.ts b/src/main/frontend/src/app/modules/company/pages/add/add.component.ts index 096f910..fd0db62 100644 --- a/src/main/frontend/src/app/modules/company/pages/add/add.component.ts +++ b/src/main/frontend/src/app/modules/company/pages/add/add.component.ts @@ -1,9 +1,9 @@ import {Component} from '@angular/core'; import {CompanyService} from "../../service/company.service"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-add', @@ -28,9 +28,10 @@ export class AddComponent extends SubscribingComponent { constructor( private companyService: CompanyService, - private router: Router + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } submit(values) { @@ -39,12 +40,12 @@ export class AddComponent extends SubscribingComponent { { next: () => this.router.navigate(['/catalog/company/list']), error: err => { - if (err.status == 409) { + if (err.status === 409) { this.errorMessage = `Une bannière avec le nom '${values.name}' existe déjà` } else { this.unknownError = true + console.log(err) } - console.log(err) } } ) diff --git a/src/main/frontend/src/app/modules/company/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/company/pages/edit/edit.component.ts index 33bdec1..b828812 100644 --- a/src/main/frontend/src/app/modules/company/pages/edit/edit.component.ts +++ b/src/main/frontend/src/app/modules/company/pages/edit/edit.component.ts @@ -2,7 +2,7 @@ import {Component} from '@angular/core'; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {Company} from "../../../shared/model/company.model"; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; import {CompanyService} from "../../service/company.service"; import {ActivatedRoute, Router} from "@angular/router"; @@ -30,10 +30,10 @@ export class EditComponent extends SubscribingComponent { constructor( private companyService: CompanyService, - private router: Router, - private activatedRoute: ActivatedRoute + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/company/pages/list/list.component.ts b/src/main/frontend/src/app/modules/company/pages/list/list.component.ts index 4f0d118..83c85f0 100644 --- a/src/main/frontend/src/app/modules/company/pages/list/list.component.ts +++ b/src/main/frontend/src/app/modules/company/pages/list/list.component.ts @@ -2,6 +2,8 @@ import {Component} from '@angular/core'; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {CompanyService} from "../../service/company.service"; import {EmployeePermission} from "../../../shared/model/employee"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-list', @@ -20,8 +22,10 @@ export class ListComponent extends SubscribingComponent { }] constructor( - private companyService: CompanyService + private companyService: CompanyService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } } diff --git a/src/main/frontend/src/app/modules/employees/pages/add/add.component.sass b/src/main/frontend/src/app/modules/employees/pages/add/add.component.sass index e69de29..3c27c57 100644 --- a/src/main/frontend/src/app/modules/employees/pages/add/add.component.sass +++ b/src/main/frontend/src/app/modules/employees/pages/add/add.component.sass @@ -0,0 +1,2 @@ +mat-card + max-width: 90rem diff --git a/src/main/frontend/src/app/modules/employees/pages/add/add.component.ts b/src/main/frontend/src/app/modules/employees/pages/add/add.component.ts index dd757b9..3e8cc51 100644 --- a/src/main/frontend/src/app/modules/employees/pages/add/add.component.ts +++ b/src/main/frontend/src/app/modules/employees/pages/add/add.component.ts @@ -1,12 +1,12 @@ import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {PermissionsFieldComponent} from "../../../shared/components/permissions-field/permissions-field.component"; import {EmployeeGroup} from "../../../shared/model/employee"; import {Observable, Subject} from "rxjs"; import {GroupService} from "../../../groups/services/group.service"; import {take, takeUntil} from "rxjs/operators"; import {EmployeeService} from "../../services/employee.service"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; @Component({ @@ -30,9 +30,10 @@ export class AddComponent extends SubscribingComponent { constructor( private employeeService: EmployeeService, private groupService: GroupService, - private router: Router + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.sass b/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.sass index e69de29..3c27c57 100644 --- a/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.sass +++ b/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.sass @@ -0,0 +1,2 @@ +mat-card + max-width: 90rem diff --git a/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.ts index 10aee43..6199b9c 100644 --- a/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.ts +++ b/src/main/frontend/src/app/modules/employees/pages/edit/edit.component.ts @@ -29,11 +29,11 @@ export class EditComponent extends SubscribingComponent { private accountService: AccountService, private employeeService: EmployeeService, private groupService: GroupService, - private activatedRoute: ActivatedRoute, - private router: Router, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/employees/pages/list/list.component.ts b/src/main/frontend/src/app/modules/employees/pages/list/list.component.ts index 0e365bc..f5c6f02 100644 --- a/src/main/frontend/src/app/modules/employees/pages/list/list.component.ts +++ b/src/main/frontend/src/app/modules/employees/pages/list/list.component.ts @@ -6,6 +6,8 @@ import {takeUntil} from "rxjs/operators"; import {AccountService} from "../../../accounts/services/account.service"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-employees', @@ -27,9 +29,11 @@ export class ListComponent extends SubscribingComponent { constructor( private employeeService: EmployeeService, - private accountService: AccountService + private accountService: AccountService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/employees/pages/password-edit/password-edit.component.ts b/src/main/frontend/src/app/modules/employees/pages/password-edit/password-edit.component.ts index f0f12d8..eda5cc9 100644 --- a/src/main/frontend/src/app/modules/employees/pages/password-edit/password-edit.component.ts +++ b/src/main/frontend/src/app/modules/employees/pages/password-edit/password-edit.component.ts @@ -19,10 +19,10 @@ export class PasswordEditComponent extends SubscribingComponent { constructor( private employeeService: EmployeeService, private formBuilder: FormBuilder, - private router: Router, - private activatedRoute: ActivatedRoute + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/groups/pages/add/add.component.sass b/src/main/frontend/src/app/modules/groups/pages/add/add.component.sass index 5586bfb..d194b62 100644 --- a/src/main/frontend/src/app/modules/groups/pages/add/add.component.sass +++ b/src/main/frontend/src/app/modules/groups/pages/add/add.component.sass @@ -1,5 +1,5 @@ mat-card - width: max-content + max-width: 90rem mat-checkbox font-size: .8em diff --git a/src/main/frontend/src/app/modules/groups/pages/add/add.component.ts b/src/main/frontend/src/app/modules/groups/pages/add/add.component.ts index c448a6e..efa8da0 100644 --- a/src/main/frontend/src/app/modules/groups/pages/add/add.component.ts +++ b/src/main/frontend/src/app/modules/groups/pages/add/add.component.ts @@ -1,7 +1,7 @@ import {Component, ViewChild} from '@angular/core'; import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; import {GroupService} from "../../services/group.service"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; import {PermissionsFieldComponent} from "../../../shared/components/permissions-field/permissions-field.component"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; @@ -20,9 +20,10 @@ export class AddComponent extends SubscribingComponent { constructor( private formBuilder: FormBuilder, private groupService: GroupService, - private router: Router + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.sass b/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.sass index e69de29..3c27c57 100644 --- a/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.sass +++ b/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.sass @@ -0,0 +1,2 @@ +mat-card + max-width: 90rem diff --git a/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.ts index f50737b..0ef4658 100644 --- a/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.ts +++ b/src/main/frontend/src/app/modules/groups/pages/edit/edit.component.ts @@ -21,13 +21,13 @@ export class EditComponent extends SubscribingComponent { private _nameControl: FormControl constructor( - private activatedRoute: ActivatedRoute, - private router: Router, private accountService: AccountService, private groupService: GroupService, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/groups/pages/list/list.component.ts b/src/main/frontend/src/app/modules/groups/pages/list/list.component.ts index 7afe55f..822c9d0 100644 --- a/src/main/frontend/src/app/modules/groups/pages/list/list.component.ts +++ b/src/main/frontend/src/app/modules/groups/pages/list/list.component.ts @@ -6,6 +6,8 @@ import {takeUntil} from "rxjs/operators"; import {animate, state, style, transition, trigger} from "@angular/animations"; import {AccountService} from "../../../accounts/services/account.service"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-groups', @@ -27,9 +29,11 @@ export class ListComponent extends SubscribingComponent { constructor( private groupService: GroupService, - private accountService: AccountService + private accountService: AccountService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { diff --git a/src/main/frontend/src/app/modules/material-type/pages/add/add.component.ts b/src/main/frontend/src/app/modules/material-type/pages/add/add.component.ts index ac6d7e5..1e18920 100644 --- a/src/main/frontend/src/app/modules/material-type/pages/add/add.component.ts +++ b/src/main/frontend/src/app/modules/material-type/pages/add/add.component.ts @@ -1,9 +1,9 @@ import {Component} from '@angular/core'; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; import {MaterialTypeService} from "../../service/material-type.service"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-add', @@ -48,9 +48,10 @@ export class AddComponent extends SubscribingComponent { constructor( private materialTypeService: MaterialTypeService, - private router: Router + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } submit(values) { diff --git a/src/main/frontend/src/app/modules/material-type/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/material-type/pages/edit/edit.component.ts index 7f28964..89ad89e 100644 --- a/src/main/frontend/src/app/modules/material-type/pages/edit/edit.component.ts +++ b/src/main/frontend/src/app/modules/material-type/pages/edit/edit.component.ts @@ -4,7 +4,7 @@ import {ActivatedRoute, Router} from "@angular/router"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {MaterialTypeService} from "../../service/material-type.service"; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; @Component({ selector: 'cre-edit', @@ -44,10 +44,10 @@ export class EditComponent extends SubscribingComponent { constructor( private materialTypeService: MaterialTypeService, - private router: Router, - private activatedRoute: ActivatedRoute + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { diff --git a/src/main/frontend/src/app/modules/material-type/pages/list/list.component.ts b/src/main/frontend/src/app/modules/material-type/pages/list/list.component.ts index 0b16d66..a758493 100644 --- a/src/main/frontend/src/app/modules/material-type/pages/list/list.component.ts +++ b/src/main/frontend/src/app/modules/material-type/pages/list/list.component.ts @@ -2,6 +2,8 @@ import {Component} from '@angular/core'; import {MaterialTypeService} from "../../service/material-type.service"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {EmployeePermission} from "../../../shared/model/employee"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-list', @@ -25,8 +27,10 @@ export class ListComponent extends SubscribingComponent { ] constructor( - private materialTypeService: MaterialTypeService + private materialTypeService: MaterialTypeService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } } diff --git a/src/main/frontend/src/app/modules/material/pages/add/add.component.ts b/src/main/frontend/src/app/modules/material/pages/add/add.component.ts index 02dce14..b80da7b 100644 --- a/src/main/frontend/src/app/modules/material/pages/add/add.component.ts +++ b/src/main/frontend/src/app/modules/material/pages/add/add.component.ts @@ -1,9 +1,9 @@ import {Component} from '@angular/core'; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; import {MaterialService} from "../../service/material.service"; import {MaterialTypeService} from "../../../material-type/service/material-type.service"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; import {SubscribingComponent} from "../../../shared/components/subscribing.component"; import {map} from "rxjs/operators"; @@ -63,9 +63,10 @@ export class AddComponent extends SubscribingComponent { constructor( private materialService: MaterialService, private materialTypeService: MaterialTypeService, - private router: Router + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } submit(values) { diff --git a/src/main/frontend/src/app/modules/material/pages/edit/edit.component.ts b/src/main/frontend/src/app/modules/material/pages/edit/edit.component.ts index a1165a3..f8d1369 100644 --- a/src/main/frontend/src/app/modules/material/pages/edit/edit.component.ts +++ b/src/main/frontend/src/app/modules/material/pages/edit/edit.component.ts @@ -1,6 +1,6 @@ import {Component, ViewChild} from '@angular/core'; import {FormField} from "../../../shared/components/entity-add/entity-add.component"; -import {Validators} from "@angular/forms"; +import {FormBuilder, Validators} from "@angular/forms"; import {map} from "rxjs/operators"; import {MaterialTypeService} from "../../../material-type/service/material-type.service"; import {MaterialService} from "../../service/material.service"; @@ -71,10 +71,10 @@ export class EditComponent extends SubscribingComponent { constructor( private materialService: MaterialService, private materialTypeService: MaterialTypeService, - private router: Router, - private activatedRoute: ActivatedRoute, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { @@ -114,8 +114,8 @@ export class EditComponent extends SubscribingComponent { this.errorMessage = `Un produit avec le nom '${values.name}' existe déjà` } else { this.unknownError = true + console.error(err) } - console.log(err) } } ) @@ -128,7 +128,7 @@ export class EditComponent extends SubscribingComponent { next: () => this.router.navigate(['/catalog/material/list']), error: err => { this.unknownError = true - console.log(err) + console.error(err) } } ) diff --git a/src/main/frontend/src/app/modules/material/pages/list/list.component.ts b/src/main/frontend/src/app/modules/material/pages/list/list.component.ts index 2d8b608..43c578a 100644 --- a/src/main/frontend/src/app/modules/material/pages/list/list.component.ts +++ b/src/main/frontend/src/app/modules/material/pages/list/list.component.ts @@ -3,6 +3,8 @@ import {SubscribingComponent} from "../../../shared/components/subscribing.compo import {MaterialService} from "../../service/material.service"; import {EmployeePermission} from "../../../shared/model/employee"; import {environment} from "../../../../../environments/environment"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-list', @@ -39,9 +41,11 @@ export class ListComponent extends SubscribingComponent { private hasSimdutMap: any = {} constructor( - private materialService: MaterialService + private materialService: MaterialService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { diff --git a/src/main/frontend/src/app/modules/material/service/material.service.ts b/src/main/frontend/src/app/modules/material/service/material.service.ts index 70276d9..fc167fb 100644 --- a/src/main/frontend/src/app/modules/material/service/material.service.ts +++ b/src/main/frontend/src/app/modules/material/service/material.service.ts @@ -17,6 +17,14 @@ export class MaterialService { return this.api.get('/material') } + getAllForMixCreation(recipeId: number): Observable { + return this.api.get(`/material/mix/create/${recipeId}`) + } + + getAllForMixUpdate(mixId: number): Observable { + return this.api.get(`/material/mix/update/${mixId}`) + } + getById(id: number): Observable { return this.api.get(`/material/${id}`) } diff --git a/src/main/frontend/src/app/modules/shared/components/entity-add/entity-add.component.ts b/src/main/frontend/src/app/modules/shared/components/entity-add/entity-add.component.ts index 063ec29..e48bda7 100644 --- a/src/main/frontend/src/app/modules/shared/components/entity-add/entity-add.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/entity-add/entity-add.component.ts @@ -2,6 +2,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; import {SubscribingComponent} from "../subscribing.component"; import {FormBuilder, FormControl, FormGroup, ValidatorFn} from "@angular/forms"; import {Observable} from "rxjs"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-entity-add', @@ -19,9 +20,11 @@ export class EntityAddComponent extends SubscribingComponent { form: FormGroup | null constructor( - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { @@ -45,10 +48,6 @@ export class EntityAddComponent extends SubscribingComponent { getControl(controlName: string): FormControl { return this.form.controls[controlName] as FormControl } - - test(any) { - console.log(any) - } } export class FormField { @@ -61,6 +60,7 @@ export class FormField { public errorMessages?: FormErrorMessage[], public valueFn?: (any) => any, public template?: any, + public readonly?: boolean, // Specifics to some types public step?: string, public options$?: Observable<{ value: any, label: string }[]>, diff --git a/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.html b/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.html index fabafb1..60bfc00 100644 --- a/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.html +++ b/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.html @@ -2,7 +2,7 @@ {{title}} - +

Une erreur est survenue

{{customError}}

@@ -32,10 +32,12 @@ - + - + @@ -44,7 +46,7 @@ let-control="control" let-field="field"> {{field.label}} - + @@ -59,7 +61,7 @@ let-control="control" let-field="field"> {{field.label}} - + {{option.label}} diff --git a/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.ts b/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.ts index b13d8f5..8539297 100644 --- a/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/entity-edit/entity-edit.component.ts @@ -4,6 +4,7 @@ import {SubscribingComponent} from "../subscribing.component"; import {FormField} from "../entity-add/entity-add.component"; import {EmployeePermission} from "../../model/employee"; import {AccountService} from "../../../accounts/services/account.service"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-entity-edit', @@ -19,6 +20,7 @@ export class EntityEditComponent extends SubscribingComponent { @Input() deletePermission: EmployeePermission @Input() unknownError = false @Input() customError: string | null + @Input() disableButtons: boolean @Output() submit = new EventEmitter() @Output() delete = new EventEmitter() @@ -26,9 +28,11 @@ export class EntityEditComponent extends SubscribingComponent { constructor( private accountService: AccountService, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit() { @@ -42,17 +46,21 @@ export class EntityEditComponent extends SubscribingComponent { } submitForm() { - const values = {} - this.formFields.forEach(f => { - values[f.name] = this.getControl(f.name).value - }) - this.submit.emit(values) + this.submit.emit(this.values) } getControl(controlName: string): FormControl { return this.form.controls[controlName] as FormControl } + get values(): any { + const values = {} + this.formFields.forEach(f => { + values[f.name] = this.getControl(f.name).value + }) + return values + } + get canDelete(): boolean { return this.accountService.hasPermission(this.deletePermission) } diff --git a/src/main/frontend/src/app/modules/shared/components/entity-list/entity-list.component.ts b/src/main/frontend/src/app/modules/shared/components/entity-list/entity-list.component.ts index 3b5d102..93eb95a 100644 --- a/src/main/frontend/src/app/modules/shared/components/entity-list/entity-list.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/entity-list/entity-list.component.ts @@ -3,6 +3,8 @@ import {Observable} from "rxjs"; import {SubscribingComponent} from "../subscribing.component"; import {AccountService} from "../../../accounts/services/account.service"; import {EmployeePermission} from "../../model/employee"; +import {FormBuilder} from "@angular/forms"; +import {ActivatedRoute, Router} from "@angular/router"; @Component({ selector: 'cre-entity-list', @@ -18,9 +20,11 @@ export class EntityListComponent extends SubscribingComponent { @Input() addPermission: EmployeePermission constructor( - private accountService: AccountService + private accountService: AccountService, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } hasPermissionToUseButton(button: TableButton): boolean { diff --git a/src/main/frontend/src/app/modules/shared/components/header/header.component.ts b/src/main/frontend/src/app/modules/shared/components/header/header.component.ts index cd40f17..65ab5d0 100644 --- a/src/main/frontend/src/app/modules/shared/components/header/header.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/header/header.component.ts @@ -1,9 +1,10 @@ import {Component} from '@angular/core'; -import {ResolveEnd, Router} from "@angular/router"; +import {ActivatedRoute, ResolveEnd, Router} from "@angular/router"; import {AppState} from "../../app-state"; import {Employee, EmployeePermission} from "../../model/employee"; import {AccountService} from "../../../accounts/services/account.service"; import {SubscribingComponent} from "../subscribing.component"; +import {FormBuilder} from "@angular/forms"; @Component({ selector: 'cre-header', @@ -12,9 +13,10 @@ import {SubscribingComponent} from "../subscribing.component"; }) export class HeaderComponent extends SubscribingComponent { links: HeaderLink[] = [ + {route: '/color', title: 'Couleurs', requiredPermission: EmployeePermission.VIEW_RECIPE}, {route: '/catalog', title: 'Catalogue', enabled: true}, - new HeaderLink('/employee', 'Employés', EmployeePermission.VIEW_EMPLOYEE), - new HeaderLink('/group', 'Groupes', EmployeePermission.VIEW_EMPLOYEE_GROUP), + {route: '/employee', title: 'Employés', requiredPermission: EmployeePermission.VIEW_EMPLOYEE}, + {route: '/group', title: 'Groupes', requiredPermission: EmployeePermission.VIEW_EMPLOYEE_GROUP}, {route: '/account/login', title: 'Connexion', enabled: true}, {route: '/account/logout', title: 'Déconnexion', enabled: false}, ]; @@ -22,10 +24,11 @@ export class HeaderComponent extends SubscribingComponent { constructor( private accountService: AccountService, - private router: Router, - private appState: AppState + private appState: AppState, + router: Router, + activatedRoute: ActivatedRoute ) { - super() + super(activatedRoute, router) } ngOnInit(): void { @@ -89,7 +92,7 @@ class HeaderLink { public route: string, public title: string, public requiredPermission?: EmployeePermission, - public enabled = false + public enabled? ) { } } diff --git a/src/main/frontend/src/app/modules/shared/components/subscribing.component.ts b/src/main/frontend/src/app/modules/shared/components/subscribing.component.ts index 7cd70f3..7e9e805 100644 --- a/src/main/frontend/src/app/modules/shared/components/subscribing.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/subscribing.component.ts @@ -1,14 +1,24 @@ import {take, takeUntil} from "rxjs/operators"; import {OnDestroy, OnInit} from "@angular/core"; import {Observable, Subject} from "rxjs"; +import {ActivatedRoute, Router} from "@angular/router"; +import {UrlUtils} from "../utils/url.utils"; export abstract class SubscribingComponent implements OnInit, OnDestroy { protected subscribers$ = [] protected destroy$ = new Subject() + unknownError = false + + protected constructor( + protected activatedRoute: ActivatedRoute, + protected router: Router, + protected urlUtils = new UrlUtils(activatedRoute, router) + ) { + } subscribe(observable: Observable, observer, take_count = -1) { if (!observer.error) { - observer.error = err => console.log(err) + observer.error = err => this.handleObserverError(err) } if (take_count >= 0) { @@ -19,6 +29,24 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { this.subscribers$.push(observable.subscribe(observer)) } + subscribeEntityByIdFromRoute(service: any, entitySubject: Subject, notFoundRoute: string, paramName: string = 'id') { + const id = this.urlUtils.parseIntUrlParam(paramName) + this.subscribe( + service.getById(id), + { + next: e => entitySubject.next(e), + error: err => { + if (err.status === 404) { + this.urlUtils.navigateTo(notFoundRoute) + } else { + this.handleObserverError(err) + } + } + }, + 1 + ) + } + ngOnInit(): void { } @@ -26,4 +54,17 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { this.destroy$.next(true) this.destroy$.complete() } + + handleObserverError(error: any) { + console.error(error) + this.unknownError = true + } + + handleNotFoundError(error: any, route: string) { + if (error.status === 404) { + this.urlUtils.navigateTo(route) + } else { + this.handleObserverError(error) + } + } } diff --git a/src/main/frontend/src/app/modules/shared/model/recipe.model.ts b/src/main/frontend/src/app/modules/shared/model/recipe.model.ts new file mode 100644 index 0000000..6a8bada --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/model/recipe.model.ts @@ -0,0 +1,55 @@ +import {Material} from "./material.model"; +import {LocalDate} from "js-joda"; +import {Company} from "./company.model"; + +export class Recipe { + constructor( + public id: number, + public name: string, + public description: string, + public sample: number, + public approbationDate: LocalDate, + public remark: string, + public note: string, + public company: Company, + public mixes: Mix[], + public steps: RecipeStep[] + ) { + } +} + +export class Mix { + constructor( + public id: number, + public mixType: MixType, + public mixMaterials: MixMaterial[], + public location: string, + ) { + } +} + +export class MixMaterial { + constructor( + public id: number, + public material: Material, + public quantity: number + ) { + } +} + +class MixType { + constructor( + public id: number, + public name: string, + public material: Material + ) { + } +} + +export class RecipeStep { + constructor( + public id: number, + public message: string + ) { + } +} diff --git a/src/main/frontend/src/app/modules/shared/units.ts b/src/main/frontend/src/app/modules/shared/units.ts new file mode 100644 index 0000000..8ee70e5 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/units.ts @@ -0,0 +1,32 @@ +export const UNIT_MILLILITER = 'mL' +export const UNIT_LITER = 'L' +export const UNIT_GALLON = 'gal' + +export const LITER_TO_MILLILITER = 1000 +export const GALLON_TO_MILLILITER = 3785.411784 + +export const UNIT_RATIOS = { + mL: { + mL: 1, + L: 1 / LITER_TO_MILLILITER, + gal: 1 / GALLON_TO_MILLILITER + }, + L: { + mL: LITER_TO_MILLILITER, + L: 1, + gal: LITER_TO_MILLILITER / GALLON_TO_MILLILITER + }, + gal: { + mL: GALLON_TO_MILLILITER, + L: 1 / (LITER_TO_MILLILITER / GALLON_TO_MILLILITER), + gal: 1 + } +} + +export function convertMixMaterialQuantity(computedQuantity: { percents: boolean, quantity: number }, from: string, to: string): number { + return !computedQuantity.percents ? convertQuantity(computedQuantity.quantity, from, to) : computedQuantity.quantity +} + +export function convertQuantity(quantity: number, from: string, to: string): number { + return quantity * UNIT_RATIOS[from][to] +} diff --git a/src/main/frontend/src/app/modules/shared/utils/url.utils.ts b/src/main/frontend/src/app/modules/shared/utils/url.utils.ts new file mode 100644 index 0000000..35ff4f1 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/utils/url.utils.ts @@ -0,0 +1,21 @@ +import {ActivatedRoute, Router} from "@angular/router"; + +export class UrlUtils { + constructor( + private activatedRoute: ActivatedRoute, + private router: Router + ) { + } + + parseUrlParam(param: string): string { + return this.activatedRoute.snapshot.paramMap.get(param) + } + + parseIntUrlParam(param: string): number { + return parseInt(this.parseUrlParam(param)) + } + + navigateTo(url: string) { + this.router.navigate([url]) + } +} diff --git a/src/main/frontend/src/custom-theme.scss b/src/main/frontend/src/custom-theme.scss index a44e030..c6a6653 100644 --- a/src/main/frontend/src/custom-theme.scss +++ b/src/main/frontend/src/custom-theme.scss @@ -92,6 +92,7 @@ $color-recipes-explorer-frontend-theme: mat-light-theme($theme-primary, $theme-a $color-primary: map-get($theme-primary, 500); $color-accent: map-get($theme-accent, 500); +$color-warn: map-get($theme-warn, 500); html, body { diff --git a/src/main/frontend/src/styles.sass b/src/main/frontend/src/styles.sass index bb3e444..cee6919 100644 --- a/src/main/frontend/src/styles.sass +++ b/src/main/frontend/src/styles.sass @@ -1,8 +1,10 @@ @import "custom-theme" +@import "~material-design-icons/iconfont/material-icons.css" mat-card padding: 0 !important width: max-content + max-width: 50rem &.x-centered margin: auto @@ -22,6 +24,9 @@ mat-card margin-top: 16px padding: 0 16px + &.no-action + padding: 0 24px 16px 24px !important + mat-form-field width: 100% @@ -38,6 +43,7 @@ mat-card table 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) + max-width: 90vw th background-color: $color-primary @@ -72,12 +78,62 @@ table .disabled * display: none +mat-expansion-panel.table-title + mat-expansion-panel-header + background-color: $color-primary + + &:hover, &:focus + background-color: $color-primary !important + + mat-panel-title + color: $light-primary-text !important + font-weight: bold + text-transform: uppercase + + mat-panel-description + color: $light-primary-text + + mat-form-field + display: inline + + &.mat-expanded mat-expansion-panel-header + border-bottom-left-radius: 0 + border-bottom-right-radius: 0 + + .mat-expansion-panel-body + padding: 0 !important + + table + width: 100% + + th + border-top-left-radius: 0 + border-top-right-radius: 0 + +cre-mix-table.no-top-margin mat-expansion-panel + margin-top: 0 !important + button text-transform: uppercase font-weight: 500 &.mat-accent - color: white !important + color: $light-primary-text !important + +mat-form-field + &.w-auto + .mat-form-field-infix + width: auto !important + + &.dark + input + caret-color: $light-primary-text !important + + mat-label, input + color: $light-primary-text + + .mat-form-field-underline, .mat-form-field-ripple + background-color: $light-primary-text !important div.empty color: $dark-secondary-text @@ -92,6 +148,13 @@ div.empty button margin-left: 1rem + &.backward + justify-content: flex-start + + button + margin-left: 0 + margin-right: 1rem + .alert p margin-bottom: 0 @@ -112,3 +175,6 @@ div.empty left: 0 background-color: black opacity: 0.4 + +.color-warning + color: #fdd835 diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt index e44627c..1c75639 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt @@ -60,7 +60,7 @@ open class MixUpdateDto( @field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE) val name: String?, - val materialType: MaterialType?, + val materialTypeId: Long?, val mixMaterials: Map? ) : EntityDto { @@ -88,7 +88,7 @@ fun mixSaveDto( fun mixUpdateDto( id: Long = 0L, name: String? = "name", - materialType: MaterialType? = materialType(), + materialTypeId: Long? = 0L, mixMaterials: Map? = mapOf(), op: MixUpdateDto.() -> Unit = {} -) = MixUpdateDto(id, name, materialType, mixMaterials).apply(op) +) = MixUpdateDto(id, name, materialTypeId, mixMaterials).apply(op) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt index 957139c..2ba3fa7 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt @@ -55,17 +55,17 @@ class MixServiceImpl( override fun update(entity: MixUpdateDto): Mix { val mix = getById(entity.id) - if (entity.name != null || entity.materialType != null) { + if (entity.name != null || entity.materialTypeId != null) { mix.mixType = if (mixTypeIsShared(mix.mixType)) { mixTypeService.createForNameAndMaterialType( entity.name ?: mix.mixType.name, - entity.materialType ?: mix.mixType.material.materialType!! + if (entity.materialTypeId != null) materialTypeService.getById(entity.materialTypeId) else mix.mixType.material.materialType!! ) } else { mixTypeService.updateForNameAndMaterialType( mix.mixType, entity.name ?: mix.mixType.name, - entity.materialType ?: mix.mixType.material.materialType!! + if (entity.materialTypeId != null) materialTypeService.getById(entity.materialTypeId) else mix.mixType.material.materialType!! ) } } diff --git a/src/main/resources/thymeleaf/templates/recipe/explore.html b/src/main/resources/thymeleaf/templates/recipe/explore.html index f926da9..4657d24 100644 --- a/src/main/resources/thymeleaf/templates/recipe/explore.html +++ b/src/main/resources/thymeleaf/templates/recipe/explore.html @@ -272,7 +272,7 @@ let i = 0; $(".mixContainer").each(function () { - formData[i++] = getMixQuantities($(this)); + formData[i++] = getMixMaterials($(this)); }); clearNotEnoughClasses(); @@ -284,7 +284,7 @@ $(".useMixSubmit").on({ click: function () { showConfirm(formatMessage("[[#{inventory.askUseMix}]]"), false, () => { - let formData = [getMixQuantities($(this).parents(".mixContainer"))]; + let formData = [getMixMaterials($(this).parents(".mixContainer"))]; clearNotEnoughClasses(); sendPost(formData, "/inventory/use", r => displayNotEnoughReason(r)); @@ -384,7 +384,7 @@ }); } - function getMixQuantities(mixContainer) { + function getMixMaterials(mixContainer) { mix = $(mixContainer).find(".mix"); const mixId = mix.data("mixid");