diff --git a/.gitignore b/.gitignore index 2e789dc..ccbebdb 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ speed-measure-plugin*.json .history/* # misc +/.angular/cache /.sass-cache /connect.lock /coverage diff --git a/angular.json b/angular.json index bf30028..fb6d6d1 100644 --- a/angular.json +++ b/angular.json @@ -22,7 +22,6 @@ "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", - "aot": true, "assets": [ "src/favicon.ico", "src/assets", @@ -37,7 +36,13 @@ "src/custom-theme.scss", "src/styles.sass" ], - "scripts": [] + "scripts": [], + "vendorChunk": true, + "extractLicenses": false, + "buildOptimizer": false, + "sourceMap": false, + "optimization": false, + "namedChunks": true }, "configurations": { "production": { @@ -67,7 +72,8 @@ } ] } - } + }, + "defaultConfiguration": "" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", diff --git a/docker-compose.yml b/docker-compose.yml index 11391cd..b6c8985 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,11 @@ version: "3.1" services: - database: - image: mysql - command: --default-authentication-plugin=mysql_native_password - environment: - MYSQL_ROOT_PASSWORD: "pass" - MYSQL_DATABASE: "cre" - ports: - - "3306:3306" - backend: + cre.backend: image: registry.fyloz.dev:5443/colorrecipesexplorer/backend:latest environment: spring_profiles_active: "mysql,debug" - cre_database_url: "mysql://database:3306/cre" + cre_database_url: "mysql://database/cre" cre_database_username: "root" cre_database_password: "pass" CRE_ENABLE_DB_UPDATE: 1 @@ -23,6 +15,14 @@ services: volumes: - cre_data:/usr/bin/cre/data - cre_config:/usr/bin/cre/config + cre.database: + image: mysql + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_ROOT_PASSWORD: "pass" + MYSQL_DATABASE: "cre" + ports: + - "3307:3306" volumes: cre_data: diff --git a/package.json b/package.json index c0d8071..b93082c 100644 --- a/package.json +++ b/package.json @@ -11,54 +11,54 @@ }, "private": true, "dependencies": { - "@angular/animations": "~11.2.10", - "@angular/cdk": "^11.2.11", - "@angular/common": "~11.2.10", - "@angular/compiler": "~11.2.10", - "@angular/core": "~11.2.10", - "@angular/forms": "~11.2.10", - "@angular/material": "^11.2.9", - "@angular/platform-browser": "~11.2.10", - "@angular/platform-browser-dynamic": "~11.2.10", - "@angular/router": "~11.2.10", - "@mdi/angular-material": "^5.7.55", + "@angular/animations": "~12.2.14", + "@angular/cdk": "^12.2.13", + "@angular/common": "~12.2.14", + "@angular/compiler": "~12.2.14", + "@angular/core": "~12.2.14", + "@angular/forms": "~12.2.14", + "@angular/material": "^12.2.13", + "@angular/platform-browser": "~12.2.14", + "@angular/platform-browser-dynamic": "~12.2.14", + "@angular/router": "~12.2.14", + "@mdi/angular-material": "^6.5.95", "bootstrap": "^4.5.2", - "copy-webpack-plugin": "^6.2.1", - "js-joda": "^1.11.0", + "copy-webpack-plugin": "^10.0.0", + "@js-joda/core": "^4.3.1", "material-design-icons": "^3.0.1", "ngx-material-file-input": "^2.1.1", - "rxjs": "~6.5.4", - "tslib": "^2.0.0", - "zone.js": "~0.10.2" + "rxjs": "^7.4.0", + "tslib": "^2.3.1", + "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.1102.9", + "@angular-devkit/build-angular": "^12.2.13", "@angular-eslint/builder": "4.3.0", "@angular-eslint/eslint-plugin": "4.3.0", "@angular-eslint/eslint-plugin-template": "4.3.0", "@angular-eslint/schematics": "4.3.0", "@angular-eslint/template-parser": "4.3.0", - "@angular/cli": "^11.2.11", - "@angular/compiler-cli": "~11.2.10", - "@angular/language-service": "~11.2.10", + "@angular/cli": "^12.2.13", + "@angular/compiler-cli": "~12.2.14", + "@angular/language-service": "~12.2.14", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "4.16.1", "@typescript-eslint/parser": "4.16.1", - "eslint": "^7.6.0", + "eslint": "^8.3.0", "eslint-plugin-import": "latest", "eslint-plugin-jsdoc": "latest", "eslint-plugin-prefer-arrow": "latest", - "jasmine-core": "~3.6.0", - "jasmine-spec-reporter": "~5.0.0", + "jasmine-core": "^3.10.1", + "jasmine-spec-reporter": "^7.0.0", "karma": "~6.3.2", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "protractor": "~7.0.0", - "ts-node": "~8.3.0", - "typescript": "~4.0.7" + "ts-node": "^10.4.0", + "typescript": "~4.3.5" } } diff --git a/src/_variables.scss b/src/_variables.scss index 4ff7528..c616181 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -18,3 +18,7 @@ $text-color-primary: white; $color-accent: map-get($theme-accent, 500); $color-warn: map-get($theme-error, 500); + +$light-primary-text: white; +$dark-primary-text: black; +$dark-secondary-text: black; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index a0ef43d..3d9f34f 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -7,7 +7,7 @@ import {CreConfigEditor} from './modules/configuration/config-editor' const routes: Routes = [{ path: 'color', - loadChildren: () => import('./modules/colors/colors.module').then(m => m.ColorsModule) + loadChildren: () => import('./modules/recipes/recipes.module').then(m => m.RecipesModule) }, { path: 'account', loadChildren: () => import('./modules/accounts/accounts.module').then(m => m.AccountsModule) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7f5758f..0d6e334 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,7 +5,6 @@ import {SubscribingComponent} from './modules/shared/components/subscribing.comp import {ActivatedRoute, Router} from '@angular/router' import {ErrorService} from './modules/shared/service/error.service' import {ConfigService} from './modules/shared/service/config.service' -import {Config} from './modules/shared/model/config.model' import {environment} from '../environments/environment' @Component({ @@ -38,7 +37,7 @@ export class AppComponent extends SubscribingComponent { online => this.isServerOnline = online ) - this.favIcon.href = environment.apiUrl + "/file?path=images%2Ficon" + this.favIcon.href = environment.apiUrl + "/config/icon" } reload() { diff --git a/src/app/modules/colors/colors-routing.module.ts b/src/app/modules/colors/colors-routing.module.ts deleted file mode 100644 index 879d0a1..0000000 --- a/src/app/modules/colors/colors-routing.module.ts +++ /dev/null @@ -1,39 +0,0 @@ -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"; - -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 { -} diff --git a/src/app/modules/colors/colors.module.ts b/src/app/modules/colors/colors.module.ts deleted file mode 100644 index c994650..0000000 --- a/src/app/modules/colors/colors.module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {NgModule} from '@angular/core' - -import {ColorsRoutingModule} from './colors-routing.module' -import {SharedModule} from '../shared/shared.module' -import {ListComponent} from './pages/list/list.component' -import {AddComponent} from './pages/add/add.component' -import {EditComponent} from './pages/edit/edit.component' -import {MatExpansionModule} from '@angular/material/expansion' -import {FormsModule} from '@angular/forms' -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' -import {ImagesEditorComponent} from './components/images-editor/images-editor.component' -import {MixesCardComponent} from './components/mixes-card/mixes-card.component' -import {MatSortModule} from '@angular/material/sort' - - -@NgModule({ - declarations: [ListComponent, AddComponent, EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, StepListComponent, StepTableComponent, MixEditorComponent, UnitSelectorComponent, MixAddComponent, MixEditComponent, ImagesEditorComponent, MixesCardComponent], - exports: [ - UnitSelectorComponent - ], - imports: [ - ColorsRoutingModule, - SharedModule, - MatExpansionModule, - FormsModule, - MatSortModule - ] -}) -export class ColorsModule { -} diff --git a/src/app/modules/colors/components/mix-editor/mix-editor.component.html b/src/app/modules/colors/components/mix-editor/mix-editor.component.html deleted file mode 100644 index a89926b..0000000 --- a/src/app/modules/colors/components/mix-editor/mix-editor.component.html +++ /dev/null @@ -1,136 +0,0 @@ - - - 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 - {{mixMaterial.position}} - - - - - - Produit - - - - {{materialDisplayName(material)}} - - - - Quantité - - - - Unités - - -

%

-
- - - {{units}} - - - - - -
-
- - - - - -
-
- - - diff --git a/src/app/modules/colors/components/mix-editor/mix-editor.component.sass b/src/app/modules/colors/components/mix-editor/mix-editor.component.sass deleted file mode 100644 index 3eb5b9b..0000000 --- a/src/app/modules/colors/components/mix-editor/mix-editor.component.sass +++ /dev/null @@ -1,6 +0,0 @@ -mat-card - max-width: unset !important - -td.units-wrapper p - width: 3rem - margin-bottom: 0 diff --git a/src/app/modules/colors/components/mix-editor/mix-editor.component.ts b/src/app/modules/colors/components/mix-editor/mix-editor.component.ts deleted file mode 100644 index d4eee9e..0000000 --- a/src/app/modules/colors/components/mix-editor/mix-editor.component.ts +++ /dev/null @@ -1,220 +0,0 @@ -import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core' -import { - Mix, - MixMaterial, - MixMaterialDto, - mixMaterialsAsMixMaterialsDto, - Recipe, - sortMixMaterialsDto -} from '../../../shared/model/recipe.model' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {MixService} from '../../services/mix.service' -import {RecipeService} from '../../services/recipe.service' -import {Material} from '../../../shared/model/material.model' -import {MaterialService} from '../../../material/service/material.service' -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' -import {ConfirmBoxComponent} from '../../../shared/components/confirm-box/confirm-box.component' -import {AccountService} from '../../../accounts/services/account.service' -import {ErrorService} from '../../../shared/service/error.service' - -@Component({ - selector: 'cre-mix-editor', - templateUrl: './mix-editor.component.html', - styleUrls: ['./mix-editor.component.sass'] -}) -export class MixEditorComponent extends ErrorHandlingComponent { - @ViewChild('matTable') mixTable: MatTable - @ViewChild('deleteConfirmBox') deleteConfirmBox: ConfirmBoxComponent - - @Input() mixId: number | null - @Input() recipeId: number | null - @Input() materials: Material[] - - @Output() save = new EventEmitter() - - mix: Mix | null - recipe: Recipe | null - materialTypes$ = this.materialTypeService.all - - form: FormGroup - nameControl: FormControl - materialTypeControl: FormControl - - mixMaterials: MixMaterialDto[] = [] - editionMode = false - units = UNIT_MILLILITER - hoveredMixMaterial: MixMaterial | null - columns = ['position', 'buttonsPosition', 'material', 'quantity', 'units', 'buttonRemove'] - - deleting = false - errorHandlers = [{ - filter: error => error.type === 'notfound-mix-id', - consumer: error => this.urlUtils.navigateTo('/color/list') - }, { - filter: error => error.type === 'exists-material-name', - messageProducer: error => `Un produit avec le nom '${error.name}' existe déjà` - }, { - filter: error => error.type === 'cannotdelete-mix', - messageProducer: error => 'Ce mélange est utilisé par un ou plusieurs autres mélanges' - }, { - filter: error => error.type === 'invalid-mixmaterial-first', - messageProducer: error => 'La quantité du premier ingrédient du mélange ne peut pas être exprimée en pourcentage' - }] - - constructor( - private mixService: MixService, - private recipeService: RecipeService, - private materialService: MaterialService, - private materialTypeService: MaterialTypeService, - private accountService: AccountService, - private formBuilder: FormBuilder, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - } - - ngOnInit() { - super.ngOnInit() - - this.mixId = this.urlUtils.parseIntUrlParam('id') - if (this.mixId) { - this.editionMode = true - } - - this.subscribeEntityById( - this.recipeService, - this.recipeId, - r => { - this.recipe = r - if (this.editionMode) { - this.mix = this.recipe.mixes.find(m => m.id === this.mixId) - this.mixMaterials = mixMaterialsAsMixMaterialsDto(this.mix) - } else { - this.addBlankMixMaterial() - } - this.generateForm() - } - ) - } - - addRow() { - this.addBlankMixMaterial() - this.mixTable.renderRows() - } - - removeRow(position: number) { - this.mixMaterials.splice(position, 1) - - // Decreases the position of each mix material above the removed one - for (let i = position; i < this.mixMaterials.length; i++) { - this.mixMaterials[i].position -= 1 - } - - this.mixTable.renderRows() - } - - increasePosition(mixMaterial: MixMaterialDto, table: MatTable) { - this.updateMixMaterialPosition(mixMaterial, mixMaterial.position + 1) - this.sort(table) - } - - decreasePosition(mixMaterial: MixMaterialDto, table: MatTable) { - this.updateMixMaterialPosition(mixMaterial, mixMaterial.position - 1) - this.sort(table) - } - - sort(table: MatTable) { - this.mixMaterials = sortMixMaterialsDto(this.mixMaterials) - table.renderRows() - } - - setMixMaterialMaterial(mixMaterial: MixMaterialDto, materialId: number) { - mixMaterial.isPercents = this.materials.find(m => m.id === materialId).materialType.usePercentages - mixMaterial.materialId = materialId - } - - submit() { - this.save.emit({ - name: this.nameControl.value, - recipeId: this.recipeId, - materialTypeId: this.materialTypeControl.value, - mixMaterials: this.mixMaterials, - units: this.units - }) - } - - delete() { - this.deleting = true - this.subscribeAndNavigate(this.mixService.delete(this.mixId), `/color/edit/${this.recipeId}`) - } - - getAvailableMaterials(mixMaterial: MixMaterialDto): Material[] { - return this.materials.filter(m => mixMaterial.materialId === m.id || this.mixMaterials.filter(mm => mm.materialId === m.id).length === 0) - } - - materialDisplayName(material: Material): string { - if (material.materialType.prefix) { - return `[${material.materialType.prefix}] ${material.name}` - } - return material.name - } - - sortedMaterials(materials: Material[]): Material[] { - return materials.sort((a, b) => { - const aPrefixName = a.materialType.prefix.toLowerCase() - const bPrefixName = b.materialType.prefix.toLowerCase() - - if (aPrefixName < bPrefixName) { - return -1 - } else if (aPrefixName > bPrefixName) { - return 1 - } else { - const aName = a.name.toLowerCase() - const bName = b.name.toLowerCase() - - if (aName < bName) { - return -1 - } else if (aName > bName) { - return 1 - } else { - return 0 - } - } - }) - } - - 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 - }) - } - - private addBlankMixMaterial() { - this.mixMaterials.push( - new MixMaterialDto(null, 0, false, this.mixMaterials.length + 1) - ) - } - - private updateMixMaterialPosition(mixMaterial: MixMaterialDto, updatedPosition: number) { - if (!this.mixMaterialAtPosition(updatedPosition)) { - mixMaterial.position = updatedPosition - } else { - const conflictingStep = this.mixMaterialAtPosition(updatedPosition) - conflictingStep.position = mixMaterial.position - mixMaterial.position = updatedPosition - } - } - - private mixMaterialAtPosition(position: number): MixMaterialDto { - return this.mixMaterials.find(m => m.position === position) - } -} diff --git a/src/app/modules/colors/pages/add/add.component.html b/src/app/modules/colors/pages/add/add.component.html deleted file mode 100644 index f95fa2b..0000000 --- a/src/app/modules/colors/pages/add/add.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/src/app/modules/colors/pages/add/add.component.sass b/src/app/modules/colors/pages/add/add.component.sass deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/modules/colors/pages/add/add.component.ts b/src/app/modules/colors/pages/add/add.component.ts deleted file mode 100644 index e5bcf7e..0000000 --- a/src/app/modules/colors/pages/add/add.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -import {Component} from '@angular/core' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {RecipeService} from '../../services/recipe.service' -import {FormField} from '../../../shared/components/entity-add/entity-add.component' -import {Validators} from '@angular/forms' -import {CompanyService} from '../../../company/service/company.service' -import {map} from 'rxjs/operators' -import {ActivatedRoute, Router} from '@angular/router' -import {ErrorService} from '../../../shared/service/error.service' -import {AppState} from '../../../shared/app-state' - -@Component({ - selector: 'cre-add', - templateUrl: './add.component.html', - styleUrls: ['./add.component.sass'] -}) -export class AddComponent extends ErrorHandlingComponent { - formFields: FormField[] = [ - { - name: 'name', - label: 'Nom', - icon: 'form-textbox', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un nom est requis'} - ] - }, - { - name: 'description', - label: 'Description', - icon: 'text', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une description est requise'} - ] - }, - { - name: 'color', - label: 'Couleur', - icon: 'palette', - type: 'color', - defaultValue: "#ffffff", - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une couleur est requise'} - ] - }, - { - name: 'gloss', - label: 'Lustre', - type: 'slider', - min: 0, - max: 100, - defaultValue: 0, - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'} - ] - }, - { - name: 'sample', - label: 'Échantillon', - icon: 'pound', - type: 'number', - validator: Validators.min(0), - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un numéro d\'échantillon est requis'}, - {conditionFn: errors => errors.min, message: 'Le numéro d\'échantillon doit être supérieur ou égal à 0'} - ] - }, - { - name: 'approbationDate', - label: 'Date d\'approbation', - icon: 'calendar', - type: 'date' - }, - { - name: 'remark', - label: 'Remarque', - icon: 'text', - type: 'text' - }, - { - name: 'company', - label: 'Bannière', - icon: 'domain', - type: 'select', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une bannière est requise'} - ], - options$: this.companyService.all.pipe(map(companies => companies.map(c => { - return {value: c.id, label: c.name} - }))) - } - ] - - errorHandlers = [{ - filter: error => error.type === `exists-recipe-company-name`, - messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}` - }] - - constructor( - private recipeService: RecipeService, - private companyService: CompanyService, - private appState: AppState, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - this.appState.title = "Nouvelle couleur" - } - - submit(values) { - this.subscribe( - this.recipeService.save(values.name, values.description, values.color, values.gloss, values.sample, values.approbationDate, values.remark, values.company), - recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`) - ) - } -} diff --git a/src/app/modules/colors/pages/edit/edit.component.html b/src/app/modules/colors/pages/edit/edit.component.html deleted file mode 100644 index 81461f8..0000000 --- a/src/app/modules/colors/pages/edit/edit.component.html +++ /dev/null @@ -1,68 +0,0 @@ -
-
-
-
- - - -
- - Unités - - Millilitres - Litres - Gallons - - -
-
-
- -
-
- - -
- -
- -
- -
- - -
- -
- -
-
-
- - - diff --git a/src/app/modules/colors/pages/edit/edit.component.sass b/src/app/modules/colors/pages/edit/edit.component.sass deleted file mode 100644 index 2533b3c..0000000 --- a/src/app/modules/colors/pages/edit/edit.component.sass +++ /dev/null @@ -1,2 +0,0 @@ -.recipe-wrapper > div - margin: 0 3rem 3rem diff --git a/src/app/modules/colors/pages/edit/edit.component.ts b/src/app/modules/colors/pages/edit/edit.component.ts deleted file mode 100644 index ab663dc..0000000 --- a/src/app/modules/colors/pages/edit/edit.component.ts +++ /dev/null @@ -1,186 +0,0 @@ -import {Component, ViewChild} from '@angular/core' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {Recipe, recipeMixCount, RecipeStep, recipeStepCount} from '../../../shared/model/recipe.model' -import {RecipeService} from '../../services/recipe.service' -import {ActivatedRoute, Router} from '@angular/router' -import {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 {EntityEditComponent} from '../../../shared/components/entity-edit/entity-edit.component' -import {ImagesEditorComponent} from '../../components/images-editor/images-editor.component' -import {ErrorHandler, ErrorService} from '../../../shared/service/error.service' -import {AlertService} from '../../../shared/service/alert.service' -import {GroupService} from '../../../groups/services/group.service' -import {AppState} from '../../../shared/app-state' -import {StepTableComponent} from '../../components/step-table/step-table.component' - -@Component({ - selector: 'cre-edit', - templateUrl: './edit.component.html', - styleUrls: ['./edit.component.sass'] -}) -export class EditComponent extends ErrorHandlingComponent { - readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} - - @ViewChild('imagesEditor') imagesEditor: ImagesEditorComponent - - recipe: Recipe | null - groups$ = this.groupService.all - formFields = [ - { - name: 'name', - label: 'Nom', - icon: 'form-textbox', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un nom est requis'} - ] - }, - { - name: 'description', - label: 'Description', - icon: 'text', - type: 'text', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une description est requise'} - ] - }, - { - name: 'color', - label: 'Couleur', - icon: 'palette', - type: 'color', - required: true, - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Une couleur est requise'} - ] - }, - { - name: 'gloss', - label: 'Lustre', - type: 'slider', - min: 0, - max: 100, - validator: Validators.compose([Validators.required, Validators.min(0), Validators.max(100)]), - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'} - ] - }, - { - name: 'sample', - label: 'Échantillon', - icon: 'pound', - type: 'number', - validator: Validators.min(0), - errorMessages: [ - {conditionFn: errors => errors.required, message: 'Un numéro d\'échantillon est requis'}, - {conditionFn: errors => errors.min, message: 'Le numéro d\'échantillon doit être supérieur ou égal à 0'} - ] - }, - { - name: 'approbationDate', - label: 'Date d\'approbation', - icon: 'calendar', - type: 'date' - }, - { - name: 'remark', - label: 'Remarque', - icon: 'text', - type: 'text' - }, - { - name: 'company', - label: 'Bannière', - icon: 'domain', - type: 'text', - readonly: true, - valueFn: recipe => recipe.company.name, - } - ] - units$ = new Subject() - submittedValues: any | null - - errorHandlers: ErrorHandler[] = [{ - filter: error => error.type === 'notfound-recipe-id', - consumer: error => this.urlUtils.navigateTo('/color/list') - }] - - constructor( - private recipeService: RecipeService, - private groupService: GroupService, - private accountService: AccountService, - private alertService: AlertService, - private appState: AppState, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - } - - ngOnInit() { - super.ngOnInit() - - this.subscribeEntityById( - this.recipeService, - parseInt(this.activatedRoute.snapshot.paramMap.get('id')), - recipe => { - this.recipe = recipe - this.appState.title = `${recipe.name} (Modifications)` - - if (recipeMixCount(this.recipe) == 0) { - this.alertService.pushWarning('Il n\'y a aucun mélange dans cette recette') - } - if (recipeStepCount(this.recipe) == 0) { - this.alertService.pushWarning('Il n\'y a aucune étape dans cette recette') - } - } - ) - } - - changeUnits(unit: string) { - this.units$.next(unit) - } - - submit(editComponent: EntityEditComponent, stepTable: StepTableComponent) { - const values = editComponent.values - this.submittedValues = values - - const steps = stepTable.mappedUpdatedSteps - if (!this.stepsPositionsAreValid(steps)) { - this.alertService.pushError('Les étapes ne peuvent pas avoir une position inférieure à 1') - return - } - - this.subscribeAndNavigate( - this.recipeService.update(this.recipe.id, values.name, values.description, values.color, values.gloss, values.sample, values.approbationDate, values.remark, steps), - '/color/list' - ) - } - - delete() { - this.subscribeAndNavigate( - this.recipeService.delete(this.recipe.id), - '/color/list' - ) - } - - get loggedInUserGroupId(): number { - return this.appState.authenticatedUser.group?.id - } - - private stepsPositionsAreValid(steps: Map): boolean { - let valid = true - steps.forEach((steps, _) => { - if (steps.find(s => s.position === 0)) { - valid = false - return - } - }) - return valid - } -} diff --git a/src/app/modules/colors/pages/explore/explore.component.html b/src/app/modules/colors/pages/explore/explore.component.html deleted file mode 100644 index 93b3b3e..0000000 --- a/src/app/modules/colors/pages/explore/explore.component.html +++ /dev/null @@ -1,86 +0,0 @@ -
- - -
-
-
- - - -
- -
- - - Groupe - - - {{group.name}} - - - -
-
- -
- - - Note - - -

{{selectedGroupNote}}

-
- -
- -
- - -
- - -
- -
- - -
- -
-
-
- - - diff --git a/src/app/modules/colors/pages/explore/explore.component.sass b/src/app/modules/colors/pages/explore/explore.component.sass deleted file mode 100644 index 1775c49..0000000 --- a/src/app/modules/colors/pages/explore/explore.component.sass +++ /dev/null @@ -1,2 +0,0 @@ -.recipe-content > div - margin: 0 3rem 3rem diff --git a/src/app/modules/colors/pages/list/list.component.sass b/src/app/modules/colors/pages/list/list.component.sass deleted file mode 100644 index e5fb34c..0000000 --- a/src/app/modules/colors/pages/list/list.component.sass +++ /dev/null @@ -1,9 +0,0 @@ -mat-expansion-panel - width: 60rem - margin: 20px auto - -.button-add - margin-top: .8rem - -.recipe-color-circle - box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12) diff --git a/src/app/modules/colors/pages/list/list.component.ts b/src/app/modules/colors/pages/list/list.component.ts deleted file mode 100644 index 637061b..0000000 --- a/src/app/modules/colors/pages/list/list.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {ChangeDetectorRef, Component} from '@angular/core' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {RecipeService} from '../../services/recipe.service' -import {Permission} from '../../../shared/model/user' -import {AccountService} from '../../../accounts/services/account.service' -import {getRecipeLuma, Recipe} from '../../../shared/model/recipe.model' -import {ActivatedRoute, Router} from '@angular/router' -import {ErrorService} from '../../../shared/service/error.service' -import {AppState} from '../../../shared/app-state' -import {ConfigService} from '../../../shared/service/config.service' -import {Config} from '../../../shared/model/config.model' - -@Component({ - selector: 'cre-list', - templateUrl: './list.component.html', - styleUrls: ['./list.component.sass'] -}) -export class ListComponent extends ErrorHandlingComponent { - recipes: { company: string, recipes: Recipe[] }[] = [] - tableCols = ['name', 'description', 'color', 'sample', 'iconNotApproved', 'buttonView', 'buttonEdit'] - searchQuery = '' - panelForcedExpanded = false - hiddenRecipes = [] - - constructor( - private recipeService: RecipeService, - private accountService: AccountService, - private configService: ConfigService, - private cdRef: ChangeDetectorRef, - private appState: AppState, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - } - - ngOnInit() { - super.ngOnInit() - this.appState.title = "Explorateur" - - this.subscribe( - this.configService.get(Config.EMERGENCY_MODE), - config => { - if (config.content === "false") { - this.subscribe( - this.recipeService.allSortedByCompany, - recipes => this.recipes = recipes - ) - } else { - this.urlUtils.navigateTo("/admin/config") - } - } - ) - } - - searchRecipes() { - if (this.searchQuery.length > 0 && !this.panelForcedExpanded) { - this.panelForcedExpanded = true - this.cdRef.detectChanges() - } - - this.recipes - .flatMap(r => r.recipes) - .forEach(r => this.recipeMatchesSearchQuery(r)) - } - - isCompanyHidden(companyRecipes: Recipe[]): boolean { - return (this.searchQuery && this.searchQuery.length > 0) && companyRecipes.map(r => this.hiddenRecipes[r.id]).filter(r => !r).length <= 0 - } - - isLight(recipe: Recipe): boolean { - return getRecipeLuma(recipe) > 200 - } - - get hasEditPermission(): boolean { - return this.accountService.hasPermission(Permission.EDIT_RECIPES) - } - - private recipeMatchesSearchQuery(recipe: Recipe) { - const matches = this.searchString(recipe.company.name) || - this.searchString(recipe.name) || - this.searchString(recipe.description) || - (recipe.sample && this.searchString(recipe.sample.toString())) - this.hiddenRecipes[recipe.id] = !matches - } - - private searchString(value: string): boolean { - return value.toLowerCase().indexOf(this.searchQuery.toLowerCase()) >= 0 - } -} diff --git a/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html b/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html deleted file mode 100644 index 128e7d2..0000000 --- a/src/app/modules/colors/pages/mix/mix-add/mix-add.component.html +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/src/app/modules/colors/pages/mix/mix-add/mix-add.component.sass b/src/app/modules/colors/pages/mix/mix-add/mix-add.component.sass deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts b/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts deleted file mode 100644 index 0cae5af..0000000 --- a/src/app/modules/colors/pages/mix/mix-add/mix-add.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -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 {ErrorHandlingComponent} from '../../../../shared/components/subscribing.component' -import {MixService} from '../../../services/mix.service' -import {ErrorService} from '../../../../shared/service/error.service' - -@Component({ - selector: 'cre-mix-add', - templateUrl: './mix-add.component.html', - styleUrls: ['./mix-add.component.sass'] -}) -export class MixAddComponent extends ErrorHandlingComponent { - recipeId: number | null - materials: Material[] | null - - constructor( - private materialService: MaterialService, - private mixService: MixService, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, activatedRoute, router) - } - - ngOnInit(): void { - super.ngOnInit() - - this.recipeId = this.urlUtils.parseIntUrlParam('recipeId') - - this.subscribe( - this.materialService.getAllForMixCreation(this.recipeId), - m => this.materials = m - ) - } - - submit(values) { - this.subscribeAndNavigate( - this.mixService.saveWithUnits(values.name, values.recipeId, values.materialTypeId, values.mixMaterials, values.units), - `/color/edit/${this.recipeId}` - ) - } -} diff --git a/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html b/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html deleted file mode 100644 index 762bae6..0000000 --- a/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.sass b/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.sass deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts b/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts deleted file mode 100644 index 5b360fd..0000000 --- a/src/app/modules/colors/pages/mix/mix-edit/mix-edit.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {Component} from '@angular/core' -import {ActivatedRoute, Router} from '@angular/router' -import {ErrorHandlingComponent} 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' -import {ErrorHandlerComponent, ErrorService} from '../../../../shared/service/error.service' -import {MixMaterialDto} from '../../../../shared/model/recipe.model' -import {AlertService} from '../../../../shared/service/alert.service' - -@Component({ - selector: 'cre-mix-edit', - templateUrl: './mix-edit.component.html', - styleUrls: ['./mix-edit.component.sass'] -}) -export class MixEditComponent extends ErrorHandlingComponent { - mixId: number | null - recipeId: number | null - materials: Material[] | null - - constructor( - private materialService: MaterialService, - private mixService: MixService, - private alertService: AlertService, - errorService: ErrorService, - router: Router, - activatedRoute: ActivatedRoute - ) { - super(errorService, 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), - m => this.materials = m - ) - } - - submit(values) { - if(!this.mixMaterialsPositionAreValid(values.mixMaterials)) { - this.alertService.pushError('Les ingrédients ne peuvent pas avoir une position inférieure à 1') - return - } - - this.subscribeAndNavigate( - this.mixService.updateWithUnits(this.mixId, values.name, values.materialTypeId, values.mixMaterials, values.units), - `/color/edit/${this.recipeId}` - ) - } - - private mixMaterialsPositionAreValid(mixMaterials: MixMaterialDto[]): boolean { - return !mixMaterials.find(m => m.position <= 0) - } -} diff --git a/src/app/modules/company/company.module.ts b/src/app/modules/company/company.module.ts index 4d86746..5f9d07b 100644 --- a/src/app/modules/company/company.module.ts +++ b/src/app/modules/company/company.module.ts @@ -5,6 +5,9 @@ import { AddComponent } from './pages/add/add.component'; import { EditComponent } from './pages/edit/edit.component'; import {CompanyRoutingModule} from "./company-routing.module"; import {SharedModule} from "../shared/shared.module"; +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' +import {CreTablesModule} from '../shared/components/tables/tables.module' @@ -13,7 +16,10 @@ import {SharedModule} from "../shared/shared.module"; imports: [ CommonModule, CompanyRoutingModule, - SharedModule + SharedModule, + CreActionBarModule, + CreButtonsModule, + CreTablesModule ] }) export class CompanyModule { } diff --git a/src/app/modules/company/pages/list/list.component.html b/src/app/modules/company/pages/list/list.component.html index e0781de..1fc8be5 100644 --- a/src/app/modules/company/pages/list/list.component.html +++ b/src/app/modules/company/pages/list/list.component.html @@ -1,7 +1,26 @@ - - + + + Ajouter + + + + +

Il n'y a actuellement aucune bannière enregistrée dans le système.

+

Vous pouvez en créer une ici.

+
+ + + + Nom + {{company.name}} + + + + + + + Modifier + + + + diff --git a/src/app/modules/company/pages/list/list.component.ts b/src/app/modules/company/pages/list/list.component.ts index 6104358..5899e96 100644 --- a/src/app/modules/company/pages/list/list.component.ts +++ b/src/app/modules/company/pages/list/list.component.ts @@ -5,6 +5,8 @@ import {Permission} from '../../../shared/model/user' import {ActivatedRoute, Router} from '@angular/router' import {ErrorService} from '../../../shared/service/error.service' import {AppState} from '../../../shared/app-state' +import {tap} from 'rxjs/operators' +import {AccountService} from '../../../accounts/services/account.service' @Component({ selector: 'cre-list', @@ -12,18 +14,14 @@ import {AppState} from '../../../shared/app-state' styleUrls: ['./list.component.sass'] }) export class ListComponent extends ErrorHandlingComponent { - companies$ = this.companyService.all - columns = [ - {def: 'name', title: 'Nom', valueFn: c => c.name} - ] - buttons = [{ - text: 'Modifier', - linkFn: t => `/catalog/company/edit/${t.id}`, - permission: Permission.EDIT_COMPANIES - }] + companies$ = this.companyService.all.pipe(tap(companies => this.companiesEmpty = companies.length <= 0)) + companiesEmpty = false + + columns = ['name', 'editButton'] constructor( private companyService: CompanyService, + private accountService: AccountService, private appState: AppState, errorService: ErrorService, router: Router, @@ -32,4 +30,8 @@ export class ListComponent extends ErrorHandlingComponent { super(errorService, activatedRoute, router) this.appState.title = 'Bannières' } + + get hasEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_COMPANIES) + } } diff --git a/src/app/modules/company/service/company.service.ts b/src/app/modules/company/service/company.service.ts index 238eaef..90f3a78 100644 --- a/src/app/modules/company/service/company.service.ts +++ b/src/app/modules/company/service/company.service.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; -import {ApiService} from "../../shared/service/api.service"; -import {Observable} from "rxjs"; -import {Company} from "../../shared/model/company.model"; +import {ApiService} from '../../shared/service/api.service'; +import {Observable} from 'rxjs'; +import {Company} from '../../shared/model/company.model'; @Injectable({ providedIn: 'root' diff --git a/src/app/modules/configuration/config-editor.html b/src/app/modules/configuration/config-editor.html index d9fa87b..c8af260 100644 --- a/src/app/modules/configuration/config-editor.html +++ b/src/app/modules/configuration/config-editor.html @@ -11,23 +11,23 @@
- - - - - - - - + + - - - - - - - - + + diff --git a/src/app/modules/configuration/config-editor.ts b/src/app/modules/configuration/config-editor.ts index bde9502..b12502a 100644 --- a/src/app/modules/configuration/config-editor.ts +++ b/src/app/modules/configuration/config-editor.ts @@ -16,8 +16,8 @@ export class CreConfigEditor extends ErrorHandlingComponent { keys = { INSTANCE_NAME: Config.INSTANCE_NAME, - INSTANCE_LOGO_PATH: Config.INSTANCE_LOGO_PATH, - INSTANCE_ICON_PATH: Config.INSTANCE_ICON_PATH, + INSTANCE_LOGO_SET: Config.INSTANCE_LOGO_SET, + INSTANCE_ICON_SET: Config.INSTANCE_ICON_SET, INSTANCE_URL: Config.INSTANCE_URL, DATABASE_URL: Config.DATABASE_URL, DATABASE_USER: Config.DATABASE_USER, diff --git a/src/app/modules/configuration/config-image.html b/src/app/modules/configuration/config-image.html index 084aad1..c801323 100644 --- a/src/app/modules/configuration/config-image.html +++ b/src/app/modules/configuration/config-image.html @@ -18,7 +18,7 @@
diff --git a/src/app/modules/configuration/config.ts b/src/app/modules/configuration/config.ts index 29acafb..d40479e 100644 --- a/src/app/modules/configuration/config.ts +++ b/src/app/modules/configuration/config.ts @@ -1,10 +1,20 @@ -import {AfterViewInit, Component, ContentChild, Directive, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core' +import { + AfterViewInit, + Component, + ContentChild, + Directive, + EventEmitter, + Input, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core' import {ConfigService} from '../shared/service/config.service' import {Config, ConfigControl} from '../shared/model/config.model' import {SubscribingComponent} from '../shared/components/subscribing.component' import {ErrorService} from '../shared/service/error.service' import {ActivatedRoute, Router} from '@angular/router' -import {formatDate, formatDateTime, getFileUrl, readFile} from '../shared/utils/utils' +import {formatDate, formatDateTime, getConfiguredImageUrl, getFileUrl, readFile} from '../shared/utils/utils' import {AbstractControl} from '@angular/forms' import {CrePromptDialog} from '../shared/components/dialogs/dialogs' @@ -121,9 +131,9 @@ export class CreImageConfig extends _CreConfigBase { readFile(file, (content) => this.updatedImage = content) } - - get configuredImageUrl(): string { - return getFileUrl(this.config.content) + get imageUrl(): string { + const path = this.config.key == Config.INSTANCE_ICON_SET ? 'icon' : 'logo' + return getConfiguredImageUrl(path) } } diff --git a/src/app/modules/material-type/material-type.module.ts b/src/app/modules/material-type/material-type.module.ts index 591a8a8..1df904c 100644 --- a/src/app/modules/material-type/material-type.module.ts +++ b/src/app/modules/material-type/material-type.module.ts @@ -6,6 +6,9 @@ import { ListComponent } from './pages/list/list.component'; import {SharedModule} from "../shared/shared.module"; import { AddComponent } from './pages/add/add.component'; import { EditComponent } from './pages/edit/edit.component'; +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' +import {CreTablesModule} from '../shared/components/tables/tables.module' @NgModule({ @@ -13,7 +16,10 @@ import { EditComponent } from './pages/edit/edit.component'; imports: [ CommonModule, MaterialTypeRoutingModule, - SharedModule + SharedModule, + CreActionBarModule, + CreButtonsModule, + CreTablesModule ] }) export class MaterialTypeModule { } diff --git a/src/app/modules/material-type/pages/list/list.component.html b/src/app/modules/material-type/pages/list/list.component.html index f70251f..bf32816 100644 --- a/src/app/modules/material-type/pages/list/list.component.html +++ b/src/app/modules/material-type/pages/list/list.component.html @@ -1,7 +1,40 @@ - - + + + Ajouter + + + + +

Il n'y a actuellement aucun type de produit enregistré dans le système.

+

Vous pouvez en créer un ici.

+
+ + + + Nom + {{materialType.name}} + + + + Préfix + {{materialType.prefix}} + + + + Utilise les pourcentages + {{materialType.usePercentages ? 'Oui' : 'Non'}} + + + + + + + Modifier + + + + diff --git a/src/app/modules/material-type/pages/list/list.component.ts b/src/app/modules/material-type/pages/list/list.component.ts index 2556388..c25e58f 100644 --- a/src/app/modules/material-type/pages/list/list.component.ts +++ b/src/app/modules/material-type/pages/list/list.component.ts @@ -5,6 +5,8 @@ import {Permission} from '../../../shared/model/user' import {ActivatedRoute, Router} from '@angular/router' import {ErrorService} from '../../../shared/service/error.service' import {AppState} from '../../../shared/app-state' +import {tap} from 'rxjs/operators' +import {AccountService} from '../../../accounts/services/account.service' @Component({ selector: 'cre-list', @@ -12,23 +14,16 @@ import {AppState} from '../../../shared/app-state' styleUrls: ['./list.component.sass'] }) export class ListComponent extends ErrorHandlingComponent { - materialTypes$ = this.materialTypeService.all - columns = [ - {def: 'name', title: 'Nom', valueFn: t => t.name}, - {def: 'prefix', title: 'Préfixe', valueFn: t => t.prefix}, - {def: 'usePercentages', title: 'Utilise les pourcentages', valueFn: t => t.usePercentages ? 'Oui' : 'Non'} - ] - buttons = [ - { - text: 'Modifier', - linkFn: t => `/catalog/materialtype/edit/${t.id}`, - permission: Permission.EDIT_MATERIAL_TYPES, - disabledFn: t => t.systemType - } - ] + materialTypes$ = this.materialTypeService.all.pipe( + tap(materialTypes => this.materialTypesEmpty = materialTypes.length <= 0) + ) + materialTypesEmpty = false + + columns = ['name', 'prefix', 'usePercentages', 'editButton'] constructor( private materialTypeService: MaterialTypeService, + private accountService: AccountService, private appState: AppState, errorService: ErrorService, router: Router, @@ -37,4 +32,8 @@ export class ListComponent extends ErrorHandlingComponent { super(errorService, activatedRoute, router) this.appState.title = 'Types de produit' } + + get hasEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_COMPANIES) + } } diff --git a/src/app/modules/material/material.module.ts b/src/app/modules/material/material.module.ts index 6ccb444..782b3ef 100644 --- a/src/app/modules/material/material.module.ts +++ b/src/app/modules/material/material.module.ts @@ -6,9 +6,13 @@ import {InventoryComponent} from './pages/inventory/inventory.component'; import {SharedModule} from "../shared/shared.module"; import {AddComponent} from './pages/add/add.component'; import {EditComponent} from './pages/edit/edit.component'; -import {ColorsModule} from '../colors/colors.module' +import {RecipesModule} from '../recipes/recipes.module' import {MatSortModule} from '@angular/material/sort' import {FormsModule} from '@angular/forms' +import {CreTablesModule} from '../shared/components/tables/tables.module' +import {CreInputsModule} from '../shared/components/inputs/inputs.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module' @NgModule({ @@ -17,9 +21,13 @@ import {FormsModule} from '@angular/forms' CommonModule, MaterialRoutingModule, SharedModule, - ColorsModule, + RecipesModule, MatSortModule, - FormsModule + FormsModule, + CreTablesModule, + CreInputsModule, + CreButtonsModule, + CreActionBarModule ] }) export class MaterialModule { diff --git a/src/app/modules/material/pages/inventory/inventory.component.html b/src/app/modules/material/pages/inventory/inventory.component.html index a964091..130ff57 100644 --- a/src/app/modules/material/pages/inventory/inventory.component.html +++ b/src/app/modules/material/pages/inventory/inventory.component.html @@ -1,62 +1,59 @@ -
- -
- - Recherche par code... - - - - Recherche par type de produit - - - {{materialType.name}} - - - -
- - -
- - Quantité faible - - + + + + + + + + + + + + - -
-
+ + + - +

Il n'y a actuellement aucun produit enregistré dans le système.

+

Vous pouvez en créer un ici. +

+ + + + [filterPredicate]="materialFilterPredicate" + [filter]="filter" + [data]="materials" + [columns]="columns"> - + - + @@ -68,9 +65,7 @@ + - - - - -
CodeCode {{material.name}} Type de produitType de produit {{material.materialType.name}} -
+
-
- - - + + Modifier + - - - + + Fiche signalitique +
+ diff --git a/src/app/modules/material/pages/inventory/inventory.component.sass b/src/app/modules/material/pages/inventory/inventory.component.sass index 20ded20..5867821 100644 --- a/src/app/modules/material/pages/inventory/inventory.component.sass +++ b/src/app/modules/material/pages/inventory/inventory.component.sass @@ -1,8 +1,9 @@ -.input-group-append button - border-radius: 0 4px 4px 0 - mat-select margin-top: 4px -.form-control - width: 6rem +.input-group + cre-input + width: 6rem + + .input-group-append button + border-radius: 0 4px 4px 0 diff --git a/src/app/modules/material/pages/inventory/inventory.component.ts b/src/app/modules/material/pages/inventory/inventory.component.ts index aafe764..81f914f 100644 --- a/src/app/modules/material/pages/inventory/inventory.component.ts +++ b/src/app/modules/material/pages/inventory/inventory.component.ts @@ -4,14 +4,17 @@ import {MaterialService} from '../../service/material.service' import {Permission} from '../../../shared/model/user' import {ActivatedRoute, Router} from '@angular/router' import {ErrorService} from '../../../shared/service/error.service' -import {Material, openSimdut} from '../../../shared/model/material.model' +import {Material, materialFilterFieldSeparator, materialMatchesFilter, openSimdut} from '../../../shared/model/material.model' import {AccountService} from '../../../accounts/services/account.service' import {convertQuantity, UNIT_MILLILITER} from '../../../shared/units' import {MatSort} from '@angular/material/sort' -import {MatTableDataSource} from '@angular/material/table' import {MaterialTypeService} from '../../../material-type/service/material-type.service' import {InventoryService} from '../../service/inventory.service' import {AppState} from '../../../shared/app-state' +import {FormControl} from '@angular/forms' +import {map} from 'rxjs/operators' +import {CreInputEntry} from '../../../shared/components/inputs/inputs' +import {round} from '../../../shared/utils/utils' @Component({ selector: 'cre-list', @@ -21,9 +24,10 @@ import {AppState} from '../../../shared/app-state' export class InventoryComponent extends ErrorHandlingComponent { @ViewChild(MatSort) sort: MatSort - materials: Material[] | null - materialTypes$ = this.materialTypeService.all - dataSource: MatTableDataSource + materials: Material[] | null = [] + materialTypesEntries$ = this.materialTypeService.all.pipe(map(materialTypes => { + return materialTypes.map(materialType => new CreInputEntry(materialType.id, materialType.name)) + })) columns = ['name', 'materialType', 'quantity', 'addQuantity', 'lowQuantityIcon', 'simdutIcon', 'editButton', 'openSimdutButton'] hoveredMaterial: Material | null @@ -31,8 +35,16 @@ export class InventoryComponent extends ErrorHandlingComponent { units = UNIT_MILLILITER lowQuantityThreshold = 100 // TEMPORARY will be in the application settings - materialTypeFilter = 1 - materialNameFilter = '' + + materialFilterPredicate = materialMatchesFilter + + private materialTypeFilter = 1 + private materialNameFilter = '' + private hideLowQuantity = false + + materialTypeFilterControl = new FormControl(this.materialTypeFilter) + materialNameFilterControl = new FormControl(this.materialNameFilter) + hideLowQuantityControl = new FormControl(this.hideLowQuantity) constructor( private materialService: MaterialService, @@ -53,38 +65,25 @@ export class InventoryComponent extends ErrorHandlingComponent { this.subscribe( this.materialService.allNotMixType, - materials => { - this.materials = materials - this.dataSource = this.setupDataSource() - }, + materials => this.materials = materials, true, 1 ) - } - setupDataSource(): MatTableDataSource { - this.dataSource = new MatTableDataSource(this.materials) - this.dataSource.sortingDataAccessor = (material, header) => { - switch (header) { - case 'materialType': - return material[header].name - case 'lowQuantityIcon': - return this.isLowQuantity(material) - default: - return material[header] - } - } - this.dataSource.filterPredicate = (material, filter) => { - return (!this.materialTypeFilter || this.materialTypeFilter === 1 || material.materialType.id === this.materialTypeFilter) && - (!this.materialNameFilter || material.name.toLowerCase().includes(this.materialNameFilter.toLowerCase())) - } + this.subscribe( + this.materialTypeFilterControl.valueChanges, + filter => this.materialTypeFilter = filter + ) - this.dataSource.sort = this.sort - return this.dataSource - } + this.subscribe( + this.materialNameFilterControl.valueChanges, + filter => this.materialNameFilter = filter + ) - filterDataSource() { - this.dataSource.filter = 'filter' + this.subscribe( + this.hideLowQuantityControl.valueChanges, + filter => this.hideLowQuantity = filter + ) } isLowQuantity(material: Material) { @@ -92,7 +91,7 @@ export class InventoryComponent extends ErrorHandlingComponent { } getQuantity(material: Material): number { - return Math.round(convertQuantity(material.inventoryQuantity, UNIT_MILLILITER, this.units) * 100) / 100 + return round(convertQuantity(material.inventoryQuantity, UNIT_MILLILITER, this.units), 2) } materialHasSimdut(material: Material): boolean { @@ -118,6 +117,10 @@ export class InventoryComponent extends ErrorHandlingComponent { ) } + get filter(): string { + return [this.materialTypeFilter, this.materialNameFilter, this.hideLowQuantity, this.lowQuantityThreshold].join(materialFilterFieldSeparator) + } + get canEditMaterial(): boolean { return this.accountService.hasPermission(Permission.EDIT_MATERIALS) } diff --git a/src/app/modules/recipes/add.html b/src/app/modules/recipes/add.html new file mode 100644 index 0000000..aa827e1 --- /dev/null +++ b/src/app/modules/recipes/add.html @@ -0,0 +1,10 @@ + + + Retour + + + + + + + diff --git a/src/app/modules/colors/bpac.js b/src/app/modules/recipes/bpac.js similarity index 100% rename from src/app/modules/colors/bpac.js rename to src/app/modules/recipes/bpac.js diff --git a/src/app/modules/colors/components/images-editor/images-editor.component.html b/src/app/modules/recipes/components/images-editor/images-editor.component.html similarity index 89% rename from src/app/modules/colors/components/images-editor/images-editor.component.html rename to src/app/modules/recipes/components/images-editor/images-editor.component.html index e07f7b8..abd0574 100644 --- a/src/app/modules/colors/components/images-editor/images-editor.component.html +++ b/src/app/modules/recipes/components/images-editor/images-editor.component.html @@ -4,6 +4,8 @@
+

Aucune image n'est associée à cette couleur

+
diff --git a/src/app/modules/colors/components/images-editor/images-editor.component.sass b/src/app/modules/recipes/components/images-editor/images-editor.component.sass similarity index 100% rename from src/app/modules/colors/components/images-editor/images-editor.component.sass rename to src/app/modules/recipes/components/images-editor/images-editor.component.sass diff --git a/src/app/modules/colors/components/images-editor/images-editor.component.ts b/src/app/modules/recipes/components/images-editor/images-editor.component.ts similarity index 100% rename from src/app/modules/colors/components/images-editor/images-editor.component.ts rename to src/app/modules/recipes/components/images-editor/images-editor.component.ts diff --git a/src/app/modules/colors/components/mix-table/mix-table.component.html b/src/app/modules/recipes/components/mix-table/mix-table.component.html similarity index 100% rename from src/app/modules/colors/components/mix-table/mix-table.component.html rename to src/app/modules/recipes/components/mix-table/mix-table.component.html diff --git a/src/app/modules/colors/components/mix-table/mix-table.component.sass b/src/app/modules/recipes/components/mix-table/mix-table.component.sass similarity index 100% rename from src/app/modules/colors/components/mix-table/mix-table.component.sass rename to src/app/modules/recipes/components/mix-table/mix-table.component.sass diff --git a/src/app/modules/colors/components/mix-table/mix-table.component.ts b/src/app/modules/recipes/components/mix-table/mix-table.component.ts similarity index 97% rename from src/app/modules/colors/components/mix-table/mix-table.component.ts rename to src/app/modules/recipes/components/mix-table/mix-table.component.ts index 2018c5a..bd430a6 100644 --- a/src/app/modules/colors/components/mix-table/mix-table.component.ts +++ b/src/app/modules/recipes/components/mix-table/mix-table.component.ts @@ -1,5 +1,5 @@ import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core' -import {Mix, MixMaterial, MixMaterialDto, mixMaterialsAsMixMaterialsDto, Recipe} from '../../../shared/model/recipe.model' +import {Mix, MixMaterial, MixMaterialDto, mixMaterialsToMixMaterialsDto, Recipe} from '../../../shared/model/recipe.model' import {Subject} from 'rxjs' import {SubscribingComponent} from '../../../shared/components/subscribing.component' import {convertMixMaterialQuantity, UNIT_MILLILITER} from '../../../shared/units' @@ -60,7 +60,7 @@ export class MixTableComponent extends SubscribingComponent { this.mixColumns = this.COLUMNS_EDIT } - this.mixMaterials = mixMaterialsAsMixMaterialsDto(this.mix) + this.mixMaterials = mixMaterialsToMixMaterialsDto(this.mix) this.subscribe( this.units$, @@ -191,12 +191,13 @@ export class MixTableComponent extends SubscribingComponent { materialId: quantity.materialId, quantity: this.calculateQuantity(index), isPercents: quantity.isPercents, - position: quantity.position + position: quantity.position, + units: UNIT_MILLILITER }) } private convertQuantities(newUnit: string) { - this.mixMaterials.forEach(q => q.quantity = convertMixMaterialQuantity(q, this.units, newUnit)) + this.mixMaterials.forEach(q => q.quantity = convertMixMaterialQuantity(q, newUnit)) this.units = newUnit } diff --git a/src/app/modules/colors/components/mixes-card/mixes-card.component.html b/src/app/modules/recipes/components/mixes-card/mixes-card.component.html similarity index 88% rename from src/app/modules/colors/components/mixes-card/mixes-card.component.html rename to src/app/modules/recipes/components/mixes-card/mixes-card.component.html index b6766df..c8a093a 100644 --- a/src/app/modules/colors/components/mixes-card/mixes-card.component.html +++ b/src/app/modules/recipes/components/mixes-card/mixes-card.component.html @@ -3,6 +3,8 @@ Mélanges +

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

+ Étapes - + {{step.position}}.{{step.message}} diff --git a/src/app/modules/colors/components/step-list/step-list.component.sass b/src/app/modules/recipes/components/step-list/step-list.component.sass similarity index 100% rename from src/app/modules/colors/components/step-list/step-list.component.sass rename to src/app/modules/recipes/components/step-list/step-list.component.sass diff --git a/src/app/modules/colors/components/step-list/step-list.component.ts b/src/app/modules/recipes/components/step-list/step-list.component.ts similarity index 100% rename from src/app/modules/colors/components/step-list/step-list.component.ts rename to src/app/modules/recipes/components/step-list/step-list.component.ts diff --git a/src/app/modules/colors/components/step-table/step-table.component.html b/src/app/modules/recipes/components/step-table/step-table.component.html similarity index 100% rename from src/app/modules/colors/components/step-table/step-table.component.html rename to src/app/modules/recipes/components/step-table/step-table.component.html diff --git a/src/app/modules/colors/components/step-table/step-table.component.sass b/src/app/modules/recipes/components/step-table/step-table.component.sass similarity index 100% rename from src/app/modules/colors/components/step-table/step-table.component.sass rename to src/app/modules/recipes/components/step-table/step-table.component.sass diff --git a/src/app/modules/colors/components/step-table/step-table.component.ts b/src/app/modules/recipes/components/step-table/step-table.component.ts similarity index 100% rename from src/app/modules/colors/components/step-table/step-table.component.ts rename to src/app/modules/recipes/components/step-table/step-table.component.ts diff --git a/src/app/modules/colors/components/unit-selector/unit-selector.component.html b/src/app/modules/recipes/components/unit-selector/unit-selector.component.html similarity index 89% rename from src/app/modules/colors/components/unit-selector/unit-selector.component.html rename to src/app/modules/recipes/components/unit-selector/unit-selector.component.html index 4b82721..b00e879 100644 --- a/src/app/modules/colors/components/unit-selector/unit-selector.component.html +++ b/src/app/modules/recipes/components/unit-selector/unit-selector.component.html @@ -1,6 +1,6 @@ Unités - + Millilitres Litres diff --git a/src/app/modules/colors/components/unit-selector/unit-selector.component.sass b/src/app/modules/recipes/components/unit-selector/unit-selector.component.sass similarity index 100% rename from src/app/modules/colors/components/unit-selector/unit-selector.component.sass rename to src/app/modules/recipes/components/unit-selector/unit-selector.component.sass diff --git a/src/app/modules/colors/components/unit-selector/unit-selector.component.ts b/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts similarity index 53% rename from src/app/modules/colors/components/unit-selector/unit-selector.component.ts rename to src/app/modules/recipes/components/unit-selector/unit-selector.component.ts index 418677f..9f8d192 100644 --- a/src/app/modules/colors/components/unit-selector/unit-selector.component.ts +++ b/src/app/modules/recipes/components/unit-selector/unit-selector.component.ts @@ -1,17 +1,28 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core' import {UNIT_GALLON, UNIT_LITER, UNIT_MILLILITER} from "../../../shared/units"; +import {FormControl} from '@angular/forms' @Component({ selector: 'cre-unit-selector', templateUrl: './unit-selector.component.html', styleUrls: ['./unit-selector.component.sass'] }) -export class UnitSelectorComponent { +export class UnitSelectorComponent implements OnInit { readonly unitConstants = {UNIT_MILLILITER, UNIT_LITER, UNIT_GALLON} @Input() unit = UNIT_MILLILITER @Input() showLabel = true @Input() short = false + @Input() control: FormControl | null @Output() unitChange = new EventEmitter() + + ngOnInit() { + this.control?.setValue(this.unit) + } + + onUnitChange(newUnit: string) { + this.control?.setValue(newUnit) + this.unitChange.emit(newUnit) + } } diff --git a/src/app/modules/recipes/edit.html b/src/app/modules/recipes/edit.html new file mode 100644 index 0000000..690dbf6 --- /dev/null +++ b/src/app/modules/recipes/edit.html @@ -0,0 +1,36 @@ +
+ + + Retour + + + + Supprimer + Enregistrer + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + diff --git a/src/app/modules/recipes/explore.html b/src/app/modules/recipes/explore.html new file mode 100644 index 0000000..8d8f714 --- /dev/null +++ b/src/app/modules/recipes/explore.html @@ -0,0 +1,52 @@ +
+ + + + + + Retour + + + + + + + + + Version Excel + + Enregistrer + + + + +
+ +
+ + +
+ + +
+ +
+ + +
+ +
+
+
+ + + diff --git a/src/app/modules/colors/pages/explore/explore.component.ts b/src/app/modules/recipes/explore.ts similarity index 60% rename from src/app/modules/colors/pages/explore/explore.component.ts rename to src/app/modules/recipes/explore.ts index 4fef579..084693e 100644 --- a/src/app/modules/colors/pages/explore/explore.component.ts +++ b/src/app/modules/recipes/explore.ts @@ -1,27 +1,34 @@ import {Component} from '@angular/core' -import {RecipeService} from '../../services/recipe.service' +import {RecipeService} from './services/recipe.service' import {ActivatedRoute, Router} from '@angular/router' -import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component' -import {MixMaterialDto, Recipe, recipeMixCount, recipeNoteForGroupId, recipeStepCount} from '../../../shared/model/recipe.model' +import {ErrorHandlingComponent} from '../shared/components/subscribing.component' +import { + MixMaterialDto, + Recipe, + recipeMixCount, + recipeNoteForGroupId, + recipeStepCount +} from '../shared/model/recipe.model' import {Observable, Subject} from 'rxjs' -import {ErrorHandler, ErrorService} from '../../../shared/service/error.service' -import {AlertService} from '../../../shared/service/alert.service' -import {GlobalAlertHandlerComponent} from '../../../shared/components/global-alert-handler/global-alert-handler.component' -import {InventoryService} from '../../../material/service/inventory.service' -import {ConfirmBoxComponent} from '../../../shared/components/confirm-box/confirm-box.component' -import {GroupService} from '../../../groups/services/group.service' -import {AppState} from '../../../shared/app-state' -import {AccountService} from '../../../accounts/services/account.service' -import {Permission} from '../../../shared/model/user' +import {ErrorHandler, ErrorService} from '../shared/service/error.service' +import {AlertService} from '../shared/service/alert.service' +import {GlobalAlertHandlerComponent} from '../shared/components/global-alert-handler/global-alert-handler.component' +import {InventoryService} from '../material/service/inventory.service' +import {ConfirmBoxComponent} from '../shared/components/confirm-box/confirm-box.component' +import {GroupService} from '../groups/services/group.service' +import {AppState} from '../shared/app-state' +import {AccountService} from '../accounts/services/account.service' +import {Permission} from '../shared/model/user' +import {FormControl} from '@angular/forms'; +import {map} from 'rxjs/operators'; +import {CreInputEntry} from '../shared/components/inputs/inputs'; @Component({ - selector: 'cre-explore', - templateUrl: './explore.component.html', - styleUrls: ['./explore.component.sass'] + selector: 'cre-recipe-explore', + templateUrl: './explore.html', + styleUrls: ['./recipes.sass'] }) -export class ExploreComponent extends ErrorHandlingComponent { - recipe: Recipe | null - groups$ = this.groupService.all +export class CreRecipeExplore extends ErrorHandlingComponent { deductErrorBody = {} units$ = new Subject() selectedGroupId: number | null @@ -33,6 +40,12 @@ export class ExploreComponent extends ErrorHandlingComponent { deductedMixId: number | null + groupControl: FormControl + noteControl: FormControl + groupEntries$ = this.groupService.all.pipe(map(groups => { + return groups.map(group => new CreInputEntry(group.id, group.name)) + })) + errorHandlers: ErrorHandler[] = [{ filter: error => error.type === 'notfound-recipe-id', consumer: error => this.urlUtils.navigateTo('/color/list') @@ -42,6 +55,9 @@ export class ExploreComponent extends ErrorHandlingComponent { messageProducer: () => 'Certains produit ne sont pas en quantité suffisante dans l\'inventaire' }] + private _recipe: Recipe | null + private _notePlaceholder = !this.canEditRecipesPublicData ? 'N/A' : '' + constructor( private recipeService: RecipeService, private inventoryService: InventoryService, @@ -62,18 +78,30 @@ export class ExploreComponent extends ErrorHandlingComponent { this.selectedGroupId = this.loggedInUserGroupId - const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id')) + this.fetchRecipe() + + this.groupControl = new FormControl(this.selectedGroupId) + this.subscribe( + this.groupControl.valueChanges, + groupId => { + this.selectedGroupId = groupId + this.noteControl.setValue(this.selectedGroupNote, {emitEvent: false}) + } + ) + + this.noteControl = new FormControl({value: this._notePlaceholder, disabled: !this.canEditRecipesPublicData}) + this.subscribe( + this.noteControl.valueChanges, + _ => this.hasModifications = true + ) + } + + fetchRecipe() { + const recipeId = parseInt(this.activatedRoute.snapshot.paramMap.get('id')) this.subscribeEntityById( this.recipeService, - id, - r => { - this.recipe = r - this.appState.title = r.name - - if (recipeMixCount(this.recipe) <= 0 || recipeStepCount(this.recipe) <= 0) { - this.alertService.pushWarning('Cette recette n\'est pas complète') - } - } + recipeId, + recipe => this.recipe = recipe ) } @@ -128,11 +156,24 @@ export class ExploreComponent extends ErrorHandlingComponent { subscribeDeductMix(observable: Observable) { this.subscribe( observable, - () => this.alertService.pushSuccess('Les quantités quantités ont été déduites de l\'inventaire'), + () => this.alertService.pushSuccess('Les quantités ont été déduites de l\'inventaire'), true ) } + get recipe(): Recipe { + return this._recipe + } + + set recipe(recipe: Recipe) { + this._recipe = recipe + this.appState.title = recipe.name + + if (recipeMixCount(recipe) <= 0 || recipeStepCount(recipe) <= 0) { + this.alertService.pushWarning('Cette recette n\'est pas complète') + } + } + get loggedInUserGroupId(): number { return this.appState.authenticatedUser.group?.id } @@ -141,11 +182,7 @@ export class ExploreComponent extends ErrorHandlingComponent { if (!this.groupsNote.has(this.selectedGroupId)) { this.groupsNote.set(this.selectedGroupId, recipeNoteForGroupId(this.recipe, this.selectedGroupId)) } - return this.groupsNote.get(this.selectedGroupId) - } - - set selectedGroupNote(value: string) { - this.groupsNote.set(this.selectedGroupId, value) + return this.groupsNote.get(this.selectedGroupId) ?? this._notePlaceholder } get canEditRecipesPublicData(): boolean { @@ -160,7 +197,9 @@ export class ExploreComponent extends ErrorHandlingComponent { }) this.groupsNote.forEach((content, groupId) => { - updatedNotes.set(groupId, content) + if (content) { + updatedNotes.set(groupId, content) + } }) return updatedNotes diff --git a/src/app/modules/recipes/form.html b/src/app/modules/recipes/form.html new file mode 100644 index 0000000..3dc9805 --- /dev/null +++ b/src/app/modules/recipes/form.html @@ -0,0 +1,21 @@ +
+ +

Il n'y a actuellement aucune bannière enregistrée dans le système.

+

Vous pouvez en créer une ici.

+
+
+ + + Ajouter une couleur + Modifier la couleur {{recipe.name}} + + + + + + + + + + + diff --git a/src/app/modules/colors/pages/list/list.component.html b/src/app/modules/recipes/list.html similarity index 57% rename from src/app/modules/colors/pages/list/list.component.html rename to src/app/modules/recipes/list.html index 84493bf..37ed007 100644 --- a/src/app/modules/colors/pages/list/list.component.html +++ b/src/app/modules/recipes/list.html @@ -1,43 +1,47 @@ -
- - Recherche - - - -
- -
+ + + + + + Ajouter + + + +
+ +

Il n'y a actuellement aucune bannière enregistrée dans le système.

+

Vous pouvez en créer une ici. +

+
+ +

Il n'y a actuellement aucune recette enregistrée dans le système.

+

Vous pouvez en créer une ici.

+
- {{companyRecipes.company}} + {{company.name}} - + - + @@ -85,19 +89,16 @@ - - - - - -
Nom - + + Voir - + + Modifier
+
diff --git a/src/app/modules/recipes/list.ts b/src/app/modules/recipes/list.ts new file mode 100644 index 0000000..121f6e7 --- /dev/null +++ b/src/app/modules/recipes/list.ts @@ -0,0 +1,110 @@ +import {ChangeDetectorRef, Component} from '@angular/core' +import {ErrorHandlingComponent} from '../shared/components/subscribing.component' +import {Company} from '../shared/model/company.model' +import {getRecipeLuma, Recipe, recipeMatchesFilter} from '../shared/model/recipe.model' +import {CompanyService} from '../company/service/company.service' +import {RecipeService} from './services/recipe.service' +import {AccountService} from '../accounts/services/account.service' +import {ConfigService} from '../shared/service/config.service' +import {AppState} from '../shared/app-state' +import {ErrorService} from '../shared/service/error.service' +import {ActivatedRoute, Router} from '@angular/router' +import {Config} from '../shared/model/config.model' +import {Permission} from '../shared/model/user' +import {FormControl} from '@angular/forms' + +@Component({ + selector: 'cre-recipe-list', + templateUrl: 'list.html', + styleUrls: ['recipes.sass'] +}) +export class RecipeList extends ErrorHandlingComponent { + companies: Company[] = [] + recipes: Map = new Map() + columns = ['name', 'description', 'color', 'sample', 'iconNotApproved', 'buttonView', 'buttonEdit'] + panelForcedExpanded = false + + searchControl: FormControl + searchQuery = '' + + recipeFilterPredicate = recipeMatchesFilter + + constructor( + private companyService: CompanyService, + private recipeService: RecipeService, + private accountService: AccountService, + private configService: ConfigService, + private cdRef: ChangeDetectorRef, + private appState: AppState, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit() + this.appState.title = 'Explorateur' + + // Navigate to configs if server is in emergency mode + this.subscribe( + this.configService.get(Config.EMERGENCY_MODE), + config => { + if (config.content == 'true') { + this.urlUtils.navigateTo('/admin/config/') + } + } + ) + + this.fetchCompanies() + this.fetchRecipes() + + this.searchControl = new FormControl('') + this.subscribe( + this.searchControl.valueChanges, + value => { + this.searchQuery = value + if (value.length > 0 && !this.panelForcedExpanded) { + this.panelForcedExpanded = true + this.cdRef.detectChanges() + } + } + ) + } + + private fetchCompanies() { + this.subscribe( + this.companyService.all, + companies => this.companies = companies + ) + } + + private fetchRecipes() { + this.subscribe( + this.recipeService.allByCompany, + recipes => this.recipes = recipes, + true + ) + } + + isCompanyHidden(company: Company): boolean { + const companyRecipes = this.recipes.get(company.id) + return !(companyRecipes && companyRecipes.length >= 0) || + this.searchQuery && this.searchQuery.length > 0 && + !companyRecipes.some(recipe => this.recipeFilterPredicate(recipe, this.searchQuery)) + } + + + isLight(recipe: Recipe): boolean { + return getRecipeLuma(recipe) > 200 + } + + get hasEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_RECIPES) + } + + get hasCompanyEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_COMPANIES) + } +} diff --git a/src/app/modules/recipes/mix/add.html b/src/app/modules/recipes/mix/add.html new file mode 100644 index 0000000..d6a6301 --- /dev/null +++ b/src/app/modules/recipes/mix/add.html @@ -0,0 +1,20 @@ + + + + Retour + + + Enregistrer + + + + + + Ajouter un mélange à la couleur {{recipe.company.name}} - {{recipe.name}} + + + diff --git a/src/app/modules/recipes/mix/edit.html b/src/app/modules/recipes/mix/edit.html new file mode 100644 index 0000000..14267ca --- /dev/null +++ b/src/app/modules/recipes/mix/edit.html @@ -0,0 +1,25 @@ + + + + Retour + + + + Enregistrer + + + + + + + Modification du mélange {{mix.mixType.name}} de la recette {{recipe.company.name}} - {{recipe.name}} + + + diff --git a/src/app/modules/recipes/mix/form.html b/src/app/modules/recipes/mix/form.html new file mode 100644 index 0000000..8deb6d4 --- /dev/null +++ b/src/app/modules/recipes/mix/form.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/src/app/modules/recipes/mix/info-form.html b/src/app/modules/recipes/mix/info-form.html new file mode 100644 index 0000000..adb993d --- /dev/null +++ b/src/app/modules/recipes/mix/info-form.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/app/modules/recipes/mix/materials-form-combo-box.html b/src/app/modules/recipes/mix/materials-form-combo-box.html new file mode 100644 index 0000000..fc440c9 --- /dev/null +++ b/src/app/modules/recipes/mix/materials-form-combo-box.html @@ -0,0 +1,5 @@ + + diff --git a/src/app/modules/recipes/mix/materials-form.html b/src/app/modules/recipes/mix/materials-form.html new file mode 100644 index 0000000..5c7742c --- /dev/null +++ b/src/app/modules/recipes/mix/materials-form.html @@ -0,0 +1,76 @@ +
+ +

Il n'y a actuellement aucun produit enregistré dans le système.

+

Vous pouvez en créer un ici. +

+
+ + + + Position + {{mixMaterial.position}} + + + + + + + + + + + + Produit + + + + + + + + Quantité + + + + + + + Unités + + + + + + % + + + + + + + Ajouter + + + + Retirer + + + + +
diff --git a/src/app/modules/recipes/mix/materials-form.ts b/src/app/modules/recipes/mix/materials-form.ts new file mode 100644 index 0000000..06b22d7 --- /dev/null +++ b/src/app/modules/recipes/mix/materials-form.ts @@ -0,0 +1,255 @@ +import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild, ViewChildren} from '@angular/core' +import {CreTable} from '../../shared/components/tables/tables' +import {Mix, MixMaterialDto, mixMaterialsToMixMaterialsDto, sortMixMaterialsDto} from '../../shared/model/recipe.model' +import {Observable, Subject} from 'rxjs' +import {Material, materialComparator} from '../../shared/model/material.model' +import {FormControl, Validators} from '@angular/forms' +import {takeUntil} from 'rxjs/operators' +import {CreComboBoxComponent, CreInputEntry} from '../../shared/components/inputs/inputs' +import {AccountService} from '../../accounts/services/account.service' +import {Permission} from '../../shared/model/user' +import {UNIT_MILLILITER} from '../../shared/units' + +@Component({ + selector: 'cre-mix-materials-form-combo-box', + templateUrl: 'materials-form-combo-box.html' +}) +export class MixMaterialsFormComboBox implements OnInit { + @ViewChild(CreComboBoxComponent) comboBox: CreComboBoxComponent + + @Input() mixMaterial: MixMaterialDto + @Input() mix: Mix | null + @Input() mixMaterials: MixMaterialDto[] + @Input() control: FormControl + @Input() materials: Material[] + @Input() position: number + + entries: CreInputEntry[] + + ngOnInit() { + this.entries = this.filterMaterials() + } + + updateEntries() { + this.entries = this.filterMaterials() + this.comboBox.reloadEntries() + } + + private filterMaterials(): CreInputEntry[] { + return this.materials + .filter(material => { + if (this.mix && this.mix.mixType.material.id === material.id) { + return false + } + + // Prevent use of percents in first position + if (material.materialType.usePercentages && this.mixMaterial.position <= 1) { + return false + } + + if (this.mixMaterial.materialId === material.id) { + return true + } + + return this.mixMaterials.filter(x => x.materialId === material.id).length <= 0 + }) + .sort(materialComparator) + .map(material => new CreInputEntry(material.id, material.name, material.materialType.prefix ? `[${material.materialType.prefix}] ${material.name}` : material.name)) + } +} + +@Component({ + selector: 'cre-mix-materials-form', + templateUrl: 'materials-form.html' +}) +export class MixMaterialsForm implements AfterViewInit, OnDestroy { + @ViewChild(CreTable) table: CreTable + @ViewChildren(MixMaterialsFormComboBox) comboBoxes: MixMaterialsFormComboBox[] + + @Input() materials: Observable + @Input() mix: Mix | null + + mixMaterials: MixMaterialDto[] = [] + columns = ['position', 'positionButtons', 'material', 'quantity', 'units', 'endButton'] + allMaterials: Material[] + + private _controls: ControlsByPosition[] = [] + private _destroy$ = new Subject() + + constructor( + private accountService: AccountService, + private cdRef: ChangeDetectorRef + ) { + } + + ngAfterViewInit() { + this.materials.subscribe({ + next: materials => { + this.allMaterials = materials + + if (!this.mix) { + this.addRow() + } else { + mixMaterialsToMixMaterialsDto(this.mix).forEach(x => this.insertRow(x)) + } + + this.table.renderRows() + this.cdRef.detectChanges() + } + }) + } + + ngOnDestroy() { + this._destroy$.next(true) + this._destroy$.complete() + } + + addRow() { + const mixMaterial = new MixMaterialDto(null, 0, false, this.nextPosition, UNIT_MILLILITER) + this.insertRow(mixMaterial) + this.table.renderRows() + } + + insertRow(mixMaterial: MixMaterialDto) { + const materialIdControl = new FormControl(mixMaterial.materialId, Validators.required) + const quantityControl = new FormControl(mixMaterial.quantity, Validators.required) + const unitsControl = new FormControl(mixMaterial.units, Validators.required) + + materialIdControl.valueChanges + .pipe(takeUntil(this._destroy$)) + .subscribe({ + next: materialId => { + mixMaterial.materialId = materialId + this.refreshAvailableMaterials() + this.cdRef.detectChanges() + } + }) + + this.mixMaterials.push(mixMaterial) + this._controls.push({ + position: mixMaterial.position, + controls: { + materialId: materialIdControl, + quantity: quantityControl, + units: unitsControl + } + }) + } + + removeRow(mixMaterial: MixMaterialDto) { + this.mixMaterials = this.mixMaterials.filter(x => x.position !== mixMaterial.position) + this._controls = this._controls.filter(x => x.position !== mixMaterial.position) + + for (let position = mixMaterial.position + 1; position < this.mixMaterials.length; position++) { + this.updatePosition(this.getMixMaterialByPosition(position), position - 1, false) + } + } + + updatePosition(mixMaterial: MixMaterialDto, newPosition: number, switchPositions = true) { + const currentPosition = mixMaterial.position + const currentControls = this.getControlsByPosition(currentPosition) + + // Update before current to prevent position conflicts + if (switchPositions) { + this.updatePosition(this.getMixMaterialByPosition(newPosition), currentPosition, false) + } + + mixMaterial.position = newPosition + currentControls.position = newPosition + + this.sortTable() + this.refreshAvailableMaterials() + } + + getControls(position: number): MixMaterialControls { + return this.getControlsByPosition(position).controls + } + + areUnitsPercents(mixMaterial: MixMaterialDto): boolean { + if (!mixMaterial) { + return false + } + + return mixMaterial.materialId ? this.allMaterials?.filter(x => x.id === mixMaterial.materialId)[0].materialType.usePercentages : false + } + + isDecreasePositionButtonDisabled(mixMaterial: MixMaterialDto): boolean { + return mixMaterial.position <= 2 && this.areUnitsPercents(mixMaterial) + } + + isIncreasePositionButtonDisabled(mixMaterial: MixMaterialDto): boolean { + if (mixMaterial.position === this.mixMaterials.length) { + return true + } + + if (mixMaterial.position > 1) { + return false + } + + const nextMixMaterial = this.getMixMaterialByPosition(mixMaterial.position + 1) + return this.areUnitsPercents(nextMixMaterial) + } + + get hasMaterialEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_MATERIALS) + } + + get materialCount(): number { + return this.allMaterials ? this.allMaterials.length : 0 + } + + get updatedMixMaterials(): MixMaterialDto[] { + const updatedMixMaterials: MixMaterialDto[] = [] + this.mixMaterials.forEach(mixMaterial => { + const controls = this.getControlsByPosition(mixMaterial.position).controls + updatedMixMaterials.push({ + materialId: controls.materialId.value, + quantity: controls.quantity.value, + position: mixMaterial.position, + units: controls.units.value, + isPercents: this.areUnitsPercents(mixMaterial) + }) + }) + return updatedMixMaterials + } + + get valid(): boolean { + return this._controls + .map(controls => controls.controls) + .map(controls => [controls.materialId, controls.quantity]) + .flatMap(controls => controls) + .every(control => control.valid) + } + + private get nextPosition(): number { + return this.mixMaterials.length + 1 + } + + private getMixMaterialByPosition(position: number): MixMaterialDto { + return this.mixMaterials.filter(x => x.position === position)[0] + } + + private getControlsByPosition(position: number): ControlsByPosition { + return this._controls.filter(control => control.position === position)[0] + } + + private refreshAvailableMaterials() { + this.comboBoxes.forEach(x => x.updateEntries()) + } + + private sortTable() { + this.mixMaterials = sortMixMaterialsDto(this.mixMaterials) + this.table.renderRows() + } +} + +interface MixMaterialControls { + materialId: FormControl + quantity: FormControl + units: FormControl +} + +interface ControlsByPosition { + position: number + controls: MixMaterialControls +} diff --git a/src/app/modules/recipes/mix/mix.ts b/src/app/modules/recipes/mix/mix.ts new file mode 100644 index 0000000..a91833f --- /dev/null +++ b/src/app/modules/recipes/mix/mix.ts @@ -0,0 +1,171 @@ +import {Component, Directive, Input, OnInit, ViewChild} from '@angular/core' +import {SubscribingComponent} from '../../shared/components/subscribing.component' +import {Mix, Recipe} from '../../shared/model/recipe.model' +import {ErrorService} from '../../shared/service/error.service' +import {ActivatedRoute, Router} from '@angular/router' +import {RecipeService} from '../services/recipe.service' +import {FormControl, Validators} from '@angular/forms' +import {Observable} from 'rxjs' +import {MaterialType} from '../../shared/model/materialtype.model' +import {MaterialTypeService} from '../../material-type/service/material-type.service' +import {CreInputEntry} from '../../shared/components/inputs/inputs' +import {map} from 'rxjs/operators' +import {Material} from '../../shared/model/material.model' +import {MaterialService} from '../../material/service/material.service' +import {CreForm} from '../../shared/components/forms/forms' +import {MixMaterialsForm} from './materials-form' +import {MixSaveDto, MixService, MixUpdateDto} from '../services/mix.service' + +@Directive() +abstract class _BaseMixPage extends SubscribingComponent { + materialTypes$ = this.materialTypeService.all + materials$: Observable + + private _recipe: Recipe | null + + constructor( + protected mixService: MixService, + private recipeService: RecipeService, + private materialTypeService: MaterialTypeService, + private materialService: MaterialService, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + } + + ngOnInit() { + this.fetchRecipe() + } + + private fetchRecipe() { + const recipeId = this.urlUtils.parseIntUrlParam('recipeId') + + this.subscribe( + this.recipeService.getById(recipeId), + recipe => this.recipe = recipe + ) + } + + set recipe(recipe: Recipe) { + this._recipe = recipe + this.materials$ = this.materialService.getAllForMixCreation(recipe.id) + } + + get recipe(): Recipe { + return this._recipe + } + + abstract submit(dto: MixSaveDto) +} + +@Component({ + selector: 'cre-mix-add', + templateUrl: 'add.html' +}) +export class MixAdd extends _BaseMixPage { + submit(dto: MixSaveDto) { + this.subscribeAndNavigate( + this.mixService.saveDto(dto), + `/color/edit/${this.recipe.id}` + ) + } +} + +@Component({ + selector: 'cre-mix-edit', + templateUrl: 'edit.html' +}) +export class MixEdit extends _BaseMixPage { + mix: Mix + + ngOnInit() { + super.ngOnInit() + + this.fetchMix() + } + + private fetchMix() { + const mixId = this.urlUtils.parseIntUrlParam('id') + + this.subscribe( + this.mixService.getById(mixId), + mix => this.mix = mix + ) + } + + submit(dto: MixSaveDto) { + this.subscribeAndNavigate( + this.mixService.updateDto({...dto, id: this.mix.id}), + `/color/edit/${this.recipe.id}` + ) + } +} + +@Component({ + selector: 'cre-mix-info-form', + templateUrl: 'info-form.html' +}) +export class MixInfoForm implements OnInit { + @ViewChild(CreForm) form: CreForm + + @Input() recipe: Recipe + @Input() mix: Mix | null + @Input() materialTypes: Observable + + materialTypeEntries: Observable + controls: any + + ngOnInit() { + this.materialTypeEntries = this.materialTypes.pipe( + map(materialTypes => { + return materialTypes.map(materialType => new CreInputEntry(materialType.id, materialType.name)) + }) + ) + + this.controls = { + name: new FormControl(this.mix?.mixType.name, Validators.required), + materialType: new FormControl(this.mix?.mixType.material.materialType.id, Validators.required) + } + } + + get mixName(): string { + return this.controls.name.value + } + + get mixMaterialTypeId(): number { + return this.controls.materialType.value + } + + get valid(): boolean { + return this.form.valid + } +} + +@Component({ + selector: 'cre-mix-form', + templateUrl: 'form.html' +}) +export class MixForm { + @ViewChild(MixInfoForm) infoForm: MixInfoForm + @ViewChild(MixMaterialsForm) mixMaterialsForm: MixMaterialsForm + + @Input() recipe: Recipe + @Input() mix: Mix | null + @Input() materialTypes: Observable + @Input() materials: Observable + + get formValues(): MixSaveDto { + return { + name: this.infoForm.mixName, + recipeId: this.recipe.id, + materialTypeId: this.infoForm.mixMaterialTypeId, + mixMaterials: this.mixMaterialsForm.updatedMixMaterials + } + } + + get valid(): boolean { + return this.infoForm?.valid && this.mixMaterialsForm?.valid + } +} diff --git a/src/app/modules/colors/ptouchPrint.js b/src/app/modules/recipes/ptouchPrint.js similarity index 100% rename from src/app/modules/colors/ptouchPrint.js rename to src/app/modules/recipes/ptouchPrint.js diff --git a/src/app/modules/recipes/recipes-routing.module.ts b/src/app/modules/recipes/recipes-routing.module.ts new file mode 100644 index 0000000..dca257e --- /dev/null +++ b/src/app/modules/recipes/recipes-routing.module.ts @@ -0,0 +1,37 @@ +import {NgModule} from '@angular/core' +import {RouterModule, Routes} from '@angular/router' +import {CreRecipeExplore} from './explore' +import {RecipeAdd, RecipeEdit} from './recipes' +import {RecipeList} from './list' +import {MixAdd, MixEdit} from './mix/mix' + +const routes: Routes = [{ + path: 'list', + component: RecipeList +}, { + path: 'add', + component: RecipeAdd +}, { + path: 'edit/:id', + component: RecipeEdit +}, { + path: 'add/mix/:recipeId', + component: MixAdd +}, { + path: 'edit/mix/:recipeId/:id', + component: MixEdit +}, { + path: 'explore/:id', + component: CreRecipeExplore +}, { + path: '', + pathMatch: 'full', + redirectTo: 'list' +}] + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class RecipesRoutingModule { +} diff --git a/src/app/modules/recipes/recipes.module.ts b/src/app/modules/recipes/recipes.module.ts new file mode 100644 index 0000000..d1787b2 --- /dev/null +++ b/src/app/modules/recipes/recipes.module.ts @@ -0,0 +1,62 @@ +import {NgModule} from '@angular/core' + +import {RecipesRoutingModule} from './recipes-routing.module' +import {SharedModule} from '../shared/shared.module' +import {MatExpansionModule} from '@angular/material/expansion' +import {FormsModule} from '@angular/forms' +import {CreRecipeExplore} from './explore' +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 {UnitSelectorComponent} from './components/unit-selector/unit-selector.component' +import {ImagesEditorComponent} from './components/images-editor/images-editor.component' +import {MixesCardComponent} from './components/mixes-card/mixes-card.component' +import {MatSortModule} from '@angular/material/sort' +import {CreInputsModule} from '../shared/components/inputs/inputs.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' +import {RecipeAdd, RecipeEdit, RecipeForm} from './recipes' +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module' +import {RecipeList} from './list' +import {MixAdd, MixEdit, MixForm, MixInfoForm} from './mix/mix' +import {CreTablesModule} from '../shared/components/tables/tables.module' +import {MixMaterialsForm, MixMaterialsFormComboBox} from './mix/materials-form' + +@NgModule({ + declarations: [ + CreRecipeExplore, + RecipeInfoComponent, + MixTableComponent, + StepListComponent, + StepTableComponent, + UnitSelectorComponent, + ImagesEditorComponent, + MixesCardComponent, + RecipeForm, + RecipeAdd, + RecipeEdit, + RecipeList, + MixAdd, + MixEdit, + MixForm, + MixInfoForm, + MixMaterialsForm, + MixMaterialsFormComboBox + ], + exports: [ + UnitSelectorComponent + ], + imports: [ + RecipesRoutingModule, + SharedModule, + MatExpansionModule, + FormsModule, + MatSortModule, + CreInputsModule, + CreButtonsModule, + CreActionBarModule, + CreTablesModule + ] +}) +export class RecipesModule { +} diff --git a/src/app/modules/recipes/recipes.sass b/src/app/modules/recipes/recipes.sass new file mode 100644 index 0000000..44c8af6 --- /dev/null +++ b/src/app/modules/recipes/recipes.sass @@ -0,0 +1,30 @@ +.recipe-wrapper > section + margin: 0 3rem 3rem + +cre-form + margin-top: 0 !important + +mat-expansion-panel + width: 60rem + margin: 20px auto + +.button-add + margin-top: .8rem + +.recipe-color-circle + box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12) + +.recipe-content > div + margin: 0 3rem 3rem + +cre-table + .mat-column-name, + .mat-column-color, + .mat-column-iconNotApproved + width: 5em + + .mat-column-description + width: 50em + + .mat-column-sample + width: 10em diff --git a/src/app/modules/recipes/recipes.ts b/src/app/modules/recipes/recipes.ts new file mode 100644 index 0000000..bdc619e --- /dev/null +++ b/src/app/modules/recipes/recipes.ts @@ -0,0 +1,211 @@ +import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component'; +import {Observable, Subject} from 'rxjs'; +import {CreInputEntry} from '../shared/components/inputs/inputs'; +import {map, tap} from 'rxjs/operators'; +import {RecipeService} from './services/recipe.service'; +import {CompanyService} from '../company/service/company.service'; +import {AppState} from '../shared/app-state'; +import {ErrorHandler, ErrorService} from '../shared/service/error.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {FormControl, Validators} from '@angular/forms'; +import {Component, EventEmitter, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core'; +import {Recipe, recipeMixCount, RecipeStep, recipeStepCount} from '../shared/model/recipe.model'; +import {AccountService} from '../accounts/services/account.service'; +import {Permission} from '../shared/model/user'; +import {AlertService} from '../shared/service/alert.service'; +import {GroupService} from '../groups/services/group.service'; +import {StepTableComponent} from './components/step-table/step-table.component'; +import {anyMap} from '../shared/utils/map.utils'; +import {CreForm, ICreForm} from '../shared/components/forms/forms'; + +@Component({ + selector: 'recipe-form', + templateUrl: 'form.html', + styleUrls: ['recipes.sass'], + encapsulation: ViewEncapsulation.None +}) +export class RecipeForm extends SubscribingComponent { + @ViewChild(CreForm) creForm: ICreForm + + @Input() recipe: Recipe | null + + @Output() submitForm = new EventEmitter(); + + controls: any + companyEntries$: Observable + hasCompanies = true + + constructor( + private companyService: CompanyService, + private accountService: AccountService, + errorService: ErrorService, + activatedRoute: ActivatedRoute, + router: Router, + ) { + super(errorService, activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit(); + + this.fetchCompanies() + + this.controls = { + name: new FormControl(this.recipe?.name, Validators.required), + description: new FormControl(this.recipe?.description, Validators.required), + color: new FormControl(this.recipe?.color ?? '#ffffff', Validators.required), + gloss: new FormControl(this.recipe?.gloss ?? 0, Validators.compose([Validators.required, Validators.min(0), Validators.max(100)])), + sample: new FormControl(this.recipe?.sample, Validators.compose([Validators.required, Validators.min(0)])), + approbationDate: new FormControl(this.recipe?.approbationDate), + remark: new FormControl(this.recipe?.remark), + company: new FormControl({value: this.recipe?.company.id, disabled: !!this.recipe}, Validators.required) + } + } + + private fetchCompanies() { + this.companyEntries$ = this.companyService.all.pipe( + tap(companies => this.hasCompanies = companies.length > 0), + map(companies => companies.map(c => new CreInputEntry(c.id, c.name))), + ) + } + + submit() { + this.submitForm.emit(this.updatedRecipe) + } + + get updatedRecipe(): Recipe { + return { + ...this.recipe, + name: this.controls.name.value, + description: this.controls.description.value, + color: this.controls.color.value, + gloss: this.controls.gloss.value, + sample: this.controls.sample.value, + approbationDate: this.controls.approbationDate.value, + remark: this.controls.remark.value, + company: this.controls.company.value, + } + } + + get hasCompanyEditPermission(): boolean { + return this.accountService.hasPermission(Permission.EDIT_COMPANIES) + } +} + +@Component({ + selector: 'cre-recipe-add', + templateUrl: 'add.html' +}) +export class RecipeAdd extends ErrorHandlingComponent { + errorHandlers = [{ + filter: error => error.type === `exists-recipe-company-name`, + messageProducer: error => `Une couleur avec le nom ${error.name} existe déjà pour la bannière ${error.company}` + }] + + constructor( + private recipeService: RecipeService, + private companyService: CompanyService, + private appState: AppState, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + this.appState.title = 'Nouvelle couleur' + } + + submit(recipe: Recipe) { + this.subscribe( + this.recipeService.save(recipe), + recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`) + ) + } +} + +@Component({ + selector: 'cre-recipe-edit', + templateUrl: 'edit.html', + styleUrls: ['recipes.sass'] +}) +export class RecipeEdit extends ErrorHandlingComponent { + @ViewChild(StepTableComponent) stepTable: StepTableComponent + @ViewChild(RecipeForm) form: RecipeForm + + recipe: Recipe + groups$ = this.groupService.all + units$ = new Subject() + + errorHandlers: ErrorHandler[] = [{ + filter: error => error.type === 'notfound-recipe-id', + consumer: _ => this.urlUtils.navigateTo('/color/list') + }] + + constructor( + private recipeService: RecipeService, + private companyService: CompanyService, + private groupService: GroupService, + private appState: AppState, + private alertService: AlertService, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + + this.fetchRecipe() + } + + private fetchRecipe() { + const recipeId = this.urlUtils.parseIntUrlParam('id') + this.subscribe( + this.recipeService.getById(recipeId), + recipe => { + this.recipe = recipe + this.appState.title = `${recipe.name} (Modifications)` + + if (recipeMixCount(this.recipe) == 0) { + this.alertService.pushWarning('Il n\'y a aucun mélange dans cette recette') + } + if (recipeStepCount(this.recipe) == 0) { + this.alertService.pushWarning('Il n\'y a aucune étape dans cette recette') + } + }, + true, + 1 + ) + } + + changeUnits(unit: string) { + this.units$.next(unit) + } + + submit() { + const recipe = this.form.updatedRecipe + const steps = this.stepTable.mappedUpdatedSteps + + if (!this.stepsPositionsAreValid(steps)) { + this.alertService.pushError('Les étapes ne peuvent pas avoir une position inférieure à 1') + return + } + + this.subscribeAndNavigate( + this.recipeService.update(recipe, steps), + '/color/list' + ) + } + + delete() { + this.subscribeAndNavigate( + this.recipeService.delete(this.recipe.id), + '/color/list' + ) + } + + get loggedInUserGroupId(): number { + return this.appState.authenticatedUser.group?.id + } + + private stepsPositionsAreValid(steps: Map): boolean { + return !anyMap(steps, (groupId, steps) => !!steps.find(s => s.position === 0)) + } +} diff --git a/src/app/modules/colors/services/mix.service.ts b/src/app/modules/recipes/services/mix.service.ts similarity index 66% rename from src/app/modules/colors/services/mix.service.ts rename to src/app/modules/recipes/services/mix.service.ts index cfd57bf..6a523e1 100644 --- a/src/app/modules/colors/services/mix.service.ts +++ b/src/app/modules/recipes/services/mix.service.ts @@ -21,8 +21,17 @@ export class MixService { return this.api.get(`/recipe/mix/${id}`) } - saveWithUnits(name: string, recipeId: number, materialTypeId: number, mixMaterials: MixMaterialDto[], units: string): Observable { - return this.save(name, recipeId, materialTypeId, this.convertMixMaterialsToMl(mixMaterials, units)) + saveDto(dto: MixSaveDto): Observable { + return this.saveWithUnits( + dto.name, + dto.recipeId, + dto.materialTypeId, + dto.mixMaterials, + ) + } + + saveWithUnits(name: string, recipeId: number, materialTypeId: number, mixMaterials: MixMaterialDto[]): Observable { + return this.save(name, recipeId, materialTypeId, this.convertMixMaterialsToMl(mixMaterials)) } save(name: string, recipeId: number, materialTypeId: number, mixMaterials: MixMaterialDto[]): Observable { @@ -36,8 +45,17 @@ export class MixService { return this.api.post('/recipe/mix', body) } - updateWithUnits(id: number, name: string, materialTypeId: number, mixMaterials: MixMaterialDto[], units: string): Observable { - return this.update(id, name, materialTypeId, this.convertMixMaterialsToMl(mixMaterials, units)) + updateDto(dto: MixUpdateDto): Observable { + return this.updateWithUnits( + dto.id, + dto.name, + dto.materialTypeId, + dto.mixMaterials + ) + } + + updateWithUnits(id: number, name: string, materialTypeId: number, mixMaterials: MixMaterialDto[]): Observable { + return this.update(id, name, materialTypeId, this.convertMixMaterialsToMl(mixMaterials)) } update(id: number, name: string, materialTypeId: number, mixMaterials: MixMaterialDto[]): Observable { @@ -56,11 +74,12 @@ export class MixService { return this.api.delete(`/recipe/mix/${id}`) } - private convertMixMaterialsToMl(mixMaterials: MixMaterialDto[], units: string): MixMaterialDto[] { - return mixMaterials.map(m => { - m.quantity = convertMixMaterialQuantity(m, units, UNIT_MILLILITER) - return m - }) + private convertMixMaterialsToMl(mixMaterials: MixMaterialDto[]): MixMaterialDto[] { + return mixMaterials.map(mixMaterial => ({ + ...mixMaterial, + quantity: convertMixMaterialQuantity(mixMaterial, UNIT_MILLILITER), + units: UNIT_MILLILITER + })) } private appendMixMaterialsToBody(mixMaterials: MixMaterialDto[], body: any) { @@ -74,3 +93,17 @@ export class MixService { } } +export interface MixSaveDto { + name: string + recipeId: number + materialTypeId: number + mixMaterials: MixMaterialDto[] +} + +export interface MixUpdateDto { + id: number + name: string + materialTypeId: number + mixMaterials: MixMaterialDto[] +} + diff --git a/src/app/modules/colors/services/recipe-image.service.ts b/src/app/modules/recipes/services/recipe-image.service.ts similarity index 100% rename from src/app/modules/colors/services/recipe-image.service.ts rename to src/app/modules/recipes/services/recipe-image.service.ts diff --git a/src/app/modules/colors/services/recipe.service.ts b/src/app/modules/recipes/services/recipe.service.ts similarity index 61% rename from src/app/modules/colors/services/recipe.service.ts rename to src/app/modules/recipes/services/recipe.service.ts index 1cda0ce..4554569 100644 --- a/src/app/modules/colors/services/recipe.service.ts +++ b/src/app/modules/recipes/services/recipe.service.ts @@ -21,16 +21,16 @@ export class RecipeService { return this.api.get(`/recipe?name=${name}`) } - get allSortedByCompany(): Observable<{ company: string, recipes: Recipe[] }[]> { + get allByCompany(): Observable> { return this.all.pipe(map(recipes => { - const mapped = [] + const map = new Map() recipes.forEach(r => { - if (!mapped[r.company.id]) { - mapped[r.company.id] = {company: r.company.name, recipes: []} + if (!map.has(r.company.id)) { + map.set(r.company.id, []) } - mapped[r.company.id].recipes.push(r) + map.get(r.company.id).push(r) }) - return mapped.filter(e => e != null) // Filter to remove empty elements in the array that appears for some reason + return map })) } @@ -38,20 +38,19 @@ export class RecipeService { return this.api.get(`/recipe/${id}`) } - save(name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, companyId: number): Observable { - const body = {name, description, color, gloss, sample, remark, companyId} - if (approbationDate) { - // @ts-ignore - body.approbationDate = approbationDate + save(recipe: Recipe): Observable { + const body = { + ...recipe, + companyId: recipe.company } + return this.api.post('/recipe', body) } - update(id: number, name: string, description: string, color: string, gloss: number, sample: number, approbationDate: string, remark: string, steps: Map) { - const body = {id, name, description, color, gloss, sample, remark, steps: []} - if (approbationDate) { - // @ts-ignore - body.approbationDate = approbationDate + update(recipe: Recipe, steps: Map) { + const body = { + ...recipe, + steps: [] } steps.forEach((groupSteps, groupId) => { diff --git a/src/app/modules/shared/components/action-bar/action-bar.html b/src/app/modules/shared/components/action-bar/action-bar.html index 4bad334..6aa7ad0 100644 --- a/src/app/modules/shared/components/action-bar/action-bar.html +++ b/src/app/modules/shared/components/action-bar/action-bar.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/app/modules/shared/components/action-bar/action-bar.ts b/src/app/modules/shared/components/action-bar/action-bar.ts index 506bed9..61df16b 100644 --- a/src/app/modules/shared/components/action-bar/action-bar.ts +++ b/src/app/modules/shared/components/action-bar/action-bar.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core' +import {Component, Input} from '@angular/core' @Component({ selector: 'cre-action-group', @@ -11,4 +11,6 @@ export class CreActionGroup {} selector: 'cre-action-bar', templateUrl: 'action-bar.html' }) -export class CreActionBar {} +export class CreActionBar { + @Input() reverse = false +} diff --git a/src/app/modules/shared/components/action-bar/action-group.html b/src/app/modules/shared/components/action-bar/action-group.html index dfdbcc7..01b5f5d 100644 --- a/src/app/modules/shared/components/action-bar/action-group.html +++ b/src/app/modules/shared/components/action-bar/action-group.html @@ -1,3 +1,6 @@ -
- +
+
+ +
+
diff --git a/src/app/modules/shared/components/alerts/alerts.html b/src/app/modules/shared/components/alerts/alerts.html new file mode 100644 index 0000000..f9a034d --- /dev/null +++ b/src/app/modules/shared/components/alerts/alerts.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/modules/shared/components/alerts/alerts.module.ts b/src/app/modules/shared/components/alerts/alerts.module.ts new file mode 100644 index 0000000..110fde4 --- /dev/null +++ b/src/app/modules/shared/components/alerts/alerts.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from '@angular/core'; +import {WarningAlert} from './alerts'; + +@NgModule({ + declarations: [ + WarningAlert + ], + exports: [ + WarningAlert + ], + imports: [] +}) +export class CreAlertsModule { +} diff --git a/src/app/modules/shared/components/alerts/alerts.ts b/src/app/modules/shared/components/alerts/alerts.ts new file mode 100644 index 0000000..e31da46 --- /dev/null +++ b/src/app/modules/shared/components/alerts/alerts.ts @@ -0,0 +1,10 @@ +import {Component, ViewEncapsulation} from '@angular/core'; + +@Component({ + selector: 'cre-warning-alert', + templateUrl: 'alerts.html', + encapsulation: ViewEncapsulation.None +}) +export class WarningAlert { + +} diff --git a/src/app/modules/shared/components/forms/buttons.ts b/src/app/modules/shared/components/forms/buttons.ts new file mode 100644 index 0000000..0cebdc4 --- /dev/null +++ b/src/app/modules/shared/components/forms/buttons.ts @@ -0,0 +1,17 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {ICreForm} from './forms'; + +@Component({ + selector: 'cre-submit-button', + templateUrl: 'submit-button.html' +}) +export class CreSubmitButton { + @Input() form: ICreForm + @Input() valid: boolean | null + + @Output() submit = new EventEmitter() + + get disableButton(): boolean { + return !this.form || !(this.valid ?? this.form.valid) + } +} diff --git a/src/app/modules/shared/components/forms/forms.module.ts b/src/app/modules/shared/components/forms/forms.module.ts index 2ee4a91..4e1a91c 100644 --- a/src/app/modules/shared/components/forms/forms.module.ts +++ b/src/app/modules/shared/components/forms/forms.module.ts @@ -1,28 +1,34 @@ import {NgModule} from '@angular/core' -import {CreFormActions, CreFormComponent, CreFormContent, CreFormTitle} from './forms' +import {CreFormActions, CreForm, CreFormContent, CreFormTitle} from './forms' import {MatCardModule} from '@angular/material/card' import {CommonModule} from '@angular/common' import {MatButtonModule} from '@angular/material/button' import {ReactiveFormsModule} from '@angular/forms' +import {CreSubmitButton} from './buttons'; +import {CreButtonsModule} from '../buttons/buttons.module'; @NgModule({ declarations: [ - CreFormComponent, + CreForm, CreFormTitle, CreFormContent, - CreFormActions + CreFormActions, + CreSubmitButton ], exports: [ - CreFormComponent, + CreForm, CreFormTitle, CreFormContent, - CreFormActions + CreFormActions, + CreSubmitButton ], - imports: [ - MatCardModule, - CommonModule, - MatButtonModule, - ReactiveFormsModule - ] + imports: [ + MatCardModule, + CommonModule, + MatButtonModule, + ReactiveFormsModule, + CreButtonsModule + ] }) -export class CreFormsModule {} +export class CreFormsModule { +} diff --git a/src/app/modules/shared/components/forms/forms.sass b/src/app/modules/shared/components/forms/forms.sass index fffedc8..1a3fb08 100644 --- a/src/app/modules/shared/components/forms/forms.sass +++ b/src/app/modules/shared/components/forms/forms.sass @@ -1,8 +1,12 @@ cre-form display: block + width: max-content + min-width: 50rem + margin-top: 3rem mat-card width: inherit + min-width: inherit cre-form-actions display: flex diff --git a/src/app/modules/shared/components/forms/forms.ts b/src/app/modules/shared/components/forms/forms.ts index 5770d23..7619f15 100644 --- a/src/app/modules/shared/components/forms/forms.ts +++ b/src/app/modules/shared/components/forms/forms.ts @@ -1,6 +1,12 @@ import {Component, ContentChild, Directive, Input, OnInit, ViewEncapsulation} from '@angular/core' import {FormBuilder, FormGroup} from '@angular/forms' +export interface ICreForm { + form: FormGroup + valid: boolean + invalid: boolean +} + @Directive({ selector: 'cre-form-title' }) @@ -17,7 +23,6 @@ export class CreFormContent { selector: 'cre-form-actions' }) export class CreFormActions { - } @Component({ @@ -26,7 +31,7 @@ export class CreFormActions { styleUrls: ['forms.sass'], encapsulation: ViewEncapsulation.None }) -export class CreFormComponent implements OnInit { +export class CreForm implements ICreForm, OnInit { @ContentChild(CreFormActions) formActions: CreFormActions @Input() formControls: { [key: string]: any } @@ -42,7 +47,11 @@ export class CreFormComponent implements OnInit { } get hasActions(): boolean { - return this.formActions === true + return !!this.formActions + } + + get valid(): boolean { + return this.form && this.form.valid } get invalid(): boolean { diff --git a/src/app/modules/shared/components/forms/submit-button.html b/src/app/modules/shared/components/forms/submit-button.html new file mode 100644 index 0000000..8e9289a --- /dev/null +++ b/src/app/modules/shared/components/forms/submit-button.html @@ -0,0 +1 @@ +Enregistrer diff --git a/src/app/modules/shared/components/header/header.component.ts b/src/app/modules/shared/components/header/header.component.ts index 94f4563..6d113fd 100644 --- a/src/app/modules/shared/components/header/header.component.ts +++ b/src/app/modules/shared/components/header/header.component.ts @@ -66,7 +66,7 @@ export class HeaderComponent extends SubscribingComponent { } get logoUrl(): string { - return environment.apiUrl + "/file?path=images%2Flogo&mediaType=image/png" + return environment.apiUrl + "/config/logo" } set activeLink(link: string) { diff --git a/src/app/modules/shared/components/inputs/autocomplete.html b/src/app/modules/shared/components/inputs/autocomplete.html index bc21b24..760d6f4 100644 --- a/src/app/modules/shared/components/inputs/autocomplete.html +++ b/src/app/modules/shared/components/inputs/autocomplete.html @@ -24,7 +24,7 @@ [ngTemplateOutletContext]="{errors: control.errors}"> - + {{option}} diff --git a/src/app/modules/shared/components/inputs/chips-combo-box.html b/src/app/modules/shared/components/inputs/chips-combo-box.html index 443ff0c..f454ad3 100644 --- a/src/app/modules/shared/components/inputs/chips-combo-box.html +++ b/src/app/modules/shared/components/inputs/chips-combo-box.html @@ -30,7 +30,7 @@ - + {{option.display ? option.display : option.value}} diff --git a/src/app/modules/shared/components/inputs/combo-box.html b/src/app/modules/shared/components/inputs/combo-box.html index 5170f8c..67e0162 100644 --- a/src/app/modules/shared/components/inputs/combo-box.html +++ b/src/app/modules/shared/components/inputs/combo-box.html @@ -1,22 +1,23 @@ - + {{label}} - - Ce champ est requis + + Cette valeur est invalide + Ce champ est requis + [ngTemplateOutletContext]="{errors: internalControl.errors}"> - - {{option.value}} + + {{entry.display ? entry.display : entry.value}} diff --git a/src/app/modules/shared/components/inputs/input.html b/src/app/modules/shared/components/inputs/input.html index 2bf813e..f58c818 100644 --- a/src/app/modules/shared/components/inputs/input.html +++ b/src/app/modules/shared/components/inputs/input.html @@ -5,6 +5,7 @@ @@ -13,6 +14,7 @@ diff --git a/src/app/modules/shared/components/inputs/inputs.module.ts b/src/app/modules/shared/components/inputs/inputs.module.ts index 1797478..e45c685 100644 --- a/src/app/modules/shared/components/inputs/inputs.module.ts +++ b/src/app/modules/shared/components/inputs/inputs.module.ts @@ -4,7 +4,7 @@ import { CreChipComboBoxComponent, CreChipInputComponent, CreComboBoxComponent, CreFileInputComponent, - CreInputComponent, CrePeriodInputComponent + CreInputComponent, CrePeriodInputComponent, CreSelectComponent, CreSliderInputComponent, CreTextareaComponent } from './inputs' import {MatInputModule} from '@angular/material/input' import {MatIconModule} from '@angular/material/icon' @@ -17,6 +17,7 @@ import {MatChipsModule} from '@angular/material/chips' import {CreButtonsModule} from '../buttons/buttons.module' import {MatCheckboxModule} from '@angular/material/checkbox' import {MatSelectModule} from '@angular/material/select' +import {MatSliderModule} from '@angular/material/slider'; @NgModule({ declarations: [ @@ -27,7 +28,10 @@ import {MatSelectModule} from '@angular/material/select' CreChipComboBoxComponent, CreFileInputComponent, CreCheckboxInputComponent, - CrePeriodInputComponent + CrePeriodInputComponent, + CreSliderInputComponent, + CreTextareaComponent, + CreSelectComponent ], imports: [ MatInputModule, @@ -42,6 +46,7 @@ import {MatSelectModule} from '@angular/material/select' CreButtonsModule, MatCheckboxModule, MatSelectModule, + MatSliderModule, ], exports: [ CreInputComponent, @@ -51,7 +56,10 @@ import {MatSelectModule} from '@angular/material/select' CreAutocompleteInputComponent, CreFileInputComponent, CreCheckboxInputComponent, - CrePeriodInputComponent + CrePeriodInputComponent, + CreSliderInputComponent, + CreTextareaComponent, + CreSelectComponent ] }) export class CreInputsModule { diff --git a/src/app/modules/shared/components/inputs/inputs.ts b/src/app/modules/shared/components/inputs/inputs.ts index dc7204e..cfca06f 100644 --- a/src/app/modules/shared/components/inputs/inputs.ts +++ b/src/app/modules/shared/components/inputs/inputs.ts @@ -1,5 +1,5 @@ import { - AfterViewInit, + AfterViewInit, ChangeDetectorRef, Component, ContentChild, Directive, @@ -15,14 +15,14 @@ import { } from '@angular/core' import {AbstractControl, FormControl, ValidationErrors, ValidatorFn, Validators} from '@angular/forms' import {COMMA, ENTER} from '@angular/cdk/keycodes' -import {Observable, Subject} from 'rxjs' -import {map, takeUntil} from 'rxjs/operators' +import {isObservable, Observable, Subject} from 'rxjs' +import {map, startWith, takeUntil} from 'rxjs/operators' import {MatChipInputEvent} from '@angular/material/chips' import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete' @Directive() abstract class _CreInputBase { - @Input() control: AbstractControl | null + @Input() control: FormControl | null @Input() label: string @Input() value @Input() disabled = false @@ -50,16 +50,40 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit { @ViewChild('input') input: any @ContentChild(TemplateRef) errors: TemplateRef + fieldRequired = false + + constructor( + private cdRef: ChangeDetectorRef + ) { + super() + } + ngAfterViewInit() { const element = this.input.nativeElement element.type = this.type element.step = this.step.toString() element.placeholder = this.placeholder - element.required = this.required element.autocomplete = this.autocomplete ? 'on' : 'off' + + this.fieldRequired = this.control ? this.control.validator && this.control.validator({} as AbstractControl)?.required : this.required + + this.cdRef.detectChanges() } } +@Component({ + selector: 'cre-textarea', + templateUrl: 'textarea.html', + encapsulation: ViewEncapsulation.None +}) +export class CreTextareaComponent { + @Input() label: string + @Input() control: FormControl + @Input() cols = 40 + @Input() rows = 3 + @Input() placeholder: string | null +} + @Component({ selector: 'cre-autocomplete-input', templateUrl: 'autocomplete.html', @@ -70,7 +94,7 @@ export class CreAutocompleteInputComponent { @Input() label: string @Input() icon: string @Input() required = true - @Input() options: Observable + @Input() entries: Observable @Input() value @Output() valueChange = new EventEmitter() @@ -137,9 +161,112 @@ export class CreComboBoxComponent { @Input() label: string @Input() icon: string @Input() required = true - @Input() options: Observable @ContentChild(TemplateRef) errors: TemplateRef + + internalControl: FormControl + filteredEntries: CreInputEntry[] + validValue = false + + private _destroy$ = new Subject() + private _entries: CreInputEntry[] + private _controlsInitialized = false + + @Input() + set entries(entries: Observable | CreInputEntry[]) { + if (isObservable(entries)) { + (entries as Observable).pipe(takeUntil(this._destroy$)) + .subscribe({ + next: entries => { + this.initControls(entries) + } + }) + } else { + this.initControls((entries as CreInputEntry[])) + } + } + + reloadEntries() { + this.filteredEntries = this.filterEntries(this.internalControl.value) + } + + private initControls(entries) { + this._entries = entries + if (this._controlsInitialized) { + return + } + + this.internalControl = new FormControl({ + value: null, + disabled: false + }, Validators.compose([this.control.validator, this.valueValidator()])) + this.internalControl.valueChanges + .pipe(takeUntil(this._destroy$)) + .subscribe({ + next: value => { + if (this.internalControl.valid) { + this.control.setValue(this.findEntryByValue(value).key) + } else { + this.control.setValue(null) + } + + this.filteredEntries = this.filterEntries(value) + } + }) + + if (this.control.value) { + this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value) + } + + if (this.control.disabled) { + this.internalControl.disable() + } + + this.reloadEntries() + this._controlsInitialized = true + } + + private filterEntries(value: string): CreInputEntry[] { + if (!value) { + return this._entries + } + + const valueLowerCase = value.toLowerCase() + return this._entries.filter(entry => { + if (entry.display) { + return entry.display.toLowerCase().includes(valueLowerCase) + } else { + return entry.value.toLowerCase().includes(valueLowerCase) + } + }) + } + + private findEntryByKey(key: any): CreInputEntry | null { + const found = this._entries.filter(e => e.key === key) + if (found.length <= 0) { + return null + } + return found[0] + } + + private findEntryByValue(value: any): CreInputEntry | null { + const found = this._entries.filter(e => e.value === value) + if (found.length <= 0) { + return null + } + return found[0] + } + + private existsEntryByValue(value: any): boolean { + return this._entries && this._entries.filter(o => o.value === value).length > 0 + } + + private valueValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const valid = this.existsEntryByValue(control.value) + return valid ? null : {invalidValue: {value: control.value}} + } + } } @Component({ @@ -148,25 +275,27 @@ export class CreComboBoxComponent { encapsulation: ViewEncapsulation.None }) export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy { - @Input() options: Observable + @Input() entries: Observable @ContentChild(TemplateRef) errors: TemplateRef @ViewChild('chipInput') chipInput: ElementRef @ViewChild('auto') matAutocomplete: MatAutocomplete - filteredOptions: Observable + filteredEntries: Observable - private _options: ComboBoxEntry[] + private _entries: CreInputEntry[] private _destroy$ = new Subject() ngOnInit() { super.ngOnInit() - this.options.pipe(takeUntil(this._destroy$)) - .subscribe({next: options => this._options = options}) + this.entries.pipe(takeUntil(this._destroy$)) + .subscribe({ + next: entries => this._entries = entries + }) - this.filteredOptions = this.inputControl.valueChanges.pipe( - map((query: string | null) => query ? this._filter(query) : this._options.slice()) + this.filteredEntries = this.inputControl.valueChanges.pipe( + map((query: string | null) => query ? this._filter(query) : this._entries.slice()) ) } @@ -185,13 +314,13 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O return this.selectedValues.length <= 0 } - private _filter(query: string): ComboBoxEntry[] { + private _filter(query: string): CreInputEntry[] { const filterValue = query.toString().toLowerCase() - return this._options.filter(option => option.value.toString().toLowerCase().indexOf(filterValue) === 0) + return this._entries.filter(e => e.value.toString().toLowerCase().indexOf(filterValue) === 0) } private findValueByKey(key: any): any { - return this._options.filter(o => o.key === key)[0].value + return this._entries.filter(e => e.key === key)[0].value } } @@ -294,7 +423,45 @@ export class CrePeriodInputComponent implements OnInit { } } -export class ComboBoxEntry { +@Component({ + selector: 'cre-slider-input', + templateUrl: 'slider.html' +}) +export class CreSliderInputComponent { + @Input() control: FormControl + @Input() label: string + @Input() min: number + @Input() max: number + @Input() step = 1 + @Input() percents = false + @Input() thumbLabel = true + + formatValueForDisplay(value: number): string { + return this.percents ? `${value}%` : value.toString() + } +} + +@Component({ + selector: 'cre-select', + templateUrl: 'select.html' +}) +export class CreSelectComponent extends _CreInputBase { + @Input() entries: CreInputEntry[] | Observable + + get entriesAreObservable(): boolean { + return isObservable(this.entries) + } + + get arrayEntries(): CreInputEntry[] { + return this.entries as CreInputEntry[] + } + + get observableEntries(): Observable { + return this.entries as Observable + } +} + +export class CreInputEntry { constructor( public key: any, public value: any, diff --git a/src/app/modules/shared/components/inputs/select.html b/src/app/modules/shared/components/inputs/select.html new file mode 100644 index 0000000..14a03ee --- /dev/null +++ b/src/app/modules/shared/components/inputs/select.html @@ -0,0 +1,16 @@ + + {{label}} + + + + {{entry.display || entry.value}} + + + + + {{entry.display || entry.value}} + + + + diff --git a/src/app/modules/shared/components/inputs/slider.html b/src/app/modules/shared/components/inputs/slider.html new file mode 100644 index 0000000..1873422 --- /dev/null +++ b/src/app/modules/shared/components/inputs/slider.html @@ -0,0 +1,13 @@ +
+

{{label}}

+ + +
diff --git a/src/app/modules/shared/components/inputs/textarea.html b/src/app/modules/shared/components/inputs/textarea.html new file mode 100644 index 0000000..18653c0 --- /dev/null +++ b/src/app/modules/shared/components/inputs/textarea.html @@ -0,0 +1,9 @@ + + {{label}} + + diff --git a/src/app/modules/shared/components/subscribing.component.ts b/src/app/modules/shared/components/subscribing.component.ts index a31dcd4..d003d0a 100644 --- a/src/app/modules/shared/components/subscribing.component.ts +++ b/src/app/modules/shared/components/subscribing.component.ts @@ -11,6 +11,8 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { protected subscribers$ = [] protected destroy$ = new Subject() + loading = false + protected constructor( protected errorService: ErrorService, protected activatedRoute: ActivatedRoute, @@ -74,12 +76,14 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { protected showLoadingWheel(shouldShowWheel) { if (shouldShowWheel) { + this.loading = true globalLoadingWheel.show() } } protected hideLoadingWheel(shouldShowWheel) { if (shouldShowWheel) { + this.loading = false globalLoadingWheel.hide() } } diff --git a/src/app/modules/shared/components/tables/position-buttons.html b/src/app/modules/shared/components/tables/position-buttons.html new file mode 100644 index 0000000..f953d7b --- /dev/null +++ b/src/app/modules/shared/components/tables/position-buttons.html @@ -0,0 +1,17 @@ + + + + diff --git a/src/app/modules/shared/components/tables/table.html b/src/app/modules/shared/components/tables/table.html index 46560ec..9c9a748 100644 --- a/src/app/modules/shared/components/tables/table.html +++ b/src/app/modules/shared/components/tables/table.html @@ -1,4 +1,6 @@ - +
diff --git a/src/app/modules/shared/components/tables/tables.module.ts b/src/app/modules/shared/components/tables/tables.module.ts index 2e9576d..2e5ebc7 100644 --- a/src/app/modules/shared/components/tables/tables.module.ts +++ b/src/app/modules/shared/components/tables/tables.module.ts @@ -1,20 +1,28 @@ import {NgModule} from '@angular/core' import {MatTableModule} from '@angular/material/table' import {CommonModule} from '@angular/common' -import {CreInteractiveCell, CreTable} from './tables' +import {CreInteractiveCell, CrePositionButtons, CreTable} from './tables' +import {MatButtonModule} from "@angular/material/button"; +import {MatIconModule} from "@angular/material/icon"; +import {MatSortModule} from '@angular/material/sort' @NgModule({ declarations: [ CreTable, - CreInteractiveCell - ], - imports: [ - MatTableModule, - CommonModule + CreInteractiveCell, + CrePositionButtons ], + imports: [ + MatTableModule, + CommonModule, + MatButtonModule, + MatIconModule, + MatSortModule + ], exports: [ CreTable, CreInteractiveCell, + CrePositionButtons ] }) export class CreTablesModule { diff --git a/src/app/modules/shared/components/tables/tables.ts b/src/app/modules/shared/components/tables/tables.ts index 59cec5c..a7e7d5d 100644 --- a/src/app/modules/shared/components/tables/tables.ts +++ b/src/app/modules/shared/components/tables/tables.ts @@ -3,13 +3,15 @@ import { Component, ContentChildren, Directive, + EventEmitter, HostBinding, Input, + Output, QueryList, ViewChild, ViewEncapsulation } from '@angular/core' -import {MatColumnDef, MatHeaderRowDef, MatRowDef, MatTable} from '@angular/material/table' +import {MatColumnDef, MatHeaderRowDef, MatRowDef, MatTable, MatTableDataSource} from '@angular/material/table' @Directive({ selector: '[creInteractiveCell]' @@ -57,17 +59,38 @@ export class CreTable implements AfterContentInit { @ViewChild(MatTable, {static: true}) table: MatTable @Input() columns: string[] - @Input() dataSource: T[] @Input() interactive = true + @Input() filterPredicate: (t: T, filter: string) => boolean = () => true + @Input() sortingDataAccessor: (t: T, header: string) => string | number + + @Input() set filter(filter: string) { + if (this.dataSource) { + this.dataSource.filter = filter + } + } + + @Input() set data(data: T[]) { + this.setupDataSource(data) + } selectedIndex = 0 + dataSource: MatTableDataSource + ngAfterContentInit(): void { this.columnDefs.forEach(columnDef => this.table.addColumnDef(columnDef)) this.rowDefs.forEach(rowDef => this.table.addRowDef(rowDef)) this.headerRowDefs.forEach(headerRowDef => this.table.addHeaderRowDef(headerRowDef)) } + private setupDataSource(data: T[]) { + this.dataSource = new MatTableDataSource(data) + + if (this.filterPredicate) { + this.dataSource.filterPredicate = (t, filter) => this.filterPredicate(t, filter) + } + } + onRowHover(index: number) { if (this.interactive) { this.interactiveCells.forEach(cell => cell.hoverIndex = index) @@ -80,4 +103,33 @@ export class CreTable implements AfterContentInit { this.interactiveCells.forEach(cell => cell.selectedIndex = index) } } + + renderRows() { + this.table.renderRows() + } +} + +@Component({ + selector: 'cre-table-position-buttons', + templateUrl: 'position-buttons.html' +}) +export class CrePositionButtons { + @Input() position = 0 + @Input() min = 0 + @Input() max: number + @Input() hidden = false + @Input() disableDecreaseButton = false + @Input() disableIncreaseButton = false + + @Output() positionChange = new EventEmitter() + + increasePosition() { + this.position += 1 + this.positionChange.emit(this.position) + } + + decreasePosition() { + this.position -= 1 + this.positionChange.emit(this.position) + } } diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts index 02e4923..70011e1 100644 --- a/src/app/modules/shared/model/config.model.ts +++ b/src/app/modules/shared/model/config.model.ts @@ -3,8 +3,8 @@ import {filterMap} from '../utils/map.utils' export class Config { static readonly INSTANCE_NAME = 'instance.name' - static readonly INSTANCE_LOGO_PATH = 'instance.logo.path' - static readonly INSTANCE_ICON_PATH = 'instance.icon.path' + static readonly INSTANCE_LOGO_SET = 'instance.logo.set' + static readonly INSTANCE_ICON_SET = 'instance.icon.set' static readonly INSTANCE_URL = 'instance.url' static readonly DATABASE_URL = 'database.url' static readonly DATABASE_USER = 'database.user' @@ -20,8 +20,8 @@ export class Config { static readonly OPERATING_SYSTEM = 'env.os' static readonly IMAGE_CONFIG_KEYS = [ - Config.INSTANCE_LOGO_PATH, - Config.INSTANCE_ICON_PATH + Config.INSTANCE_LOGO_SET, + Config.INSTANCE_ICON_SET ] static readonly PASSWORD_CONFIG_KEYS = [ diff --git a/src/app/modules/shared/model/material.model.ts b/src/app/modules/shared/model/material.model.ts index 56187fd..681e1f6 100644 --- a/src/app/modules/shared/model/material.model.ts +++ b/src/app/modules/shared/model/material.model.ts @@ -1,4 +1,4 @@ -import {MaterialType} from "./materialtype.model"; +import {MaterialType} from './materialtype.model' import {openPdf} from '../utils/utils' export class Material { @@ -15,3 +15,38 @@ export class Material { export function openSimdut(material: Material) { openPdf(material.simdutUrl) } + +export const materialComparator = (a: Material, b: Material): number => { + const aPrefixName = a.materialType.prefix.toLowerCase() + const bPrefixName = b.materialType.prefix.toLowerCase() + + if (aPrefixName < bPrefixName) { + return -1 + } else if (aPrefixName > bPrefixName) { + return 1 + } else { + const aName = a.name.toLowerCase() + const bName = b.name.toLowerCase() + + if (aName < bName) { + return -1 + } else if (aName > bName) { + return 1 + } else { + return 0 + } + } +} + +// Uses private use UTF-8 char to separate the two fields, change if a better method is found +export const materialFilterFieldSeparator = '􀃿' + +export function materialMatchesFilter(material: Material, filter: string): boolean { + const [materialTypeFilter, materialNameFilter, hideLowQuantity, lowQuantityThreshold] = filter.split(materialFilterFieldSeparator) + const materialTypeId = parseInt(materialTypeFilter) + const matchesMaterialType = materialTypeId === 1 || materialTypeId == material.materialType.id + const matchesMaterialName = !materialNameFilter || material.name.toLowerCase().includes(materialNameFilter.toLowerCase()) + const matchesLowQuantity = material.inventoryQuantity < parseInt(lowQuantityThreshold) + + return matchesMaterialType && matchesMaterialName && (hideLowQuantity === 'false' || matchesLowQuantity) +} diff --git a/src/app/modules/shared/model/recipe.model.ts b/src/app/modules/shared/model/recipe.model.ts index 2fb9e52..7134171 100644 --- a/src/app/modules/shared/model/recipe.model.ts +++ b/src/app/modules/shared/model/recipe.model.ts @@ -1,25 +1,22 @@ import {Material} from './material.model' -import {LocalDate} from 'js-joda' import {Company} from './company.model' import {Group} from './user' +import {UNIT_MILLILITER} from "../units"; export class Recipe { - constructor( - public id: number, - public name: string, - public description: string, - public color: string, - public gloss: number, - public sample: number, - public approbationDate: string, - public approbationExpired: boolean, - public remark: string, - public company: Company, - public mixes: Mix[], - public groupsInformation: RecipeGroupInformation[], - public imagesUrls: string[] - ) { - } + public id: number + public name: string + public description: string + public color: string + public gloss: number + public sample: number + public approbationDate: string + public remark: string + public company: Company + public mixes: Mix[] + public approbationExpired: boolean + public groupsInformation: RecipeGroupInformation[] + public imagesUrls: string[] } export class RecipeGroupInformation { @@ -57,7 +54,8 @@ export class MixMaterialDto { public materialId: number, public quantity: number, public isPercents: boolean, - public position: number + public position: number, + public units: string ) { } } @@ -102,12 +100,13 @@ export function sortRecipeSteps(steps: RecipeStep[]): RecipeStep[] { return steps.sort((a, b) => a.position - b.position) } -export function mixMaterialsAsMixMaterialsDto(mix: Mix): MixMaterialDto[] { +export function mixMaterialsToMixMaterialsDto(mix: Mix): MixMaterialDto[] { return sortMixMaterialsDto(mix.mixMaterials.map(m => new MixMaterialDto( m.material.id, m.quantity, m.material.materialType.usePercentages, - m.position + m.position, + UNIT_MILLILITER ))) } @@ -125,3 +124,8 @@ export function getRecipeLuma(recipe: Recipe): number { return 0.2126 * r + 0.7152 * g + 0.0722 * b // per ITU-R BT.709 } + +export function recipeMatchesFilter(recipe: Recipe, filter: string): boolean { + const recipeStr = recipe.company.name + recipe.name + recipe.description + recipe.sample + return recipeStr.toLowerCase().indexOf(filter.toLowerCase()) >= 0 +} diff --git a/src/app/modules/shared/service/config.service.ts b/src/app/modules/shared/service/config.service.ts index dcfd95d..546ce89 100644 --- a/src/app/modules/shared/service/config.service.ts +++ b/src/app/modules/shared/service/config.service.ts @@ -39,11 +39,12 @@ export class ConfigService { } setImage(key: string, image: File): Observable { - const body = new FormData() - body.append('key', key) - body.append('image', image) + const path = key == Config.INSTANCE_ICON_SET ? 'icon' : 'logo'; - return this.api.put('/config/image', body) + const body = new FormData() + body.append(path, image) + + return this.api.put(`/config/${path}`, body) } restart(): Observable { diff --git a/src/app/modules/shared/shared.module.ts b/src/app/modules/shared/shared.module.ts index 05aaf24..be216ae 100644 --- a/src/app/modules/shared/shared.module.ts +++ b/src/app/modules/shared/shared.module.ts @@ -37,6 +37,8 @@ import {CreFormsModule} from './components/forms/forms.module' import {VarDirective} from './directives/var.directive' import {CreColorPreview} from './components/color-preview/color-preview' import {CreDialogsModule} from './components/dialogs/dialogs.module' +import {CreAlertsModule} from './components/alerts/alerts.module'; +import {CreActionBarModule} from './components/action-bar/action-bar.module' @NgModule({ declarations: [VarDirective, HeaderComponent, UserMenuComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent, LoadingWheelComponent, CreColorPreview], @@ -73,7 +75,8 @@ import {CreDialogsModule} from './components/dialogs/dialogs.module' CreFormsModule, VarDirective, CreColorPreview, - CreDialogsModule + CreDialogsModule, + CreAlertsModule ], imports: [ MatTabsModule, diff --git a/src/app/modules/shared/units.ts b/src/app/modules/shared/units.ts index 403f136..adbb475 100644 --- a/src/app/modules/shared/units.ts +++ b/src/app/modules/shared/units.ts @@ -25,8 +25,8 @@ export const UNIT_RATIOS = { } } -export function convertMixMaterialQuantity(computedQuantity: MixMaterialDto, from: string, to: string): number { - return !computedQuantity.isPercents ? convertQuantity(computedQuantity.quantity, from, to) : computedQuantity.quantity +export function convertMixMaterialQuantity(mixMaterial: MixMaterialDto, to: string): number { + return !mixMaterial.isPercents ? convertQuantity(mixMaterial.quantity, mixMaterial.units, to) : mixMaterial.quantity } export function convertQuantity(quantity: number, from: string, to: string): number { diff --git a/src/app/modules/shared/utils/map.utils.ts b/src/app/modules/shared/utils/map.utils.ts index ba99aec..dd43b65 100644 --- a/src/app/modules/shared/utils/map.utils.ts +++ b/src/app/modules/shared/utils/map.utils.ts @@ -1,3 +1,7 @@ +export function anyMap(map: Map, predicate: (key: K, value: V) => boolean): boolean { + return filterMap(map, predicate).size > 0 +} + export function filterMap(map: Map, predicate: (key: K, value: V) => boolean): Map { const filteredMap = new Map() map.forEach((value, key) => { diff --git a/src/app/modules/shared/utils/utils.ts b/src/app/modules/shared/utils/utils.ts index bd54ea9..dfa6bf9 100644 --- a/src/app/modules/shared/utils/utils.ts +++ b/src/app/modules/shared/utils/utils.ts @@ -1,5 +1,5 @@ /** Returns [value] if it is not null or [or]. */ -import {DateTimeFormatter, LocalDate, LocalDateTime} from 'js-joda' +import {DateTimeFormatter, LocalDate, LocalDateTime} from '@js-joda/core' import {TouchUpKit} from '../model/touch-up-kit.model' import {environment} from '../../../../environments/environment' @@ -60,3 +60,12 @@ export function readFile(file: File, consumer: (any) => void) { export function getFileUrl(path: string) { return `${environment.apiUrl}/file?path=${encodeURIComponent(path)}` } + +export function getConfiguredImageUrl(path: string) { + return `${environment.apiUrl}/config/${path}` +} + +export function round(n: number, digits: number): number { + const power = Math.pow(10, digits) + return Math.round(n * power) / power +} diff --git a/src/app/modules/touch-up-kit/components/finish.sass b/src/app/modules/touch-up-kit/components/finish.sass index 30c9991..7713c99 100644 --- a/src/app/modules/touch-up-kit/components/finish.sass +++ b/src/app/modules/touch-up-kit/components/finish.sass @@ -1,4 +1,4 @@ -@import '../../../../custom-theme' +@import '~src/variables' .touchupkit-finish-container display: inline-block diff --git a/src/app/modules/touch-up-kit/components/finish.ts b/src/app/modules/touch-up-kit/components/finish.ts index d91f01b..09b2fd7 100644 --- a/src/app/modules/touch-up-kit/components/finish.ts +++ b/src/app/modules/touch-up-kit/components/finish.ts @@ -1,6 +1,6 @@ import {Component, Input} from '@angular/core' import {SubscribingComponent} from '../../shared/components/subscribing.component' -import {RecipeService} from '../../colors/services/recipe.service' +import {RecipeService} from '../../recipes/services/recipe.service' import {ErrorService} from '../../shared/service/error.service' import {ActivatedRoute, Router} from '@angular/router' import {Recipe} from '../../shared/model/recipe.model' diff --git a/src/app/modules/touch-up-kit/components/form.html b/src/app/modules/touch-up-kit/components/form.html index 999b820..a6dd9eb 100644 --- a/src/app/modules/touch-up-kit/components/form.html +++ b/src/app/modules/touch-up-kit/components/form.html @@ -5,7 +5,7 @@ @@ -18,7 +18,7 @@ diff --git a/src/app/modules/touch-up-kit/components/form.ts b/src/app/modules/touch-up-kit/components/form.ts index 0a77b7f..5fa1a89 100644 --- a/src/app/modules/touch-up-kit/components/form.ts +++ b/src/app/modules/touch-up-kit/components/form.ts @@ -1,9 +1,9 @@ import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core' -import {chipListRequired, ComboBoxEntry, CreChipComboBoxComponent} from '../../shared/components/inputs/inputs' -import {CreFormComponent} from '../../shared/components/forms/forms' +import {chipListRequired, CreInputEntry, CreChipComboBoxComponent} from '../../shared/components/inputs/inputs' +import {CreForm} from '../../shared/components/forms/forms' import {TouchUpKitProductEditor} from './product-editor' import {FormControl, Validators} from '@angular/forms' -import {RecipeService} from '../../colors/services/recipe.service' +import {RecipeService} from '../../recipes/services/recipe.service' import {CompanyService} from '../../company/service/company.service' import {ErrorService} from '../../shared/service/error.service' import {ActivatedRoute, Router} from '@angular/router' @@ -18,14 +18,14 @@ import {map} from 'rxjs/operators' export class TouchUpKitForm extends SubscribingComponent { @ViewChild('finishInput') finishInput: CreChipComboBoxComponent @ViewChild('materialInput') materialInput: CreChipComboBoxComponent - @ViewChild(CreFormComponent) form: CreFormComponent + @ViewChild(CreForm) form: CreForm @ViewChild(TouchUpKitProductEditor) contentEditor: TouchUpKitProductEditor @Input() touchUpKit: TouchUpKit | null controls: any finish$ = this.recipeService.all.pipe( - map(recipes => recipes.map(recipe => new ComboBoxEntry(recipe.id, recipe.name, `${recipe.name} - ${recipe.company.name}`))) + map(recipes => recipes.map(recipe => new CreInputEntry(recipe.id, recipe.name, `${recipe.name} - ${recipe.company.name}`))) ) companies$ = this.companyService.all.pipe( map(companies => companies.map(company => company.name)) diff --git a/src/app/modules/touch-up-kit/pages/details.html b/src/app/modules/touch-up-kit/pages/details.html index bb5b358..93ebf4c 100644 --- a/src/app/modules/touch-up-kit/pages/details.html +++ b/src/app/modules/touch-up-kit/pages/details.html @@ -14,7 +14,7 @@ - + diff --git a/src/app/modules/touch-up-kit/pages/list.html b/src/app/modules/touch-up-kit/pages/list.html index 1cb372b..877aa6d 100644 --- a/src/app/modules/touch-up-kit/pages/list.html +++ b/src/app/modules/touch-up-kit/pages/list.html @@ -5,7 +5,7 @@ - + @@ -57,7 +57,7 @@ Kits de retouche complétés - + diff --git a/src/app/modules/touch-up-kit/pages/touchupkit.ts b/src/app/modules/touch-up-kit/pages/touchupkit.ts index 57b3ed5..2cdf146 100644 --- a/src/app/modules/touch-up-kit/pages/touchupkit.ts +++ b/src/app/modules/touch-up-kit/pages/touchupkit.ts @@ -7,10 +7,10 @@ import {AccountService} from '../../accounts/services/account.service' import {ErrorService} from '../../shared/service/error.service' import {ActivatedRoute, Router} from '@angular/router' import {Permission} from '../../shared/model/user' -import {RecipeService} from '../../colors/services/recipe.service' +import {RecipeService} from '../../recipes/services/recipe.service' import {AppState} from '../../shared/app-state' import {map} from 'rxjs/operators' -import {LocalDate, Period} from 'js-joda' +import {LocalDate, Period} from '@js-joda/core' import {ConfigService} from '../../shared/service/config.service' import {Config} from '../../shared/model/config.model' diff --git a/src/custom-theme.scss b/src/custom-theme.scss index 5f766ef..cc4d450 100644 --- a/src/custom-theme.scss +++ b/src/custom-theme.scss @@ -1,21 +1,21 @@ // Custom Theming for Angular Material +@use '@angular/material' as mat; // For more information: https://material.angular.io/guide/theming -@import '~@angular/material/theming'; // Plus imports for other components in your app. -$custom-typography: mat-typography-config( +$custom-typography: mat.define-typography-config( $font-family: "Open Sans" ); // Include the common styles for Angular Material. We include this here so that you only // have to load a single css file for Angular Material in your app. // Be sure that you only ever include this mixin once! -@include mat-core($custom-typography); +@include mat.core($custom-typography); // Define the palettes for your theme using the Material Design palettes available in palette.sass // (imported above). For each palette, you can optionally specify a default, lighter, and darker // hue. Available color palettes: https://material.io/design/color/ -$theme-primary: mat-palette(( +$theme-primary: mat.define-palette(( 50 : #e0e0e0, 100 : #b3b3b3, 200 : #808080, @@ -46,7 +46,7 @@ $theme-primary: mat-palette(( A400 : #ffffff, A700 : #ffffff, ))); -$theme-accent: mat-palette(( +$theme-accent: mat.define-palette(( 50 : #edf9e0, 100 : #d1f0b3, 200 : #b3e680, @@ -78,7 +78,7 @@ $theme-accent: mat-palette(( A700 : #000000, ) )); -$theme-warning: mat-palette(( +$theme-warning: mat.define-palette(( 50 : #fff8e4, 100 : #feefbd, 200 : #fee491, @@ -112,15 +112,15 @@ $theme-warning: mat-palette(( )); // The warn palette is optional (defaults to red). -$theme-error: mat-palette($mat-red); +$theme-error: mat.define-palette(mat.$red-palette); // Create the theme object (a Sass map containing all of the palettes). -$color-recipes-explorer-frontend-theme: mat-light-theme($theme-primary, $theme-accent, $theme-error); +$color-recipes-explorer-frontend-theme: mat.define-light-theme($theme-primary, $theme-accent, $theme-error); // Include theme styles for core and each component used in your app. // Alternatively, you can import and @include the theme mixins for each component // that you are using. -@include angular-material-theme($color-recipes-explorer-frontend-theme); +@include mat.all-component-themes($color-recipes-explorer-frontend-theme); html, body { diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 07d5091..8a40323 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -14,4 +14,4 @@ export const environment = { * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/src/polyfills.ts b/src/polyfills.ts index 03711e5..dcd18ea 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -18,16 +18,6 @@ * BROWSER POLYFILLS */ -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags @@ -55,7 +45,7 @@ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'zone.js'; // Included with Angular CLI. /*************************************************************************************************** diff --git a/src/test.ts b/src/test.ts index 50193eb..4bf4afb 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,6 +1,6 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files -import 'zone.js/dist/zone-testing'; +import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, @@ -17,7 +17,9 @@ declare const require: { // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, - platformBrowserDynamicTesting() + platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false } +} ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/);
Nom {{product.name}} Project {{touchUpKit.project}} Project {{touchUpKit.project}}