Ajout du support complet pour les kits de retouche

This commit is contained in:
FyloZ 2021-05-20 11:59:55 -04:00
parent af7bf65f8d
commit f6d4dc5da5
63 changed files with 1711 additions and 115 deletions

View File

@ -3,7 +3,6 @@ import {Routes, RouterModule} from '@angular/router'
import {CatalogComponent} from './pages/catalog/catalog.component'
import {AdministrationComponent} from './pages/administration/administration.component'
import {MiscComponent} from './pages/others/misc.component'
import {TouchupkitComponent} from './pages/others/touchupkit/touchupkit.component'
const routes: Routes = [{
@ -49,12 +48,12 @@ const routes: Routes = [{
path: 'misc',
component: MiscComponent,
children: [{
path: 'touchupkit',
component: TouchupkitComponent
path: 'touch-up-kit',
loadChildren: () => import('./modules/touch-up-kit/touch-up-kit.module').then(m => m.TouchUpKitModule)
}, {
path: '',
pathMatch: 'full',
redirectTo: 'touchupkit'
redirectTo: 'touch-up-kit'
}]
}]

View File

@ -1,30 +1,34 @@
import {DomSanitizer} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser'
import {NgModule} from '@angular/core'
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {MatIconRegistry} from "@angular/material/icon";
import {SharedModule} from "./modules/shared/shared.module";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {CatalogComponent} from './pages/catalog/catalog.component';
import {CompanyModule} from './modules/company/company.module';
import { AdministrationComponent } from './pages/administration/administration.component';
import { MiscComponent } from './pages/others/misc.component';
import { TouchupkitComponent } from './pages/others/touchupkit/touchupkit.component';
import {AppRoutingModule} from './app-routing.module'
import {AppComponent} from './app.component'
import {MatIconRegistry} from '@angular/material/icon'
import {SharedModule} from './modules/shared/shared.module'
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'
import {CatalogComponent} from './pages/catalog/catalog.component'
import {CompanyModule} from './modules/company/company.module'
import {AdministrationComponent} from './pages/administration/administration.component'
import {MiscComponent} from './pages/others/misc.component'
import {CreTablesModule} from './modules/shared/components/tables/tables.module'
import {CreButtonsModule} from './modules/shared/components/buttons/buttons.module'
import {TouchUpKitModule} from './modules/touch-up-kit/touch-up-kit.module'
@NgModule({
declarations: [
AppComponent,
CatalogComponent,
AdministrationComponent,
MiscComponent,
TouchupkitComponent
MiscComponent
],
imports: [
AppRoutingModule,
SharedModule,
BrowserAnimationsModule,
CompanyModule
CompanyModule,
CreTablesModule,
CreButtonsModule,
TouchUpKitModule
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,17 +1,12 @@
<div class="recipe-info-wrapper d-flex flex-column">
<div class="d-flex flex-row">
<h3>{{recipe.company.name}} - {{recipe.name}}</h3>
<div
class="recipe-color-circle"
[class.dark-mode]="isDarkColor"
[style.backgroundColor]="recipe.color">
<div>{{recipe.gloss}}%</div>
</div>
<cre-color-preview [recipe]="recipe"></cre-color-preview>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-column">
<p>Échantillon #{{recipe.sample}}</p>
<p *ngIf="recipe.approbationDate">Approuvée le {{recipe.approbationDate}}</p>
<p *ngIf="recipe.approbationDate">Approuvée le {{approbationDate}}</p>
<div *ngIf="!recipe.approbationDate" class="recipe-not-approved-wrapper d-flex flex-row">
<p>Non approuvée</p>
<mat-icon svgIcon="alert" class="color-warning"></mat-icon>

View File

@ -1,5 +1,6 @@
import {AfterViewInit, Component, Input} from '@angular/core'
import {getRecipeLuma, recipeApprobationExpired, Recipe} from '../../../shared/model/recipe.model'
import {formatDate} from '../../../shared/utils/utils'
@Component({
selector: 'cre-recipe-info',
@ -13,15 +14,14 @@ export class RecipeInfoComponent implements AfterViewInit {
isBPacExtensionInstalled = false
ngAfterViewInit(): void {
this.isBPacExtensionInstalled = document.querySelectorAll('.bpac-extension-installed').length > 0
}
get approbationDate(): string {
return formatDate(this.recipe.approbationDate)
}
get isApprobationExpired(): boolean {
return recipeApprobationExpired(this.recipe)
}
get isDarkColor(): boolean {
return getRecipeLuma(this.recipe) < 100
}
}

View File

@ -13,6 +13,7 @@ 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 {Title} from '@angular/platform-browser'
@Component({
selector: 'cre-explore',
@ -49,6 +50,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
private alertService: AlertService,
private accountService: AccountService,
private appState: AppState,
private titleService: Title,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
@ -68,6 +70,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
id,
r => {
this.recipe = r
this.titleService.setTitle(r.name)
if (recipeMixCount(this.recipe) <= 0 || recipeStepCount(this.recipe) <= 0) {
this.alertService.pushWarning('Cette recette n\'est pas complète')

View File

@ -17,6 +17,10 @@ export class RecipeService {
return this.api.get<Recipe[]>('/recipe')
}
getAllByName(name: string): Observable<Recipe[]> {
return this.api.get<Recipe[]>(`/recipe?name=${name}`)
}
get allSortedByCompany(): Observable<{ company: string, recipes: Recipe[] }[]> {
return this.all.pipe(map(recipes => {
const mapped = []

View File

@ -0,0 +1,3 @@
<div class="d-flex flex-row justify-content-between px-5 py-4">
<ng-content select="cre-action-group"></ng-content>
</div>

View File

@ -0,0 +1,15 @@
import {NgModule} from '@angular/core'
import {CreActionBar, CreActionGroup} from './action-bar'
@NgModule({
exports: [
CreActionBar,
CreActionGroup
],
declarations: [
CreActionBar,
CreActionGroup
]
})
export class CreActionBarModule {
}

View File

@ -0,0 +1,14 @@
import {Component} from '@angular/core'
@Component({
selector: 'cre-action-group',
templateUrl: 'action-group.html',
styleUrls: ['action-group.sass']
})
export class CreActionGroup {}
@Component({
selector: 'cre-action-bar',
templateUrl: 'action-bar.html'
})
export class CreActionBar {}

View File

@ -0,0 +1,3 @@
<div>
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,4 @@
div
display: flex
flex-direction: row
gap: 1rem

View File

@ -0,0 +1,22 @@
import {NgModule} from '@angular/core'
import {CreAccentButtonComponent, CreButtonComponent, CrePrimaryButtonComponent, CreWarnButtonComponent} from './buttons'
import {MatButtonModule} from '@angular/material/button'
@NgModule({
declarations: [
CreButtonComponent,
CrePrimaryButtonComponent,
CreAccentButtonComponent,
CreWarnButtonComponent
],
imports: [
MatButtonModule
],
exports: [
CrePrimaryButtonComponent,
CreAccentButtonComponent,
CreWarnButtonComponent
]
})
export class CreButtonsModule {
}

View File

@ -0,0 +1,51 @@
import {Component, Input} from '@angular/core'
import {ThemePalette} from '@angular/material/core'
@Component({
selector: 'cre-button',
template: `
<button mat-raised-button [color]="color" [disabled]="disabled">
<ng-content></ng-content>
</button>
`
})
export class CreButtonComponent {
@Input() color: ThemePalette
@Input() disabled = false
}
@Component({
selector: 'cre-primary-button',
template: `
<cre-button color="primary" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`
})
export class CrePrimaryButtonComponent {
@Input() disabled = false
}
@Component({
selector: 'cre-accent-button',
template: `
<cre-button color="accent" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`
})
export class CreAccentButtonComponent {
@Input() disabled = false
}
@Component({
selector: 'cre-warn-button',
template: `
<cre-button color="warn" [disabled]="disabled">
<ng-content></ng-content>
</cre-button>
`
})
export class CreWarnButtonComponent {
@Input() disabled = false
}

View File

@ -0,0 +1,6 @@
<div
class="recipe-color-circle mat-elevation-z2"
[class.dark-mode]="isDarkColor"
[style.backgroundColor]="recipe.color">
<div>{{recipe.gloss}}%</div>
</div>

View File

@ -0,0 +1,20 @@
.recipe-color-circle
color: black
width: 2.2rem
height: 2.2rem
border-radius: 1.1rem
margin-left: 1rem
font-size: 13px
&.dark-mode
color: white
width: 2.3rem
height: 2.3rem
border: solid 1px white
div
position: absolute
width: 2rem
text-align: center
margin-top: 7px
margin-left: 1px

View File

@ -0,0 +1,15 @@
import {getRecipeLuma, Recipe} from '../../model/recipe.model'
import {Component, Input} from '@angular/core'
@Component({
selector: 'cre-color-preview',
templateUrl: 'color-preview.html',
styleUrls: ['color-preview.sass']
})
export class CreColorPreview {
@Input() recipe: Recipe
get isDarkColor(): boolean {
return getRecipeLuma(this.recipe) < 100
}
}

View File

@ -0,0 +1,15 @@
<mat-card>
<mat-card-header>
<mat-card-title>
<ng-content select="cre-form-title"></ng-content>
</mat-card-title>
</mat-card-header>
<mat-card-content [class.no-action]="!hasActions">
<form [formGroup]="form">
<ng-content select="cre-form-content"></ng-content>
</form>
</mat-card-content>
<mat-card-actions *ngIf="hasActions">
<ng-content select="cre-form-actions"></ng-content>
</mat-card-actions>
</mat-card>

View File

@ -0,0 +1,28 @@
import {NgModule} from '@angular/core'
import {CreFormActions, CreFormComponent, 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'
@NgModule({
declarations: [
CreFormComponent,
CreFormTitle,
CreFormContent,
CreFormActions
],
exports: [
CreFormComponent,
CreFormTitle,
CreFormContent,
CreFormActions
],
imports: [
MatCardModule,
CommonModule,
MatButtonModule,
ReactiveFormsModule
]
})
export class CreFormsModule {}

View File

@ -0,0 +1,10 @@
cre-form
display: block
mat-card
width: inherit
cre-form-actions
display: flex
flex-direction: row
gap: 1rem

View File

@ -0,0 +1,51 @@
import {Component, ContentChild, Directive, Input, OnInit, ViewEncapsulation} from '@angular/core'
import {FormBuilder, FormGroup} from '@angular/forms'
@Directive({
selector: 'cre-form-title'
})
export class CreFormTitle {
}
@Directive({
selector: 'cre-form-content'
})
export class CreFormContent {
}
@Directive({
selector: 'cre-form-actions'
})
export class CreFormActions {
}
@Component({
selector: 'cre-form',
templateUrl: './form.html',
styleUrls: ['forms.sass'],
encapsulation: ViewEncapsulation.None
})
export class CreFormComponent implements OnInit {
@ContentChild(CreFormActions) formActions: CreFormActions
@Input() formControls: { [key: string]: any }
form: FormGroup
constructor(
private formBuilder: FormBuilder
) {
}
ngOnInit(): void {
this.form = this.formBuilder.group(this.formControls)
}
get hasActions(): boolean {
return this.formActions === true
}
get invalid(): boolean {
return !this.form || this.form.invalid
}
}

View File

@ -0,0 +1,16 @@
@import "~src/custom-theme"
.info-banner-wrapper
background-color: $color-primary
color: $light-primary-text
padding: 1rem
.info-banner-section-wrapper
margin-right: 3rem
h3
text-decoration: underline
font-weight: bold
p
margin-bottom: 0

View File

@ -0,0 +1,49 @@
import {Component} from '@angular/core'
@Component({
selector: 'info-banner',
template: `
<div class="info-banner-wrapper">
<ng-content></ng-content>
</div>
`,
styleUrls: ['info-banner.component.sass']
})
export class InfoBannerComponent {
}
@Component({
selector: 'info-banner-title',
template: `
<h3>
<ng-content></ng-content>
</h3>
`,
styleUrls: ['info-banner.component.sass']
})
export class InfoBannerTitleComponent {
}
@Component({
selector: 'info-banner-content',
template: `
<div class="info-banner-content-wrapper d-flex flex-row">
<ng-content></ng-content>
</div>
`,
styleUrls: ['info-banner.component.sass']
})
export class InfoBannerContentComponent {
}
@Component({
selector: 'info-banner-section',
template: `
<div class="info-banner-section-wrapper">
<ng-content></ng-content>
</div>
`,
styleUrls: ['info-banner.component.sass']
})
export class InfoBannerSectionComponent {
}

View File

@ -0,0 +1,24 @@
import {NgModule} from '@angular/core'
import {
InfoBannerComponent,
InfoBannerContentComponent,
InfoBannerSectionComponent,
InfoBannerTitleComponent
} from './info-banner.component'
@NgModule({
declarations: [
InfoBannerComponent,
InfoBannerTitleComponent,
InfoBannerContentComponent,
InfoBannerSectionComponent
],
exports: [
InfoBannerComponent,
InfoBannerTitleComponent,
InfoBannerContentComponent,
InfoBannerSectionComponent
]
})
export class InfoBannerModule {
}

View File

@ -0,0 +1,31 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<input
*ngIf="!control"
matInput
type="text"
autocomplete="off"
[required]="required"
[matAutocomplete]="auto"
[(ngModel)]="value"
(change)="valueChange.emit(value)"/>
<input
*ngIf="control"
matInput
type="text"
autocomplete="off"
[required]="required"
[formControl]="control"
[matAutocomplete]="auto"/>
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
</mat-error>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>

View File

@ -0,0 +1,37 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<mat-chip-list
[formControl]="control"
[required]="required"
#chipList>
<mat-chip
*ngFor="let value of selectedValues"
[removable]="true"
(removed)="remove(value)">
{{value}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
#chipInput
matInput
[formControl]="inputControl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)"/>
</mat-chip-list>
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
</mat-error>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option.key">
{{option.display ? option.display : option.value}}
</mat-option>
</mat-autocomplete>
</mat-form-field>

View File

@ -0,0 +1,23 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<mat-chip-list
[formControl]="control"
[required]="required"
#chipList>
<mat-chip
*ngFor="let value of selectedValues"
[selectable]="true"
[removable]="true"
(removed)="remove(value)">
{{value}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
[formControl]="inputControl"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="add($event)">
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
</mat-chip-list>
</mat-form-field>

View File

@ -0,0 +1,22 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<input
matInput
type="text"
[required]="required"
[formControl]="control"
[matAutocomplete]="auto">
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
</mat-error>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let option of options | async" [value]="option.key">
{{option.value}}
</mat-option>
</mat-autocomplete>
</mat-form-field>

View File

@ -0,0 +1,28 @@
<mat-form-field>
<mat-label>{{label}}</mat-label>
<input
*ngIf="!control"
matInput
[type]="type"
[step]="step"
[placeholder]="placeholder"
[(ngModel)]="value"
[required]="required"
[autocomplete]="autocomplete ? 'on' : 'off'"
(change)="valueChange.emit(value)"/>
<input
*ngIf="control"
matInput
[type]="type"
[step]="step"
[placeholder]="placeholder"
[required]="required"
[formControl]="control"
[autocomplete]="autocomplete ? 'on' : 'off'"/>
<mat-icon matSuffix [svgIcon]="icon"></mat-icon>
<mat-error *ngIf="control && control.invalid">
<span *ngIf="control.errors.required">Ce champ est requis</span>
<ng-container *ngIf="errors" [ngTemplateOutlet]="errors"
[ngTemplateOutletContext]="{errors: control.errors}"></ng-container>
</mat-error>
</mat-form-field>

View File

@ -0,0 +1,46 @@
import {NgModule} from '@angular/core'
import {
CreAutocompleteInputComponent,
CreChipComboBoxComponent,
CreChipInputComponent,
CreComboBoxComponent,
CreInputComponent
} from './inputs'
import {MatInputModule} from '@angular/material/input'
import {MatIconModule} from '@angular/material/icon'
import {FormsModule, ReactiveFormsModule} from '@angular/forms'
import {CommonModule} from '@angular/common'
import {MatCardModule} from '@angular/material/card'
import {MatListModule} from '@angular/material/list'
import {MatAutocompleteModule} from '@angular/material/autocomplete'
import {MatChipsModule} from '@angular/material/chips'
@NgModule({
declarations: [
CreInputComponent,
CreChipInputComponent,
CreAutocompleteInputComponent,
CreComboBoxComponent,
CreChipComboBoxComponent
],
imports: [
MatInputModule,
MatAutocompleteModule,
MatIconModule,
ReactiveFormsModule,
CommonModule,
MatCardModule,
MatListModule,
MatChipsModule,
FormsModule,
],
exports: [
CreInputComponent,
CreComboBoxComponent,
CreChipComboBoxComponent,
CreChipInputComponent,
CreAutocompleteInputComponent
]
})
export class CreInputsModule {
}

View File

@ -0,0 +1,190 @@
import {
Component,
ContentChild,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core'
import {AbstractControl, FormControl, ValidationErrors, ValidatorFn} from '@angular/forms'
import {COMMA, ENTER} from '@angular/cdk/keycodes'
import {Observable, Subject} from 'rxjs'
import {map, takeUntil} from 'rxjs/operators'
import {MatChipInputEvent} from '@angular/material/chips'
import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete'
@Component({
selector: 'cre-input',
templateUrl: 'input.html',
encapsulation: ViewEncapsulation.None
})
export class CreInputComponent {
@Input() control: FormControl | null
@Input() type = 'text'
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() autocomplete = true
@Input() placeholder: string | null
@Input() step = 1
@Input() value
@Output() valueChange = new EventEmitter<any>()
@ContentChild(TemplateRef) errors: TemplateRef<any>
}
@Component({
selector: 'cre-autocomplete-input',
templateUrl: 'autocomplete-input.html',
encapsulation: ViewEncapsulation.None
})
export class CreAutocompleteInputComponent {
@Input() control: FormControl | null
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() options: Observable<string[]>
@Input() value
@Output() valueChange = new EventEmitter<any>()
@ContentChild(TemplateRef) errors: TemplateRef<any>
}
@Component({
selector: 'cre-chip-input',
templateUrl: 'chip-input.html',
encapsulation: ViewEncapsulation.None
})
export class CreChipInputComponent implements OnInit {
@Input() control: FormControl
@Input() label: string
@Input() icon: string
@Input() required = true
inputControl = new FormControl()
readonly separatorKeysCodes = [ENTER, COMMA]
selectedValues = []
ngOnInit(): void {
if (this.control.value) {
this.selectedValues = this.control.value
}
}
add(event: MatChipInputEvent): void {
const input = event.input
const value = event.value
if ((value || '').trim()) {
this.selectedValues.push(value.trim())
}
if (input) {
input.value = ''
}
this.inputControl.setValue(null)
this.control.setValue(this.selectedValues)
}
remove(value): void {
const index = this.selectedValues.indexOf(value)
if (index >= 0) {
this.selectedValues.splice(index, 1)
this.control.setValue(this.selectedValues)
}
}
}
@Component({
selector: 'cre-combo-box',
templateUrl: 'combo-box.html',
encapsulation: ViewEncapsulation.None
})
export class CreComboBoxComponent {
@Input() control: FormControl
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() options: Observable<ComboBoxEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
}
@Component({
selector: 'cre-chip-combo-box',
templateUrl: 'chip-combo-box.html',
encapsulation: ViewEncapsulation.None
})
export class CreChipComboBoxComponent extends CreChipInputComponent implements OnDestroy {
@Input() options: Observable<ComboBoxEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
@ViewChild('chipInput') chipInput: ElementRef<HTMLInputElement>
@ViewChild('auto') matAutocomplete: MatAutocomplete
filteredOptions: Observable<ComboBoxEntry[]>
private _options: ComboBoxEntry[]
private _destroy$ = new Subject()
ngOnInit() {
super.ngOnInit()
this.options.pipe(takeUntil(this._destroy$))
.subscribe({next: options => this._options = options})
this.filteredOptions = this.inputControl.valueChanges.pipe(
map((query: string | null) => query ? this._filter(query) : this._options.slice())
)
}
ngOnDestroy(): void {
this._destroy$.complete()
}
selected(event: MatAutocompleteSelectedEvent) {
this.selectedValues.push(this.findValueByKey(event.option.value))
this.chipInput.nativeElement.value = ''
this.inputControl.setValue(null)
this.control.setValue(this.selectedValues)
}
get empty(): boolean {
return this.selectedValues.length <= 0
}
private _filter(query: string): ComboBoxEntry[] {
const filterValue = query.toString().toLowerCase()
return this._options.filter(option => option.value.toString().toLowerCase().indexOf(filterValue) === 0)
}
private findValueByKey(key: any): any {
return this._options.filter(o => o.key === key)[0].value
}
}
export class ComboBoxEntry {
constructor(
public key: any,
public value: any,
public display?: any
) {
}
}
export function chipListRequired(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !control.value || control.value.length <= 0 ? {required: true} : null
}
}

View File

@ -3,7 +3,6 @@
nav
position: relative
z-index: 90
padding-bottom: 1px
a
opacity: 1

View File

@ -0,0 +1,12 @@
<table mat-table [dataSource]="dataSource">
<ng-content></ng-content>
<tr mat-header-row *matHeaderRowDef="columns"></tr>
<tr
mat-row
*matRowDef="let row; let i = index; columns: columns"
[class.cre-row-selected]="interactive && selectedIndex === i"
(mouseover)="onRowHover(i)"
(click)="onRowSelected(i)">
</tr>
</table>

View File

@ -0,0 +1,12 @@
@import "~src/custom-theme"
cre-table
display: block
width: max-content
tr
&:hover
background-color: darken(white, 5%)
&.cre-row-selected
background-color: darken(white, 10%)

View File

@ -0,0 +1,21 @@
import {NgModule} from '@angular/core'
import {MatTableModule} from '@angular/material/table'
import {CommonModule} from '@angular/common'
import {CreInteractiveCell, CreTable} from './tables'
@NgModule({
declarations: [
CreTable,
CreInteractiveCell
],
imports: [
MatTableModule,
CommonModule
],
exports: [
CreTable,
CreInteractiveCell,
]
})
export class CreTablesModule {
}

View File

@ -0,0 +1,83 @@
import {
AfterContentInit,
Component,
ContentChildren,
Directive,
HostBinding,
Input,
QueryList,
ViewChild,
ViewEncapsulation
} from '@angular/core'
import {MatColumnDef, MatHeaderRowDef, MatRowDef, MatTable} from '@angular/material/table'
@Directive({
selector: '[creInteractiveCell]'
})
export class CreInteractiveCell implements AfterContentInit {
@Input('creInteractiveCell') index: number
@HostBinding() hidden = true
private _selectedIndex = 0
private _hoverIndex = 0
ngAfterContentInit(): void {
this.hidden = this.isHidden
}
set hoverIndex(index: number) {
this._hoverIndex = index
this.hidden = this.isHidden
}
set selectedIndex(index: number) {
this._selectedIndex = index
this.hidden = this.isHidden
}
get isHidden(): boolean {
return this._hoverIndex !== this.index && this._selectedIndex !== this.index
}
}
@Component({
selector: 'cre-table',
templateUrl: 'table.html',
styleUrls: ['table.sass'],
encapsulation: ViewEncapsulation.None
})
export class CreTable<T> implements AfterContentInit {
@ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList<MatHeaderRowDef>
@ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>
@ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>
@ContentChildren(CreInteractiveCell, {descendants: true}) interactiveCells: QueryList<CreInteractiveCell>
@ViewChild(MatTable, {static: true}) table: MatTable<T>
@Input() columns: string[]
@Input() dataSource: T[]
@Input() interactive = true
selectedIndex = 0
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))
}
onRowHover(index: number) {
if (this.interactive) {
this.interactiveCells.forEach(cell => cell.hoverIndex = index)
}
}
onRowSelected(index: number) {
if (this.interactive) {
this.selectedIndex = index
this.interactiveCells.forEach(cell => cell.selectedIndex = index)
}
}
}

View File

@ -0,0 +1,25 @@
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core'
@Directive({
selector: '[ngVar]'
})
export class VarDirective {
context: any = {}
constructor(
private vcRef: ViewContainerRef,
private templateRef: TemplateRef<any>
) {
}
@Input()
set ngVar(context: any) {
this.context.$implicit = this.context.ngVar = context
this.updateView()
}
private updateView() {
this.vcRef.clear()
this.vcRef.createEmbeddedView(this.templateRef, this.context)
}
}

View File

@ -11,7 +11,7 @@ export class Recipe {
public color: string,
public gloss: number,
public sample: number,
public approbationDate: LocalDate,
public approbationDate: string,
public remark: string,
public company: Company,
public mixes: Mix[],

View File

@ -0,0 +1,24 @@
export class TouchUpKit {
constructor(
public id: number,
public project: string,
public buggy: string,
public company: string,
public quantity: number,
public shippingDate: string,
public finish: string[],
public material: string[],
public content: TouchUpKitProduct[]
) {
}
}
export class TouchUpKitProduct {
constructor(
public id: number,
public name: string,
public description: string | null,
public quantity: number
) {
}
}

View File

@ -32,10 +32,13 @@ import {MatSliderModule} from '@angular/material/slider'
import {SliderFieldComponent} from './components/slider-field/slider-field.component'
import {LoadingWheelComponent} from './components/loading-wheel/loading-wheel.component'
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'
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'
@NgModule({
declarations: [HeaderComponent, UserMenuComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent, LoadingWheelComponent],
declarations: [VarDirective, HeaderComponent, UserMenuComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent, LoadingWheelComponent, CreColorPreview],
exports: [
CommonModule,
HttpClientModule,
@ -64,7 +67,11 @@ import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'
FileButtonComponent,
GlobalAlertHandlerComponent,
LoadingWheelComponent,
RouterModule
RouterModule,
InfoBannerModule,
CreFormsModule,
VarDirective,
CreColorPreview
],
imports: [
MatTabsModule,

View File

@ -1,4 +1,8 @@
/** Returns [value] if it is not null or [or]. */
import {DateTimeFormatter, LocalDate} from 'js-joda'
import {TouchUpKit} from '../model/touch-up-kit.model'
import {environment} from '../../../../environments/environment'
export function valueOr<T>(value: T, or: T): T {
return value ? value : or
}
@ -14,7 +18,27 @@ export function openJpg(url: string) {
openUrl(url, MEDIA_TYPE_JPG)
}
function openUrl(url: string, mediaType: string) {
const encodedUrl = `${url}&mediaType=${encodeURIComponent(mediaType)}`
window.open(encodedUrl, '_blank')
export function openTouchUpKit(touchUpKit: TouchUpKit) {
openRawUrl(`${environment.apiUrl}/touchupkit/pdf?project=${touchUpKit.project}`)
}
export function openUrl(url: string, mediaType: string) {
openRawUrl(`${url}&mediaType=${encodeURIComponent(mediaType)}`)
}
export function openRawUrl(url: string) {
window.open(url, '_blank')
}
const dateFormatter = DateTimeFormatter
.ofPattern('dd-MM-yyyy')
export function formatDate(date: string): string {
return LocalDate.parse(date).format(dateFormatter)
}
export function reduceDashes(arr: string[]): string {
return arr.reduce((acc, cur) => {
return `${acc} - ${cur}`
})
}

View File

@ -0,0 +1,11 @@
<div class="touchupkit-finish-container" (mouseover)="hover = true" (mouseleave)="hover = false">
<div [class.matching]="matchesRecipes">{{finish}}</div>
<div class="matching-recipes mat-elevation-z4" *ngIf="matchesRecipes && hover">
<mat-list>
<mat-list-item *ngFor="let recipe of matchingRecipes" (click)="openRecipe(recipe)">
<span>{{recipe.name}} - {{recipe.company.name}}</span>
<cre-color-preview [recipe]="recipe"></cre-color-preview>
</mat-list-item>
</mat-list>
</div>
</div>

View File

@ -0,0 +1,28 @@
@import '~src/custom-theme'
.touchupkit-finish-container
display: inline-block
position: relative
.matching
text-decoration: underline
.matching-recipes
position: absolute
top: 1.5em
transform: translateX(-25%)
width: max-content
display: inline-block
background-color: white
color: $dark-primary-text
border-radius: 4px
mat-list
padding-top: 0
mat-list-item:hover
background-color: darken(white, 5%)
cursor: pointer
cre-color-preview
display: inline-block

View File

@ -0,0 +1,42 @@
import {Component, Input} from '@angular/core'
import {SubscribingComponent} from '../../shared/components/subscribing.component'
import {RecipeService} from '../../colors/services/recipe.service'
import {ErrorService} from '../../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {Recipe} from '../../shared/model/recipe.model'
@Component({
selector: 'touchupkit-finish',
templateUrl: 'finish.html',
styleUrls: ['finish.sass']
})
export class TouchUpKitFinish extends SubscribingComponent {
@Input() finish: string
hover = false
matchesRecipes = false
matchingRecipes: Recipe[] | null
constructor(
private recipeService: RecipeService,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
}
ngOnInit(): void {
this.subscribe(
this.recipeService.getAllByName(this.finish),
recipes => {
this.matchesRecipes = recipes.length > 0
this.matchingRecipes = recipes
}
)
}
openRecipe(recipe: Recipe) {
window.open(`/color/explore/${recipe.id}`, '_blank')
}
}

View File

@ -0,0 +1,34 @@
<cre-form [formControls]="controls" class="mx-auto" style="width: 50rem">
<cre-form-title>Ajouter un kit de retouche</cre-form-title>
<cre-form-content>
<cre-input [control]="controls.project" label="Project" icon="archive"></cre-input>
<cre-input [control]="controls.buggy" label="Chariot" icon="pound"></cre-input>
<cre-autocomplete-input
[control]="controls.company"
[options]="companies$"
label="Bannière"
icon="domain">
</cre-autocomplete-input>
<cre-input [control]="controls.quantity" label="Quantité" icon="beaker-outline" type="number">
<ng-template let-errors="errors">
<span *ngIf="errors && errors.min">La quantité doit être supérieure ou égale à 1</span>
</ng-template>
</cre-input>
<cre-input [control]="controls.shippingDate" label="Date de livraison" icon="calendar" type="date"></cre-input>
<cre-chip-combo-box
#finishInput
[control]="controls.finish"
[options]="finish$"
label="Fini"
icon="flare">
</cre-chip-combo-box>
<cre-chip-input
#materialInput
[control]="controls.material"
label="Matériel"
icon="wall">
</cre-chip-input>
</cre-form-content>
</cre-form>
<touchupkit-product-editor [products]="touchUpKit?.content"></touchupkit-product-editor>

View File

@ -0,0 +1,92 @@
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 {TouchUpKitProductEditor} from './product-editor'
import {FormControl, Validators} from '@angular/forms'
import {RecipeService} from '../../colors/services/recipe.service'
import {CompanyService} from '../../company/service/company.service'
import {ErrorService} from '../../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {TouchUpKit, TouchUpKitProduct} from '../../shared/model/touch-up-kit.model'
import {SubscribingComponent} from '../../shared/components/subscribing.component'
import {map} from 'rxjs/operators'
@Component({
selector: 'touchupkit-form',
templateUrl: 'form.html'
})
export class TouchUpKitForm extends SubscribingComponent {
@ViewChild('finishInput') finishInput: CreChipComboBoxComponent
@ViewChild('materialInput') materialInput: CreChipComboBoxComponent
@ViewChild(CreFormComponent) form: CreFormComponent
@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}`)))
)
companies$ = this.companyService.all.pipe(
map(companies => companies.map(company => company.name))
)
@Output() submitForm = new EventEmitter<TouchUpKit>()
constructor(
private recipeService: RecipeService,
private companyService: CompanyService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router,
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
super.ngOnInit()
this.controls = {
project: new FormControl(this.touchUpKit?.project, Validators.required),
buggy: new FormControl(this.touchUpKit?.buggy, Validators.required),
company: new FormControl(this.touchUpKit?.company, Validators.required),
quantity: new FormControl(this.touchUpKit?.quantity, Validators.compose([Validators.required, Validators.min(1)])),
shippingDate: new FormControl(this.touchUpKit?.shippingDate, Validators.required),
finish: new FormControl(this.touchUpKit?.finish, chipListRequired()),
material: new FormControl(this.touchUpKit?.material, chipListRequired())
}
}
submit() {
this.submitForm.emit({
id: this.touchUpKit ? this.touchUpKit.id : null,
project: this.controls.project.value,
buggy: this.controls.buggy.value,
company: this.controls.company.value,
quantity: this.controls.quantity.value,
shippingDate: this.controls.shippingDate.value,
finish: this.selectedFinish,
material: this.selectedMaterial,
content: this.touchUpKitContent
})
}
get selectedFinish(): string[] {
return this.finishInput.selectedValues
}
get selectedMaterial(): string[] {
return this.materialInput.selectedValues
}
get touchUpKitContent(): TouchUpKitProduct[] {
return this.contentEditor.products
}
get formValid(): boolean {
return this.form && !this.form.invalid &&
!this.finishInput.empty &&
!this.materialInput.empty &&
!this.contentEditor.empty
}
}

View File

@ -0,0 +1,55 @@
<mat-card class="mt-5 mx-auto">
<mat-card-header>
<mat-card-title>Contenu</mat-card-title>
</mat-card-header>
<mat-card-content>
<table mat-table [dataSource]="products" style="width: 60rem">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Nom</th>
<td mat-cell *matCellDef="let product; let i = index">
<ng-container *ngVar="isFocused(i, product) as focused">
<cre-input *ngIf="focused" [(value)]="product.name"></cre-input>
<span *ngIf="!focused" class="focused">{{product.name}}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Description</th>
<td mat-cell *matCellDef="let product; let i = index">
<ng-container *ngVar="isFocused(i, product) as focused">
<cre-input *ngIf="focused" [(value)]="product.description" placeholder="-" [required]="false"></cre-input>
<span *ngIf="!focused" class="focused">{{product.description ? product.description : '-'}}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef>Quantité</th>
<td mat-cell *matCellDef="let product; let i = index">
<ng-container *ngVar="isFocused(i, product) as focused">
<cre-input *ngIf="focused" type="number" step="1" [(value)]="product.quantity"></cre-input>
<span *ngIf="!focused" class="focused">{{product.quantity}}</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="removeButton">
<th mat-header-cell *matHeaderCellDef>
<cre-accent-button (click)="addRow()">Ajouter</cre-accent-button>
</th>
<td mat-cell *matCellDef="let product">
<cre-warn-button (click)="removeRow(product)">Retirer</cre-warn-button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="productCols"></tr>
<tr
mat-row
*matRowDef="let product; columns: productCols"
(mouseover)="hoveredProduct = product"
(click)="selectedProduct = product">
</tr>
</table>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
@import '../../../../custom-theme'
touchupkit-product-editor
.mat-form-field-label-wrapper
opacity: 0
cre-input, span.focused
display: inline-block
width: 15rem
span.focused
white-space: nowrap
overflow: hidden
// Content card
mat-card-content
margin: 0
padding: 0
table
border: none
box-shadow: none
th
border-radius: 0 !important

View File

@ -0,0 +1,57 @@
import {Component, Input, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'
import {TouchUpKitProduct} from '../../shared/model/touch-up-kit.model'
import {MatTable} from '@angular/material/table'
@Component({
selector: 'touchupkit-product-editor',
templateUrl: 'product-editor.html',
styleUrls: ['product-editor.sass'],
encapsulation: ViewEncapsulation.None
})
export class TouchUpKitProductEditor implements OnInit {
productCols = ['name', 'description', 'quantity', 'removeButton']
@Input() products: TouchUpKitProduct[]
@ViewChild(MatTable) table: MatTable<TouchUpKitProduct>
hoveredProduct: TouchUpKitProduct | null
selectedProduct: TouchUpKitProduct | null
ngOnInit(): void {
if (!this.products) {
this.products = [this.emptyProduct]
}
}
addRow() {
const newProduct = this.emptyProduct
this.products.push(newProduct)
this.table.renderRows()
this.selectedProduct = newProduct
}
removeRow(product: TouchUpKitProduct) {
this.products = this.products.filter(p => p !== product)
this.table.renderRows()
}
isFocused(index: number, product: TouchUpKitProduct): boolean {
return (!this.hoveredProduct && index === 0) ||
this.hoveredProduct === product ||
this.selectedProduct === product
}
get empty(): boolean {
return this.products.length <= 0
}
get emptyProduct(): TouchUpKitProduct {
return {
id: null,
name: '',
description: '',
quantity: 1
}
}
}

View File

@ -0,0 +1,11 @@
<cre-action-bar>
<cre-action-group>
<cre-primary-button routerLink="/misc/touch-up-kit/list">Retour</cre-primary-button>
</cre-action-group>
<cre-action-group>
<cre-accent-button [disabled]="!form.formValid" (click)="form.submit()">Enregistrer</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<touchupkit-form #form (submitForm)="submit($event)">
</touchupkit-form>

View File

@ -0,0 +1,19 @@
<info-banner>
<info-banner-title>{{touchUpKit.project}} - {{touchUpKit.company}}</info-banner-title>
<info-banner-content>
<info-banner-section>
<p>Chariot: {{touchUpKit.buggy}}</p>
<p>Quantité: {{touchUpKit.quantity}}</p>
<p>Date de livraison: {{shippingDate}}</p>
</info-banner-section>
<info-banner-section>
<p>Fini:
<ng-container *ngFor="let finish of touchUpKit.finish; let i = index">
<touchupkit-finish [finish]="finish"></touchupkit-finish>
<ng-container *ngIf="i < touchUpKit.finish.length - 1"> - </ng-container>
</ng-container>
</p>
<p>Matériel: {{material}}</p>
</info-banner-section>
</info-banner-content>
</info-banner>

View File

@ -0,0 +1,28 @@
<div *ngIf="touchUpKit">
<touchupkit-banner [touchUpKit]="touchUpKit"></touchupkit-banner>
<div class="action-bar backward">
<button mat-raised-button color="primary" routerLink="/misc/touch-up-kit">Retour</button>
<button mat-raised-button color="accent" (click)="openPdf()">PDF</button>
</div>
<cre-table class="mx-auto" [dataSource]="touchUpKit.content" [columns]="contentTableCols" [interactive]="false">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Nom</th>
<td mat-cell *matCellDef="let product">{{product.name}}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Description</th>
<td mat-cell *matCellDef="let product">
<ng-container *ngIf="product.description">{{product.description}}</ng-container>
<ng-container *ngIf="!product.description">-</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef>Quantité</th>
<td mat-cell *matCellDef="let product">{{product.quantity}}</td>
</ng-container>
</cre-table>
</div>

View File

@ -0,0 +1,2 @@
info-banner-section p
margin-bottom: 0

View File

@ -0,0 +1,24 @@
<ng-container *ngIf="touchUpKit">
<cre-action-bar>
<cre-action-group>
<cre-primary-button routerLink="/misc/touch-up-kit/list">Retour</cre-primary-button>
</cre-action-group>
<cre-action-group>
<cre-warn-button (click)="deleteConfirmBox.show()">Supprimer</cre-warn-button>
<cre-accent-button [disabled]="!form.formValid" (click)="form.submit()">Enregistrer</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<touchupkit-form
#form
[touchUpKit]="touchUpKit"
(submitForm)="submit($event)">
</touchupkit-form>
<cre-confirm-box
#deleteConfirmBox
message="Voulez-vous vraiment supprimer le kit de retouche du projet {{touchUpKit.project}}? ({{touchUpKit.buggy}})"
(confirm)="delete()">
</cre-confirm-box>
</ng-container>

View File

@ -0,0 +1,53 @@
<cre-action-bar>
<cre-action-group></cre-action-group>
<cre-action-group>
<cre-accent-button routerLink="/misc/touch-up-kit/add">Ajouter</cre-accent-button>
</cre-action-group>
</cre-action-bar>
<cre-table class="mx-auto" [dataSource]="touchUpKits$ | async" [columns]="columns">
<ng-container matColumnDef="project">
<th mat-header-cell *matHeaderCellDef>Project</th>
<td mat-cell *matCellDef="let touchUpKit">{{touchUpKit.project}}</td>
</ng-container>
<ng-container matColumnDef="buggy">
<th mat-header-cell *matHeaderCellDef>Chariot</th>
<td mat-cell *matCellDef="let touchUpKit">{{touchUpKit.buggy}}</td>
</ng-container>
<ng-container matColumnDef="company">
<th mat-header-cell *matHeaderCellDef>Bannière</th>
<td mat-cell *matCellDef="let touchUpKit">{{touchUpKit.company}}</td>
</ng-container>
<ng-container matColumnDef="shippingDate">
<th mat-header-cell *matHeaderCellDef>Date de livraison</th>
<td mat-cell *matCellDef="let touchUpKit">{{touchUpKit.shippingDate}}</td>
</ng-container>
<ng-container matColumnDef="pdfButton">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let touchUpKit; let i = index">
<cre-accent-button [creInteractiveCell]="i" (click)="openTouchUpKitPdf(touchUpKit)">PDF</cre-accent-button>
</td>
</ng-container>
<ng-container matColumnDef="detailsButton">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let touchUpKit; let i = index">
<cre-accent-button [creInteractiveCell]="i" routerLink="/misc/touch-up-kit/details/{{touchUpKit.id}}">
Détails
</cre-accent-button>
</td>
</ng-container>
<ng-container matColumnDef="editButton">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell [class.disabled]="!canEditTouchUpKits" *matCellDef="let touchUpKit; let i = index">
<cre-accent-button [creInteractiveCell]="i" routerLink="/misc/touch-up-kit/edit/{{touchUpKit.id}}">
Modifier
</cre-accent-button>
</td>
</ng-container>
</cre-table>

View File

@ -0,0 +1,152 @@
import {Component, Input} from '@angular/core'
import {TouchUpKit} from '../../shared/model/touch-up-kit.model'
import {formatDate, openTouchUpKit, reduceDashes} from '../../shared/utils/utils'
import {ErrorHandlingComponent} from '../../shared/components/subscribing.component'
import {TouchUpKitService} from '../service/touch-up-kit.service'
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'
@Component({
selector: 'touchupkit-banner',
templateUrl: 'banner.html',
styles: [
'p { margin-bottom: 0 }'
]
})
export class TouchUpKitBanner {
@Input() touchUpKit: TouchUpKit
get shippingDate(): string {
return formatDate(this.touchUpKit.shippingDate)
}
get material(): string {
return reduceDashes(this.touchUpKit.material)
}
}
@Component({
selector: 'touchupkit-list',
templateUrl: './list.html'
})
export class TouchUpKitList extends ErrorHandlingComponent {
touchUpKits$ = this.touchUpKitService.all
columns = ['project', 'buggy', 'company', 'shippingDate', 'pdfButton', 'detailsButton', 'editButton']
constructor(
private touchUpKitService: TouchUpKitService,
private accountService: AccountService,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
}
openTouchUpKitPdf(touchUpKit: TouchUpKit) {
openTouchUpKit(touchUpKit)
}
get canEditTouchUpKits(): boolean {
return this.accountService.hasPermission(Permission.EDIT_TOUCH_UP_KITS)
}
}
@Component({
selector: 'touchupkit-details',
templateUrl: 'details.html'
})
export class TouchUpKitDetails extends ErrorHandlingComponent {
touchUpKit: TouchUpKit | null
contentTableCols = ['name', 'description', 'quantity']
constructor(
private touchUpKitService: TouchUpKitService,
private recipeService: RecipeService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
super.ngOnInit()
this.subscribeEntityById(
this.touchUpKitService,
this.urlUtils.parseIntUrlParam('id'),
t => this.touchUpKit = t
)
}
openPdf() {
openTouchUpKit(this.touchUpKit)
}
}
@Component({
selector: 'touchupkit-add',
templateUrl: 'add.html'
})
export class TouchUpKitAdd extends ErrorHandlingComponent {
constructor(
private touchUpKitService: TouchUpKitService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router,
) {
super(errorService, activatedRoute, router)
}
submit(touchUpKit) {
this.subscribeAndNavigate(
this.touchUpKitService.save(touchUpKit),
'/misc/touch-up-kit/list'
)
}
}
@Component({
selector: 'touchupkit-edit',
templateUrl: 'edit.html'
})
export class TouchUpKitEdit extends ErrorHandlingComponent {
touchUpKit: TouchUpKit | null
constructor(
private touchUpKitService: TouchUpKitService,
errorService: ErrorService,
activatedRoute: ActivatedRoute,
router: Router
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
super.ngOnInit()
this.subscribeEntityById(
this.touchUpKitService,
this.urlUtils.parseIntUrlParam('id'),
touchUpKit => this.touchUpKit = touchUpKit
)
}
submit(touchUpKit) {
this.subscribeAndNavigate(
this.touchUpKitService.update(touchUpKit),
'/misc/touch-up-kit/list'
)
}
delete() {
this.subscribeAndNavigate(
this.touchUpKitService.delete(this.touchUpKit.id),
'/misc/touch-up-kit/list'
)
}
}

View File

@ -0,0 +1,34 @@
import {Injectable} from '@angular/core'
import {ApiService} from '../../shared/service/api.service'
import {Observable} from 'rxjs'
import {TouchUpKit, TouchUpKitProduct} from '../../shared/model/touch-up-kit.model'
@Injectable({
providedIn: 'root'
})
export class TouchUpKitService {
constructor(
private api: ApiService
) {
}
get all(): Observable<TouchUpKit[]> {
return this.api.get<TouchUpKit[]>('/touchupkit')
}
getById(id: number): Observable<TouchUpKit> {
return this.api.get<TouchUpKit>(`/touchupkit/${id}`)
}
save(touchUpKit: TouchUpKit): Observable<void> {
return this.api.post<void>('/touchupkit', touchUpKit)
}
update(touchUpKit: TouchUpKit): Observable<void> {
return this.api.put<void>('/touchupkit', touchUpKit)
}
delete(id: number): Observable<void> {
return this.api.delete<void>(`/touchupkit/${id}`)
}
}

View File

@ -0,0 +1,28 @@
import {NgModule} from '@angular/core'
import {RouterModule, Routes} from '@angular/router'
import {TouchUpKitAdd, TouchUpKitDetails, TouchUpKitEdit, TouchUpKitList} from './pages/touchupkit'
const routes: Routes = [{
path: 'list',
component: TouchUpKitList
}, {
path: 'details/:id',
component: TouchUpKitDetails
}, {
path: 'add',
component: TouchUpKitAdd
}, {
path: 'edit/:id',
component: TouchUpKitEdit
}, {
path: '',
pathMatch: 'full',
redirectTo: 'list'
}]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TouchUpKitRoutingModule {
}

View File

@ -0,0 +1,41 @@
import {NgModule} from '@angular/core'
import {CommonModule} from '@angular/common'
import {TouchUpKitRoutingModule} from './touch-up-kit-routing.module'
import {SharedModule} from '../shared/shared.module'
import {CreInputsModule} from '../shared/components/inputs/inputs.module'
import {CreButtonsModule} from '../shared/components/buttons/buttons.module'
import {TouchUpKitProductEditor} from './components/product-editor'
import {FormsModule} from '@angular/forms'
import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module'
import {TouchUpKitForm} from './components/form'
import {CreTablesModule} from '../shared/components/tables/tables.module'
import {TouchUpKitAdd, TouchUpKitBanner, TouchUpKitDetails, TouchUpKitEdit, TouchUpKitList} from './pages/touchupkit'
import {TouchUpKitFinish} from './components/finish'
import {MatTooltipModule} from '@angular/material/tooltip'
@NgModule({
declarations: [
TouchUpKitForm,
TouchUpKitProductEditor,
TouchUpKitBanner,
TouchUpKitFinish,
TouchUpKitList,
TouchUpKitDetails,
TouchUpKitAdd,
TouchUpKitEdit
],
imports: [
CommonModule,
TouchUpKitRoutingModule,
SharedModule,
CreInputsModule,
CreButtonsModule,
FormsModule,
CreActionBarModule,
CreTablesModule,
MatTooltipModule
]
})
export class TouchUpKitModule {
}

View File

@ -10,6 +10,6 @@ import {Permission} from '../../modules/shared/model/user'
})
export class MiscComponent extends SubMenuComponent{
links: NavLink[] = [
{route: '/misc/touchupkit', title: 'Kits de retouche', permission: Permission.VIEW_TOUCH_UP_KITS}
{route: '/misc/touch-up-kit', title: 'Kits de retouche', permission: Permission.VIEW_TOUCH_UP_KITS}
]
}

View File

@ -1,23 +0,0 @@
<mat-card class="x-centered mt-5">
<mat-card-header>
<mat-card-title>Génération d'un kit de retouche</mat-card-title>
</mat-card-header>
<mat-card-content>
<form [formGroup]="formGroup">
<mat-form-field>
<mat-label>Job</mat-label>
<input matInput type="text" [formControl]="jobControl" required/>
<mat-icon matSuffix svgIcon="briefcase"></mat-icon>
</mat-form-field>
</form>
</mat-card-content>
<mat-card-actions>
<button
mat-flat-button
color="accent"
[disabled]="formGroup.invalid"
(click)="submit()">
Générer
</button>
</mat-card-actions>
</mat-card>

View File

@ -1,30 +0,0 @@
import {Component, OnInit} from '@angular/core'
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
import {TouchupkitService} from '../../../modules/shared/service/touchupkit.service'
@Component({
selector: 'cre-touchupkit',
templateUrl: './touchupkit.component.html',
styleUrls: ['./touchupkit.component.sass']
})
export class TouchupkitComponent implements OnInit {
formGroup: FormGroup
jobControl: FormControl
constructor(
private touchUpKitService: TouchupkitService,
private formBuilder: FormBuilder
) {
}
ngOnInit(): void {
this.jobControl = new FormControl(null, Validators.required)
this.formGroup = this.formBuilder.group({
job: this.jobControl
})
}
submit() {
this.touchUpKitService.generateJobPdfDocument(this.jobControl.value)
}
}

View File

@ -5,7 +5,6 @@
mat-card
padding: 0 !important
width: max-content
max-width: 50rem
&.x-centered
margin: auto
@ -168,27 +167,6 @@ div.empty
margin-left: 0
margin-right: 1rem
.recipe-color-circle
color: black
width: 2.2rem
height: 2.2rem
border-radius: 1.1rem
margin-left: 1rem
font-size: 13px
&.dark-mode
color: white
width: 2.3rem
height: 2.3rem
border: solid 1px white
div
position: absolute
width: 2rem
text-align: center
margin-top: 7px
margin-left: 1px
.alert p
margin-bottom: 0