Commit 607e52fb authored by PECQUOT's avatar PECQUOT

[enh] add parameter module

TODO: add qualitative values view
[enh] update Sumaris to 1.6.6
parent c504d4c4
......@@ -20,36 +20,36 @@
"e2e": "npm run ng e2e"
},
"dependencies": {
"@angular/animations": "^11.0.2",
"@angular/cdk": "^11.0.1",
"@angular/common": "^11.0.2",
"@angular/core": "^11.0.2",
"@angular/forms": "^11.0.2",
"@angular/material": "^11.0.1",
"@angular/material-moment-adapter": "^11.0.1",
"@angular/platform-browser": "^11.0.2",
"@angular/platform-browser-dynamic": "^11.0.2",
"@angular/router": "^11.0.2",
"@apollo/client": "^3.3.2",
"@angular/animations": "^11.0.4",
"@angular/cdk": "^11.0.2",
"@angular/common": "^11.0.4",
"@angular/core": "^11.0.4",
"@angular/forms": "^11.0.4",
"@angular/material": "^11.0.2",
"@angular/material-moment-adapter": "^11.0.2",
"@angular/platform-browser": "^11.0.4",
"@angular/platform-browser-dynamic": "^11.0.4",
"@angular/router": "^11.0.4",
"@apollo/client": "^3.3.4",
"@asymmetrik/ngx-leaflet": "^8.1.0",
"@e-is/ngx-material-table": "0.11.1",
"@ionic-native/audio-management": "^5.29.0",
"@ionic-native/camera": "^5.29.0",
"@ionic-native/core": "^5.29.0",
"@ionic-native/downloader": "^5.29.0",
"@ionic-native/geolocation": "^5.29.0",
"@ionic-native/in-app-browser": "^5.29.0",
"@ionic-native/keyboard": "^5.29.0",
"@ionic-native/native-audio": "^5.29.0",
"@ionic-native/network": "^5.29.0",
"@ionic-native/splash-screen": "^5.29.0",
"@ionic-native/status-bar": "^5.29.0",
"@ionic-native/vibration": "^5.29.0",
"@ionic/angular": "^5.5.0",
"@ionic/core": "^5.5.0",
"@ionic-native/audio-management": "^5.30.0",
"@ionic-native/camera": "^5.30.0",
"@ionic-native/core": "^5.30.0",
"@ionic-native/downloader": "^5.30.0",
"@ionic-native/geolocation": "^5.30.0",
"@ionic-native/in-app-browser": "^5.30.0",
"@ionic-native/keyboard": "^5.30.0",
"@ionic-native/native-audio": "^5.30.0",
"@ionic-native/network": "^5.30.0",
"@ionic-native/splash-screen": "^5.30.0",
"@ionic-native/status-bar": "^5.30.0",
"@ionic-native/vibration": "^5.30.0",
"@ionic/angular": "^5.5.2",
"@ionic/core": "^5.5.2",
"@ionic/pwa-elements": "^3.0.1",
"@ionic/storage": "^2.3.1",
"@ngtools/webpack": "^11.0.2",
"@ngtools/webpack": "^11.0.4",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"angular2-text-mask": "^9.0.0",
......@@ -72,28 +72,28 @@
"ionic-cache": "^5.2.0",
"ionicons": "^5.2.3",
"leaflet": "^1.6.0",
"localforage": "1.7.1",
"localforage": "1.7.4",
"lodash.clonedeep": "^4.5.0",
"luxon": "^1.24.1",
"luxon": "^1.25.0",
"material-design-icons": "^3.0.1",
"moment": "^2.27.0",
"moment": "^2.29.1",
"ng2-charts": "^2.4.2",
"ng2-charts-schematics": "^0.1.7",
"ngx-color-picker": "^10.1.0",
"ngx-markdown": "^11.0.0",
"ngx-markdown": "^11.0.1",
"ngx-material-timepicker": "5.5.3",
"ngx-quicklink": "^0.2.4",
"ngx-quicklink": "^0.2.6",
"roboto-fontface": "^0.10.0",
"rxjs": "^6.6.3",
"scrypt-async": "^2.0.1",
"seedrandom": "^3.0.5",
"subscriptions-transport-ws": "^0.9.16",
"subscriptions-transport-ws": "^0.9.18",
"sumaris-lib": "file:/home/ludovic/dev/sumaris/sumaris-app/dist",
"tslib": "^2.0.3",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^3.3.3",
"zone.js": "~0.10.3"
"zone.js": "~0.11.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.2",
......
......@@ -5,7 +5,7 @@ import {ReferentialQueries} from "@app/referential/services/referential.queries"
import gql from "graphql-tag";
import {referentialFragments} from "@app/referential/services/referential.fragments";
import {Observable} from "rxjs";
import {asPodObject, ReferentialFilter, ReferentialType} from "@app/referential/model/referential.model";
import {BaseReferentialFilter, ReferentialFilter, ReferentialType} from "@app/referential/model/referential.model";
import {ErrorCodes} from "@app/referential/services/errors";
import {map} from "rxjs/operators";
import {SortDirection} from "@angular/material/sort";
......@@ -74,7 +74,7 @@ const loadReferentialTypesQuery: any = gql`
@Injectable({providedIn: 'root'})
export class ReferentialGenericService extends BaseEntityService<Referential>
implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Referential, ReferentialFilter> {
implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Referential, ReferentialFilter> {
constructor(
protected graphql: GraphqlService,
......@@ -119,7 +119,7 @@ implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Refer
size: size || 100,
sortBy: sortBy,
sortDirection: sortDirection || 'asc',
filter: asPodObject(filter)
filter: BaseReferentialFilter.asPodObject(filter)
};
let now = new Date();
......@@ -182,7 +182,7 @@ implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Refer
size: size || 100,
sortBy: sortBy || filter.searchAttribute,
sortDirection: sortDirection || 'asc',
filter: asPodObject(filter)
filter: BaseReferentialFilter.asPodObject(filter)
};
const now = Date.now();
......@@ -211,8 +211,8 @@ implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Refer
if (ReferentialUtils.isNotEmpty(value)) return [value];
value = (typeof value === "string" && value !== '*') && value || undefined;
const res = await this.loadAll(0, !value ? 30 : 10, sortBy, sortDirection,
{ ...filter, searchText: value},
{ withTotal: false /* total not need */ }
{...filter, searchText: value},
{withTotal: false /* total not need */}
);
return res.data;
}
......@@ -249,10 +249,8 @@ implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Refer
if (data && data.data) {
// Update entities (id and update date)
entities.forEach(entity => {
const savedEntity = data.data.find(e => (e.id === entity.id || e.name === entity.name));
if (savedEntity !== entity) {
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
}
const savedEntity = data.data.find(e => (e.id === entity.id || e.label === entity.label || e.name === entity.name));
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
});
// Update the cache
......@@ -299,10 +297,8 @@ implements EntitiesService<Referential, ReferentialFilter>, SuggestService<Refer
update: (proxy, {data}) => {
// Update entity
const savedEntity = data && data.data && data.data[0];
if (savedEntity === entity) {
if (this._debug) console.debug(`[referential-service] ${entity.entityName} saved in ${Date.now() - now}ms`, entity);
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
}
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
if (this._debug) console.debug(`[referential-service] ${entity.entityName} saved in ${Date.now() - now}ms`, entity);
// Update the cache
if (isNew) {
......
......@@ -4,7 +4,7 @@ import {ReferentialFilter, ReferentialType} from "@app/referential/model/referen
import {ActivatedRoute} from "@angular/router";
import {ReferentialValidatorService} from "@app/referential/validator/referential.validator";
import {ReferentialTable} from "@app/referential/table/referential.table";
import {ReferentialGenericService} from "@app/referential/services/referential-generic.service";
import {ReferentialGenericService} from "@app/referential/generic/referential-generic.service";
import {BehaviorSubject, Subject} from "rxjs";
import {filter} from "rxjs/operators";
import {first} from "rxjs/internal/operators";
......
......@@ -15,7 +15,7 @@ const REFERENTIAL_PMFM_MENU: MenuItem = {
title: 'REFERENTIAL.MENU.PMFM',
children: [
{title: 'REFERENTIAL.ENTITY.PMFM'},
{title: 'REFERENTIAL.ENTITY.PARAMETER'},
{title: 'REFERENTIAL.ENTITY.PARAMETER', path: '/referential/Parameter'},
{title: 'REFERENTIAL.ENTITY.MATRIX'},
{title: 'REFERENTIAL.ENTITY.FRACTION'},
{title: 'REFERENTIAL.ENTITY.METHOD'},
......
......@@ -12,17 +12,29 @@ export class BaseReferentialFilter {
searchText?: string;
searchAttribute?: string;
static isEmpty(f: BaseReferentialFilter|any): boolean {
static isEmpty(f: BaseReferentialFilter | any): boolean {
return Beans.isEmpty<BaseReferentialFilter>(f, baseReferentialFilterKeys, {
blankStringLikeEmpty: true
});
}
static asPodObject(filter: BaseReferentialFilter) {
if (!filter) return filter;
return {
id: filter.id,
name: filter.name,
searchText: filter.searchText,
searchAttribute: filter.searchAttribute,
searchJoin: filter.searchJoin,
statusIds: isNotNil(filter.statusId) ? [filter.statusId] : (filter.statusIds)
};
}
}
export class ReferentialFilter extends BaseReferentialFilter {
entityName: string;
static isEmpty(f: ReferentialFilter|any): boolean {
static isEmpty(f: ReferentialFilter | any): boolean {
return Beans.isEmpty<ReferentialFilter>(f, referentialFilterKeys, {
blankStringLikeEmpty: true
});
......@@ -30,19 +42,7 @@ export class ReferentialFilter extends BaseReferentialFilter {
}
export function asPodObject(filter: BaseReferentialFilter) {
if (!filter) return filter;
return {
id: filter.id,
name: filter.name,
searchText: filter.searchText,
searchAttribute: filter.searchAttribute,
searchJoin: filter.searchJoin,
statusIds: isNotNil(filter.statusId) ? [filter.statusId] : (filter.statusIds)
};
}
const baseReferentialFilterKeys: KeysEnum<BaseReferentialFilter> = {
export const baseReferentialFilterKeys: KeysEnum<BaseReferentialFilter> = {
id: true,
name: true,
statusId: true,
......@@ -52,27 +52,11 @@ const baseReferentialFilterKeys: KeysEnum<BaseReferentialFilter> = {
searchAttribute: true
};
// @ts-ignore
const referentialFilterKeys: KeysEnum<ReferentialFilter> =
Object.assign(
baseReferentialFilterKeys,
{
entityName: true
}
);
/* OR
const referentialFilterKeys: KeysEnum<ReferentialFilter> = {
entityName: true,
id: true,
name: true,
statusId: true,
statusIds: true,
searchJoin: true,
searchText: true,
searchAttribute: true
};
*/
{
...baseReferentialFilterKeys,
entityName: true
};
export interface ReferentialType {
name: string;
......
......@@ -158,20 +158,6 @@
</td>
</ng-container>
<!-- Label column -->
<ng-container matColumnDef="label">
<th mat-header-cell *matHeaderCellDef cdkDrag [resizable]="true">
<span mat-sort-header><ion-label translate>REFERENTIAL.LABEL</ion-label></span>
</th>
<td mat-cell *matCellDef="let row">
<mat-form-field floatLabel="never">
<input matInput [formControl]="row.validator.controls['label']" [placeholder]="'REFERENTIAL.LABEL'|translate"
[readonly]="!row.editing">
<mat-error *ngIf="row.validator.controls['label'].hasError('required')" translate>ERROR.FIELD_REQUIRED</mat-error>
</mat-form-field>
</td>
</ng-container>
<!-- Description column -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef cdkDrag [resizable]="true">
......@@ -197,8 +183,8 @@
[placeholder]="'REFERENTIAL.PARENT'|translate"
[required]="false"
[config]="autocompleteFields.parent">
<mat-error matError *ngIf="row.validator.controls.parent.hasError('invalid')" translate>ERROR.FIELD_INVALID</mat-error>
</mat-autocomplete-field>
<mat-error *ngIf="row.validator.controls.parent.hasError('invalid')" translate>ERROR.FIELD_INVALID</mat-error>
</td>
</ng-container>
......
......@@ -2,7 +2,7 @@ import {ChangeDetectionStrategy, Component, Inject, Injector} from "@angular/cor
import {EntitiesTableDataSource, EnvironmentService, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS} from "sumaris-lib";
import {ReferentialTable} from "@app/referential/table/referential.table";
import {ActivatedRoute} from "@angular/router";
import {ReferentialGenericService} from "@app/referential/services/referential-generic.service";
import {ReferentialGenericService} from "@app/referential/generic/referential-generic.service";
import {BaseReferentialFilter} from "@app/referential/model/referential.model";
import {ParameterGroup} from "@app/referential/parameter-group/parameter-group.model";
import {ParameterGroupValidator} from "@app/referential/parameter-group/parameter-group.validator";
......
import {Beans, isNotNil, KeysEnum, NOT_MINIFY_OPTIONS, Referential, ReferentialAsObjectOptions, ReferentialRef} from "sumaris-lib";
import {QualitativeValue} from "@app/referential/parameter/qualitative-value.model";
import {BaseReferentialFilter, baseReferentialFilterKeys, ReferentialFilter} from "@app/referential/model/referential.model";
export class Parameter extends Referential<Parameter> /*implements IEntity<Parameter, EntityAsObjectOptions, string>*/ {
static fromObject(source: any): Parameter {
if (!source || source instanceof Parameter) return source;
const res = new Parameter();
res.fromObject(source);
return res;
}
qualitative: boolean;
calculated: boolean;
taxonomic: boolean;
parameterGroup: ReferentialRef;
qualitativeValues: QualitativeValue[];
fromObject(source: any) {
super.fromObject(source);
this.qualitative = source.qualitative;
this.calculated = source.calculated;
this.taxonomic = source.taxonomic;
this.parameterGroup = source.parameterGroup && ReferentialRef.fromObject(source.parameterGroup) || undefined;
this.qualitativeValues = source.qualitativeValues && source.qualitativeValues.map(QualitativeValue.fromObject) || undefined;
}
asObject(opts?: ReferentialAsObjectOptions): any {
const target = super.asObject(opts);
target.parameterGroup = this.parameterGroup && this.parameterGroup.asObject({...opts, ...NOT_MINIFY_OPTIONS, keepEntityName: true} as ReferentialAsObjectOptions) || undefined;
target.qualitativeValues = this.qualitativeValues && this.qualitativeValues.map(qv => qv.asObject(opts)) || undefined;
return target;
}
}
export class ParameterFilter extends BaseReferentialFilter {
qualitative: boolean;
calculated: boolean;
taxonomic: boolean;
parameterGroup: any;
static isEmpty(f: ParameterFilter|any): boolean {
return Beans.isEmpty<ParameterFilter>(f, parameterFilterKeys, {
blankStringLikeEmpty: true
});
}
static asPodObject(filter: ParameterFilter) {
if (!filter) return filter;
return {
...BaseReferentialFilter.asPodObject(filter),
qualitative: filter.qualitative,
calculated: filter.calculated,
taxonomic: filter.taxonomic,
parameterGroupId: filter.parameterGroup && filter.parameterGroup.id,
};
}
}
const parameterFilterKeys: KeysEnum<ParameterFilter> =
{
...baseReferentialFilterKeys,
qualitative: true,
calculated: true,
taxonomic: true,
parameterGroup: true
};
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 {ParameterTable} from "@app/referential/parameter/parameter.table";
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: ParameterTable,
canDeactivate: [ComponentDirtyGuard],
data: {
profile: 'ADMIN'
}
}
];
@NgModule({
imports: [
QuadrigeCoreModule,
TranslateModule.forChild(),
RouterModule.forChild(routes)
],
declarations: [
ParameterTable
],
exports: [
ParameterTable
]
})
export class ParameterModule { }
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 {Parameter, ParameterFilter} from "@app/referential/parameter/parameter.model";
const fragments = {
parameter: gql`fragment ParameterFragment on ParameterVO {
id
name
description
comments
parameterGroup {
...ReferentialFragment
}
qualitative
calculated
taxonomic
qualitativeValues {
...QualitativeValueFragment
}
updateDate
creationDate
statusId
__typename
}`,
qualitativeValue: gql`fragment QualitativeValueFragment on QualitativeValueVO {
parameterId
id
name
description
comments
creationDate
updateDate
statusId
__typename
}
`
};
const queries: ReferentialQueries = {
loadAllQuery(): any {
return gql`
query Referentials($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: ParameterFilterVOInput){
data:parameters(offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...ParameterFragment
}
}
${fragments.parameter}
${fragments.qualitativeValue}
${referentialFragments.referential}
`;
},
loadAllWithTotalQuery(): any {
return gql`
query ReferentialsWithTotal($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: ParameterFilterVOInput){
data:parameters(offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...ParameterFragment
}
count:parametersCount(filter: $filter)
}
${fragments.parameter}
${fragments.qualitativeValue}
${referentialFragments.referential}
`;
},
saveAllMutation(): any {
return gql`
mutation SaveReferentials($data:[ParameterVOInput]){
data:saveParameters(parameters: $data){
...ParameterFragment
}
}
${fragments.parameter}
${fragments.qualitativeValue}
${referentialFragments.referential}
`;
},
deleteAllMutation(): any {
return gql`
mutation deleteReferentials($ids:[String]){
deleteParameters(ids: $ids)
}
`;
}
};
@Injectable({providedIn: 'root'})
export class ParameterService extends BaseReferentialService<Parameter, BaseReferentialFilter> {
constructor(
protected graphql: GraphqlService,
protected platform: PlatformService,
@Inject(EnvironmentService) protected environment
) {
super(graphql, queries, platform, environment, "Parameter", Parameter.fromObject, ParameterFilter.asPodObject); // todo add insertFilterFn ????
}
}
<app-toolbar [title]="'REFERENTIAL.ENTITY.PARAMETER'|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>