Commit c504d4c4 authored by PECQUOT's avatar PECQUOT

[enh] add parameter-group module

[enh] referential.table.ts: add default hidden columns
[enh] color.testing.page: add color changer
todo: add some color backgrounds examples
parent a489e7ad
import {AuthGuardService} from "@app/core/services/auth-guard.service";
import {NgModule} from "@angular/core";
import {SharedRoutingModule} from "sumaris-lib";
import {RouterModule, Routes} from "@angular/router";
import {AdminModule} from "@app/admin/admin.module";
const routes: Routes = [
// {
// path: 'config',
// pathMatch: 'full',
// component: ConfigurationPage,
// canActivate: [AuthGuardService],
// data: {
// profile: 'ADMIN'
// }
// }
];
@NgModule({
imports: [
SharedRoutingModule,
AdminModule,
RouterModule.forChild(routes)
],
exports: [RouterModule]
})
export class AdminRoutingModule { }
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {QuadrigeCoreModule} from "@app/core/quadrige.core.module";
@NgModule({
imports: [
CommonModule,
QuadrigeCoreModule
],
declarations: [],
})
export class AdminModule {
}
......@@ -28,11 +28,11 @@ const routes: Routes = [
// },
// Admin
// {
// path: 'admin',
// canActivate: [AuthGuardService],
// loadChildren: () => import('./admin/admin-routing.module').then(m => m.AdminRoutingModule)
// },
{
path: 'admin',
canActivate: [AuthGuardService],
loadChildren: () => import('./admin/admin-routing.module').then(m => m.AdminRoutingModule)
},
// Referential
{
......
<form class="form-container" [formGroup]="form" (ngSubmit)="doSubmit($event)">
<!-- error -->
<ion-item *ngIf="error" lines="none">
<ion-icon color="danger" slot="start" name="alert-circle"></ion-icon>
<ion-label color="danger" class="error" [innerHTML]="error|translate"></ion-label>
</ion-item>
<ion-grid>
<ion-row>
<!-- Label -->
<ion-col>
<mat-form-field>
<input matInput [placeholder]="'REFERENTIAL.LABEL'|translate" formControlName="label"
autocomplete="off" required>
<mat-error *ngIf="form.controls.label.hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
<mat-error *ngIf="form.controls.label.hasError('unique')" translate>ERROR.FIELD_NOT_UNIQUE</mat-error>
</mat-form-field>
</ion-col>
<!-- Status-->
<ion-col>
<mat-form-field>
<mat-select [formControl]="form.controls.statusId" [placeholder]="'REFERENTIAL.STATUS'|translate" required>
<mat-select-trigger>
<span *ngIf="form.controls.statusId.value &gt;= 0">
{{ statusById[form.controls.statusId.value]?.label | translate}}</span>
</mat-select-trigger>
<mat-option *ngFor="let item of statusList" [value]="item.id">
<ion-icon [name]="item.icon"></ion-icon>
{{ item.label |translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="form.controls.statusId.hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
</mat-form-field>
</ion-col>
</ion-row>
<!-- Name -->
<ion-row>
<ion-col>
<mat-form-field>
<input matInput [placeholder]="'REFERENTIAL.NAME'|translate" formControlName="name"
autocomplete="off" required>
<mat-error *ngIf="form.controls.name.hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
</mat-form-field>
</ion-col>
</ion-row>
<!-- Description -->
<ion-row *ngIf="form.controls.description">
<ion-col size="12">
<mat-form-field>
<textarea matInput #description
matTextareaAutosize="true"
matAutosizeMinRows="4"
rows="1"
maxlength="255"
[placeholder]="'REFERENTIAL.DESCRIPTION'|translate"
formControlName="description">
</textarea>
<mat-hint align="end">{{description.value.length}} / 255</mat-hint>
<mat-error *ngIf="form.controls.description.hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
</mat-form-field>
</ion-col>
</ion-row>
<!-- children -->
<ng-content></ng-content>
<ion-row *ngIf="form.controls.comments && showComments">
<!-- Comments -->
<ion-col class="ion-no-padding" size="12">
<mat-form-field appearance="outline">
<textarea matInput #comments
matTextareaAutosize="true"
matAutosizeMinRows="4"
rows="4"
maxlength="2000"
[placeholder]="'REFERENTIAL.COMMENTS'|translate"
formControlName="comments">
</textarea>
<mat-hint align="end">{{comments.value.length}} / 2000</mat-hint>
<mat-error *ngIf="form.controls.comments.hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
</mat-form-field>
</ion-col>
</ion-row>
</ion-grid>
</form>
import {DateAdapter} from "@angular/material/core";
import {Moment} from "moment";
import {ReferentialValidatorService} from "../validator/referential.validator";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from "@angular/core";
import {ValidatorService} from "@e-is/ngx-material-table";
import {AppForm} from "sumaris-lib";
import {DefaultStatusList, Referential, StatusValue} from "sumaris-lib";
import {LocalSettingsService} from "sumaris-lib";
@Component({
selector: 'app-referential-form',
templateUrl: './referential.form.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: ValidatorService,
useExisting: ReferentialValidatorService
}
]
})
export class ReferentialForm extends AppForm<Referential> implements OnInit {
private _statusList = DefaultStatusList;
statusById: { [id: number]: StatusValue };
@Input() showError = true;
@Input() showComments = true;
@Input() entityName;
@Input()
set statusList(values: StatusValue[]) {
this._statusList = values;
// Fill statusById
this.statusById = {};
this.statusList.forEach((status) => this.statusById[status.id] = status);
}
get statusList(): StatusValue[] {
return this._statusList;
}
constructor(
protected dateAdapter: DateAdapter<Moment>,
protected validatorService: ValidatorService,
protected settings?: LocalSettingsService,
protected cd?: ChangeDetectorRef
) {
super(dateAdapter, validatorService.getRowValidator(), settings);
}
ngOnInit() {
super.ngOnInit();
// Fill statusById
if (this._statusList && !this.statusById) {
this.statusById = {};
this._statusList.forEach((status) => this.statusById[status.id] = status);
}
}
setValue(data: Referential, opts?: { emitEvent?: boolean; onlySelf?: boolean }) {
super.setValue(data, opts);
// Make sure to set entityName if set from Input()
const entityNameControl = this.form.get('entityName');
if (entityNameControl && this.entityName && entityNameControl.value !== this.entityName) {
entityNameControl.setValue(this.entityName, opts);
}
}
protected markForCheck() {
if (this.cd) this.cd.markForCheck();
}
}
......@@ -14,13 +14,13 @@ const REFERENTIAL_THEMATIC_MENU: MenuItem = {
const REFERENTIAL_PMFM_MENU: MenuItem = {
title: 'REFERENTIAL.MENU.PMFM',
children: [
{title: 'REFERENTIAL.ENTITY.QUINTUPLET'},
{title: 'REFERENTIAL.ENTITY.PMFM'},
{title: 'REFERENTIAL.ENTITY.PARAMETER'},
{title: 'REFERENTIAL.ENTITY.MATRIX'},
{title: 'REFERENTIAL.ENTITY.FRACTION'},
{title: 'REFERENTIAL.ENTITY.METHOD'},
{title: 'REFERENTIAL.ENTITY.UNIT'},
{title: 'REFERENTIAL.ENTITY.PARAMETER_GROUP'},
{title: 'REFERENTIAL.ENTITY.PARAMETER_GROUP', path: '/referential/ParameterGroup'},
]
};
const REFERENTIAL_ADMINISTRATION_MENU: MenuItem = {
......
import {Beans, isNotNil, KeysEnum} from "sumaris-lib";
export interface BaseReferentialFilter {
export class BaseReferentialFilter {
id?: string;
name?: string; // todo remove this, the name can be search by searchText
......@@ -12,21 +12,16 @@ export interface BaseReferentialFilter {
searchText?: string;
searchAttribute?: string;
static isEmpty(f: BaseReferentialFilter|any): boolean {
return Beans.isEmpty<BaseReferentialFilter>(f, baseReferentialFilterKeys, {
blankStringLikeEmpty: true
});
}
}
export class ReferentialFilter implements BaseReferentialFilter {
export class ReferentialFilter extends BaseReferentialFilter {
entityName: string;
id?: string;
name?: string;
statusId?: number;
statusIds?: number[];
searchJoin?: string; // If search is on a sub entity (e.g. Metier can search on TaxonGroup)
searchText?: string;
searchAttribute?: string;
static isEmpty(f: ReferentialFilter|any): boolean {
return Beans.isEmpty<ReferentialFilter>(f, referentialFilterKeys, {
blankStringLikeEmpty: true
......@@ -47,6 +42,26 @@ export function asPodObject(filter: BaseReferentialFilter) {
};
}
const baseReferentialFilterKeys: KeysEnum<BaseReferentialFilter> = {
id: true,
name: true,
statusId: true,
statusIds: true,
searchJoin: true,
searchText: true,
searchAttribute: true
};
// @ts-ignore
const referentialFilterKeys: KeysEnum<ReferentialFilter> =
Object.assign(
baseReferentialFilterKeys,
{
entityName: true
}
);
/* OR
const referentialFilterKeys: KeysEnum<ReferentialFilter> = {
entityName: true,
id: true,
......@@ -57,6 +72,7 @@ const referentialFilterKeys: KeysEnum<ReferentialFilter> = {
searchText: true,
searchAttribute: true
};
*/
export interface ReferentialType {
name: string;
......
import {NOT_MINIFY_OPTIONS, Referential, ReferentialAsObjectOptions, ReferentialRef} from "sumaris-lib";
export class ParameterGroup extends Referential<ParameterGroup> {
static fromObject(source: any): ParameterGroup {
if (!source || source instanceof ParameterGroup) return source;
const res = new ParameterGroup();
res.fromObject(source);
return res;
}
parent: ReferentialRef;
fromObject(source: any) {
super.fromObject(source);
this.parent = source.parent && ReferentialRef.fromObject(source.parent) || undefined;
}
asObject(opts?: ReferentialAsObjectOptions): any {
const target = super.asObject(opts);
target.parent = this.parent && this.parent.asObject({...opts, ...NOT_MINIFY_OPTIONS, keepEntityName: true} as ReferentialAsObjectOptions) || undefined;
return target;
}
}
import {NgModule} from "@angular/core";
import {TranslateModule} from "@ngx-translate/core";
import {RouterModule, Routes} from "@angular/router";
import {ComponentDirtyGuard} from "@app/shared/table/component-dirty.guard";
import {QuadrigeCoreModule} from "@app/core/quadrige.core.module";
import {ParameterGroupTable} from "@app/referential/parameter-group/parameter-group.table";
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: ParameterGroupTable,
canDeactivate: [ComponentDirtyGuard],
data: {
profile: 'ADMIN'
}
}
];
@NgModule({
imports: [
QuadrigeCoreModule,
TranslateModule.forChild(),
RouterModule.forChild(routes)
],
declarations: [
ParameterGroupTable
],
exports: [
ParameterGroupTable
]
})
export class ParameterGroupModule { }
import {Inject, Injectable} from "@angular/core";
import {EnvironmentService, GraphqlService, PlatformService} from "sumaris-lib";
import {BaseReferentialService} from "@app/referential/services/base-referential.service";
import gql from "graphql-tag";
import {referentialFragments} from "@app/referential/services/referential.fragments";
import {ReferentialQueries} from "@app/referential/services/referential.queries";
import {BaseReferentialFilter} from "@app/referential/model/referential.model";
import {ParameterGroup} from "@app/referential/parameter-group/parameter-group.model";
const fragments = {
parameterGroup: gql`fragment ParameterGroupFragment on ParameterGroupVO {
id
name
description
comments
parent {
...ReferentialFragment
}
updateDate
creationDate
statusId
__typename
}`
};
const queries: ReferentialQueries = {
loadAllQuery(): any {
return gql`
query Referentials($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: ItemReferentialFilterVOInput){
data:parameterGroups(offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...ParameterGroupFragment
}
}
${fragments.parameterGroup}
${referentialFragments.referential}
`;
},
loadAllWithTotalQuery(): any {
return gql`
query ReferentialsWithTotal($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: ItemReferentialFilterVOInput){
data:parameterGroups(offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...ParameterGroupFragment
}
count:parameterGroupsCount(filter: $filter)
}
${fragments.parameterGroup}
${referentialFragments.referential}
`;
},
saveAllMutation(): any {
return gql`
mutation SaveReferentials($data:[ParameterGroupVOInput]){
data:saveParameterGroups(parameterGroups: $data){
...ParameterGroupFragment
}
}
${fragments.parameterGroup}
${referentialFragments.referential}
`;
},
deleteAllMutation(): any {
return gql`
mutation deleteReferentials($ids:[Int]){
deleteParameterGroups(ids: $ids)
}
`;
}
};
@Injectable({providedIn: 'root'})
export class ParameterGroupService extends BaseReferentialService<ParameterGroup, BaseReferentialFilter> {
constructor(
protected graphql: GraphqlService,
protected platform: PlatformService,
@Inject(EnvironmentService) protected environment
) {
super(graphql, queries, platform, environment, "ParameterGroup", ParameterGroup.fromObject); // todo add insertFilterFn ????
}
}
<app-toolbar [title]="'REFERENTIAL.ENTITY.PARAMETER_GROUP'|translate" color="primary">
</app-toolbar>
<ion-content class="ion-no-padding">
<mat-toolbar>
<mat-toolbar-row>
<ng-container *ngIf="!selection.hasValue(); else hasSelection">
<button mat-icon-button class="hidden-xs hidden-sm hidden-mobile"
*ngIf="canEdit"
[title]="'COMMON.BTN_ADD'|translate"
(click)="addRow()">
<mat-icon>add</mat-icon>
</button>
<button mat-icon-button color="light" [title]="'COMMON.BTN_REFRESH'|translate"
(click)="onRefresh.emit()">
<mat-icon>refresh</mat-icon>
</button>
<button mat-icon-button color="{{filterIsEmpty ? '' : 'primary'}}"
[title]="'COMMON.BTN_FILTER'|translate"
(click)="filterExpansionPanel.toggle()">
<mat-icon>filter_list</mat-icon>
<ion-text *ngIf="!filterIsEmpty">
<span [innerHTML]="'COMMON.TABLE.FILTERED_PARENTHESIS'|translate"></span></ion-text>
</button>
</ng-container>
<!-- if row selection -->
<ng-template #hasSelection>
<!-- delete -->
<button mat-icon-button
class="hidden-xs hidden-sm"
*ngIf="canEdit" [title]="'COMMON.BTN_DELETE'|translate"
(click)="deleteSelection($event)">
<mat-icon>delete</mat-icon>
</button>
</ng-template>
</mat-toolbar-row>
</mat-toolbar>
<!-- search -->
<mat-expansion-panel #filterExpansionPanel class="ion-no-padding filter-panel">
<form class="form-container ion-padding-top" [formGroup]="filterForm" (ngSubmit)="submitFilter()">
<ion-grid>
<ion-row>
<ion-col size="10">
<!-- search text -->
<mat-form-field>
<input matInput
formControlName="searchText"
autocomplete="off"
[placeholder]="'REFERENTIAL.LIST.FILTER.SEARCH_TEXT'|translate: {fields: searchFields}">
<button mat-icon-button matSuffix tabindex="-1"
type="button"
(click)="clearControlValue($event, filterForm.controls.searchText)"
[hidden]="filterForm.controls.searchText.disabled || !filterForm.controls.searchText.value">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</ion-col>
<ion-col size="2">
<!-- status -->
<mat-form-field>
<mat-select formControlName="statusId" [placeholder]="'REFERENTIAL.STATUS'|translate">
<mat-option [value]="null"><i><span translate>COMMON.EMPTY_OPTION</span></i></mat-option>
<mat-option *ngFor="let item of statusList" [value]="item.id">
<ion-icon [name]="item.icon"></ion-icon>
{{ item.label |translate }}
</mat-option>
</mat-select>
<button mat-icon-button matSuffix tabindex="-1"
type="button"
(click)="clearControlValue($event, filterForm.controls.statusId)"
[hidden]="filterForm.controls.statusId.disabled || !filterForm.controls.statusId.value">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</ion-col>
</ion-row>
</ion-grid>
</form>
<mat-action-row>
<!-- Counter -->
<ion-label [hidden]="(loadingSubject|async) || filterForm.dirty" [color]="resultsLength === 0 && 'danger'"
class="ion-padding">
{{ (resultsLength ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: {count: (resultsLength | numberFormat)} }}
</ion-label>
<div class="toolbar-spacer"></div>
<!-- Search button -->
<ion-button mat-button
*ngIf="!mobile"
[color]="filterForm.dirty ? 'tertiary' : undefined"
[fill]="filterForm.dirty ? 'solid' : 'clear'"
(click)="submitFilter()">
<ion-text translate>COMMON.BTN_SEARCH</ion-text>
</ion-button>
</mat-action-row>
</mat-expansion-panel>
<!-- error -->
<ion-item *ngIf="error" visible-xs visible-sm visible-mobile lines="none">
<ion-icon color="danger" slot="start" name="alert-circle"></ion-icon>
<ion-label color="danger" class="error" [innerHTML]="error|translate"></ion-label>
</ion-item>
<div class="table-container">
<table mat-table #table [dataSource]="dataSource"
matSort matSortActive="id" matSortDirection="asc" matSortDisableClear [trackBy]="trackByFn"
cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="drop($event)"
>