+
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/buttons/buttons.sass b/src/app/modules/shared/components/buttons/buttons.sass
new file mode 100644
index 0000000..3c36340
--- /dev/null
+++ b/src/app/modules/shared/components/buttons/buttons.sass
@@ -0,0 +1,6 @@
+cre-button, cre-primary-button, cre-accent-button, cre-warn-button
+ display: inline-block
+ width: inherit
+
+ button
+ width: 100%
diff --git a/src/app/modules/shared/components/buttons/buttons.ts b/src/app/modules/shared/components/buttons/buttons.ts
index 7b3679e..80ab652 100644
--- a/src/app/modules/shared/components/buttons/buttons.ts
+++ b/src/app/modules/shared/components/buttons/buttons.ts
@@ -1,51 +1,60 @@
-import {Component, Input} from '@angular/core'
+import {Component, Input, ViewEncapsulation} from '@angular/core'
import {ThemePalette} from '@angular/material/core'
@Component({
selector: 'cre-button',
template: `
-
+
- `
+ `,
+ styleUrls: ['buttons.sass'],
+ encapsulation: ViewEncapsulation.None
})
export class CreButtonComponent {
@Input() color: ThemePalette
+ @Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-primary-button',
template: `
-
+
- `
+ `,
+ styleUrls: ['buttons.sass']
})
export class CrePrimaryButtonComponent {
+ @Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-accent-button',
template: `
-
+
- `
+ `,
+ styleUrls: ['buttons.sass']
})
export class CreAccentButtonComponent {
+ @Input() type = 'button'
@Input() disabled = false
}
@Component({
selector: 'cre-warn-button',
template: `
-
+
- `
+ `,
+ styleUrls: ['buttons.sass']
})
export class CreWarnButtonComponent {
+ @Input() type = 'button'
@Input() disabled = false
}
diff --git a/src/app/modules/shared/components/dialogs/dialogs.module.ts b/src/app/modules/shared/components/dialogs/dialogs.module.ts
new file mode 100644
index 0000000..ac98cdc
--- /dev/null
+++ b/src/app/modules/shared/components/dialogs/dialogs.module.ts
@@ -0,0 +1,21 @@
+import {NgModule} from '@angular/core'
+import {CreDialogBody, CrePromptDialog} from './dialogs'
+import {MatDialogModule} from '@angular/material/dialog'
+import {CreButtonsModule} from '../buttons/buttons.module'
+
+@NgModule({
+ declarations: [
+ CrePromptDialog,
+ CreDialogBody
+ ],
+ exports: [
+ CrePromptDialog,
+ CreDialogBody
+ ],
+ imports: [
+ MatDialogModule,
+ CreButtonsModule
+ ]
+})
+export class CreDialogsModule {
+}
diff --git a/src/app/modules/shared/components/dialogs/dialogs.scss b/src/app/modules/shared/components/dialogs/dialogs.scss
new file mode 100644
index 0000000..3653e6c
--- /dev/null
+++ b/src/app/modules/shared/components/dialogs/dialogs.scss
@@ -0,0 +1,26 @@
+@import "~src/variables";
+
+.cre-dialog-panel {
+ min-width: 20rem;
+
+ mat-dialog-container {
+ padding: 0;
+
+ .mat-dialog-title, .mat-dialog-content, .mat-dialog-actions {
+ margin: 0;
+ padding: $spacer;
+ }
+
+ .mat-dialog-title {
+ background-color: $color-primary;
+ color: $text-color-primary;
+ }
+
+ .mat-dialog-actions {
+ min-height: auto;
+ justify-content: end;
+ gap: map-get($spacers, 1);
+ padding-top: 0;
+ }
+ }
+}
diff --git a/src/app/modules/shared/components/dialogs/dialogs.ts b/src/app/modules/shared/components/dialogs/dialogs.ts
new file mode 100644
index 0000000..a06f602
--- /dev/null
+++ b/src/app/modules/shared/components/dialogs/dialogs.ts
@@ -0,0 +1,74 @@
+import {Component, Directive, EventEmitter, Input, Output, TemplateRef, ViewChild, ViewEncapsulation} from '@angular/core'
+import {MatDialog, MatDialogRef} from '@angular/material/dialog'
+
+@Directive({
+ selector: 'cre-dialog-body'
+})
+export class CreDialogBody {
+}
+
+@Directive()
+abstract class CreDialog {
+ @ViewChild(TemplateRef) dialogTemplate: TemplateRef
+
+ @Output() cancel = new EventEmitter();
+ @Output() continue = new EventEmitter();
+
+ private dialogRef: MatDialogRef> | null
+
+ constructor(
+ protected dialog: MatDialog
+ ) {
+ }
+
+ protected abstract get data(): D
+
+ show() {
+ this.open()
+ }
+
+ onCancel() {
+ this.close()
+ this.cancel.emit();
+ }
+
+ onContinue() {
+ this.close()
+ this.continue.emit();
+ }
+
+ private open() {
+ const config = {
+ panelClass: 'cre-dialog-panel',
+ data: this.data
+ }
+ this.dialogRef = this.dialog.open(this.dialogTemplate, config)
+ }
+
+ private close() {
+ this.dialogRef.close()
+ }
+}
+
+@Component({
+ selector: 'cre-prompt-dialog',
+ templateUrl: 'prompt.html',
+ styleUrls: ['dialogs.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class CrePromptDialog extends CreDialog {
+ @Input() title: string
+
+ protected get data(): CrePromptDialogData {
+ return {
+ title: this.title
+ }
+ }
+}
+
+abstract class CreDialogData {
+ title: string
+}
+
+class CrePromptDialogData extends CreDialogData {
+}
diff --git a/src/app/modules/shared/components/dialogs/prompt.html b/src/app/modules/shared/components/dialogs/prompt.html
new file mode 100644
index 0000000..0d14939
--- /dev/null
+++ b/src/app/modules/shared/components/dialogs/prompt.html
@@ -0,0 +1,10 @@
+
+ {{data.title}}
+
+
+
+
+ Annuler
+ Continuer
+
+
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..ed1da16
--- /dev/null
+++ b/src/app/modules/shared/components/forms/buttons.ts
@@ -0,0 +1,18 @@
+import {Component, ContentChild, EventEmitter, Input, Output} from '@angular/core'
+import {ICreForm} from './forms';
+
+@Component({
+ selector: 'cre-form-submit-button',
+ templateUrl: 'submit-button.html'
+})
+export class CreSubmitButton {
+ @Input() form: ICreForm
+ @Input() valid: boolean | null
+ @Input() text = 'Enregistrer'
+
+ @Output() submit = new EventEmitter()
+
+ get disableButton(): boolean {
+ return !this.form || !(this.valid ?? this.form.valid)
+ }
+}
diff --git a/src/app/modules/shared/components/forms/form.html b/src/app/modules/shared/components/forms/form.html
index 1d6e89b..4b966b8 100644
--- a/src/app/modules/shared/components/forms/form.html
+++ b/src/app/modules/shared/components/forms/form.html
@@ -5,7 +5,7 @@
-
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..ae2f5d3 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 {
+ formGroup: 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,11 +31,11 @@ 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 }
- form: FormGroup
+ formGroup: FormGroup
constructor(
private formBuilder: FormBuilder
@@ -38,14 +43,18 @@ export class CreFormComponent implements OnInit {
}
ngOnInit(): void {
- this.form = this.formBuilder.group(this.formControls)
+ this.formGroup = this.formBuilder.group(this.formControls)
}
get hasActions(): boolean {
- return this.formActions === true
+ return !!this.formActions
+ }
+
+ get valid(): boolean {
+ return this.formGroup && this.formGroup.valid
}
get invalid(): boolean {
- return !this.form || this.form.invalid
+ return !this.formGroup || this.formGroup.invalid
}
}
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..3d00a97
--- /dev/null
+++ b/src/app/modules/shared/components/forms/submit-button.html
@@ -0,0 +1 @@
+{{text}}
diff --git a/src/app/modules/shared/components/header/header.component.ts b/src/app/modules/shared/components/header/header.component.ts
index 94f4563..abe4a17 100644
--- a/src/app/modules/shared/components/header/header.component.ts
+++ b/src/app/modules/shared/components/header/header.component.ts
@@ -58,20 +58,21 @@ export class HeaderComponent extends SubscribingComponent {
}
ngOnDestroy(): void {
- this.accountService.logout(() => {
- console.log('Successfully logged out')
- })
+ this.subscribe(
+ this.accountService.logout(),
+ () => console.info('Successfully logged out')
+ )
super.ngOnDestroy()
}
get logoUrl(): string {
- return environment.apiUrl + "/file?path=images%2Flogo&mediaType=image/png"
+ return environment.apiUrl + "/config/logo"
}
set activeLink(link: string) {
this._activeLink = link
- this.router.navigate([link])
+ this.urlUtils.navigateTo(link)
}
get activeLink() {
diff --git a/src/app/modules/shared/components/info-banner/info-banner.component.sass b/src/app/modules/shared/components/info-banner/info-banner.component.sass
index 558af2c..31c5568 100644
--- a/src/app/modules/shared/components/info-banner/info-banner.component.sass
+++ b/src/app/modules/shared/components/info-banner/info-banner.component.sass
@@ -1,4 +1,4 @@
-@import "~src/custom-theme"
+@import "~src/variables"
.info-banner-wrapper
background-color: $color-primary
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 d123b75..f58c818 100644
--- a/src/app/modules/shared/components/inputs/input.html
+++ b/src/app/modules/shared/components/inputs/input.html
@@ -1,25 +1,23 @@
{{label}}
-
-
+
+
+
+
+
+
+
+
()
+ @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.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({
@@ -51,11 +91,11 @@ export class CreInputComponent {
encapsulation: ViewEncapsulation.None
})
export class CreAutocompleteInputComponent {
- @Input() control: FormControl | null
+ @Input() control: AbstractControl | null
@Input() label: string
@Input() icon: string
@Input() required = true
- @Input() options: Observable
+ @Input() entries: Observable
@Input() value
@Output() valueChange = new EventEmitter()
@@ -69,7 +109,7 @@ export class CreAutocompleteInputComponent {
encapsulation: ViewEncapsulation.None
})
export class CreChipInputComponent implements OnInit {
- @Input() control: FormControl
+ @Input() control: AbstractControl
@Input() label: string
@Input() icon: string
@Input() required = true
@@ -118,13 +158,116 @@ export class CreChipInputComponent implements OnInit {
encapsulation: ViewEncapsulation.None
})
export class CreComboBoxComponent {
- @Input() control: FormControl
+ @Input() control: AbstractControl
@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({
@@ -133,25 +276,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())
)
}
@@ -170,13 +315,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
}
}
@@ -184,12 +329,16 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O
selector: 'cre-checkbox-input',
templateUrl: 'checkbox.html'
})
-export class CreCheckboxInputComponent {
+export class CreCheckboxInputComponent implements OnInit {
@Input() label: string
- @Input() control: FormControl
+ @Input() control: AbstractControl
@Input() checked: boolean
@Output() checkedChange = new EventEmitter()
+
+ ngOnInit(): void {
+ this.control?.setValue(this.control.value === 'true')
+ }
}
@Component({
@@ -200,7 +349,7 @@ export class CreFileInputComponent implements OnInit {
@Input() label: string
@Input() icon: string
@Input() accept = ''
- @Input() control: FormControl | null
+ @Input() control: AbstractControl | null
@Output() selection = new EventEmitter()
@Output() invalidFormat = new EventEmitter()
@@ -234,7 +383,7 @@ export class CreFileInputComponent implements OnInit {
encapsulation: ViewEncapsulation.None
})
export class CrePeriodInputComponent implements OnInit {
- @Input() control: FormControl
+ @Input() control: AbstractControl
@Input() label: string
@Input() hint: string | null
@@ -264,15 +413,56 @@ export class CrePeriodInputComponent implements OnInit {
}
private setValuesFromPeriod(period: string) {
+ if (!period) {
+ return
+ }
+
const periodTypeChar = period.slice(-1)
period = period.slice(1, -1)
-
this.selectControl.setValue(periodTypeChar)
this.inputControl.setValue(period)
}
}
-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 @@
+
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 @@
+
+
+
+
+ = max"
+ (click)="increasePosition()">
+
+
+
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/table.sass b/src/app/modules/shared/components/tables/table.sass
index 4458b6e..9cbb67d 100644
--- a/src/app/modules/shared/components/tables/table.sass
+++ b/src/app/modules/shared/components/tables/table.sass
@@ -1,4 +1,4 @@
-@import "~src/custom-theme"
+@import "../../../../../custom-theme"
cre-table
display: block
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/components/user-info/user-menu.component.sass b/src/app/modules/shared/components/user-info/user-menu.component.sass
index d3ffcd7..329544c 100644
--- a/src/app/modules/shared/components/user-info/user-menu.component.sass
+++ b/src/app/modules/shared/components/user-info/user-menu.component.sass
@@ -1,4 +1,4 @@
-@import "../../../../../custom-theme"
+@import "~src/variables"
p, labeled-icon
margin: 0
diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts
index 642a736..70011e1 100644
--- a/src/app/modules/shared/model/config.model.ts
+++ b/src/app/modules/shared/model/config.model.ts
@@ -1,7 +1,10 @@
+import {AbstractControl, Form, FormControl, Validators} from '@angular/forms'
+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'
@@ -16,12 +19,63 @@ export class Config {
static readonly JAVA_VERSION = 'env.java.version'
static readonly OPERATING_SYSTEM = 'env.os'
- constructor(
- public key: string,
- public content: string,
- public lastUpdated: string,
- public requireRestart: boolean,
- public editable: boolean
- ) {
- }
+ static readonly IMAGE_CONFIG_KEYS = [
+ Config.INSTANCE_LOGO_SET,
+ Config.INSTANCE_ICON_SET
+ ]
+
+ static readonly PASSWORD_CONFIG_KEYS = [
+ Config.DATABASE_PASSWORD
+ ]
+
+ public key: string
+ public requireRestart: boolean
+ public editable: boolean
+ public content?: string
+ public lastUpdated?: string
+}
+
+export class ConfigKeyContent {
+ public key: string
+ public content: string
+}
+
+export class ConfigControl {
+ public config: Config
+ public control: AbstractControl
+}
+
+export function buildFormControl(config: Config): AbstractControl {
+ return new FormControl({value: config.content, disabled: !config.editable}, !configKeyIsPassword(config.key) ? Validators.required : null)
+}
+
+export function configKeyIsPassword(key: string): boolean {
+ return Config.PASSWORD_CONFIG_KEYS.indexOf(key) >= 0
+}
+
+export function filterConfigKeyControlMap(map: Map): Map {
+ return filterMap(map, (key, control) => {
+ return control.dirty &&
+ Config.IMAGE_CONFIG_KEYS.indexOf(key) < 0 && // Filter image configs because they are sent to a different endpoint
+ control.value !== undefined &&
+ control.value !== null
+ })
+}
+
+export function filterImageConfigKeyControlMap(map: Map): Map {
+ return filterMap(map, (key, control) => {
+ return Config.IMAGE_CONFIG_KEYS.indexOf(key) >= 0 && control.dirty
+ })
+}
+
+export function mapToConfigKeyContent(key: string, control: AbstractControl): ConfigKeyContent {
+ return {key, content: control.value}
+}
+
+export function mapToConfigKeyContentArray(map: Map): ConfigKeyContent[] {
+ const array: ConfigKeyContent[] = []
+ map.forEach((control, key) => {
+ array.push(mapToConfigKeyContent(key, control))
+ })
+ return array
}
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/alert.service.ts b/src/app/modules/shared/service/alert.service.ts
index fd32550..5a6a0a5 100644
--- a/src/app/modules/shared/service/alert.service.ts
+++ b/src/app/modules/shared/service/alert.service.ts
@@ -38,7 +38,7 @@ export class AlertService {
* An alert handler component is a component that will show the alerts pushed by the alert system to the user.
*/
@Directive()
-// tslint:disable-next-line:directive-class-suffix
+// eslint-disable-next-line @angular-eslint/directive-class-suffix
export abstract class AlertHandlerComponent implements OnDestroy {
protected static readonly DEFAULT_ALERT_BUFFER_SIZE = 3
protected static readonly DEFAULT_ALERT_DURATION = 5
diff --git a/src/app/modules/shared/service/api.service.ts b/src/app/modules/shared/service/api.service.ts
index 7c39b28..462531b 100644
--- a/src/app/modules/shared/service/api.service.ts
+++ b/src/app/modules/shared/service/api.service.ts
@@ -4,10 +4,9 @@ import {Observable, Subject} from 'rxjs'
import {environment} from '../../../../environments/environment'
import {AppState} from '../app-state'
import {Router} from '@angular/router'
-import {map, share, takeUntil, tap} from 'rxjs/operators'
+import {map, share, takeUntil} from 'rxjs/operators'
import {valueOr} from '../utils/utils'
import {ErrorService} from './error.service'
-import {globalLoadingWheel} from '../components/loading-wheel/loading-wheel.component'
@Injectable({
providedIn: 'root'
@@ -71,14 +70,13 @@ export class ApiService implements OnDestroy {
observe: 'response'
}
if (needAuthentication) {
- if (this.checkAuthenticated()) {
+ if (this.appState.hasCredentials) {
if (httpOptions) {
httpOptions.withCredentials = true
} else {
console.error('httpOptions need to be specified to use credentials in HTTP methods.')
}
} else {
- this.appState.resetAuthenticatedUser()
this.navigateToLogin()
}
}
@@ -90,11 +88,6 @@ export class ApiService implements OnDestroy {
.pipe(takeUntil(this._destroy$), map(r => r.body), share())
}
- private checkAuthenticated(): boolean {
- return (this.appState.isAuthenticated && Date.now() <= this.appState.authenticationExpiration) ||
- (this.appState.authenticatedUser && this.appState.authenticatedUser.group != null)
- }
-
private navigateToLogin() {
this.router.navigate(['/account/login'])
}
diff --git a/src/app/modules/shared/service/config.service.ts b/src/app/modules/shared/service/config.service.ts
index 880b697..546ce89 100644
--- a/src/app/modules/shared/service/config.service.ts
+++ b/src/app/modules/shared/service/config.service.ts
@@ -1,13 +1,9 @@
import {Injectable} from '@angular/core'
-import {Config} from '../model/config.model'
+import {Config, filterConfigKeyControlMap, filterImageConfigKeyControlMap, mapToConfigKeyContentArray} from '../model/config.model'
import {Observable} from 'rxjs'
import {ApiService} from './api.service'
-import {FormControl} from '@angular/forms'
-
-const imageConfigsKeys = [
- Config.INSTANCE_LOGO_PATH,
- Config.INSTANCE_ICON_PATH
-]
+import {AbstractControl, FormGroup} from '@angular/forms'
+import {transformMap} from '../utils/map.utils'
@Injectable({
providedIn: 'root'
@@ -18,44 +14,55 @@ export class ConfigService {
) {
}
+ get all(): Observable {
+ return this.api.get('/config')
+ }
+
get(key: string): Observable {
return this.api.get(`/config/${key}`)
}
- set(configs: Map): Observable {
- const body = []
- for (let key in configs) {
- const control = configs[key]
- if (control.dirty && key.indexOf('path') < 0) {
- body.push({key, content: control.value})
- }
+ setFromForm(form: FormGroup): Observable {
+ const map = new Map()
+ for (let key in form.controls) {
+ map.set(key, form.controls[key])
}
+ return this.set(map);
+ }
- const subscriptions = []
- imageConfigsKeys.forEach(key => {
- if (configs[key].dirty) {
- subscriptions.push(this.setImage(key, configs[key].value))
- }
- })
-
- while (subscriptions.length > 0) {
- const subscription = subscriptions.pop().subscribe({
- next: () => subscription.unsubscribe()
- })
- }
+ set(configs: Map): Observable {
+ const body = mapToConfigKeyContentArray(filterConfigKeyControlMap(configs))
+ const imageConfigs = filterImageConfigKeyControlMap(configs)
+ this.setImages(imageConfigs)
return this.api.put('/config', body)
}
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 {
return this.api.post('/config/restart')
}
+
+ private setImages(configs: Map) {
+ const subscriptions = this.getImageConfigsSubscriptions(configs)
+ while (subscriptions.length > 0) {
+ const subscription = subscriptions.pop().subscribe({
+ next: () => subscription.unsubscribe()
+ })
+ }
+ }
+
+ private getImageConfigsSubscriptions(configs: Map): Observable[] {
+ return transformMap(configs, (key, control) => {
+ return this.setImage(key, control.value)
+ })
+ }
}
diff --git a/src/app/modules/shared/shared.module.ts b/src/app/modules/shared/shared.module.ts
index 1d7d62e..be216ae 100644
--- a/src/app/modules/shared/shared.module.ts
+++ b/src/app/modules/shared/shared.module.ts
@@ -36,6 +36,9 @@ import {InfoBannerModule} from './components/info-banner/info-banner.module'
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],
@@ -71,7 +74,9 @@ import {CreColorPreview} from './components/color-preview/color-preview'
InfoBannerModule,
CreFormsModule,
VarDirective,
- CreColorPreview
+ CreColorPreview,
+ 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
new file mode 100644
index 0000000..dd43b65
--- /dev/null
+++ b/src/app/modules/shared/utils/map.utils.ts
@@ -0,0 +1,21 @@
+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) => {
+ if (predicate(key, value)) {
+ filteredMap.set(key, value)
+ }
+ })
+ return filteredMap
+}
+
+export function transformMap(map: Map, transform: (key: K, value: V) => T): T[] {
+ const transformedArray = []
+ map.forEach((value, key) => {
+ transformedArray.push(transform(key, value))
+ })
+ return transformedArray
+}
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 899e060..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 '~src/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 @@
-
+
Nom
{{product.name}}
diff --git a/src/app/modules/touch-up-kit/pages/list.html b/src/app/modules/touch-up-kit/pages/list.html
index 1cb372b..f5bacdf 100644
--- a/src/app/modules/touch-up-kit/pages/list.html
+++ b/src/app/modules/touch-up-kit/pages/list.html
@@ -1,11 +1,11 @@
- Ajouter
+ Ajouter
-
+
Project
{{touchUpKit.project}}
@@ -57,7 +57,7 @@
Kits de retouche complétés
-
+
Project
{{touchUpKit.project}}
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 5f9a53b..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,19 +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);
-
-$color-primary: map-get($theme-primary, 500);
-$color-accent: map-get($theme-accent, 500);
-$color-warn: map-get($theme-error, 500);
+@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/styles.sass b/src/styles.sass
index e026f75..c096222 100644
--- a/src/styles.sass
+++ b/src/styles.sass
@@ -1,6 +1,4 @@
-@import 'assets/sass/modules/_fonts.sass'
-@import "custom-theme"
-@import "~material-design-icons/iconfont/material-icons.css"
+@import "variables"
mat-card
padding: 0 !important
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$/);
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index a45c509..0000000
--- a/tslint.json
+++ /dev/null
@@ -1,153 +0,0 @@
-{
- "extends": "tslint:recommended",
- "rules": {
- "align": {
- "options": [
- "parameters",
- "statements"
- ]
- },
- "array-type": false,
- "arrow-parens": false,
- "arrow-return-shorthand": true,
- "deprecation": {
- "severity": "warning"
- },
- "component-class-suffix": true,
- "contextual-lifecycle": true,
- "curly": true,
- "directive-class-suffix": true,
- "directive-selector": [
- true,
- "attribute",
- "cre",
- "camelCase"
- ],
- "component-selector": [
- true,
- "element",
- "cre",
- "kebab-case"
- ],
- "eofline": true,
- "import-blacklist": [
- true,
- "rxjs/Rx"
- ],
- "import-spacing": true,
- "indent": {
- "options": [
- "spaces"
- ]
- },
- "interface-name": false,
- "max-classes-per-file": false,
- "max-line-length": [
- false,
- 140
- ],
- "member-access": false,
- "member-ordering": [
- true,
- {
- "order": [
- "static-field",
- "instance-field",
- "static-method",
- "instance-method"
- ]
- }
- ],
- "no-consecutive-blank-lines": false,
- "no-console": [
- true,
- "debug",
- "info",
- "time",
- "timeEnd",
- "trace"
- ],
- "no-empty": false,
- "no-inferrable-types": [
- true,
- "ignore-params"
- ],
- "no-non-null-assertion": true,
- "no-redundant-jsdoc": true,
- "no-switch-case-fall-through": true,
- "no-var-requires": false,
- "object-literal-key-quotes": [
- true,
- "as-needed"
- ],
- "object-literal-sort-keys": false,
- "ordered-imports": false,
- "quotemark": [
- true,
- "single"
- ],
- "trailing-comma": false,
- "no-conflicting-lifecycle": true,
- "no-host-metadata-property": true,
- "no-input-rename": true,
- "no-inputs-metadata-property": true,
- "no-output-native": true,
- "no-output-on-prefix": true,
- "no-output-rename": true,
- "no-outputs-metadata-property": true,
- "space-before-function-paren": {
- "options": {
- "anonymous": "never",
- "asyncArrow": "always",
- "constructor": "never",
- "method": "never",
- "named": "never"
- }
- },
- "template-banana-in-box": true,
- "template-no-negated-async": true,
- "use-lifecycle-interface": true,
- "use-pipe-transform-interface": true,
- "typedef-whitespace": {
- "options": [
- {
- "call-signature": "nospace",
- "index-signature": "nospace",
- "parameter": "nospace",
- "property-declaration": "nospace",
- "variable-declaration": "nospace"
- },
- {
- "call-signature": "onespace",
- "index-signature": "onespace",
- "parameter": "onespace",
- "property-declaration": "onespace",
- "variable-declaration": "onespace"
- }
- ]
- },
- "semicolon": [false, "never"],
- "triple-equals": false,
- "variable-name": {
- "options": [
- "ban-keywords",
- "check-format",
- "allow-pascal-case"
- ]
- },
- "whitespace": {
- "options": [
- "check-branch",
- "check-decl",
- "check-operator",
- "check-separator",
- "check-type",
- "check-typecast"
- ]
- },
- "ban-types": false
- },
- "rulesDirectory": [
- "codelyzer"
- ]
-}