Merge branch 'bug-solving' into 'master'
Bug solving See merge request color-recipes-explorer/frontend!8
This commit is contained in:
commit
e693625ab7
|
@ -1,5 +1,6 @@
|
|||
<cre-header></cre-header>
|
||||
<cre-global-alert-handler></cre-global-alert-handler>
|
||||
<cre-loading-wheel></cre-loading-wheel>
|
||||
<div class="pb-5">
|
||||
<div class="dark-background"></div>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {environment} from '../../../../environments/environment'
|
|||
import {ApiService} from '../../shared/service/api.service'
|
||||
import {Employee, EmployeePermission} from '../../shared/model/employee'
|
||||
import {ErrorService} from '../../shared/service/error.service'
|
||||
import {globalLoadingWheel} from '../../shared/components/loading-wheel/loading-wheel.component'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -47,6 +48,7 @@ export class AccountService implements OnDestroy {
|
|||
|
||||
login(id: number, password: string, success: () => void) {
|
||||
const loginForm = {id, password}
|
||||
globalLoadingWheel.show()
|
||||
this.http.post<any>(`${environment.apiUrl}/login`, loginForm, {
|
||||
withCredentials: true,
|
||||
observe: 'response' as 'body'
|
||||
|
@ -62,7 +64,10 @@ export class AccountService implements OnDestroy {
|
|||
this.setLoggedInEmployeeFromApi()
|
||||
success()
|
||||
},
|
||||
error: err => this.errorService.handleError(err)
|
||||
error: err => {
|
||||
this.errorService.handleError(err)
|
||||
globalLoadingWheel.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -92,7 +97,11 @@ export class AccountService implements OnDestroy {
|
|||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe({
|
||||
next: employee => this.appState.authenticatedEmployee = employee,
|
||||
next: employee => {
|
||||
this.appState.authenticatedEmployee = employee
|
||||
// At this point the loading wheel should be visible
|
||||
globalLoadingWheel.hide()
|
||||
},
|
||||
error: err => this.errorService.handleError(err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {Observable} from 'rxjs'
|
|||
import {RecipeImageService} from '../../services/recipe-image.service'
|
||||
import {environment} from '../../../../../environments/environment'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {globalLoadingWheel} from '../../../shared/components/loading-wheel/loading-wheel.component'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-images-editor',
|
||||
|
@ -36,6 +37,7 @@ export class ImagesEditorComponent extends SubscribingComponent {
|
|||
this.subscribe(
|
||||
this.imageIds$,
|
||||
ids => this.hasImages = ids.length > 0,
|
||||
false,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'
|
||||
import {Mix, MixMaterial, Recipe} from '../../../shared/model/recipe.model'
|
||||
import {SubscribingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {MixService} from '../../services/mix.service'
|
||||
import {Observable} from 'rxjs'
|
||||
import {RecipeService} from '../../services/recipe.service'
|
||||
|
@ -23,7 +23,7 @@ import {MatSelect} from '@angular/material/select'
|
|||
templateUrl: './mix-editor.component.html',
|
||||
styleUrls: ['./mix-editor.component.sass']
|
||||
})
|
||||
export class MixEditorComponent extends SubscribingComponent {
|
||||
export class MixEditorComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('mixTable') mixTable: MatTable<MixMaterial>
|
||||
@ViewChild('deleteConfirmBox') deleteConfirmBox: ConfirmBoxComponent
|
||||
|
||||
|
@ -47,6 +47,16 @@ export class MixEditorComponent extends SubscribingComponent {
|
|||
hoveredMixMaterial: MixMaterial | null
|
||||
columns = ['position', 'material', 'quantity', 'units', 'buttonRemove']
|
||||
|
||||
deleting = false
|
||||
handledErrorModels = [{
|
||||
filter: error => error.status === 409 && !this.deleting,
|
||||
messageProducer: error => `Un mélange avec le nom '${error.id}' existe déjà dans cette recette`
|
||||
}, {
|
||||
filter: error => error.error.status === 409 && this.deleting,
|
||||
consumer: () => this.deleting = false,
|
||||
messageProducer: () => 'Ce mélange est utilisé par un ou plusieurs autres mélanges'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private mixService: MixService,
|
||||
private recipeService: RecipeService,
|
||||
|
@ -116,6 +126,7 @@ export class MixEditorComponent extends SubscribingComponent {
|
|||
}
|
||||
|
||||
delete() {
|
||||
this.deleting = true
|
||||
this.subscribeAndNavigate(this.mixService.delete(this.mixId), `/color/edit/${this.recipeId}`)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="mix-actions d-flex flex-row justify-content-between">
|
||||
<ng-container *ngIf="!editionMode">
|
||||
<div class="flex-grow-1">
|
||||
<mat-form-field class="dark">
|
||||
<mat-form-field class="dark location-input">
|
||||
<mat-label>Casier</mat-label>
|
||||
<input matInput type="text" [(ngModel)]="mix.location" (change)="changeLocation($event)"/>
|
||||
</mat-form-field>
|
||||
|
@ -15,12 +15,17 @@
|
|||
<button mat-raised-button color="accent" (click)="printingConfirmBox.show()">Imprimer</button>
|
||||
</div>
|
||||
<div>
|
||||
<button mat-raised-button color="accent" (click)="deduct.emit()">Déduire</button>
|
||||
<button mat-raised-button color="accent" (click)="deduct.emit()" disabled title="WIP">Déduire</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="editionMode">
|
||||
<div class="flex-grow-1"></div>
|
||||
<button mat-raised-button color="accent" routerLink="/color/edit/mix/{{recipe.id}}/{{mix.id}}">Modifier</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
routerLink="/color/edit/mix/{{recipe.id}}/{{mix.id}}">
|
||||
Modifier
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
@ -90,9 +95,29 @@
|
|||
<td mat-footer-cell *matFooterCellDef>{{units}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="simdut">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let mixMaterial; let i = index">
|
||||
<ng-container
|
||||
*ngIf="(!hoveredMixMaterial && i === 0) || hoveredMixMaterial === mixMaterial">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[disabled]="!hasSimdutMap[mixMaterial.material.id]"
|
||||
(click)="openSimdutFile(mixMaterial)">
|
||||
Fiche signalitique
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td mat-footer-cell *matFooterCellDef></td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="mixColumns"></tr>
|
||||
<tr mat-row *matRowDef="let mixMaterial; columns: mixColumns"
|
||||
[class.low-quantity]="!editionMode && isInLowQuantity(mixMaterial.id)"></tr>
|
||||
<tr mat-row
|
||||
*matRowDef="let mixMaterial; columns: mixColumns"
|
||||
[class.low-quantity]="!editionMode && isInLowQuantity(mixMaterial.id)"
|
||||
(mouseover)="hoveredMixMaterial = mixMaterial">
|
||||
</tr>
|
||||
<ng-container *ngIf="!editionMode">
|
||||
<tr mat-footer-row *matFooterRowDef="mixColumns"></tr>
|
||||
</ng-container>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import '../../../../../custom-theme'
|
||||
|
||||
mat-expansion-panel
|
||||
width: 40rem
|
||||
width: 48rem
|
||||
margin: 2rem 0
|
||||
|
||||
.mix-actions
|
||||
|
@ -14,6 +14,9 @@ mat-expansion-panel
|
|||
.low-quantity
|
||||
background-color: #ffb3b3
|
||||
|
||||
.location-input
|
||||
width: 10rem
|
||||
|
||||
::ng-deep span.mix-calculated-quantity
|
||||
&:first-child
|
||||
color: green
|
||||
|
|
|
@ -8,6 +8,8 @@ import {PtouchPrinter} from '../../ptouchPrint'
|
|||
import {ConfirmBoxComponent} from '../../../shared/components/confirm-box/confirm-box.component'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {AlertService} from '../../../shared/service/alert.service'
|
||||
import {environment} from '../../../../../environments/environment'
|
||||
import {MaterialService} from '../../../material/service/material.service'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-mix-table',
|
||||
|
@ -15,7 +17,7 @@ import {AlertService} from '../../../shared/service/alert.service'
|
|||
styleUrls: ['./mix-table.component.sass']
|
||||
})
|
||||
export class MixTableComponent extends SubscribingComponent {
|
||||
private readonly COLUMNS = ['material', 'materialType', 'quantity', 'quantityCalculated', 'quantityUnits']
|
||||
private readonly COLUMNS = ['material', 'materialType', 'quantity', 'quantityCalculated', 'quantityUnits', 'simdut']
|
||||
private readonly COLUMNS_STATIC = ['material', 'materialType', 'quantityStatic', 'quantityUnits']
|
||||
|
||||
@ViewChild('printingConfirmBox') printingConfirmBox: ConfirmBoxComponent
|
||||
|
@ -33,11 +35,14 @@ export class MixTableComponent extends SubscribingComponent {
|
|||
mixColumns = this.COLUMNS
|
||||
units = UNIT_MILLILITER
|
||||
computedQuantities: { id: number, percents: boolean, quantity: number }[] = []
|
||||
hoveredMixMaterial: MixMaterial | null
|
||||
hasSimdutMap: any = {}
|
||||
|
||||
// BPac printer
|
||||
printer: PtouchPrinter | null
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private alertService: AlertService,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
|
@ -63,6 +68,12 @@ export class MixTableComponent extends SubscribingComponent {
|
|||
this.units$,
|
||||
u => this.convertQuantities(u)
|
||||
)
|
||||
|
||||
this.mix.mixMaterials.map(mm => mm.material).forEach(material => this.subscribe(
|
||||
this.materialService.hasSimdut(material.id),
|
||||
b => this.hasSimdutMap[material.id] = b
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
changeLocation(event: any) {
|
||||
|
@ -115,6 +126,10 @@ export class MixTableComponent extends SubscribingComponent {
|
|||
return Math.round(quantity * 1000) / 1000
|
||||
}
|
||||
|
||||
openSimdutFile(mixMaterial: MixMaterial) {
|
||||
window.open(`${environment.apiUrl}/material/${mixMaterial.material.id}/simdut`, '_blank')
|
||||
}
|
||||
|
||||
async print() {
|
||||
const base = this.mix.mixMaterials
|
||||
.map(ma => ma.material)
|
||||
|
|
|
@ -23,11 +23,13 @@
|
|||
<button mat-raised-button color="accent" (click)="addStep()">Ajouter</button>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let step; let i = index">
|
||||
<button mat-raised-button color="warn" (click)="removeStep(i)">Retirer</button>
|
||||
<ng-container *ngIf="(!hoveredStep && i === 0) || hoveredStep === step">
|
||||
<button mat-raised-button color="warn" (click)="removeStep(i)">Retirer</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns"></tr>
|
||||
<tr mat-row *matRowDef="let step; columns: columns"></tr>
|
||||
<tr mat-row *matRowDef="let step; columns: columns" (mouseover)="hoveredStep = step"></tr>
|
||||
</table>
|
||||
</mat-expansion-panel>
|
||||
|
|
|
@ -13,6 +13,8 @@ export class StepTableComponent {
|
|||
|
||||
@Input() steps: RecipeStep[]
|
||||
|
||||
hoveredStep : RecipeStep | null
|
||||
|
||||
addStep() {
|
||||
this.steps.push({id: null, message: ""})
|
||||
this.stepTable.renderRows()
|
||||
|
|
|
@ -20,7 +20,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
|
@ -30,7 +30,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Description',
|
||||
icon: 'text',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une description est requise'}
|
||||
]
|
||||
|
@ -41,7 +41,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
icon: 'palette',
|
||||
type: 'color',
|
||||
defaultValue: "#ffffff",
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une couleur est requise'}
|
||||
]
|
||||
|
@ -53,7 +53,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
min: 0,
|
||||
max: 100,
|
||||
defaultValue: 10,
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Le lustre de la couleur est requis'}
|
||||
]
|
||||
|
@ -86,7 +86,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Bannière',
|
||||
icon: 'domain',
|
||||
type: 'select',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une bannière est requise'}
|
||||
],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Component, ViewChild} from '@angular/core'
|
||||
import {ErrorHandlingComponent, SubscribingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {Recipe} from '../../../shared/model/recipe.model'
|
||||
import {RecipeService} from '../../services/recipe.service'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
|
@ -10,7 +10,7 @@ import {AccountService} from '../../../accounts/services/account.service'
|
|||
import {EmployeePermission} from '../../../shared/model/employee'
|
||||
import {EntityEditComponent} from '../../../shared/components/entity-edit/entity-edit.component'
|
||||
import {ImagesEditorComponent} from '../../components/images-editor/images-editor.component'
|
||||
import {ErrorHandler, ErrorModel, ErrorService} from '../../../shared/service/error.service'
|
||||
import {ErrorModel, ErrorService} from '../../../shared/service/error.service'
|
||||
import {AlertService} from '../../../shared/service/alert.service'
|
||||
|
||||
@Component({
|
||||
|
@ -30,7 +30,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
|
@ -40,7 +40,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Description',
|
||||
icon: 'text',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une description est requise'}
|
||||
]
|
||||
|
@ -50,7 +50,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Couleur',
|
||||
icon: 'palette',
|
||||
type: 'color',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une couleur est requise'}
|
||||
]
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<div class="d-flex flex-column">
|
||||
<div class="mt-1 pb-2">
|
||||
<button mat-raised-button color="primary" routerLink="/color/list">Retour</button>
|
||||
<button mat-raised-button color="primary">Version Excel</button>
|
||||
<button mat-raised-button color="primary" disabled title="WIP">Version Excel</button>
|
||||
<button mat-raised-button color="accent" (click)="saveModifications()" [disabled]="!hasModifications">
|
||||
Enregistrer
|
||||
</button>
|
||||
<button mat-raised-button color="accent" (click)="deductQuantities()">Déduire</button>
|
||||
<button mat-raised-button color="accent" (click)="deductQuantities()" disabled title="WIP">Déduire</button>
|
||||
</div>
|
||||
<cre-unit-selector (unitChange)="changeUnits($event)"></cre-unit-selector>
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,8 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
saveModifications() {
|
||||
this.subscribe(
|
||||
this.recipeService.saveExplorerModifications(this.recipe.id, this.note, this.mixesLocationChanges),
|
||||
() => this.alertService.pushSuccess('Les modifications ont été enregistrées')
|
||||
() => this.alertService.pushSuccess('Les modifications ont été enregistrées'),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -103,7 +104,8 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
performDeductQuantities(observable: Observable<void>) {
|
||||
this.subscribe(
|
||||
observable,
|
||||
() => this.alertService.pushSuccess('Les quantités des produits utilisés ont été déduites de l\'inventaire')
|
||||
() => this.alertService.pushSuccess('Les quantités des produits utilisés ont été déduites de l\'inventaire'),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {Component} from '@angular/core'
|
||||
import {CompanyService} from '../../service/company.service'
|
||||
import {Validators} from '@angular/forms'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
|
@ -18,7 +17,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
|
|
|
@ -2,7 +2,6 @@ import {Component} from '@angular/core'
|
|||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {Company} from '../../../shared/model/company.model'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
import {Validators} from '@angular/forms'
|
||||
import {CompanyService} from '../../service/company.service'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {ErrorModel, ErrorService} from '../../../shared/service/error.service'
|
||||
|
@ -20,16 +19,20 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
deleting = false
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 409,
|
||||
filter: error => error.status === 409 && !this.deleting,
|
||||
messageProducer: error => `Une bannière avec le nom '${error.error.id}' existe déjà`
|
||||
}, {
|
||||
filter: error => error.status === 409 && this.deleting,
|
||||
messageProducer: () => 'Cette bannière est utilisée par une ou plusieurs recettes'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
|
@ -61,6 +64,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
}
|
||||
|
||||
delete() {
|
||||
this.deleting = true
|
||||
this.subscribeAndNavigate(
|
||||
this.companyService.delete(this.company.id),
|
||||
'/catalog/company/list'
|
||||
|
|
|
@ -1,56 +1,10 @@
|
|||
<mat-card class="x-centered mt-5">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Création d'un utilisateur</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field>
|
||||
<mat-label>Numéro d'employé</mat-label>
|
||||
<input matInput type="text" [formControl]="idControl"/>
|
||||
<mat-icon svgIcon="pound" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="idControl.invalid">
|
||||
<span *ngIf="idControl.errors.required">Un numéro d'employé est requis</span>
|
||||
<span *ngIf="idControl.errors.pattern">Le numéro d'employé doit être un nombre</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Prénom</mat-label>
|
||||
<input matInput type="text" [formControl]="firstNameControl"/>
|
||||
<mat-icon svgIcon="account" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="firstNameControl.invalid">
|
||||
<span *ngIf="firstNameControl.errors.required">Un prénom est requis</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nom</mat-label>
|
||||
<input matInput type="text" [formControl]="lastNameControl"/>
|
||||
<mat-icon svgIcon="account" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="lastNameControl.invalid">
|
||||
<span *ngIf="lastNameControl.errors.required">Un nom est requis</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Mot de passe</mat-label>
|
||||
<input matInput type="password" [formControl]="passwordControl"/>
|
||||
<mat-icon svgIcon="lock" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="passwordControl.invalid">
|
||||
<span *ngIf="passwordControl.errors.required">Un mot de passe est requis</span>
|
||||
<span *ngIf="passwordControl.errors.minlength">Le mot de passe doit comprendre au moins 8 caractères</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="group$ | async as groups">
|
||||
<mat-label>Groupe</mat-label>
|
||||
<mat-select [formControl]="groupControl">
|
||||
<mat-option [value]="null">Aucun</mat-option>
|
||||
<mat-option *ngFor="let group of groups" [value]="group.id">{{group.name}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon svgIcon="account-multiple" matSuffix></mat-icon>
|
||||
</mat-form-field>
|
||||
<cre-permissions-field #permissionsField></cre-permissions-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" routerLink="/employee/list">Retour</button>
|
||||
<button mat-raised-button color="accent" (click)="submit()" [disabled]="form.invalid">Créer</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
<cre-entity-add
|
||||
title="Création d'un utilisateur"
|
||||
backButtonLink="/employee/list"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)">
|
||||
</cre-entity-add>
|
||||
|
||||
<ng-template #permissionsTemplateRef>
|
||||
<cre-permissions-field></cre-permissions-field>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
mat-card
|
||||
max-width: 90rem
|
||||
cre-entity-add
|
||||
::ng-deep mat-card
|
||||
max-width: 90rem
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import {Component, ViewChild} from '@angular/core'
|
||||
import {FormControl, FormGroup, Validators} from '@angular/forms'
|
||||
import {PermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {EmployeeGroup} from '../../../shared/model/employee'
|
||||
import {Observable} from 'rxjs'
|
||||
import {Component, ContentChildren, ViewChild, ViewContainerRef} from '@angular/core'
|
||||
import {Validators} from '@angular/forms'
|
||||
import {
|
||||
currentPermissionsFieldComponent,
|
||||
PermissionsFieldComponent
|
||||
} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {GroupService} from '../../../groups/services/group.service'
|
||||
import {EmployeeService} from '../../services/employee.service'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
import {map} from 'rxjs/operators'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-add',
|
||||
|
@ -15,16 +18,62 @@ import {ErrorService} from '../../../shared/service/error.service'
|
|||
styleUrls: ['./add.component.sass']
|
||||
})
|
||||
export class AddComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('permissionsField', {static: true}) permissionsField: PermissionsFieldComponent
|
||||
@ViewChild('permissionsTemplateRef', {static: true}) permissionsTemplateRef
|
||||
|
||||
form: FormGroup
|
||||
idControl: FormControl
|
||||
firstNameControl: FormControl
|
||||
lastNameControl: FormControl
|
||||
passwordControl: FormControl
|
||||
groupControl: FormControl
|
||||
|
||||
group$: Observable<EmployeeGroup[]> | null
|
||||
formFields: FormField[] = [{
|
||||
name: 'id',
|
||||
label: 'Numéro d\'employé',
|
||||
icon: 'pound',
|
||||
type: 'number',
|
||||
required: true,
|
||||
validator: Validators.compose([Validators.pattern(new RegExp('^[0-9]+$')), Validators.min(0)]),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un numéro d\'employé est requis'},
|
||||
{conditionFn: errors => errors.pattern, message: 'Le numéro d\'employé doit être un nombre'}
|
||||
]
|
||||
}, {
|
||||
name: 'firstName',
|
||||
label: 'Prénom',
|
||||
icon: 'account',
|
||||
type: 'text',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un prénom est requis'}
|
||||
]
|
||||
}, {
|
||||
name: 'lastName',
|
||||
label: 'Nom',
|
||||
icon: 'account',
|
||||
type: 'text',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
}, {
|
||||
name: 'password',
|
||||
label: 'Mot de passe',
|
||||
icon: 'lock',
|
||||
type: 'password',
|
||||
required: true,
|
||||
validator: Validators.minLength(8),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un mot de passe est requis'},
|
||||
{conditionFn: errors => errors.minlength, message: 'Le mot de passe doit comprendre au moins 8 caractères'}
|
||||
]
|
||||
}, {
|
||||
name: 'groupId',
|
||||
label: 'Groupe',
|
||||
icon: 'account-multiple',
|
||||
type: 'select',
|
||||
defaultValue: -1,
|
||||
options$: this.groupService.all.pipe(map(groups => groups.map(g => {
|
||||
return {value: g.id, label: g.name}
|
||||
})))
|
||||
}, {
|
||||
name: 'permissions',
|
||||
label: 'Permissions',
|
||||
type: 'permissions'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private employeeService: EmployeeService,
|
||||
|
@ -36,35 +85,23 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
|
||||
this.group$ = this.groupService.all
|
||||
|
||||
this.idControl = new FormControl(null, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$')), Validators.min(0)]))
|
||||
this.firstNameControl = new FormControl(null, Validators.required)
|
||||
this.lastNameControl = new FormControl(null, Validators.required)
|
||||
this.passwordControl = new FormControl(null, Validators.compose([Validators.required, Validators.minLength(8)]))
|
||||
this.groupControl = new FormControl(null, Validators.min(0))
|
||||
this.form = new FormGroup({
|
||||
id: this.idControl,
|
||||
firstName: this.firstNameControl,
|
||||
lastName: this.lastNameControl,
|
||||
password: this.passwordControl,
|
||||
group: this.groupControl
|
||||
})
|
||||
this.formFields[this.formFields.length - 1].template = this.permissionsTemplateRef
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.permissionsField.valid() && this.form.valid) {
|
||||
submit(values) {
|
||||
const permissionsField = currentPermissionsFieldComponent
|
||||
if (permissionsField.valid()) {
|
||||
this.subscribeAndNavigate(
|
||||
this.employeeService.save(
|
||||
parseInt(this.idControl.value),
|
||||
this.firstNameControl.value,
|
||||
this.lastNameControl.value,
|
||||
this.passwordControl.value,
|
||||
this.groupControl.value,
|
||||
this.permissionsField.allEnabledPermissions
|
||||
values.id,
|
||||
values.firstName,
|
||||
values.lastName,
|
||||
values.password,
|
||||
values.groupId,
|
||||
permissionsField.allEnabledPermissions
|
||||
),
|
||||
'/employee/list'
|
||||
)
|
||||
|
|
|
@ -1,50 +1,15 @@
|
|||
<mat-card *ngIf="employee" class="x-centered mt-5">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Modification de l'utilisateur #{{employee.id}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field>
|
||||
<mat-label>Numéro d'employé</mat-label>
|
||||
<input matInput type="text" [formControl]="idControl"/>
|
||||
<mat-icon svgIcon="pound" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="idControl.invalid">
|
||||
<span *ngIf="idControl.errors.required">Un numéro d'employé est requis</span>
|
||||
<span *ngIf="idControl.errors.pattern">Le numéro d'employé doit être un nombre</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Prénom</mat-label>
|
||||
<input matInput type="text" [formControl]="firstNameControl"/>
|
||||
<mat-icon svgIcon="account" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="firstNameControl.invalid">
|
||||
<span *ngIf="firstNameControl.errors.required">Un prénom est requis</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nom</mat-label>
|
||||
<input matInput type="text" [formControl]="lastNameControl"/>
|
||||
<mat-icon svgIcon="account" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="lastNameControl.invalid">
|
||||
<span *ngIf="lastNameControl.errors.required">Un nom est requis</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="group$ | async as groups">
|
||||
<mat-label>Groupe</mat-label>
|
||||
<mat-select [formControl]="groupControl">
|
||||
<mat-option [value]="null">Aucun</mat-option>
|
||||
<mat-option *ngFor="let group of groups" [value]="group.id">{{group.name}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon svgIcon="account-multiple" matSuffix></mat-icon>
|
||||
</mat-form-field>
|
||||
<cre-permissions-field #permissionsField [enabledPermissions]="employee.permissions"></cre-permissions-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" routerLink="/employee/list">Retour</button>
|
||||
<button mat-raised-button color="warn" *ngIf="canRemoveEmployee" (click)="confirmBoxComponent.show()">Supprimer
|
||||
</button>
|
||||
<button mat-raised-button color="accent" (click)="submit(permissionsField)" [disabled]="form.invalid">Enregistrer</button>
|
||||
</mat-card-actions>
|
||||
<cre-confirm-box #confirmBoxComponent message="Voulez-vous vraiment supprimer l'employé {{employee.id}}?" (confirm)="delete()"></cre-confirm-box>
|
||||
</mat-card>
|
||||
<cre-entity-edit
|
||||
*ngIf="employee"
|
||||
title="Modification de l'utilisateur #{{employee.id}}"
|
||||
backButtonLink="/employee/list"
|
||||
deletePermission="REMOVE_EMPLOYEE"
|
||||
deleteConfirmMessage="Voulez-vous vraiment supprimer l'utilisateur #{{employee.id}}?"
|
||||
[entity]="employee"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)"
|
||||
(delete)="delete()">
|
||||
</cre-entity-edit>
|
||||
|
||||
<ng-template #permissionsTemplateRef>
|
||||
<cre-permissions-field [enabledPermissions]="employee.explicitPermissions"></cre-permissions-field>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
mat-card
|
||||
max-width: 90rem
|
||||
cre-entity-edit
|
||||
::ng-deep mat-card
|
||||
max-width: 90rem
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {Component} from '@angular/core'
|
||||
import {PermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
|
||||
import {Component, ViewChild} from '@angular/core'
|
||||
import {currentPermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {EmployeeService} from '../../services/employee.service'
|
||||
import {GroupService} from '../../../groups/services/group.service'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {Observable} from 'rxjs'
|
||||
import {Employee, EmployeeGroup, EmployeePermission} from '../../../shared/model/employee'
|
||||
import {Employee} from '../../../shared/model/employee'
|
||||
import {AccountService} from '../../../accounts/services/account.service'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
import {map} from 'rxjs/operators'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-edit',
|
||||
|
@ -16,20 +16,52 @@ import {ErrorService} from '../../../shared/service/error.service'
|
|||
styleUrls: ['./edit.component.sass']
|
||||
})
|
||||
export class EditComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('permissionsTemplateRef', {static: true}) permissionsTemplateRef
|
||||
|
||||
employee: Employee | null
|
||||
|
||||
group$: Observable<EmployeeGroup[]> | null
|
||||
|
||||
private _idControl: FormControl
|
||||
private _firstNameControl: FormControl
|
||||
private _lastNameControl: FormControl
|
||||
private _groupControl: FormControl
|
||||
formFields: FormField[] = [{
|
||||
name: 'id',
|
||||
label: 'Numéro d\'employé',
|
||||
icon: 'pound',
|
||||
type: 'number',
|
||||
readonly: true
|
||||
}, {
|
||||
name: 'firstName',
|
||||
label: 'Prénom',
|
||||
icon: 'account',
|
||||
type: 'text',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un prénom est requis'}
|
||||
]
|
||||
}, {
|
||||
name: 'lastName',
|
||||
label: 'Nom',
|
||||
icon: 'account',
|
||||
type: 'text',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'}
|
||||
]
|
||||
}, {
|
||||
name: 'groupId',
|
||||
label: 'Groupe',
|
||||
icon: 'account-multiple',
|
||||
type: 'select',
|
||||
valueFn: employee => employee.group ? employee.group.id : -1,
|
||||
options$: this.groupService.all.pipe(map(groups => groups.map(g => {
|
||||
return {value: g.id, label: g.name}
|
||||
})))
|
||||
}, {
|
||||
name: 'permissions',
|
||||
label: 'Permissions',
|
||||
type: 'permissions'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private employeeService: EmployeeService,
|
||||
private groupService: GroupService,
|
||||
private formBuilder: FormBuilder,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
|
@ -46,21 +78,22 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
'/employee/list'
|
||||
)
|
||||
|
||||
this.group$ = this.groupService.all
|
||||
this.formFields[this.formFields.length - 1].template = this.permissionsTemplateRef
|
||||
}
|
||||
|
||||
submit(permissionsField: PermissionsFieldComponent) {
|
||||
if (permissionsField.valid() && this.form.valid) {
|
||||
submit(values) {
|
||||
const permissionsField = currentPermissionsFieldComponent
|
||||
if (permissionsField.valid()) {
|
||||
this.subscribe(
|
||||
this.employeeService.update(
|
||||
parseInt(this.idControl.value),
|
||||
this.firstNameControl.value,
|
||||
this.lastNameControl.value,
|
||||
parseInt(values.id),
|
||||
values.firstName,
|
||||
values.lastName,
|
||||
permissionsField.allEnabledPermissions
|
||||
),
|
||||
() => {
|
||||
const group = parseInt(this._groupControl.value)
|
||||
if (!isNaN(group)) {
|
||||
const group = values.groupId
|
||||
if (group >= 0) {
|
||||
this.subscribeAndNavigate(
|
||||
this.groupService.addEmployeeToGroup(group, this.employee),
|
||||
'/employee/list'
|
||||
|
@ -72,7 +105,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
'/employee/list'
|
||||
)
|
||||
} else {
|
||||
this.router.navigate(['/employee/list'])
|
||||
this.urlUtils.navigateTo('/employee/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,47 +119,4 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
'/employee/list'
|
||||
)
|
||||
}
|
||||
|
||||
get form(): FormGroup {
|
||||
return this.formBuilder.group({
|
||||
id: this._idControl,
|
||||
firstName: this._firstNameControl,
|
||||
lastName: this._lastNameControl,
|
||||
group: this._groupControl
|
||||
})
|
||||
}
|
||||
|
||||
get idControl(): FormControl {
|
||||
this._idControl = this.lazyControl(this._idControl, () => new FormControl({value: this.employee.id, disabled: true}, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$')), Validators.min(0)])))
|
||||
return this._idControl
|
||||
}
|
||||
|
||||
get firstNameControl(): FormControl {
|
||||
this._firstNameControl = this.lazyControl(this._firstNameControl, () => new FormControl(this.employee.firstName, Validators.required))
|
||||
return this._firstNameControl
|
||||
}
|
||||
|
||||
get lastNameControl(): FormControl {
|
||||
this._lastNameControl = this.lazyControl(this._lastNameControl, () => new FormControl(this.employee.lastName, Validators.required))
|
||||
return this._lastNameControl
|
||||
}
|
||||
|
||||
get groupControl(): FormControl {
|
||||
this._groupControl = this.lazyControl(this._groupControl, () => new FormControl(this.employee.group?.id))
|
||||
return this._groupControl
|
||||
}
|
||||
|
||||
private lazyControl(control: FormControl, provider: () => FormControl): FormControl {
|
||||
if (control) {
|
||||
return control
|
||||
}
|
||||
if (this.employee) {
|
||||
return provider()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
get canRemoveEmployee(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.REMOVE_EMPLOYEE)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,13 @@
|
|||
<div class="action-bar">
|
||||
<button *ngIf="canEditEmployee" mat-raised-button color="accent" routerLink="/employee/add">Ajouter</button>
|
||||
</div>
|
||||
<cre-entity-list
|
||||
addLink="/employee/add"
|
||||
addPermission="EDIT_EMPLOYEE"
|
||||
[entities$]="employees$"
|
||||
[columns]="columns"
|
||||
[buttons]="buttons"
|
||||
[expandable]="true"
|
||||
[rowDetailsTemplate]="employeeDetailsTemplate">
|
||||
</cre-entity-list>
|
||||
|
||||
<table class="mx-auto" *ngIf="employees$ | async as employees" mat-table multiTemplateDataRows [dataSource]="employees">
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>Numéro d'employé</th>
|
||||
<td mat-cell *matCellDef="let employee">{{employee.id}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Nom</th>
|
||||
<td mat-cell *matCellDef="let employee">{{employee.firstName}} {{employee.lastName}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="group">
|
||||
<th mat-header-cell *matHeaderCellDef>Groupe</th>
|
||||
<td mat-cell *matCellDef="let employee">
|
||||
<ng-container *ngIf="employee.group">{{employee.group.name}}</ng-container>
|
||||
<ng-container *ngIf="!employee.group">Aucun</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="lastLogin">
|
||||
<th mat-header-cell *matHeaderCellDef>Dernière connexion</th>
|
||||
<td mat-cell *matCellDef="let employee">
|
||||
<ng-container *ngIf="employee.lastLoginTime">{{getDate(employee.lastLoginTime).toLocaleString()}}</ng-container>
|
||||
<ng-container *ngIf="!employee.lastLoginTime">Jamais</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="permissionCount">
|
||||
<th mat-header-cell *matHeaderCellDef>Permissions</th>
|
||||
<td mat-cell *matCellDef="let employee">
|
||||
<ng-container *ngIf="employee.permissions">{{employee.permissions.length}}</ng-container>
|
||||
<ng-container *ngIf="!employee.permissions">0</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="editButton">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!canEditEmployee" *matCellDef="let employee">
|
||||
<button mat-raised-button color="accent" routerLink="/employee/edit/{{employee.id}}">Modifier</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="editPasswordButton">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!canEditEmployeePassword" *matCellDef="let employee">
|
||||
<button mat-raised-button color="accent" routerLink="/employee/password/edit/{{employee.id}}">Modifier mot de passe</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let employee" [attr.colspan]="columns.length">
|
||||
<div class="entity-detail"
|
||||
[@detailExpand]="employee === expandedElement ? 'expanded' : 'collapsed'">
|
||||
<cre-permissions-list [employee]="employee" class="w-100"></cre-permissions-list>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let employee; columns: columns"
|
||||
class="entity-row can-expand"
|
||||
[class.expanded-row]="expandedElement === employee"
|
||||
(click)="expandedElement = expandedElement === employee ? null : employee">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="detail-row"></tr>
|
||||
</table>
|
||||
<ng-template #employeeDetailsTemplate let-employee="entity">
|
||||
<cre-permissions-list [employee]="employee" class="w-100"></cre-permissions-list>
|
||||
</ng-template>
|
||||
|
|
|
@ -23,9 +23,22 @@ import {ErrorService} from '../../../shared/service/error.service'
|
|||
})
|
||||
export class ListComponent extends ErrorHandlingComponent {
|
||||
employees$: Observable<Employee[]>
|
||||
columns = ['id', 'name', 'group', 'permissionCount', 'lastLogin', 'editButton', 'editPasswordButton']
|
||||
|
||||
expandedElement: Employee | null
|
||||
columns = [
|
||||
{def: 'id', title: 'Numéro d\'employé', valueFn: e => e.id},
|
||||
{def: 'name', title: 'Nom', valueFn: e => `${e.firstName} ${e.lastName}`},
|
||||
{def: 'group', title: 'Groupe', valueFn: e => e.group ? e.group.name : 'Aucun'},
|
||||
{def: 'permissionCount', title: 'Nombre de permissions', valueFn: e => e.permissions.length},
|
||||
{def: 'lastLogin', title: 'Dernière connexion', valueFn: e => e.lastLoginTime ? this.getDate(e.lastLoginTime).toLocaleString() : 'Jamais'}
|
||||
]
|
||||
buttons = [{
|
||||
text: 'Modifier',
|
||||
linkFn: employee => `/employee/edit/${employee.id}`,
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE
|
||||
}, {
|
||||
text: 'Modifier mot de passe',
|
||||
linkFn: employee => `/employee/password/edit/${employee.id}`,
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE_PASSWORD
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private employeeService: EmployeeService,
|
||||
|
@ -44,12 +57,4 @@ export class ListComponent extends ErrorHandlingComponent {
|
|||
getDate(dateString: string) {
|
||||
return new Date(dateString)
|
||||
}
|
||||
|
||||
get canEditEmployee(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.EDIT_EMPLOYEE)
|
||||
}
|
||||
|
||||
get canEditEmployeePassword(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.EDIT_EMPLOYEE_PASSWORD)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
<mat-card class="x-centered mt-5">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Création d'un groupe</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field class="pb-3">
|
||||
<mat-label>Nom</mat-label>
|
||||
<input matInput type="text" [formControl]="nameControl"/>
|
||||
<mat-icon svgIcon="account-multiple" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="nameControl.invalid">
|
||||
<span *ngIf="nameControl.errors.required">Un nom est requis</span>
|
||||
<span *ngIf="nameControl.errors.minlength">Le nom d'un groupe doit comprendre au moins 3 caractères</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<cre-permissions-field #permissionsField></cre-permissions-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" routerLink="/group/list">Retour</button>
|
||||
<button mat-raised-button color="accent" (click)="submit()" [disabled]="form.invalid">Créer</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
<cre-entity-add
|
||||
title="Création d'un groupe"
|
||||
backButtonLink="/group/list"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)">
|
||||
</cre-entity-add>
|
||||
|
||||
<ng-template #permissionsTemplateRef>
|
||||
<cre-permissions-field></cre-permissions-field>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
mat-card
|
||||
max-width: 90rem
|
||||
|
||||
mat-checkbox
|
||||
font-size: .8em
|
||||
cre-entity-add
|
||||
::ng-deep mat-card
|
||||
max-width: 90rem
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import {Component, ViewChild} from '@angular/core'
|
||||
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
|
||||
import {Validators} from '@angular/forms'
|
||||
import {GroupService} from '../../services/group.service'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {PermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {currentPermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-add',
|
||||
|
@ -12,13 +13,26 @@ import {ErrorService} from '../../../shared/service/error.service'
|
|||
styleUrls: ['./add.component.sass']
|
||||
})
|
||||
export class AddComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('permissionsField', {static: true}) permissionsField: PermissionsFieldComponent
|
||||
@ViewChild('permissionsTemplateRef', {static: true}) permissionsTemplateRef
|
||||
|
||||
form: FormGroup
|
||||
nameControl: FormControl
|
||||
formFields: FormField[] = [{
|
||||
name: 'name',
|
||||
label: 'Nom',
|
||||
icon: 'account-multiple',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validator: Validators.minLength(3),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'},
|
||||
{conditionFn: errors => errors.minlength, message: 'Le nom d\'un groupe doit comprendre au moins 3 caractères'}
|
||||
]
|
||||
}, {
|
||||
name: 'permissions',
|
||||
label: 'Permissions',
|
||||
type: 'permissions'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private groupService: GroupService,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
|
@ -27,17 +41,17 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.nameControl = new FormControl(null, Validators.compose([Validators.required, Validators.minLength(3)]))
|
||||
this.form = this.formBuilder.group({
|
||||
name: this.nameControl
|
||||
})
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.formFields[this.formFields.length - 1].template = this.permissionsTemplateRef
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.form.valid && this.permissionsField.valid()) {
|
||||
submit(values) {
|
||||
const permissionsField = currentPermissionsFieldComponent
|
||||
if (permissionsField.valid()) {
|
||||
this.subscribeAndNavigate(
|
||||
this.groupService.save(this.nameControl.value, this.permissionsField.allEnabledPermissions),
|
||||
this.groupService.save(values.name, permissionsField.allEnabledPermissions),
|
||||
'/group/list'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,15 @@
|
|||
<mat-card *ngIf="group" class="mt-5 x-centered">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Modifier le groupe {{group.name}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field>
|
||||
<mat-label>Nom</mat-label>
|
||||
<input matInput type="text" [formControl]="nameControl"/>
|
||||
<mat-icon svgIcon="account-multiple" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="nameControl.invalid">
|
||||
<span *ngIf="nameControl.errors.required">Un nom est requis</span>
|
||||
<span *ngIf="nameControl.errors.minlength">Le nom d'un groupe doit comprendre au moins 3 caractères</span>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<cre-permissions-field [enabledPermissions]="group.permissions" #permissionsField></cre-permissions-field>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" routerLink="/employee/list">Retour</button>
|
||||
<button mat-raised-button color="warn" *ngIf="canRemoveGroup" [disabled]="group.employeeCount > 0"
|
||||
[title]="group.employeeCount > 0 ? 'Il y a des employés dans le groupe' : ''"
|
||||
(click)="confirmBoxComponent.show()">Supprimer
|
||||
</button>
|
||||
<button mat-raised-button color="accent" (click)="submit()" [disabled]="form.invalid">Enregistrer</button>
|
||||
</mat-card-actions>
|
||||
<cre-confirm-box #confirmBoxComponent [message]="confirmBoxMessage" (confirm)="delete()"></cre-confirm-box>
|
||||
</mat-card>
|
||||
<cre-entity-edit
|
||||
*ngIf="group"
|
||||
title="Modifier le groupe {{group.name}}"
|
||||
backButtonLink="/group/list"
|
||||
deletePermission="REMOVE_EMPLOYEE_GROUP"
|
||||
deleteConfirmMessage="Voulez-vous vraiment supprimer le groupe {{group.name}}?"
|
||||
[entity]="group"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)"
|
||||
(delete)="delete()">
|
||||
</cre-entity-edit>
|
||||
|
||||
<ng-template #permissionsTemplateRef>
|
||||
<cre-permissions-field [enabledPermissions]="group.permissions"></cre-permissions-field>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
mat-card
|
||||
max-width: 90rem
|
||||
cre-entity-edit
|
||||
::ng-deep mat-card
|
||||
max-width: 90rem
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import {Component, ViewChild} from '@angular/core'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {EmployeeGroup, EmployeePermission} from '../../../shared/model/employee'
|
||||
import {EmployeeGroup} from '../../../shared/model/employee'
|
||||
import {GroupService} from '../../services/group.service'
|
||||
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
|
||||
import {PermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {Validators} from '@angular/forms'
|
||||
import {currentPermissionsFieldComponent} from '../../../shared/components/permissions-field/permissions-field.component'
|
||||
import {AccountService} from '../../../accounts/services/account.service'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-edit',
|
||||
|
@ -14,16 +15,29 @@ import {ErrorService} from '../../../shared/service/error.service'
|
|||
styleUrls: ['./edit.component.sass']
|
||||
})
|
||||
export class EditComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('permissionsField') permissionsField: PermissionsFieldComponent
|
||||
@ViewChild('permissionsTemplateRef', {static: true}) permissionsTemplateRef
|
||||
|
||||
formFields: FormField[] = [{
|
||||
name: 'name',
|
||||
label: 'Nom',
|
||||
icon: 'account-multiple',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validator: Validators.minLength(3),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un nom est requis'},
|
||||
{conditionFn: errors => errors.minlength, message: 'Le nom d\'un groupe doit comprendre au moins 3 caractères'}
|
||||
]
|
||||
}, {
|
||||
name: 'permissions',
|
||||
label: 'Permissions',
|
||||
type: 'permissions'
|
||||
}]
|
||||
group: EmployeeGroup | null
|
||||
|
||||
private _nameControl: FormControl
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private groupService: GroupService,
|
||||
private formBuilder: FormBuilder,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
|
@ -39,12 +53,15 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
group => this.group = group,
|
||||
'/group/list'
|
||||
)
|
||||
|
||||
this.formFields[this.formFields.length - 1].template = this.permissionsTemplateRef
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.form.valid && this.permissionsField.valid()) {
|
||||
submit(values): void {
|
||||
const permissionsField = currentPermissionsFieldComponent
|
||||
if (permissionsField.valid()) {
|
||||
this.subscribeAndNavigate(
|
||||
this.groupService.update(this.group.id, this.nameControl.value, this.permissionsField.allEnabledPermissions),
|
||||
this.groupService.update(this.group.id, values.name, permissionsField.allEnabledPermissions),
|
||||
'/group/list'
|
||||
)
|
||||
}
|
||||
|
@ -56,29 +73,4 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
'/group/list'
|
||||
)
|
||||
}
|
||||
|
||||
get form(): FormGroup {
|
||||
return this.formBuilder.group({
|
||||
name: this.nameControl
|
||||
})
|
||||
}
|
||||
|
||||
get confirmBoxMessage(): string {
|
||||
return `Voulez-vous vraiment supprimer le groupe ${this.group.name}?`
|
||||
}
|
||||
|
||||
get nameControl(): FormControl {
|
||||
if (this._nameControl) {
|
||||
return this._nameControl
|
||||
}
|
||||
if (this.group) {
|
||||
this._nameControl = new FormControl(this.group.name, Validators.compose([Validators.required, Validators.minLength(3)]))
|
||||
return this._nameControl
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
get canRemoveGroup(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.REMOVE_EMPLOYEE_GROUP)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,13 @@
|
|||
<div class="action-bar">
|
||||
<button *ngIf="canEditGroup" mat-raised-button color="accent" routerLink="/group/add">Ajouter</button>
|
||||
</div>
|
||||
<cre-entity-list
|
||||
addLink="/group/add"
|
||||
addPermission="EDIT_EMPLOYEE_GROUP"
|
||||
[entities$]="groups$"
|
||||
[columns]="columns"
|
||||
[buttons]="buttons"
|
||||
[expandable]="true"
|
||||
[rowDetailsTemplate]="groupDetailsTemplate">
|
||||
</cre-entity-list>
|
||||
|
||||
<table *ngIf="groups$ | async as groups" mat-table class="mx-auto" multiTemplateDataRows [dataSource]="groups">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>Nom</th>
|
||||
<td mat-cell *matCellDef="let group">{{group.name}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="permissionCount">
|
||||
<th mat-header-cell *matHeaderCellDef>Nombre de permissions</th>
|
||||
<td mat-cell *matCellDef="let group">{{group.permissions.length}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="employeeCount">
|
||||
<th mat-header-cell *matHeaderCellDef>Nombre d'utilisateurs</th>
|
||||
<td mat-cell *matCellDef="let group">{{group.employeeCount}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="defaultGroup">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!canSetBrowserDefaultGroup" *matCellDef="let group">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[disabled]="isDefaultGroup(group) ? true : null"
|
||||
(click)="setDefaultGroup(group)">
|
||||
<ng-container *ngIf="!isDefaultGroup(group)">
|
||||
Définir par défaut
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isDefaultGroup(group)">
|
||||
Par défaut
|
||||
</ng-container>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="editGroup">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!canEditGroup" *matCellDef="let group">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
routerLink="/group/edit/{{group.id}}">
|
||||
Modifier
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let group" [attr.colspan]="columns.length">
|
||||
<div class="entity-detail"
|
||||
[@detailExpand]="group == expandedElement && canViewEmployee ? 'expanded' : 'collapsed'">
|
||||
<cre-permissions-list [group]="group"></cre-permissions-list>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let group; columns: columns"
|
||||
class="entity-row"
|
||||
[class.expanded-row]="expandedElement === group"
|
||||
[class.can-expand]="canViewEmployee"
|
||||
(click)="expandedElement = expandedElement === group ? null : group">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="detail-row"></tr>
|
||||
</table>
|
||||
<ng-template #groupDetailsTemplate let-group="entity">
|
||||
<cre-permissions-list [group]="group"></cre-permissions-list>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import {Component} from '@angular/core'
|
||||
import {Observable} from 'rxjs'
|
||||
import {GroupService} from '../../services/group.service'
|
||||
import {EmployeeGroup, EmployeePermission} from '../../../shared/model/employee'
|
||||
import {takeUntil} from 'rxjs/operators'
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations'
|
||||
import {map} from 'rxjs/operators'
|
||||
import {AccountService} from '../../../accounts/services/account.service'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
|
@ -13,20 +11,26 @@ import {AlertService} from '../../../shared/service/alert.service'
|
|||
@Component({
|
||||
selector: 'cre-groups',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.sass'],
|
||||
animations: [
|
||||
trigger('detailExpand', [
|
||||
state('collapsed', style({height: '0px', minHeight: '0'})),
|
||||
state('expanded', style({height: '*'})),
|
||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
|
||||
])
|
||||
]
|
||||
styleUrls: ['./list.component.sass']
|
||||
})
|
||||
export class ListComponent extends ErrorHandlingComponent {
|
||||
groups$: Observable<EmployeeGroup[]>
|
||||
groups$ = this.groupService.all.pipe(map(groups => groups.filter(g => g.id >= 0)))
|
||||
defaultGroup: EmployeeGroup = null
|
||||
columns = ['name', 'permissionCount', 'employeeCount', 'defaultGroup', 'editGroup']
|
||||
expandedElement: EmployeeGroup | null
|
||||
columns = [
|
||||
{def: 'name', title: 'Nom', valueFn: g => g.name},
|
||||
{def: 'permissionCount', title: 'Nombre de permissions', valueFn: g => g.permissions.length},
|
||||
{def: 'employeeCount', title: 'Nombre d\'utilisateurs', valueFn: g => g.employeeCount}
|
||||
]
|
||||
buttons = [{
|
||||
text: 'Définir par défaut',
|
||||
clickFn: group => this.setDefaultGroup(group),
|
||||
permission: EmployeePermission.SET_BROWSER_DEFAULT_GROUP,
|
||||
disabledFn: group => this.isDefaultGroup(group)
|
||||
}, {
|
||||
text: 'Modifier',
|
||||
linkFn: group => `/group/edit/${group.id}`,
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE_GROUP
|
||||
}]
|
||||
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 404,
|
||||
|
@ -45,33 +49,22 @@ export class ListComponent extends ErrorHandlingComponent {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.groups$ = this.groupService.all.pipe(takeUntil(this.destroy$))
|
||||
this.subscribe(
|
||||
this.groupService.defaultGroup,
|
||||
group => this.defaultGroup = group,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
setDefaultGroup(group: EmployeeGroup) {
|
||||
this.subscribe(
|
||||
this.groupService.setDefaultGroup(group),
|
||||
() => this.defaultGroup = group
|
||||
() => this.defaultGroup = group,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
isDefaultGroup(group: EmployeeGroup): boolean {
|
||||
return this.defaultGroup && this.defaultGroup.id == group.id
|
||||
}
|
||||
|
||||
get canViewEmployee(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.VIEW_EMPLOYEE)
|
||||
}
|
||||
|
||||
get canEditGroup(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.EDIT_EMPLOYEE_GROUP)
|
||||
}
|
||||
|
||||
get canSetBrowserDefaultGroup(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.SET_BROWSER_DEFAULT_GROUP)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../shared/service/api.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {Employee, EmployeeGroup, EmployeePermission} from "../../shared/model/employee";
|
||||
import {Injectable} from '@angular/core'
|
||||
import {ApiService} from '../../shared/service/api.service'
|
||||
import {Observable} from 'rxjs'
|
||||
import {Employee, EmployeeGroup, EmployeePermission} from '../../shared/model/employee'
|
||||
import {tap} from 'rxjs/operators'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -14,10 +15,16 @@ export class GroupService {
|
|||
|
||||
get all(): Observable<EmployeeGroup[]> {
|
||||
return this.api.get<EmployeeGroup[]>('/employee/group')
|
||||
.pipe(tap(groups => groups.unshift({
|
||||
id: -1,
|
||||
name: 'Aucun',
|
||||
permissions: [],
|
||||
employeeCount: 0
|
||||
})))
|
||||
}
|
||||
|
||||
getById(id: number): Observable<EmployeeGroup> {
|
||||
return this.api.get<EmployeeGroup>(`/employee/group/${id}`);
|
||||
return this.api.get<EmployeeGroup>(`/employee/group/${id}`)
|
||||
}
|
||||
|
||||
get defaultGroup(): Observable<EmployeeGroup> {
|
||||
|
|
|
@ -18,7 +18,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un nom est requis'},
|
||||
]
|
||||
|
@ -28,7 +28,8 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Préfixe',
|
||||
icon: 'label-variant',
|
||||
type: 'text',
|
||||
validator: Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(3)]),
|
||||
required: true,
|
||||
validator: Validators.compose([Validators.minLength(3), Validators.maxLength(3)]),
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un préfixe est requis'},
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un nom est requis'},
|
||||
]
|
||||
|
@ -30,7 +30,8 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
label: 'Préfixe',
|
||||
icon: 'label-variant',
|
||||
type: 'text',
|
||||
validator: Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(3)]),
|
||||
required: true,
|
||||
validator: Validators.compose([Validators.minLength(3), Validators.maxLength(3)]),
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un préfixe est requis'},
|
||||
{
|
||||
|
@ -40,14 +41,19 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
]
|
||||
}
|
||||
]
|
||||
submittedValues: any | null
|
||||
|
||||
deleting = false
|
||||
submittedValues: any | null
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 409 && error.error.id === this.submittedValues.name,
|
||||
filter: error => error.status === 409 && !this.deleting && error.error.id === this.submittedValues.name,
|
||||
messageProducer: error => `Un type de produit avec le nom '${error.error.id}' existe déjà`
|
||||
}, {
|
||||
filter: error => error.status === 409 && error.error.id === this.submittedValues.prefix,
|
||||
filter: error => error.status === 409 && !this.deleting && error.error.id === this.submittedValues.prefix,
|
||||
messageProducer: error => `Un type de produit avec le préfixe '${error.error.id}' existe déjà`
|
||||
}, {
|
||||
filter: error => error.status === 409 && this.deleting,
|
||||
consumer: () => this.deleting = true,
|
||||
messageProducer: () => 'Ce type de produit est utilisé dans une ou plusieurs recettes ou produits'
|
||||
}]
|
||||
|
||||
constructor(
|
||||
|
@ -80,6 +86,7 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
}
|
||||
|
||||
delete() {
|
||||
this.deleting = true
|
||||
this.subscribeAndNavigate(
|
||||
this.materialTypeService.delete(this.materialType.id),
|
||||
'/catalog/materialtype/list'
|
||||
|
|
|
@ -20,7 +20,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Code',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un code est requis'}
|
||||
]
|
||||
|
@ -30,7 +30,8 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Quantité en inventaire',
|
||||
icon: 'beaker-outline',
|
||||
type: 'number',
|
||||
validator: Validators.compose([Validators.required, Validators.min(0)]),
|
||||
required: true,
|
||||
validator: Validators.min(0),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une quantité en inventaire est requise'},
|
||||
{conditionFn: errors => errors.min, message: 'La quantité doit être supérieure ou égale à 0'}
|
||||
|
@ -42,7 +43,7 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
label: 'Type de produit',
|
||||
icon: 'shape-outline',
|
||||
type: 'select',
|
||||
validator: Validators.required,
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un type de produit est requis'}
|
||||
],
|
||||
|
|
|
@ -11,112 +11,119 @@ import {environment} from '../../../../../environments/environment'
|
|||
import {ErrorModel, ErrorService} from '../../../shared/service/error.service'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-edit',
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrls: ['./edit.component.sass']
|
||||
selector: 'cre-edit',
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrls: ['./edit.component.sass']
|
||||
})
|
||||
export class EditComponent extends ErrorHandlingComponent {
|
||||
@ViewChild('simdutTemplate', {static: true}) simdutTemplateRef
|
||||
@ViewChild('simdutTemplate', {static: true}) simdutTemplateRef
|
||||
|
||||
material: Material | null
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Code',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un code est requis'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'inventoryQuantity',
|
||||
label: 'Quantité en inventaire',
|
||||
icon: 'beaker-outline',
|
||||
type: 'number',
|
||||
validator: Validators.compose([Validators.required, Validators.min(0)]),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une quantité en inventaire est requise'},
|
||||
{conditionFn: errors => errors.min, message: 'La quantité doit être supérieure ou égale à 0'}
|
||||
],
|
||||
step: '0.01'
|
||||
},
|
||||
{
|
||||
name: 'materialType',
|
||||
label: 'Type de produit',
|
||||
icon: 'shape-outline',
|
||||
type: 'select',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un type de produit est requis'}
|
||||
],
|
||||
valueFn: material => material.materialType.id,
|
||||
options$: this.materialTypeService.all.pipe(map(types => types.map(t => {
|
||||
return {value: t.id, label: t.name}
|
||||
})))
|
||||
},
|
||||
{
|
||||
name: 'simdutFile',
|
||||
label: 'Fiche signalitique',
|
||||
icon: 'file-outline',
|
||||
type: 'file',
|
||||
fileType: 'application/pdf'
|
||||
material: Material | null
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Code',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un code est requis'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'inventoryQuantity',
|
||||
label: 'Quantité en inventaire',
|
||||
icon: 'beaker-outline',
|
||||
type: 'number',
|
||||
required: true,
|
||||
validator: Validators.min(0),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une quantité en inventaire est requise'},
|
||||
{conditionFn: errors => errors.min, message: 'La quantité doit être supérieure ou égale à 0'}
|
||||
],
|
||||
step: '0.01'
|
||||
},
|
||||
{
|
||||
name: 'materialType',
|
||||
label: 'Type de produit',
|
||||
icon: 'shape-outline',
|
||||
type: 'select',
|
||||
required: true,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un type de produit est requis'}
|
||||
],
|
||||
valueFn: material => material.materialType.id,
|
||||
options$: this.materialTypeService.all.pipe(map(types => types.map(t => {
|
||||
return {value: t.id, label: t.name}
|
||||
})))
|
||||
},
|
||||
{
|
||||
name: 'simdutFile',
|
||||
label: 'Fiche signalitique',
|
||||
icon: 'file-outline',
|
||||
type: 'file',
|
||||
fileType: 'application/pdf'
|
||||
}
|
||||
]
|
||||
hasSimdut = false
|
||||
selectedSimdutFile: File | null
|
||||
|
||||
deleting = false
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 409 && !this.deleting,
|
||||
messageProducer: error => `Un produit avec le nom '${error.error.id}' existe déjà`
|
||||
}, {
|
||||
filter: error => error.status === 409 && this.deleting,
|
||||
consumer: () => this.deleting = false,
|
||||
messageProducer: () => `Ce produit est utilisé dans une plusieurs recettes`
|
||||
}]
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private materialTypeService: MaterialTypeService,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
]
|
||||
hasSimdut = false
|
||||
selectedSimdutFile: File | null
|
||||
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 409,
|
||||
messageProducer: error => `Un produit avec le nom '${error.error.id}' existe déjà`
|
||||
}]
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private materialTypeService: MaterialTypeService,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
this.formFields[3].template = this.simdutTemplateRef
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id'))
|
||||
this.subscribeEntityById(
|
||||
this.materialService,
|
||||
id,
|
||||
material => this.material = material,
|
||||
'/catalog/material/list'
|
||||
)
|
||||
|
||||
this.formFields[3].template = this.simdutTemplateRef
|
||||
this.subscribe(
|
||||
this.materialService.hasSimdut(id),
|
||||
b => this.hasSimdut = b
|
||||
)
|
||||
}
|
||||
|
||||
const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id'))
|
||||
this.subscribeEntityById(
|
||||
this.materialService,
|
||||
id,
|
||||
material => this.material = material,
|
||||
'/catalog/material/list'
|
||||
)
|
||||
submit(values) {
|
||||
this.subscribeAndNavigate(
|
||||
this.materialService.update(this.material.id, values.name, values.inventoryQuantity, values.materialType, this.selectedSimdutFile),
|
||||
'/catalog/material/list'
|
||||
)
|
||||
}
|
||||
|
||||
this.subscribe(
|
||||
this.materialService.hasSimdut(id),
|
||||
b => this.hasSimdut = b
|
||||
)
|
||||
}
|
||||
delete() {
|
||||
this.deleting = true
|
||||
this.subscribeAndNavigate(
|
||||
this.materialService.delete(this.material.id),
|
||||
'/catalog/material/list'
|
||||
)
|
||||
}
|
||||
|
||||
submit(values) {
|
||||
this.subscribeAndNavigate(
|
||||
this.materialService.update(this.material.id, values.name, values.inventoryQuantity, values.materialType, this.selectedSimdutFile),
|
||||
'/catalog/material/list'
|
||||
)
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.subscribeAndNavigate(
|
||||
this.materialService.delete(this.material.id),
|
||||
'/catalog/material/list'
|
||||
)
|
||||
}
|
||||
|
||||
openSimdutUrl() {
|
||||
const simdutUrl = environment.apiUrl + `/material/${this.material.id}/simdut`
|
||||
window.open(simdutUrl, '_blank')
|
||||
}
|
||||
openSimdutUrl() {
|
||||
const simdutUrl = environment.apiUrl + `/material/${this.material.id}/simdut`
|
||||
window.open(simdutUrl, '_blank')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ export class ListComponent extends ErrorHandlingComponent {
|
|||
)
|
||||
})
|
||||
},
|
||||
false,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<form *ngIf="form" [formGroup]="form">
|
||||
<ng-container *ngFor="let field of formFields">
|
||||
<ng-container
|
||||
*ngIf="field.type != 'checkbox' && field.type != 'select' && field.type != 'file' && field.type != 'slider'"
|
||||
*ngIf="!field.template && field.type != 'checkbox' && field.type != 'select' && field.type != 'file' && field.type != 'slider'"
|
||||
[ngTemplateOutlet]="fieldTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
|
@ -30,6 +30,10 @@
|
|||
[ngTemplateOutlet]="sliderTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="field.template"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
@ -44,7 +48,12 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<input matInput [type]="field.type" [formControl]="control" [step]="field.step ? field.step : null"/>
|
||||
<input
|
||||
matInput
|
||||
[type]="field.type"
|
||||
[formControl]="control"
|
||||
[step]="field.step ? field.step : null"
|
||||
[required]="field.required"/>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="control.invalid && field.errorMessages">
|
||||
<ng-container *ngFor="let errorMessage of field.errorMessages">
|
||||
|
@ -57,7 +66,7 @@
|
|||
<ng-template
|
||||
#checkboxTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-checkbox [formControl]="control">
|
||||
<mat-checkbox [formControl]="control" [required]="field.required">
|
||||
{{field.label}}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
|
@ -67,7 +76,7 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field *ngIf="field.options$ | async as options">
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="control">
|
||||
<mat-select [formControl]="control" [required]="field.required">
|
||||
<mat-option *ngFor="let option of options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</mat-option>
|
||||
|
@ -81,7 +90,11 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<ngx-mat-file-input [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
|
||||
<ngx-mat-file-input
|
||||
[accept]="field.fileType"
|
||||
[formControl]="control"
|
||||
[required]="field.required">
|
||||
</ngx-mat-file-input>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Component, EventEmitter, Input, Output} from '@angular/core'
|
||||
import {FormBuilder, FormControl, FormGroup, ValidatorFn} from '@angular/forms'
|
||||
import {FormBuilder, FormControl, FormGroup, ValidatorFn, Validators} from '@angular/forms'
|
||||
import {Observable} from 'rxjs'
|
||||
|
||||
@Component({
|
||||
|
@ -23,7 +23,8 @@ export class EntityAddComponent {
|
|||
ngOnInit() {
|
||||
const formGroup = {}
|
||||
this.formFields.forEach(f => {
|
||||
formGroup[f.name] = new FormControl(f.defaultValue, f.validator)
|
||||
const validator = f.required ? Validators.compose([Validators.required, f.validator]) : f.validator
|
||||
formGroup[f.name] = new FormControl(f.defaultValue, validator)
|
||||
})
|
||||
this.form = this.formBuilder.group(formGroup)
|
||||
}
|
||||
|
@ -47,6 +48,7 @@ export class FormField {
|
|||
public label?: string,
|
||||
public icon?: string,
|
||||
public type?: string,
|
||||
public required?: boolean,
|
||||
public validator?: ValidatorFn,
|
||||
public errorMessages?: FormErrorMessage[],
|
||||
public valueFn?: (any) => any,
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="field.template"
|
||||
*ngIf="field.type == 'slider'"
|
||||
[ngTemplateOutlet]="sliderTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'slider'"
|
||||
[ngTemplateOutlet]="sliderTemplate"
|
||||
[ngTemplateOutlet]="field.template"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
@ -46,7 +46,12 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<input matInput [type]="field.type" [formControl]="control" [readonly]="field.readonly"/>
|
||||
<input
|
||||
matInput
|
||||
[type]="field.type"
|
||||
[formControl]="control"
|
||||
[readonly]="field.readonly"
|
||||
[required]="field.required"/>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="control.invalid && field.errorMessages">
|
||||
<ng-container *ngFor="let errorMessage of field.errorMessages">
|
||||
|
@ -61,7 +66,7 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field *ngIf="field.options$ | async as options">
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="control">
|
||||
<mat-select [formControl]="control" [required]="field.required">
|
||||
<mat-option *ngFor="let option of options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</mat-option>
|
||||
|
@ -75,7 +80,11 @@
|
|||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<ngx-mat-file-input [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
|
||||
<ngx-mat-file-input
|
||||
[accept]="field.fileType"
|
||||
[formControl]="control"
|
||||
[required]="field.required">
|
||||
</ngx-mat-file-input>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<button *ngIf="hasPermission(addPermission)" mat-raised-button color="accent" [routerLink]="addLink">Ajouter</button>
|
||||
</div>
|
||||
|
||||
<table class="mx-auto" *ngIf="entities$ | async as entities" mat-table [dataSource]="entities">
|
||||
<table class="mx-auto" *ngIf="entities$ | async as entities" mat-table multiTemplateDataRows [dataSource]="entities">
|
||||
<!-- Columns -->
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.def">
|
||||
<th mat-header-cell *matHeaderCellDef>{{column.title}}</th>
|
||||
|
@ -20,18 +20,40 @@
|
|||
<!-- Buttons -->
|
||||
<ng-container *ngFor="let button of buttons; let buttonIndex = index" matColumnDef="button{{buttonIndex}}">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!hasPermissionToUseButton(button)" *matCellDef="let entity">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[routerLink]="button.link ? button.link.externalLink ? undefined : button.link : button.linkFn(entity).externalLink ? undefined : button.linkFn(entity)"
|
||||
[disabled]="button.disabledFn && button.disabledFn(entity)"
|
||||
(click)="openExternalLink(button, entity)">
|
||||
{{button.text}}
|
||||
</button>
|
||||
<td mat-cell [class.disabled]="!hasPermissionToUseButton(button)" *matCellDef="let entity; let i = dataIndex">
|
||||
<ng-container *ngIf="(!hoveredEntity && i === 0) || hoveredEntity === entity">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[routerLink]="getRouterLink(button, entity)"
|
||||
[disabled]="button.disabledFn && button.disabledFn(entity)"
|
||||
(click)="clickButton(button, entity)">
|
||||
{{button.text}}
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="expandable" matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let entity" [attr.colspan]="tableCols.length">
|
||||
<div class="entity-detail" [@detailExpand]="entity === expandedEntity ? 'expanded' : 'collapsed'">
|
||||
<ng-container [ngTemplateOutlet]="rowDetailsTemplate"
|
||||
[ngTemplateOutletContext]="{entity: entity, expandedEntity: expandedEntity}"></ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="tableCols"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: tableCols"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
class="entity-row"
|
||||
*matRowDef="let row; columns: tableCols"
|
||||
[class.expanded-row]="expandedEntity === row"
|
||||
[class.can-expand]="expandable"
|
||||
(mouseover)="hoveredEntity = row"
|
||||
(click)="expandedEntity = expandedEntity === row ? null : row">
|
||||
</tr>
|
||||
<ng-container *ngIf="expandable">
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="detail-row"></tr>
|
||||
</ng-container>
|
||||
</table>
|
||||
|
|
|
@ -2,11 +2,19 @@ import {Component, Input} from '@angular/core'
|
|||
import {Observable} from 'rxjs'
|
||||
import {AccountService} from '../../../accounts/services/account.service'
|
||||
import {EmployeePermission} from '../../model/employee'
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-entity-list',
|
||||
templateUrl: './entity-list.component.html',
|
||||
styleUrls: ['./entity-list.component.sass']
|
||||
styleUrls: ['./entity-list.component.sass'],
|
||||
animations: [
|
||||
trigger('detailExpand', [
|
||||
state('collapsed', style({height: '0px', minHeight: '0'})),
|
||||
state('expanded', style({height: '*'})),
|
||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
|
||||
])
|
||||
]
|
||||
})
|
||||
export class EntityListComponent<T> {
|
||||
@Input() entities$: Observable<T>
|
||||
|
@ -15,6 +23,11 @@ export class EntityListComponent<T> {
|
|||
@Input() buttons?: TableButton[]
|
||||
@Input() addLink: string
|
||||
@Input() addPermission: EmployeePermission
|
||||
@Input() expandable = false
|
||||
@Input() rowDetailsTemplate
|
||||
|
||||
hoveredEntity: T | null
|
||||
expandedEntity: T | null
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService
|
||||
|
@ -29,23 +42,43 @@ export class EntityListComponent<T> {
|
|||
return this.accountService.hasPermission(permission)
|
||||
}
|
||||
|
||||
openExternalLink(button: TableButton, entity: T) {
|
||||
let externalLink = null
|
||||
getRouterLink(button: TableButton, entity: T): string {
|
||||
// @ts-ignore
|
||||
if (button.link && button.link.externalLink) {
|
||||
if (button.link && !button.link.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = button.link.externalLink
|
||||
} else {
|
||||
const linkFnResult = button.linkFn(entity)
|
||||
return button.link
|
||||
} else if (button.linkFn) {
|
||||
const fnResult = button.linkFn(entity)
|
||||
// @ts-ignore
|
||||
if (linkFnResult && linkFnResult.externalLink) {
|
||||
if (!fnResult.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = linkFnResult.externalLink
|
||||
return fnResult
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (externalLink) {
|
||||
window.open(externalLink, '_blank')
|
||||
clickButton(button: TableButton, entity: T) {
|
||||
if (button.link || button.linkFn) {
|
||||
let externalLink = null
|
||||
// @ts-ignore
|
||||
if (button.link && button.link.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = button.link.externalLink
|
||||
} else {
|
||||
const linkFnResult = button.linkFn(entity)
|
||||
// @ts-ignore
|
||||
if (linkFnResult && linkFnResult.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = linkFnResult.externalLink
|
||||
}
|
||||
}
|
||||
|
||||
if (externalLink) {
|
||||
window.open(externalLink, '_blank')
|
||||
}
|
||||
} else if (button.clickFn) {
|
||||
button.clickFn(entity)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +118,7 @@ export class TableButton {
|
|||
public text: string,
|
||||
public link: string | { externalLink: string } | null,
|
||||
public linkFn: (T) => string | { externalLink: string } | null,
|
||||
public clickFn: (T) => void,
|
||||
public permission: EmployeePermission | null,
|
||||
public disabledFn: (T) => boolean | null
|
||||
) {
|
||||
|
|
|
@ -46,6 +46,7 @@ export class HeaderComponent extends SubscribingComponent {
|
|||
this._activeLink = data.url
|
||||
}
|
||||
},
|
||||
false,
|
||||
1
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div class="spinner-wrapper" [class.visible]="visible">
|
||||
<div class="darker-background"></div>
|
||||
<div class="spinner p-2">
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,18 @@
|
|||
.spinner-wrapper
|
||||
transition: all 100ms
|
||||
|
||||
&.visible
|
||||
opacity: 1 !important
|
||||
visibility: unset !important
|
||||
|
||||
&:not(.visible)
|
||||
opacity: 0
|
||||
visibility: hidden
|
||||
|
||||
.spinner
|
||||
max-width: max-content
|
||||
position: fixed
|
||||
left: 50%
|
||||
top: 50%
|
||||
transform: translate(-50%, -50%)
|
||||
z-index: 11
|
|
@ -0,0 +1,24 @@
|
|||
import {Component} from '@angular/core'
|
||||
|
||||
export let globalLoadingWheel: LoadingWheelComponent | null
|
||||
|
||||
@Component({
|
||||
selector: 'cre-loading-wheel',
|
||||
templateUrl: './loading-wheel.component.html',
|
||||
styleUrls: ['./loading-wheel.component.sass']
|
||||
})
|
||||
export class LoadingWheelComponent {
|
||||
visible = false
|
||||
|
||||
constructor() {
|
||||
globalLoadingWheel = this
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<div #permissions class="permissions-field">
|
||||
<p>{{title}}</p>
|
||||
<p>{{title}} *</p>
|
||||
<div class="d-flex flex-row justify-content-between permissions-list">
|
||||
<ng-container *ngTemplateOutlet="permissionTemplate;context:{type: 'view'}"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="permissionTemplate;context:{type: 'edit'}"></ng-container>
|
||||
|
|
|
@ -3,6 +3,10 @@ import {EmployeePermission, mapped_permissions} from "../../model/employee";
|
|||
import {FormControl} from "@angular/forms";
|
||||
import {AccountService} from "../../../accounts/services/account.service";
|
||||
|
||||
// The current permissions field component. Workaround to be able to access a permission field in template. (See employee/AddComponent)
|
||||
// This workaround prevent the use of several permissions field component at the same time.
|
||||
export let currentPermissionsFieldComponent: PermissionsFieldComponent | null
|
||||
|
||||
@Component({
|
||||
selector: 'cre-permissions-field',
|
||||
templateUrl: './permissions-field.component.html',
|
||||
|
@ -35,6 +39,8 @@ export class PermissionsFieldComponent implements OnInit {
|
|||
this.togglePermission(control, true)
|
||||
})
|
||||
}
|
||||
|
||||
currentPermissionsFieldComponent = this
|
||||
}
|
||||
|
||||
togglePermission(permission: any, bypassValue?: boolean) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import {Observable, Subject} from 'rxjs'
|
|||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {UrlUtils} from '../utils/url.utils'
|
||||
import {ErrorHandler, ErrorModel, ErrorService} from '../service/error.service'
|
||||
import {globalLoadingWheel} from './loading-wheel/loading-wheel.component'
|
||||
|
||||
export abstract class SubscribingComponent implements OnInit, OnDestroy {
|
||||
protected subscribers$ = []
|
||||
|
@ -17,33 +18,48 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy {
|
|||
) {
|
||||
}
|
||||
|
||||
subscribe<T>(observable: Observable<T>, resultConsumer: (T) => void, take_count = -1) {
|
||||
subscribe<T>(observable: Observable<T>, resultConsumer: (T) => void, showWheel = false, take_count = -1) {
|
||||
if (take_count >= 0) {
|
||||
observable.pipe(take(take_count), takeUntil(this.destroy$))
|
||||
} else {
|
||||
observable.pipe(takeUntil(this.destroy$))
|
||||
}
|
||||
|
||||
this.showLoadingWheel(showWheel)
|
||||
this.subscribers$.push(observable.subscribe({
|
||||
next: resultConsumer,
|
||||
error: err => this.errorService.handleError(err)
|
||||
next: t => {
|
||||
resultConsumer(t)
|
||||
this.hideLoadingWheel(showWheel)
|
||||
},
|
||||
error: err => {
|
||||
this.errorService.handleError(err)
|
||||
this.hideLoadingWheel(showWheel)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
subscribeAndNavigate(observable: Observable<any>, route: string) {
|
||||
subscribeAndNavigate(observable: Observable<any>, route: string, showWheel = true) {
|
||||
this.subscribe(
|
||||
observable,
|
||||
() => this.urlUtils.navigateTo(route),
|
||||
showWheel,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
subscribeEntityById<T>(service: any, id: number, resultConsumer: (T) => void, notFoundRoute: string) {
|
||||
subscribeEntityById<T>(service: any, id: number, resultConsumer: (T) => void, notFoundRoute: string, showWheel = true) {
|
||||
this.showLoadingWheel(showWheel)
|
||||
this.subscribers$.push(service.getById(id)
|
||||
.pipe(take(1), takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: e => resultConsumer(e),
|
||||
error: err => this.handleNotFoundError(err, notFoundRoute)
|
||||
next: e => {
|
||||
resultConsumer(e)
|
||||
this.hideLoadingWheel(showWheel)
|
||||
},
|
||||
error: err => {
|
||||
this.handleNotFoundError(err, notFoundRoute)
|
||||
this.hideLoadingWheel(showWheel)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -62,6 +78,18 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy {
|
|||
this.errorService.handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
protected showLoadingWheel(shouldShowWheel) {
|
||||
if (shouldShowWheel) {
|
||||
globalLoadingWheel.show()
|
||||
}
|
||||
}
|
||||
|
||||
protected hideLoadingWheel(shouldShowWheel) {
|
||||
if (shouldShowWheel) {
|
||||
globalLoadingWheel.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ErrorHandlingComponent extends SubscribingComponent implements ErrorHandler {
|
||||
|
|
|
@ -3,6 +3,7 @@ export class Employee {
|
|||
public id: number,
|
||||
public firstName: string,
|
||||
public lastName: string,
|
||||
public explicitPermissions: EmployeePermission[],
|
||||
public permissions: EmployeePermission[],
|
||||
public group?: EmployeeGroup,
|
||||
public lastLoginTime?: Date
|
||||
|
|
|
@ -4,10 +4,10 @@ import {Observable, Subject} from 'rxjs'
|
|||
import {environment} from '../../../../environments/environment'
|
||||
import {AppState} from '../app-state'
|
||||
import {Router} from '@angular/router'
|
||||
import {map, share, takeUntil} from 'rxjs/operators'
|
||||
import {map, share, takeUntil, tap} from 'rxjs/operators'
|
||||
import {valueOr} from '../utils/utils'
|
||||
import {ErrorService} from './error.service'
|
||||
import {AccountService} from '../../accounts/services/account.service'
|
||||
import {globalLoadingWheel} from '../components/loading-wheel/loading-wheel.component'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
|
@ -28,12 +28,14 @@ import {MatOptionModule} from '@angular/material/core'
|
|||
import {MaterialFileInputModule} from 'ngx-material-file-input'
|
||||
import {FileButtonComponent} from './file-button/file-button.component'
|
||||
import {GlobalAlertHandlerComponent} from './components/global-alert-handler/global-alert-handler.component'
|
||||
import {MatSliderModule} from '@angular/material/slider';
|
||||
import { SliderFieldComponent } from './components/slider-field/slider-field.component'
|
||||
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'
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent],
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent, SliderFieldComponent, LoadingWheelComponent],
|
||||
exports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
|
@ -60,7 +62,8 @@ import { SliderFieldComponent } from './components/slider-field/slider-field.com
|
|||
EntityAddComponent,
|
||||
EntityEditComponent,
|
||||
FileButtonComponent,
|
||||
GlobalAlertHandlerComponent
|
||||
GlobalAlertHandlerComponent,
|
||||
LoadingWheelComponent
|
||||
],
|
||||
imports: [
|
||||
MatTabsModule,
|
||||
|
@ -76,6 +79,7 @@ import { SliderFieldComponent } from './components/slider-field/slider-field.com
|
|||
MatSelectModule,
|
||||
MatOptionModule,
|
||||
MatSliderModule,
|
||||
MatProgressSpinnerModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
CommonModule,
|
||||
|
|
|
@ -200,7 +200,7 @@ div.empty
|
|||
left: 0
|
||||
background-color: black
|
||||
opacity: 0.4
|
||||
z-index: -1
|
||||
z-index: 10
|
||||
|
||||
.color-warning
|
||||
color: #fdd835
|
||||
|
|
Loading…
Reference in New Issue