Commit 835065d5 authored by PECQUOT's avatar PECQUOT

[enh] generic referential OK except sampling-equipment

parent 7cddc52a
import {Component, Inject} from '@angular/core';
import {DOCUMENT} from "@angular/common";
import {throttleTime} from "rxjs/operators";
import {ReferentialRefService} from "./referential/services/referential-ref.service";
import {MatIconRegistry} from "@angular/material/icon";
import {DomSanitizer} from "@angular/platform-browser";
import {
AccountService,
changeCaseToUnderscore,
ConfigService,
Configuration,
FormFieldDefinition,
getColorContrast,
getColorShade,
getColorTint,
hexToRgbArray,
isNotNil,
joinPropertiesPath,
LocalSettingsService,
mixHex,
PlatformService
......@@ -33,8 +32,7 @@ export class AppComponent {
@Inject(DOCUMENT) private _document: HTMLDocument,
private platform: PlatformService,
private accountService: AccountService,
private referentialRefService: ReferentialRefService,
// private configService: ConfigService,
private configService: ConfigService,
private settings: LocalSettingsService,
private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer
......@@ -49,10 +47,7 @@ export class AppComponent {
await this.platform.start();
// Listen for config changed
// this.configService.config.subscribe(config => this.onConfigChanged(config));
// Add additional account fields
this.addAccountFields();
this.configService.config.subscribe(config => this.onConfigChanged(config));
this.addSettingsFields();
......@@ -74,35 +69,35 @@ export class AppComponent {
}, 16);
}
// protected onConfigChanged(config: Configuration) {
//
// this.logo = config.smallLogo || config.largeLogo;
// this.appName = config.label;
//
// // Set document title
// const title = isNotNil(config.name) ? `${config.label} - ${config.name}` : config.label;
// this._document.getElementById('appTitle').textContent = title;
//
// // Set document favicon
// const favicon = config.properties && config.properties["quadrige3.favicon"];
// if (isNotNil(favicon)) {
// this._document.getElementById('appFavicon').setAttribute('href', favicon);
// }
//
// if (config.properties) {
// this.updateTheme({
// colors: {
// primary: config.properties["quadrige3.color.primary"],
// secondary: config.properties["quadrige3.color.secondary"],
// tertiary: config.properties["quadrige3.color.tertiary"],
// success: config.properties["quadrige3.color.success"],
// warning: config.properties["quadrige3.color.warning"],
// accent: config.properties["quadrige3.color.accent"],
// danger: config.properties["quadrige3.color.danger"]
// }
// });
// }
// }
protected onConfigChanged(config: Configuration) {
this.logo = config.smallLogo || config.largeLogo;
this.appName = config.label;
// Set document title
const title = isNotNil(config.name) ? `${config.label} - ${config.name}` : config.label;
this._document.getElementById('appTitle').textContent = title;
// Set document favicon
const favicon = config.properties && config.properties["quadrige3.favicon"];
if (isNotNil(favicon)) {
this._document.getElementById('appFavicon').setAttribute('href', favicon);
}
if (config.properties) {
this.updateTheme({
colors: {
primary: config.properties["quadrige3.color.primary"],
secondary: config.properties["quadrige3.color.secondary"],
tertiary: config.properties["quadrige3.color.tertiary"],
success: config.properties["quadrige3.color.success"],
warning: config.properties["quadrige3.color.warning"],
accent: config.properties["quadrige3.color.accent"],
danger: config.properties["quadrige3.color.danger"]
}
});
}
}
protected updateTheme(options: { colors?: { [color: string]: string } }) {
if (!options) return;
......@@ -153,47 +148,6 @@ export class AppComponent {
}
}
protected addAccountFields() {
console.debug("[app] Add additional account fields...");
const attributes = this.settings.getFieldDisplayAttributes('department');
const departmentDefinition = <FormFieldDefinition>{
key: 'department',
label: 'USER.DEPARTMENT.TITLE',
type: 'entity',
autocomplete: {
service: this.referentialRefService,
filter: {entityName: 'Department'},
displayWith: (value) => value && joinPropertiesPath(value, attributes),
attributes: attributes,
columnSizes: attributes.map(attr => attr === 'label' ? 3 : undefined)
},
extra: {
registration: {
required: true
},
account: {
required: true,
disable: true
}
}
};
// Add account field: department
this.accountService.registerAdditionalField(departmentDefinition);
// When settings changed
this.settings.onChange
.pipe(throttleTime(400))
.subscribe(() => {
// Update the display fn
const attributes = this.settings.getFieldDisplayAttributes('department');
departmentDefinition.autocomplete.attributes = attributes;
departmentDefinition.autocomplete.displayWith = (value) => value && joinPropertiesPath(value, attributes) || undefined;
});
}
protected addSettingsFields() {
console.debug("[app] Add additional settings fields...");
......
......@@ -29,7 +29,7 @@ import {
APP_CONFIG_OPTIONS,
APP_GRAPHQL_TYPE_POLICIES,
APP_HOME_BUTTONS,
APP_LOCAL_SETTINGS_OPTIONS,
APP_LOCAL_SETTINGS_OPTIONS, APP_LOCALES,
APP_MENU_ITEMS,
APP_TESTING_PAGES,
AppGestureConfig,
......@@ -106,7 +106,32 @@ import {environment} from "@environments/environment";
AudioManagement,
{provide: APP_BASE_HREF, useValue: (environment.baseUrl || '/')},
//{ provide: ErrorHandler, useClass: IonicErrorHandler },
{provide: APP_LOCALES, useValue:
[
{
key: 'fr',
value: 'Français',
country: 'fr'
},
{
key: 'en',
value: 'English (UK)',
country: 'gb'
},
{
key: 'en-US',
value: 'English (US)',
country: 'us'
},
{
key: 'es-ES',
value: 'Spanish',
country: 'es'
}
]
},
{provide: MAT_DATE_LOCALE, useValue: 'en'},
{
provide: MAT_DATE_FORMATS, useValue: {
......@@ -138,43 +163,26 @@ import {environment} from "@environments/environment";
{ provide: APP_MENU_ITEMS, useValue: [
{title: 'MENU.HOME', path: '/', icon: 'home'},
// Data entry
{title: 'MENU.DATA_ENTRY_DIVIDER', profile: 'VIEWER'},
{title: 'MENU.TRIPS', path: '/trips',
matIcon: 'explore',
profile: 'VIEWER',
ifProperty: 'quadrige3.trip.enable',
titleProperty: 'quadrige3.trip.name'
},
{
title: 'MENU.OBSERVED_LOCATIONS', path: '/observations',
matIcon: 'verified_user',
profile: 'VIEWER',
ifProperty: 'quadrige3.observedLocation.enable',
titleProperty: 'quadrige3.observedLocation.name'
},
// Data extraction
{title: 'MENU.DATA_ACCESS_DIVIDER', profile: 'VIEWER'},
{title: 'MENU.DOWNLOADS', path: '/extraction/data', icon: 'cloud-download', profile: 'VIEWER'},
{title: 'MENU.MAP', path: '/extraction/map', icon: 'earth', profile: 'VIEWER'},
// {title: 'MENU.DATA_ACCESS_DIVIDER', profile: 'GUEST'},
// {title: 'MENU.DOWNLOADS', path: '/extraction/data', icon: 'cloud-download', profile: 'GUEST'},
// {title: 'MENU.MAP', path: '/extraction/map', icon: 'earth', profile: 'GUEST'},
// Referential
{title: 'MENU.REFERENTIAL_DIVIDER', profile: 'VIEWER'},
{title: 'MENU.VESSELS', path: '/referential/vessels', icon: 'boat', profile: 'VIEWER'},
{title: 'MENU.REFERENTIAL', path: '/referential', icon: 'list', profile: 'ADMIN'},
{title: 'MENU.USERS', path: '/admin/users', icon: 'people', profile: 'ADMIN'},
{title: 'MENU.REFERENTIAL_DIVIDER', profile: 'GUEST'},
{title: 'MENU.REFERENTIAL', path: '/referential', icon: 'list', profile: 'GUEST'},
// {title: 'MENU.USERS', path: '/admin/users', icon: 'people', profile: 'ADMIN'},
{title: 'MENU.SERVER', path: '/admin/config', icon: 'server', profile: 'ADMIN'},
// Settings
{title: '' /*empty divider*/, cssClass: 'flex-spacer'},
{title: 'MENU.TESTING', path: '/testing', icon: 'code', color: 'danger', ifProperty: 'quadrige3.testing.enable', profile: 'ADMIN'},
{title: 'MENU.TESTING', path: '/testing', icon: 'code', color: 'danger', /*ifProperty: 'quadrige3.testing.enable',*/ profile: 'ADMIN'},
{title: 'MENU.LOCAL_SETTINGS', path: '/settings', icon: 'settings', color: 'medium'},
{title: 'MENU.ABOUT', action: 'about', matIcon: 'help_outline', color: 'medium', cssClass: 'visible-mobile'},
// Logout
{title: 'MENU.LOGOUT', action: 'logout', icon: 'log-out', profile: 'VIEWER', color: 'medium hidden-mobile'},
{title: 'MENU.LOGOUT', action: 'logout', icon: 'log-out', profile: 'VIEWER', color: 'danger visible-mobile'}
{title: 'MENU.LOGOUT', action: 'logout', icon: 'log-out', profile: 'GUEST', color: 'medium hidden-mobile'},
{title: 'MENU.LOGOUT', action: 'logout', icon: 'log-out', profile: 'GUEST', color: 'danger visible-mobile'}
]
},
......@@ -182,16 +190,15 @@ import {environment} from "@environments/environment";
// Home buttons
{ provide: APP_HOME_BUTTONS, useValue: [
// Data entry
{ title: 'MENU.DATA_ENTRY_DIVIDER', profile: 'VIEWER'},
{ title: 'MENU.TRIPS', path: '/trips',
matIcon: 'explore',
profile: 'VIEWER',
profile: 'GUEST',
ifProperty: 'quadrige3.trip.enable',
titleProperty: 'quadrige3.trip.name'
},
{ title: 'MENU.OBSERVED_LOCATIONS', path: '/observations',
matIcon: 'verified_user',
profile: 'VIEWER',
profile: 'GUEST',
ifProperty: 'quadrige3.observedLocation.enable',
titleProperty: 'quadrige3.observedLocation.name'
},
......@@ -224,7 +231,7 @@ import {environment} from "@environments/environment";
// Test pages link
{ provide: APP_TESTING_PAGES, useValue: <TestingPage[]>[
{label: 'Batch tree', page: '/testing/trip/batchTree'}
{label: 'Table', page: '/testing/table'}
]},
],
bootstrap: [AppComponent],
......
......@@ -9,8 +9,8 @@
<mat-menu #matmenu="matMenu">
<button *ngFor="let item of locales"
mat-menu-item
(click)="changeLanguage(item.id)">
<ion-label>{{item.name}}</ion-label>
(click)="changeLanguage(item.key)">
<ion-label>{{item.value}}</ion-label>
</button>
</mat-menu>
......
......@@ -8,6 +8,7 @@ import {
Account,
AccountService,
accountToString,
APP_LOCALES,
ConfigService,
Configuration,
Department,
......@@ -16,7 +17,7 @@ import {
HistoryPageReference,
isNotNil,
isNotNilOrBlank,
Locales,
LocaleConfig,
LocalSettings,
LocalSettingsService,
MenuItem,
......@@ -66,8 +67,6 @@ export class HomePage implements OnDestroy {
offline: boolean;
$filteredButtons = new BehaviorSubject<MenuItem[]>(undefined);
locales = Locales;
get currentLocaleCode(): string {
return this.loading ? '' :
(this.translate.currentLang || this.translate.defaultLang).substr(0, 2);
......@@ -84,6 +83,7 @@ export class HomePage implements OnDestroy {
public network: NetworkService,
public settings: LocalSettingsService,
@Inject(EnvironmentService) protected environment,
@Inject(APP_LOCALES) public locales: LocaleConfig[],
@Optional() @Inject(APP_HOME_BUTTONS) public buttons: MenuItem[]
) {
......
import {DateAdapter} from "@angular/material/core";
import {Moment} from "moment";
import {ReferentialValidatorService} from "../services/validator/referential.validator";
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";
......
<link rel="stylesheet" href="referential-memory.table.scss">
<mat-toolbar>
<button mat-icon-button
*ngIf="canEdit && !selection.hasValue() && enabled" [title]="'COMMON.BTN_ADD'|translate" (click)="addRow()"
......
......@@ -2,15 +2,17 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Injector,
import {TableElement, ValidatorService} from "@e-is/ngx-material-table";
import {ActivatedRoute, Router} from "@angular/router";
import {ModalController, Platform} from "@ionic/angular";
import {ReferentialValidatorService} from "../services/validator/referential.validator";
import {ReferentialFilter} from "../services/referential.service";
import {ReferentialValidatorService} from "../validator/referential.validator";
import {AccountService, AppInMemoryTable, DefaultStatusList, EnvironmentService, InMemoryEntitiesService, Referential, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS} from "sumaris-lib";
import {ReferentialFilter} from "@app/referential/model/referential.model";
/*
TODO should remove this one
*/
@Component({
selector: 'app-referential-table',
templateUrl: 'referential.table.html',
styleUrls: ['referential.table.scss'],
templateUrl: 'referential-memory.table.html',
styleUrls: ['referential-memory.table.scss'],
providers: [
{provide: ValidatorService, useExisting: ReferentialValidatorService},
{
......@@ -20,7 +22,7 @@ import {AccountService, AppInMemoryTable, DefaultStatusList, EnvironmentService,
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReferentialTable extends AppInMemoryTable<Referential, ReferentialFilter> {
export class ReferentialMemoryTable extends AppInMemoryTable<Referential, ReferentialFilter> {
statusList = DefaultStatusList;
statusById: any;
......
<mat-toolbar *ngIf="showToolbar">
<!-- refresh (debug only) -->
<button mat-icon-button small color="light" *ngIf="debug && !selection.hasValue()" [title]="'COMMON.BTN_REFRESH'|translate"
(click)="onRefresh.emit()">
<mat-icon>refresh</mat-icon>
</button>
<ion-item *ngIf="error" hidden-xs hidden-sm hidden-mobile lines="none">
<ion-icon color="danger" slot="start" name="alert"></ion-icon>
<ion-label color="danger" [innerHTML]="error|translate"></ion-label>
</ion-item>
<div class="toolbar-spacer"></div>
<button mat-icon-button [title]="'COMMON.DISPLAYED_COLUMNS'|translate" (click)="openSelectColumnsModal($event)">
<mat-icon>more_vert</mat-icon>
</button>
</mat-toolbar>
<!-- error -->
<ion-item *ngIf="error" visible-xs visible-sm visible-mobile lines="none">
<ion-icon color="danger" slot="start" name="alert"></ion-icon>
<ion-label color="danger" [innerHTML]="error|translate"></ion-label>
</ion-item>
<form *ngIf="showFilter" class="form-container ion-padding-top"
[formGroup]="filterForm"
(ngSubmit)="onRefresh.emit()">
<ion-grid>
<ion-row>
<ion-col>
<!-- search text -->
<mat-form-field>
<input matInput
formControlName="searchText"
autocomplete="off"
[placeholder]=" 'REFERENTIAL.LIST.FILTER.SEARCH_TEXT'|translate">
<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-row>
<!-- TODO: add more filter fields ? -->
</ion-grid>
</form>
<mat-table [dataSource]="dataSource" matSort matSortActive="id" matSortDirection="asc"
matSortDisableClear [trackBy]="trackByFn">
<ng-container matColumnDef="select">
<mat-header-cell class="hidden-xs hidden-sm" *matHeaderCellDef>
<mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</mat-header-cell>
<mat-cell class="hidden-xs hidden-sm" *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</ng-container>
<!-- Id column -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header>
<app-loading-spinner [loading]="loadingSubject|async"><ion-label>#</ion-label></app-loading-spinner>
</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.currentData?.id }}</mat-cell>
</ng-container>
<!-- Label column -->
<ng-container matColumnDef="label">
<mat-header-cell *matHeaderCellDef mat-sort-header>
<span translate>REFERENTIAL.LABEL</span>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ion-label>{{row.currentData.label}}</ion-label>
</mat-cell>
</ng-container>
<!-- Name column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>
<span translate>REFERENTIAL.NAME</span>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ion-label>{{row.currentData.name}}</ion-label>
</mat-cell>
</ng-container>
<!-- Description column -->
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef>
<span translate>REFERENTIAL.DESCRIPTION</span>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ion-label>{{row.currentData.description}}</ion-label>
</mat-cell>
</ng-container>
<!-- Status column -->
<ng-container matColumnDef="status">
<mat-header-cell *matHeaderCellDef mat-sort-header>
<span translate>REFERENTIAL.STATUS</span>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<ion-text>
<ion-icon *ngIf="row.currentData.statusId &gt;=0" [name]="statusById[row.currentData.statusId]?.icon"></ion-icon>
<span>{{ statusById[row.currentData.statusId]?.label | translate }}</span>
</ion-text>
</mat-cell>
</ng-container>
<!-- Comment column -->
<ng-container matColumnDef="comments">
<mat-header-cell *matHeaderCellDef class="hidden-xs hidden-sm">
<span translate>REFERENTIAL.COMMENTS</span>
</mat-header-cell>
<mat-cell *matCellDef="let row" class="hidden-xs hidden-sm">
<ion-icon *ngIf="row.currentData.comments"
slot="icon-only" color="medium" name="chatbox"
[title]="row.currentData.comments">
</ion-icon>
</mat-cell>
</ng-container>
<!-- Actions buttons column -->
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef [hidden]="true">
</mat-header-cell>
<mat-cell *matCellDef="let row" [hidden]="true">
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"
(click)="clickRow($event, row)"></mat-row>
</mat-table>
<!-- Paginator -->
<ion-row class="ion-no-padding">
<ion-col></ion-col>
<ion-col class="ion-no-padding" size="auto">
<mat-paginator [length]="resultsLength" [pageSize]="20" [pageSizeOptions]="[20, 50, 100, 200]" showFirstLastButtons>
</mat-paginator>
</ion-col>
</ion-row>
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Injector, Input} from "@angular/core";
import {ActivatedRoute, Router} from "@angular/router";
import {ModalController, Platform} from "@ionic/angular";
import {Location} from "@angular/common";
import {ReferentialRefFilter, ReferentialRefService} from "../services/referential-ref.service";
import {AbstractControl, FormBuilder, FormGroup} from "@angular/forms";
import {debounceTime, filter} from "rxjs/operators";
import {AppTable, EnvironmentService, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS} from "sumaris-lib";
import {DefaultStatusList, ReferentialRef} from "sumaris-lib";
import {LocalSettingsService} from "sumaris-lib";
@Component({
selector: 'app-referential-ref-table',
templateUrl: './referential-ref.table.html',
styleUrls: ['./referential-ref.table.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReferentialRefTable extends AppTable<ReferentialRef, ReferentialRefFilter> {
statusList = DefaultStatusList;
statusById: any;
filterForm: FormGroup;
@Input() showToolbar = false;
@Input() showFilter = true;
@Input() set entityName(entityName: string) {
this.setFilter({
...this.filter,
entityName
});
}
get entityName(): string {
return this.filter.entityName;
}
/*@Input('datasource') set datasourceInput(datasource: AppTableDataSource<ReferentialRef, ReferentialRefFilter>) {
super.setDatasource(datasource);
}*/
// get dirty(): boolean {
// return this._dirty || this.memoryDataService.dirty;
// }
constructor(
protected injector: Injector,
protected referentialRefService: ReferentialRefService,
formBuilder: FormBuilder,
protected cd: ChangeDetectorRef