Compare commits

..

No commits in common. "5aae63e545c620e77b9396324006a0166d0e3651" and "63e70cd10926beefe5e57d4385ef2843fc5aefb4" have entirely different histories.

38 changed files with 168 additions and 350 deletions

View File

@ -4,69 +4,68 @@ import {CatalogComponent} from './pages/catalog/catalog.component'
import {AdministrationComponent} from './pages/administration/administration.component'
import {MiscComponent} from './pages/others/misc.component'
import {CreConfigEditor} from './modules/configuration/config-editor'
import {routes as routesConst} from './routes'
const routes: Routes = [{
path: routesConst.recipes.route,
path: 'color',
loadChildren: () => import('./modules/recipes/recipes.module').then(m => m.RecipesModule)
}, {
path: routesConst.accounts.route,
path: 'account',
loadChildren: () => import('./modules/accounts/accounts.module').then(m => m.AccountsModule)
}, {
path: routesConst.catalog.route,
path: 'catalog',
component: CatalogComponent,
children: [{
path: routesConst.catalog.children.materialTypes.simpleRoute,
path: 'materialtype',
loadChildren: () => import('./modules/material-type/material-type.module').then(m => m.MaterialTypeModule),
}, {
path: routesConst.catalog.children.materials.simpleRoute,
path: 'material',
loadChildren: () => import('./modules/material/material.module').then(m => m.MaterialModule)
}, {
path: routesConst.catalog.children.companies.simpleRoute,
path: 'company',
loadChildren: () => import('./modules/company/company.module').then(m => m.CompanyModule)
}, {
path: '',
pathMatch: 'full',
redirectTo: routesConst.catalog.children.materials.simpleRoute
redirectTo: 'materialtype'
}]
}, {
path: routesConst.administration.route,
path: 'admin',
component: AdministrationComponent,
children: [
{
path: routesConst.administration.children.users.simpleRoute,
path: 'user',
loadChildren: () => import('./modules/users/user.module').then(m => m.UserModule)
}, {
path: routesConst.administration.children.groups.simpleRoute,
path: 'group',
loadChildren: () => import('./modules/groups/group.module').then(m => m.GroupModule)
}, {
path: routesConst.administration.children.groupTokens.simpleRoute,
path: 'group-token',
loadChildren: () => import('./modules/groupTokens/group-tokens.module').then(m => m.GroupTokensModule)
}, {
path: routesConst.administration.children.configurations.simpleRoute,
path: 'config',
loadChildren: () => import('./modules/configuration/config.module').then(m => m.ConfigModule),
component: CreConfigEditor
}, {
path: '',
pathMatch: 'full',
redirectTo: routesConst.administration.children.users.simpleRoute
redirectTo: 'user'
}
]
}, {
path: routesConst.misc.route,
path: 'misc',
component: MiscComponent,
children: [{
path: routesConst.misc.children.touchUpKits.simpleRoute,
path: 'touch-up-kit',
loadChildren: () => import('./modules/touch-up-kit/touch-up-kit.module').then(m => m.TouchUpKitModule)
}, {
path: '',
pathMatch: 'full',
redirectTo: routesConst.misc.children.touchUpKits.simpleRoute
redirectTo: 'touch-up-kit'
}]
}]
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, {relativeLinkResolution: 'legacy'})],
exports: [RouterModule]
})
export class AppRoutingModule {

View File

@ -7,7 +7,6 @@ import {ErrorHandler, ErrorService} from '../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router'
import {CreForm, ICreForm} from "../shared/components/forms/forms";
import {AlertService} from "../shared/service/alert.service";
import {routes} from "../../routes";
@Component({
selector: 'cre-login',
@ -50,7 +49,7 @@ export class Login extends ErrorHandlingComponent {
submit() {
this.subscribeAndNavigate(
this.accountService.loginAsUser(this.userIdControl.value, this.passwordControl.value),
`/${routes.recipes.route}/list`
'/color/list'
)
}
@ -84,6 +83,6 @@ export class Logout extends SubscribingComponent {
this.accountService.logout()
}
this.urlUtils.navigateTo(`/${routes.accounts.route}/login`)
this.urlUtils.navigateTo('/account/login')
}
}

View File

@ -59,7 +59,7 @@ export class AccountService implements OnDestroy {
.pipe(take(1), takeUntil(this.destroy$))
login$.subscribe({
next: result => this.appState.authenticateUser(result, isGroup),
next: result => this.appState.authenticateUser(result),
error: error => {
if (errorConsumer(error)) return;

View File

@ -23,14 +23,4 @@ export class GroupTokenService {
save(name: string, groupId: number): Observable<GroupToken> {
return this.api.post<GroupToken>('/account/group/token', {name, groupId})
}
toggle(token: GroupToken): Observable<void> {
return token.enabled ?
this.api.put<void>(`/account/group/token/${token.id}/disable`) :
this.api.put<void>(`/account/group/token/${token.id}/enable`)
}
delete(id: string): Observable<void> {
return this.api.delete<void>(`/account/group/token/${id}`)
}
}

View File

@ -5,7 +5,6 @@ import {FormField} from '../../../shared/components/entity-add/entity-add.compon
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-add',
@ -51,7 +50,7 @@ export class AddComponent extends ErrorHandlingComponent {
this.submittedValues = values
this.subscribeAndNavigate(
this.companyService.save(values.name),
routes.catalog.children.companies.route + '/list'
'/catalog/company/list'
)
}
}

View File

@ -6,7 +6,6 @@ import {CompanyService} from '../../service/company.service'
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorHandler, ErrorHandlerComponent, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-edit',
@ -30,7 +29,7 @@ export class EditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-company-id',
consumer: _ => this.urlUtils.navigateTo(routes.catalog.children.companies.route + '/list')
consumer: error => this.urlUtils.navigateTo('/catalog/company/list')
}, {
filter: error => error.type === 'exists-company-name',
messageProducer: error => `Une bannière avec le nom '${error.name}' existe déjà`
@ -66,14 +65,14 @@ export class EditComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribeAndNavigate(
this.companyService.update(this.company.id, values.name),
routes.catalog.children.companies.route + '/list'
'/catalog/company/list'
)
}
delete() {
this.subscribeAndNavigate(
this.companyService.delete(this.company.id),
routes.catalog.children.companies.route + '/list'
'/catalog/company/list'
)
}
}

View File

@ -4,7 +4,7 @@
(continue)="submit()">
<cre-dialog-body>
<form [formGroup]="form" class="w-100">
<cre-input [control]="controls.name" label="Nom" icon="form-textbox"></cre-input>
<cre-input [control]="controls.name" label="Name" icon="form-textbox"></cre-input>
</form>
</cre-dialog-body>
</cre-prompt-dialog>

View File

@ -6,7 +6,8 @@ const routes: Routes = [{
path: 'list',
component: GroupTokenList
}, {
path: '', redirectTo: 'list'
path: '',
redirectTo: 'list'
}]
@NgModule({

View File

@ -4,8 +4,6 @@ import {SharedModule} from '../shared/shared.module'
import {CreInputsModule} from '../shared/components/inputs/inputs.module'
import {CreButtonsModule} from '../shared/components/buttons/buttons.module'
import {GroupTokenAdd, GroupTokenList} from "../groupTokens/group-tokens";
import {GroupTokensRoutingModule} from "./group-tokens-routing.module";
import {CreTablesModule} from "../shared/components/tables/tables.module";
@NgModule({
@ -17,11 +15,9 @@ import {CreTablesModule} from "../shared/components/tables/tables.module";
GroupTokenAdd
],
imports: [
GroupTokensRoutingModule,
SharedModule,
CreInputsModule,
CreButtonsModule,
CreTablesModule
CreButtonsModule
]
})
export class GroupTokensModule {

View File

@ -6,21 +6,15 @@ import {GroupTokenService} from "../accounts/services/group-token.service";
import {SubscribingComponent} from "../shared/components/subscribing.component";
import {ErrorService} from "../shared/service/error.service";
import {ActivatedRoute, Router} from "@angular/router";
import {tap} from "rxjs/operators";
@Component({
selector: 'cre-group-token-list',
templateUrl: 'list.html'
})
export class GroupTokenList extends SubscribingComponent {
tokens$ = this.groupTokenService.all.pipe(tap(
tokens => this.tokensEmpty = tokens.length <= 0))
tokensEmpty = false
tokens$ = this.groupTokenService.all
columns = ['id', 'name', 'groupName', 'state', 'stateButton', 'deleteButton']
@ViewChild(CrePromptDialog) removePrompt: CrePromptDialog
removePromptToken: GroupToken | null = null
columns = ['id', 'name', 'groupName', 'deleteButton']
constructor(
private groupTokenService: GroupTokenService,
@ -30,25 +24,6 @@ export class GroupTokenList extends SubscribingComponent {
) {
super(errorService, activatedRoute, router)
}
showRemovePrompt(token: GroupToken) {
this.removePromptToken = token
this.removePrompt.show()
}
toggle(token: GroupToken) {
this.subscribe(
this.groupTokenService.toggle(token),
_ => window.location.reload()
)
}
delete() {
this.subscribe(
this.groupTokenService.delete(this.removePromptToken.id),
_ => window.location.reload()
)
}
}
@Component({
@ -88,7 +63,7 @@ export class GroupTokenAdd extends SubscribingComponent {
const name = this.controls.name.value
this.subscribe(
this.groupTokenService.save(name, this.group.id),
groupToken => this.defaultGroupUpdate.emit(groupToken)
groupToken => this.defaultGroupUpdate.emit(groupToken.group)
)
}
}

View File

@ -1,54 +1 @@
<div class="mt-5"></div>
<cre-warning-alert *ngIf="tokensEmpty">
<p>Il n'y a aucun ordinateur enregistré dans le système.</p>
<p>Vous pouvez en ajouter un en définissant un groupe par défaut <b><a routerLink="/admin/group/list">ici</a></b>.</p>
</cre-warning-alert>
<cre-table *ngIf="!tokensEmpty" class="mx-auto" [data]="tokens$ | async" [columns]="columns">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>Identifiant</th>
<td mat-cell *matCellDef="let token">{{token.id}}</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Nom</th>
<td mat-cell *matCellDef="let token">{{token.name}}</td>
</ng-container>
<ng-container matColumnDef="groupName">
<th mat-header-cell *matHeaderCellDef>Groupe</th>
<td mat-cell *matCellDef="let token">{{token.group.name}}</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef>État</th>
<td mat-cell *matCellDef="let token">{{token.enabled ? 'Activé' : 'Désactivé'}}</td>
</ng-container>
<ng-container matColumnDef="stateButton">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let token; let i = index">
<cre-accent-button [creInteractiveCell]="i" (click)="toggle(token)">
<ng-container *ngIf="!token.enabled">Activer</ng-container>
<ng-container *ngIf="token.enabled">Désactiver</ng-container>
</cre-accent-button>
</td>
</ng-container>
<ng-container matColumnDef="deleteButton">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let token; let i = index">
<cre-warn-button [creInteractiveCell]="i" (click)="showRemovePrompt(token)">
Supprimer
</cre-warn-button>
</td>
</ng-container>
</cre-table>
<cre-prompt-dialog title="Retirer un ordinateur" (continue)="delete()">
<cre-dialog-body *ngIf="removePromptToken">
Voulez-vous vraiment retirer l'ordinateur '{{removePromptToken.name}}' du système?<br/>
Ses utilisateurs ne seront plus connectés automatiquement.
</cre-dialog-body>
</cre-prompt-dialog>
<div>A list</div>

View File

@ -7,7 +7,6 @@ import {ErrorHandlingComponent} from '../../../shared/components/subscribing.com
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-add',
@ -61,7 +60,7 @@ export class AddComponent extends ErrorHandlingComponent {
if (permissionsField.valid()) {
this.subscribeAndNavigate(
this.groupService.save(values.name, permissionsField.allEnabledPermissions),
routes.administration.children.groups.route + '/list'
'/admin/group/list'
)
}
}

View File

@ -3,15 +3,12 @@ import {ActivatedRoute, Router} from '@angular/router'
import {Group} from '../../../shared/model/account.model'
import {GroupService} from '../../services/group.service'
import {Validators} from '@angular/forms'
import {
currentPermissionsFieldComponent
} from '../../../shared/components/permissions-field/permissions-field.component'
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 {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-edit',
@ -41,7 +38,7 @@ export class EditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-group-id',
consumer: _ => this.urlUtils.navigateTo(routes.administration.children.groups.route + '/list')
consumer: error => this.urlUtils.navigateTo('/admin/group/list')
}, {
filter: error => error.type === 'exists-group-name',
messageProducer: error => `Un groupe avec le nom '${error.name}' existe déjà`
@ -77,7 +74,7 @@ export class EditComponent extends ErrorHandlingComponent {
if (permissionsField.valid()) {
this.subscribeAndNavigate(
this.groupService.update(this.group.id, values.name, permissionsField.allEnabledPermissions),
routes.administration.children.groups.route + '/list'
'/admin/group/list'
)
}
}
@ -85,7 +82,7 @@ export class EditComponent extends ErrorHandlingComponent {
delete() {
this.subscribeAndNavigate(
this.groupService.delete(this.group.id),
routes.administration.children.groups.route + '/list'
'/admin/group/list'
)
}
}

View File

@ -9,7 +9,6 @@ import {AlertService} from '../../../shared/service/alert.service'
import {AppState} from '../../../shared/app-state'
import {GroupTokenService} from "../../../accounts/services/group-token.service";
import {GroupTokenAdd} from "../../../groupTokens/group-tokens";
import {tap} from "rxjs/operators";
@Component({
selector: 'cre-groups',
@ -17,8 +16,7 @@ import {tap} from "rxjs/operators";
styleUrls: ['./list.component.sass']
})
export class ListComponent extends ErrorHandlingComponent {
groups$ = this.groupService.all.pipe(tap(
groups => this.groupsEmpty = groups.length <= 0))
groups$ = this.groupService.all
groupsEmpty = false
currentGroupToken: GroupToken | null

View File

@ -6,7 +6,6 @@ import {ErrorHandlingComponent} from '../../../shared/components/subscribing.com
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-add',
@ -70,7 +69,7 @@ export class AddComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribeAndNavigate(
this.materialTypeService.save(values.name, values.prefix, values.usePercentages),
routes.catalog.children.materialTypes.route + '/list'
'/catalog/materialtype/list'
)
}
}

View File

@ -7,7 +7,6 @@ import {FormField} from '../../../shared/components/entity-add/entity-add.compon
import {Validators} from '@angular/forms'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-edit',
@ -46,7 +45,7 @@ export class EditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-materialtype-id',
consumer: _ => this.urlUtils.navigateTo(routes.catalog.children.materialTypes.route + '/list')
consumer: error => this.urlUtils.navigateTo('/catalog/materialtype/list')
}, {
filter: error => error.type === 'exists-materialtype-name',
messageProducer: error => `Un type de produit avec le nom '${error.name}' existe déjà`
@ -85,14 +84,14 @@ export class EditComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribeAndNavigate(
this.materialTypeService.update(this.materialType.id, values.name, values.prefix),
routes.catalog.children.materialTypes.route + '/list'
'/catalog/materialtype/list'
)
}
delete() {
this.subscribeAndNavigate(
this.materialTypeService.delete(this.materialType.id),
routes.catalog.children.materialTypes.route + '/list'
'/catalog/materialtype/list'
)
}
}

View File

@ -8,7 +8,6 @@ import {ErrorHandlingComponent} from '../../../shared/components/subscribing.com
import {map} from 'rxjs/operators'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-add',
@ -82,7 +81,7 @@ export class AddComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribeAndNavigate(
this.materialService.save(values.name, values.inventoryQuantity, values.materialType, values.simdutFile),
routes.catalog.children.materials.route + '/list'
'/catalog/material/list'
)
}
}

View File

@ -9,7 +9,6 @@ import {ErrorHandlingComponent} from '../../../shared/components/subscribing.com
import {Material, openSimdut} from '../../../shared/model/material.model'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-edit',
@ -70,7 +69,7 @@ export class EditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-material-id',
consumer: _ => this.urlUtils.navigateTo(routes.catalog.children.materials.route + '/list')
consumer: error => this.urlUtils.navigateTo('/catalog/material/list')
}, {
filter: error => error.type === 'exists-material-name',
messageProducer: error => `Un produit avec le nom '${error.name}' existe déjà`
@ -109,14 +108,14 @@ export class EditComponent extends ErrorHandlingComponent {
submit(values) {
this.subscribeAndNavigate(
this.materialService.update(this.material.id, values.name, values.inventoryQuantity, values.materialType, this.selectedSimdutFile),
routes.catalog.children.materials.route + '/list'
'/catalog/material/list'
)
}
delete() {
this.subscribeAndNavigate(
this.materialService.delete(this.material.id),
routes.catalog.children.materials.route + '/list'
'/catalog/material/list'
)
}

View File

@ -22,7 +22,6 @@ import {Permission} from '../shared/model/account.model'
import {FormControl} from '@angular/forms';
import {map} from 'rxjs/operators';
import {CreInputEntry} from '../shared/components/inputs/inputs';
import {routes} from "../../routes";
@Component({
selector: 'cre-recipe-explore',
@ -49,7 +48,7 @@ export class CreRecipeExplore extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-recipe-id',
consumer: _ => this.urlUtils.navigateTo(routes.recipes.route + '/list')
consumer: error => this.urlUtils.navigateTo('/color/list')
}, {
filter: error => error.type === 'notenoughinventory-multiple',
consumer: error => this.deductErrorBody = {mix: this.deductedMixId, lowQuantities: error.lowQuantities},

View File

@ -12,7 +12,6 @@ import {ActivatedRoute, Router} from '@angular/router'
import {Config} from '../shared/model/config.model'
import {Permission} from '../shared/model/account.model'
import {FormControl} from '@angular/forms'
import {routes} from "../../routes";
@Component({
selector: 'cre-recipe-list',
@ -53,7 +52,7 @@ export class RecipeList extends ErrorHandlingComponent {
this.configService.get(Config.EMERGENCY_MODE),
config => {
if (config.content == 'true') {
this.urlUtils.navigateTo(routes.administration.children.configurations.route)
this.urlUtils.navigateTo('/admin/config/')
}
}
)

View File

@ -15,7 +15,6 @@ import {MaterialService} from '../../material/service/material.service'
import {CreForm} from '../../shared/components/forms/forms'
import {MixMaterialsForm} from './materials-form'
import {MixSaveDto, MixService, MixUpdateDto} from '../services/mix.service'
import {routes} from "../../../routes";
@Directive()
abstract class _BaseMixPage extends SubscribingComponent {
@ -75,7 +74,7 @@ export class MixAdd extends _BaseMixPage {
submit(dto: MixSaveDto) {
this.subscribeAndNavigate(
this.mixService.saveDto(dto),
`/${routes.recipes.route}/edit/${this.recipe.id}`
`/color/edit/${this.recipe.id}`
)
}
}
@ -111,14 +110,14 @@ export class MixEdit extends _BaseMixPage {
submit(dto: MixSaveDto) {
this.subscribeAndNavigate(
this.mixService.updateDto({...dto, id: this.mix.id}),
`/${routes.recipes.route}/edit/${this.recipe.id}`
`/color/edit/${this.recipe.id}`
)
}
delete() {
this.subscribeAndNavigate(
this.mixService.delete(this.mixId),
`/${routes.recipes.route}/edit/` + this.recipe.id
'/color/edit/' + this.recipe.id
)
}
}

View File

@ -17,7 +17,6 @@ import {GroupService} from '../groups/services/group.service';
import {StepTableComponent} from './components/step-table/step-table.component';
import {anyMap} from '../shared/utils/map.utils';
import {CreForm, ICreForm} from '../shared/components/forms/forms';
import {routes} from "../../routes";
@Component({
selector: 'recipe-form',
@ -118,7 +117,7 @@ export class RecipeAdd extends ErrorHandlingComponent {
submit(recipe: Recipe) {
this.subscribe(
this.recipeService.save(recipe),
recipe => this.urlUtils.navigateTo(routes.recipes.route + `/edit/${recipe.id}`)
recipe => this.urlUtils.navigateTo(`/color/edit/${recipe.id}`)
)
}
}
@ -138,7 +137,7 @@ export class RecipeEdit extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-recipe-id',
consumer: _ => this.urlUtils.navigateTo(routes.recipes.route + '/list')
consumer: _ => this.urlUtils.navigateTo('/color/list')
}]
constructor(
@ -191,14 +190,14 @@ export class RecipeEdit extends ErrorHandlingComponent {
this.subscribeAndNavigate(
this.recipeService.update(recipe, steps),
routes.recipes.route + '/list'
'/color/list'
)
}
delete() {
this.subscribeAndNavigate(
this.recipeService.delete(this.recipe.id),
routes.recipes.route + '/list'
'/color/list'
)
}

View File

@ -7,10 +7,11 @@ import {Title} from '@angular/platform-browser'
providedIn: 'root'
})
export class AppState {
private readonly KEY_AUTHENTICATED_USER = 'authenticated-user'
private readonly KEY_GROUP_USER = 'authenticated-user-group'
private readonly KEY_AUTHENTICATED = 'authenticated'
private readonly KEY_DEFAULT_GROUP_USER_AUTHENTICATED = 'default-group-user-authenticated'
private readonly KEY_LOGGED_IN_USER = 'logged-in-user'
authenticatedUser$ = new Subject<AuthenticationEvent>()
authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: LoginDto }>()
serverOnline$ = new Subject<boolean>()
constructor(
@ -18,49 +19,67 @@ export class AppState {
) {
}
authenticateUser(user: LoginDto, isGroup: boolean) {
this.setAuthenticatedUser(user, isGroup)
authenticateUser(user: LoginDto) {
this.authenticatedUser = user
this.isAuthenticated = true
}
authenticateGroupUser(user: LoginDto) {
this.authenticatedUser = user
this.isDefaultGroupUserAuthenticated = true
}
resetAuthenticatedUser() {
this.removeAuthenticatedUser()
this.isAuthenticated = false
this.isDefaultGroupUserAuthenticated = false
this.authenticatedUser = null
}
set isServerOnline(isOnline: boolean) {
if (!isOnline) {
this.removeAuthenticatedUser()
this.authenticatedUser = null
}
this.serverOnline$.next(isOnline)
}
get isAuthenticated(): boolean {
return !!this.authenticatedUser
return sessionStorage.getItem(this.KEY_AUTHENTICATED) === 'true'
}
get authenticatedUser(): LoginDto {
const userString = sessionStorage.getItem(this.KEY_AUTHENTICATED_USER)
return userString ? JSON.parse(userString) : null
}
get isGroupUserAuthenticated(): boolean {
return sessionStorage.getItem(this.KEY_GROUP_USER) === 'true'
}
private setAuthenticatedUser(login: LoginDto, isGroup: boolean) {
sessionStorage.setItem(this.KEY_AUTHENTICATED_USER, JSON.stringify(login))
sessionStorage.setItem(this.KEY_GROUP_USER, JSON.stringify(isGroup))
private set isAuthenticated(value: boolean) {
sessionStorage.setItem(this.KEY_AUTHENTICATED, value.toString())
this.authenticatedUser$.next({
authenticatedUser: login,
isGroup
authenticated: value,
authenticatedUser: this.authenticatedUser
})
}
private removeAuthenticatedUser() {
sessionStorage.removeItem(this.KEY_AUTHENTICATED_USER)
sessionStorage.removeItem(this.KEY_GROUP_USER)
get isDefaultGroupUserAuthenticated(): boolean {
return sessionStorage.getItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED) === 'true'
}
private set isDefaultGroupUserAuthenticated(value: boolean) {
sessionStorage.setItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED, value.toString())
}
get hasCredentials(): boolean {
return this.isAuthenticated || this.isDefaultGroupUserAuthenticated
}
get authenticatedUser(): LoginDto {
const userString = sessionStorage.getItem(this.KEY_LOGGED_IN_USER)
return userString ? JSON.parse(userString) : null
}
private set authenticatedUser(value: LoginDto) {
if (value === null) {
sessionStorage.removeItem(this.KEY_LOGGED_IN_USER)
} else {
sessionStorage.setItem(this.KEY_LOGGED_IN_USER, JSON.stringify(value))
}
this.authenticatedUser$.next({
authenticatedUser: null,
isGroup: false
authenticated: this.isAuthenticated,
authenticatedUser: value
})
}
@ -68,8 +87,3 @@ export class AppState {
this.titleService.setTitle(`CRE: ${value}`)
}
}
export interface AuthenticationEvent {
authenticatedUser: LoginDto | null
isGroup: boolean
}

View File

@ -1,9 +1,9 @@
<nav mat-tab-nav-bar backgroundColor="primary">
<ng-container *ngFor="let link of links">
<a
*ngIf="isLinkEnabled(link)"
*ngIf="link.enabled && hasPermission(link)"
mat-tab-link
[active]="isLinkActive(link)"
[active]="activeLink.startsWith(link.route)"
(click)="activeLink = link.route">
{{ link.title }}
</a>

View File

@ -47,15 +47,11 @@ export class NavComponent implements OnInit, OnDestroy {
set activeLink(link: string) {
this._activeLink = link
this.router.navigate(['/' + link])
this.router.navigate([link])
}
isLinkEnabled(link: NavLink): boolean {
return link.enabled && this.hasPermission(link)
}
isLinkActive(link: NavLink): boolean {
return this._activeLink.startsWith(link.route)
get activeLink() {
return this._activeLink
}
private updateEnabledLinks(user: LoginDto) {

View File

@ -1,17 +1,18 @@
<div class="user-menu-wrapper">
<div *ngIf="user" class="user-info-wrapper d-flex flex-column" (click)="showMenu = !showMenu">
<div *ngIf="user" class="user-info-wrapper d-flex flex-column" (click)="menuEnabled = !menuEnabled">
<labeled-icon
*ngIf="authenticated"
icon="account"
label="{{user.fullName}}">
</labeled-icon>
<div class="d-flex flex-row">
<labeled-icon
*ngIf="!groupUser"
*ngIf="authenticated"
icon="pound"
[label]="user.id.toString()">
</labeled-icon>
<labeled-icon
*ngIf="user.groupId"
*ngIf="userInGroup"
class="user-info-group"
icon="account-multiple"
[label]="user.groupName">
@ -20,21 +21,21 @@
</div>
<button
*ngIf="!user"
*ngIf="!authenticated && !userInGroup"
(click)="openLogout()">
Connexion
</button>
<mat-action-list *ngIf="showMenu">
<mat-action-list *ngIf="menuEnabled">
<button
*ngIf="!user || groupUser"
*ngIf="!authenticated && userInGroup"
mat-list-item
class="user-menu-item-login"
(click)="openLogin()">
Connexion
</button>
<button
*ngIf="user && !groupUser"
*ngIf="authenticated"
mat-list-item
class="user-menu-item-logout"
(click)="openLogout()">

View File

@ -11,7 +11,7 @@ p, labeled-icon
&:hover labeled-icon
text-decoration: underline
.user-info-group:nth-child(2)
.user-info-group
margin-left: 0.7rem
mat-action-list

View File

@ -1,11 +1,10 @@
import {Component, OnDestroy, OnInit} from '@angular/core'
import {AppState, AuthenticationEvent} from '../../app-state'
import {AppState} from '../../app-state'
import {LoginDto} from '../../model/account.model'
import {Subject} from 'rxjs'
import {takeUntil} from 'rxjs/operators'
import {UrlUtils} from '../../utils/url.utils'
import {ActivatedRoute, Router} from '@angular/router'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-user-menu',
@ -13,10 +12,10 @@ import {routes} from "../../../../routes";
styleUrls: ['./user-menu.component.sass']
})
export class UserMenuComponent implements OnInit, OnDestroy {
authenticated = false
user: LoginDto = null
groupUser: boolean = false
showMenu = false
userInGroup = false
menuEnabled = false
private destroy$ = new Subject<boolean>()
private urlUtils: UrlUtils
@ -30,12 +29,11 @@ export class UserMenuComponent implements OnInit, OnDestroy {
}
ngOnInit(): void {
this.setInitialAuthenticationState()
this.authenticationState(this.appState.isAuthenticated, this.appState.authenticatedUser)
this.appState.authenticatedUser$
.pipe(takeUntil(this.destroy$))
.subscribe({
next: event => this.processAuthenticationEvent(event)
next: authentication => this.authenticationState(authentication.authenticated, authentication.authenticatedUser)
})
}
@ -45,26 +43,20 @@ export class UserMenuComponent implements OnInit, OnDestroy {
}
openLogin() {
this.urlUtils.navigateTo(routes.accounts.route + '/login')
this.hideMenu()
this.urlUtils.navigateTo('/account/login')
this.menuEnabled = false
}
openLogout() {
this.urlUtils.navigateTo(routes.accounts.route + '/logout')
this.hideMenu()
this.urlUtils.navigateTo('/account/logout')
this.menuEnabled = false
}
private setInitialAuthenticationState() {
this.user = this.appState.authenticatedUser
this.groupUser = this.appState.isGroupUserAuthenticated
}
private processAuthenticationEvent(event: AuthenticationEvent) {
this.user = event.authenticatedUser
this.groupUser = event.isGroup
}
private hideMenu() {
this.showMenu = false
private authenticationState(authenticated: boolean, user: LoginDto) {
this.authenticated = authenticated
this.user = user
if (this.user != null) {
this.userInGroup = this.user.groupId != null
}
}
}

View File

@ -1,13 +1,12 @@
import {Injectable, OnDestroy} from '@angular/core'
import {HttpClient} from '@angular/common/http'
import {catchError, Observable, Subject} from 'rxjs'
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 {valueOr} from '../utils/utils'
import {ErrorService} from './error.service'
import {routes} from "../../../routes";
@Injectable({
providedIn: 'root'
@ -71,7 +70,7 @@ export class ApiService implements OnDestroy {
observe: 'response'
}
if (needAuthentication) {
if (this.appState.isAuthenticated) {
if (this.appState.hasCredentials) {
if (httpOptions) {
httpOptions.withCredentials = true
} else {
@ -82,26 +81,15 @@ export class ApiService implements OnDestroy {
}
}
let request$ = requestFn(httpOptions)
if (!requestOptions.takeFullResponse) {
request$ = request$.pipe(map(r => r.body))
}
return request$.pipe(share(), takeUntil(this._destroy$),
catchError(err => this.navigateOnError(err)))
}
private navigateOnError(error: any) {
if (error && error.status && error.status === 403) {
console.warn("Request was forbidden, redirecting to login")
this.navigateToLogin()
}
return error
return requestOptions.takeFullResponse
? requestFn(httpOptions)
.pipe(takeUntil(this._destroy$), share())
: requestFn(httpOptions)
.pipe(takeUntil(this._destroy$), map(r => r.body), share())
}
private navigateToLogin() {
this.router.navigate([routes.accounts.route + '/login'])
this.router.navigate(['/account/login'])
}
}

View File

@ -134,5 +134,5 @@ function isServerOfflineError(response: any): boolean {
}
function isHandledError(error: any): error is HandledError {
return error && error.status && error.type
return true
}

View File

@ -13,7 +13,6 @@ import {map} from 'rxjs/operators'
import {LocalDate, Period} from '@js-joda/core'
import {ConfigService} from '../../shared/service/config.service'
import {Config} from '../../shared/model/config.model'
import {routes} from "../../../routes";
@Component({
selector: 'touchupkit-banner',
@ -139,14 +138,14 @@ export class TouchUpKitDetails extends ErrorHandlingComponent {
save() {
this.subscribeAndNavigate(
this.touchUpKitService.update(this.touchUpKit),
routes.misc.children.touchUpKits.route + `/details/${this.touchUpKit.id}`
`/misc/touch-up-kit/details/${this.touchUpKit.id}`
)
}
complete() {
this.subscribeAndNavigate(
this.touchUpKitService.complete(this.touchUpKit),
routes.misc.children.touchUpKits.route
'/misc/touch-up-kit'
)
}
@ -174,7 +173,7 @@ export class TouchUpKitAdd extends ErrorHandlingComponent {
submit(touchUpKit) {
this.subscribeAndNavigate(
this.touchUpKitService.save(touchUpKit),
routes.misc.children.touchUpKits.route + '/list'
'/misc/touch-up-kit/list'
)
}
}
@ -212,14 +211,14 @@ export class TouchUpKitEdit extends ErrorHandlingComponent {
submit(touchUpKit) {
this.subscribeAndNavigate(
this.touchUpKitService.update(touchUpKit),
routes.misc.children.touchUpKits.route + '/list'
'/misc/touch-up-kit/list'
)
}
delete() {
this.subscribeAndNavigate(
this.touchUpKitService.delete(this.touchUpKit.id),
routes.misc.children.touchUpKits.route + '/list'
'/misc/touch-up-kit/list'
)
}
}

View File

@ -9,7 +9,6 @@ import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
import {map} from 'rxjs/operators'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-add',
@ -112,7 +111,7 @@ export class AddComponent extends ErrorHandlingComponent {
values.groupId,
permissionsField.allEnabledPermissions
),
routes.administration.children.users.route + '/list'
'/admin/user/list'
)
}
}

View File

@ -10,7 +10,6 @@ import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {FormField} from '../../../shared/components/entity-add/entity-add.component'
import {map} from 'rxjs/operators'
import {AppState} from '../../../shared/app-state'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-edit',
@ -62,7 +61,7 @@ export class EditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-user-id',
consumer: _ => this.urlUtils.navigateTo(routes.administration.children.users.route + '/list')
consumer: error => this.urlUtils.navigateTo('/admin/user/list')
}, {
filter: error => error.type === 'exists-user-fullName',
messageProducer: error => `Un utilisateur nommé '${error.fullName}' existe déjà`
@ -106,7 +105,7 @@ export class EditComponent extends ErrorHandlingComponent {
permissionsField.allEnabledPermissions
),
() => {
this.urlUtils.navigateTo(routes.administration.children.users.route + '/list')
this.urlUtils.navigateTo('/admin/user/list')
}
)
}
@ -115,7 +114,7 @@ export class EditComponent extends ErrorHandlingComponent {
delete() {
this.subscribeAndNavigate(
this.userService.delete(this.user.id),
routes.administration.children.users.route + '/list'
'/admin/user/list'
)
}
}

View File

@ -5,7 +5,6 @@ import {AccountModel} from '../../../shared/model/account.model'
import {ActivatedRoute, Router} from '@angular/router'
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
import {ErrorHandler, ErrorService} from '../../../shared/service/error.service'
import {routes} from "../../../../routes";
@Component({
selector: 'cre-password-edit',
@ -20,7 +19,7 @@ export class PasswordEditComponent extends ErrorHandlingComponent {
errorHandlers: ErrorHandler[] = [{
filter: error => error.type === 'notfound-user-id',
consumer: _ => this.urlUtils.navigateTo(routes.administration.children.users.route + '/list')
consumer: error => this.urlUtils.navigateTo('/admin/user/list')
}]
constructor(
@ -50,7 +49,7 @@ export class PasswordEditComponent extends ErrorHandlingComponent {
if (this.form.valid) {
this.subscribeAndNavigate(
this.userService.updatePassword(this.user.id, this.passwordControl.value),
routes.administration.children.users.route + '/list'
'/admin/user/list'
)
}
}

View File

@ -1,7 +1,7 @@
import {Component} from '@angular/core'
import {SubMenuComponent} from '../../modules/shared/components/sub-menu/sub-menu.component'
import {NavLink} from '../../modules/shared/components/nav/nav.component'
import {routes} from "../../routes";
import {Permission} from '../../modules/shared/model/account.model'
@Component({
selector: 'cre-administration',
@ -9,5 +9,10 @@ import {routes} from "../../routes";
styleUrls: ['./administration.component.sass']
})
export class AdministrationComponent extends SubMenuComponent {
links: NavLink[] = Object.values(routes.administration.children)
links: NavLink[] = [
{route: '/admin/user', title: 'Utilisateurs', permission: Permission.VIEW_USERS},
{route: '/admin/group', title: 'Groupes', permission: Permission.VIEW_USERS},
{route: '/admin/group-token', title: 'Ordinateurs', permission: Permission.ADMIN},
{route: '/admin/config', title: 'Configuration', permission: Permission.ADMIN}
]
}

View File

@ -1,7 +1,7 @@
import {Component} from '@angular/core'
import {NavLink} from '../../modules/shared/components/nav/nav.component'
import {Permission} from '../../modules/shared/model/account.model'
import {SubMenuComponent} from '../../modules/shared/components/sub-menu/sub-menu.component'
import {routes} from "../../routes";
@Component({
selector: 'cre-inventory-page',
@ -9,5 +9,9 @@ import {routes} from "../../routes";
styleUrls: ['./catalog.component.sass']
})
export class CatalogComponent extends SubMenuComponent {
links: NavLink[] = Object.values(routes.catalog.children)
links: NavLink[] = [
{route: '/catalog/materialtype', title: 'Types de produit', permission: Permission.VIEW_CATALOG},
{route: '/catalog/material', title: 'Inventaire', permission: Permission.VIEW_CATALOG},
{route: '/catalog/company', title: 'Bannières', permission: Permission.VIEW_CATALOG}
]
}

View File

@ -1,7 +1,7 @@
import {Component} from '@angular/core'
import {SubMenuComponent} from '../../modules/shared/components/sub-menu/sub-menu.component'
import {NavLink} from '../../modules/shared/components/nav/nav.component'
import {routes} from "../../routes";
import {Permission} from '../../modules/shared/model/account.model'
@Component({
selector: 'cre-others',
@ -9,5 +9,7 @@ import {routes} from "../../routes";
styleUrls: ['./misc.component.sass']
})
export class MiscComponent extends SubMenuComponent{
links: NavLink[] = Object.values(routes.misc.children)
links: NavLink[] = [
{route: '/misc/touch-up-kit', title: 'Kits de retouche', permission: Permission.VIEW_TOUCH_UP_KITS}
]
}

View File

@ -1,71 +0,0 @@
import {Permission} from "./modules/shared/model/account.model";
export const routes = {
recipes: {
route: 'color',
title: 'Couleurs',
requiredPermission: Permission.VIEW_RECIPES
},
catalog: {
route: 'catalog',
title: 'Catalogue',
requiredPermission: Permission.VIEW_CATALOG,
children: {
materialTypes: {
route: 'catalog/materialtype',
simpleRoute: 'materialtype',
title: 'Types de produit',
permission: Permission.VIEW_CATALOG
},
materials: {
route: 'catalog/inventory',
simpleRoute: 'inventory',
title: 'Inventaire',
permission: Permission.VIEW_CATALOG
},
companies: {
route: 'catalog/company',
simpleRoute: 'company',
title: 'Bannières',
permission: Permission.VIEW_CATALOG
}
}
},
misc: {
route: 'misc',
title: 'Autres',
enabled: true,
children: {
touchUpKits: {
route: 'misc/touch-up-kit',
simpleRoute: 'touch-up-kit',
title: 'Kits de retouche',
permission: Permission.VIEW_TOUCH_UP_KITS
}
}
},
administration: {
route: 'admin',
title: 'Administration',
requiredPermission: Permission.VIEW_USERS,
children: {
users: {route: 'admin/user', simpleRoute: 'user', title: 'Utilisateurs', permission: Permission.VIEW_USERS},
groups: {route: 'admin/group', simpleRoute: 'group', title: 'Groupes', permission: Permission.VIEW_USERS},
groupTokens: {
route: 'admin/grp-tokens',
simpleRoute: 'grp-tokens',
title: 'Ordinateurs',
permission: Permission.ADMIN
}, // Not 'group-tokens' so the navbar doesn't detect 'group' as active
configurations: {
route: 'admin/config',
simpleRoute: 'config',
title: 'Configuration',
permission: Permission.ADMIN
}
}
},
accounts: {
route: 'account'
}
}