IMP-56: New repairing table and provider autocomplete (#210)

This commit is contained in:
Rinat Arsaev 2023-04-07 15:07:31 +04:00 committed by GitHub
parent ea4a714499
commit fafe19bdfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 511 additions and 233 deletions

140
package-lock.json generated
View File

@ -22,6 +22,7 @@
"@angular/platform-browser-dynamic": "15.0.3", "@angular/platform-browser-dynamic": "15.0.3",
"@angular/platform-server": "15.0.3", "@angular/platform-server": "15.0.3",
"@angular/router": "15.0.3", "@angular/router": "15.0.3",
"@ng-matero/extensions": "15.3.0",
"@ngneat/input-mask": "6.0.0", "@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2", "@ngneat/until-destroy": "9.2.2",
"@s-libs/js-core": "15.0.0", "@s-libs/js-core": "15.0.0",
@ -2818,6 +2819,14 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz",
"integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@discoveryjs/json-ext": { "node_modules/@discoveryjs/json-ext": {
"version": "0.5.7", "version": "0.5.7",
"license": "MIT", "license": "MIT",
@ -3819,6 +3828,46 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@ng-matero/extensions": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-15.3.0.tgz",
"integrity": "sha512-NEPK9ba437d+eDRtj0wSZFUUTCpJlWuJr2ZpbTBwQ/mwN6eyprASM9dq4ydqEcGE+uefxZrSZrVp+eaztNjHjA==",
"dependencies": {
"@ng-select/ng-select": "^10.0.0",
"ngx-color": "^8.0.0",
"photoviewer": "^3.6.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@angular/animations": ">=15.0.0",
"@angular/cdk": ">=15.0.0",
"@angular/common": ">=15.0.0",
"@angular/core": ">=15.0.0",
"@angular/material": ">=15.0.0"
}
},
"node_modules/@ng-matero/extensions/node_modules/@ng-select/ng-select": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.4.tgz",
"integrity": "sha512-Vc/JIgcFkSgf47cX7+pQQo9HYhDktfqrY7o/ZPGMvu63P7E9d1MibVipqmcLbgms6Ac9lu621CDZPGHdxag7hA==",
"dependencies": {
"tslib": "^2.3.1"
},
"engines": {
"node": ">= 12.20.0",
"npm": ">= 6.0.0"
},
"peerDependencies": {
"@angular/common": "<16.0.0",
"@angular/core": "<16.0.0",
"@angular/forms": "<16.0.0"
}
},
"node_modules/@ng-matero/extensions/node_modules/tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
"node_modules/@ngneat/input-mask": { "node_modules/@ngneat/input-mask": {
"version": "6.0.0", "version": "6.0.0",
"license": "MIT", "license": "MIT",
@ -9289,6 +9338,11 @@
"version": "2.1.6", "version": "2.1.6",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/domq.js": {
"version": "0.6.7",
"resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.7.tgz",
"integrity": "sha512-WwRGORo/eYGf7v7YXZ3M6x/PEoxCsP3D0my7pnAVwtbfsKRvW6qioSdlLsy1MFzfwN1TM9oO9QJcvkE8ERYmlg=="
},
"node_modules/domutils": { "node_modules/domutils": {
"version": "2.8.0", "version": "2.8.0",
"dev": true, "dev": true,
@ -13596,6 +13650,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"node_modules/md5.js": { "node_modules/md5.js": {
"version": "1.3.5", "version": "1.3.5",
"license": "MIT", "license": "MIT",
@ -14357,6 +14416,20 @@
"rxjs": ">= 6.0.0" "rxjs": ">= 6.0.0"
} }
}, },
"node_modules/ngx-color": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-8.0.3.tgz",
"integrity": "sha512-tuLP+uIoDEu2m0bh711kb2P1M1bh/oIrOn8mJd9mb8xGL2v+OcokcxPmVvWRn0avMG1lXL53CjSlWXGkdV4CDA==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"material-colors": "^1.2.6",
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=14.0.0-0",
"@angular/core": ">=14.0.0-0"
}
},
"node_modules/ngx-mat-select-search": { "node_modules/ngx-mat-select-search": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.1.tgz",
@ -15657,6 +15730,14 @@
"node": ">=0.12" "node": ">=0.12"
} }
}, },
"node_modules/photoviewer": {
"version": "3.6.6",
"resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.6.6.tgz",
"integrity": "sha512-TYuxoEdlVkIngVnoCEO+CWjeTO/F4TOPDv9ic4zbVU8ZXMiDggUzMj7jv50Kl0n1Yks72hleujPjAfGmkl7n9w==",
"dependencies": {
"domq.js": "^0.6.7"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"license": "ISC" "license": "ISC"
@ -21195,6 +21276,11 @@
} }
} }
}, },
"@ctrl/tinycolor": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz",
"integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ=="
},
"@discoveryjs/json-ext": { "@discoveryjs/json-ext": {
"version": "0.5.7" "version": "0.5.7"
}, },
@ -22032,6 +22118,32 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@ng-matero/extensions": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-15.3.0.tgz",
"integrity": "sha512-NEPK9ba437d+eDRtj0wSZFUUTCpJlWuJr2ZpbTBwQ/mwN6eyprASM9dq4ydqEcGE+uefxZrSZrVp+eaztNjHjA==",
"requires": {
"@ng-select/ng-select": "^10.0.0",
"ngx-color": "^8.0.0",
"photoviewer": "^3.6.0",
"tslib": "^2.4.0"
},
"dependencies": {
"@ng-select/ng-select": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.4.tgz",
"integrity": "sha512-Vc/JIgcFkSgf47cX7+pQQo9HYhDktfqrY7o/ZPGMvu63P7E9d1MibVipqmcLbgms6Ac9lu621CDZPGHdxag7hA==",
"requires": {
"tslib": "^2.3.1"
}
},
"tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
}
}
},
"@ngneat/input-mask": { "@ngneat/input-mask": {
"version": "6.0.0", "version": "6.0.0",
"requires": { "requires": {
@ -25726,6 +25838,11 @@
"domino": { "domino": {
"version": "2.1.6" "version": "2.1.6"
}, },
"domq.js": {
"version": "0.6.7",
"resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.7.tgz",
"integrity": "sha512-WwRGORo/eYGf7v7YXZ3M6x/PEoxCsP3D0my7pnAVwtbfsKRvW6qioSdlLsy1MFzfwN1TM9oO9QJcvkE8ERYmlg=="
},
"domutils": { "domutils": {
"version": "2.8.0", "version": "2.8.0",
"dev": true, "dev": true,
@ -28469,6 +28586,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"requires": { "requires": {
@ -28994,6 +29116,16 @@
"webpack-merge": "^5.0.0" "webpack-merge": "^5.0.0"
} }
}, },
"ngx-color": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-8.0.3.tgz",
"integrity": "sha512-tuLP+uIoDEu2m0bh711kb2P1M1bh/oIrOn8mJd9mb8xGL2v+OcokcxPmVvWRn0avMG1lXL53CjSlWXGkdV4CDA==",
"requires": {
"@ctrl/tinycolor": "^3.4.1",
"material-colors": "^1.2.6",
"tslib": "^2.3.0"
}
},
"ngx-mat-select-search": { "ngx-mat-select-search": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.1.tgz",
@ -29850,6 +29982,14 @@
"sha.js": "^2.4.8" "sha.js": "^2.4.8"
} }
}, },
"photoviewer": {
"version": "3.6.6",
"resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.6.6.tgz",
"integrity": "sha512-TYuxoEdlVkIngVnoCEO+CWjeTO/F4TOPDv9ic4zbVU8ZXMiDggUzMj7jv50Kl0n1Yks72hleujPjAfGmkl7n9w==",
"requires": {
"domq.js": "^0.6.7"
}
},
"picocolors": { "picocolors": {
"version": "1.0.0" "version": "1.0.0"
}, },

View File

@ -12,7 +12,7 @@
"build-libs": "ng build ng-core", "build-libs": "ng build ng-core",
"build": "npm run build-libs && npm run build-app", "build": "npm run build-libs && npm run build-app",
"test": "ng test", "test": "ng test",
"lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 552", "lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 420",
"lint-fix": "npm run lint -- --fix", "lint-fix": "npm run lint -- --fix",
"lint-errors": "npm run lint -- --quiet", "lint-errors": "npm run lint -- --quiet",
"lint-libs": "eslint \"projects/**/*.{ts,js,html}\" --max-warnings 0", "lint-libs": "eslint \"projects/**/*.{ts,js,html}\" --max-warnings 0",
@ -36,6 +36,7 @@
"@angular/platform-browser-dynamic": "15.0.3", "@angular/platform-browser-dynamic": "15.0.3",
"@angular/platform-server": "15.0.3", "@angular/platform-server": "15.0.3",
"@angular/router": "15.0.3", "@angular/router": "15.0.3",
"@ng-matero/extensions": "15.3.0",
"@ngneat/input-mask": "6.0.0", "@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2", "@ngneat/until-destroy": "9.2.2",
"@s-libs/js-core": "15.0.0", "@s-libs/js-core": "15.0.0",

View File

@ -13,10 +13,10 @@
<input formControlName="ns" matInput /> <input formControlName="ns" matInput />
</mat-form-field> </mat-form-field>
<cc-date-range formControlName="timespan"></cc-date-range> <cc-date-range formControlName="timespan"></cc-date-range>
<mat-form-field> <cc-domain-object-field
<mat-label>Provider ID</mat-label> formControlName="provider_id"
<input formControlName="provider_id" matInput /> name="provider"
</mat-form-field> ></cc-domain-object-field>
<mat-form-field> <mat-form-field>
<mat-label>Status</mat-label> <mat-label>Status</mat-label>
<mat-select formControlName="status"> <mat-select formControlName="status">
@ -34,16 +34,24 @@
<input formControlName="error_message" matInput /> <input formControlName="error_message" matInput />
</mat-form-field> </mat-form-field>
</mat-card-content> </mat-card-content>
<mat-card-footer *ngIf="inProgress$ | async">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card-footer>
</mat-card> </mat-card>
<ng-container *ngIf="!(inProgress$ | async) || (machines$ | async)"> <cc-simple-table
<cc-actions> [cellTemplate]="cellTemplate"
<button mat-button (click)="update()">UPDATE</button> [columns]="columns$ | async"
[data]="machines$ | async"
[hasMore]="hasMore$ | async"
[loading]="(inProgress$ | async) && !!(columns$ | async)"
[trackBy]="trackById"
rowSelectable
(fetchMore)="fetchMore()"
(rowSelectionChange)="selected$.next($event)"
(size)="update($event)"
(update)="update($event.size)"
>
<cc-simple-table-actions>
<button <button
[disabled]="!selection?.selected?.length" [disabled]="!(selected$ | async)?.length"
color="primary" color="primary"
mat-button mat-button
(click)="repairByScenario()" (click)="repairByScenario()"
@ -51,82 +59,17 @@
REPAIR BY SCENARIO REPAIR BY SCENARIO
</button> </button>
<button <button
[disabled]="!selection?.selected?.length" [disabled]="!(selected$ | async)?.length"
color="primary" color="primary"
mat-button mat-button
(click)="repair()" (click)="repair()"
> >
SIMPLE REPAIR SIMPLE REPAIR
</button> </button>
</cc-actions> </cc-simple-table-actions>
<cc-simple-table-tooltip-cell-template
<cc-empty-search-result *ngIf="!(machines$ | async)?.length"></cc-empty-search-result> (template)="cellTemplate.status = cellTemplate.history = $event"
<mat-card *ngIf="(machines$ | async)?.length" fxLayout="column" fxLayoutGap="18px"> ></cc-simple-table-tooltip-cell-template>
<table [dataSource]="machines$ | async" mat-table> </cc-simple-table>
<cc-select-column
[dataSource]="machines$ | async"
(changed)="selection = $event"
></cc-select-column>
<ng-container [matColumnDef]="cols.def.id">
<th *matHeaderCellDef mat-header-cell>ID</th>
<td *matCellDef="let i" mat-cell>{{ i.id }}</td>
</ng-container>
<ng-container [matColumnDef]="cols.def.namespace">
<th *matHeaderCellDef mat-header-cell>Namespace</th>
<td *matCellDef="let i" mat-cell>{{ i.ns }}</td>
</ng-container>
<ng-container [matColumnDef]="cols.def.createdAt">
<th *matHeaderCellDef mat-header-cell>Created at</th>
<td *matCellDef="let i" mat-cell>
{{ i.created_at | date : 'dd.MM.yyyy HH:mm:ss' }}
</td>
</ng-container>
<ng-container [matColumnDef]="cols.def.status">
<th *matHeaderCellDef mat-header-cell matTooltip="Hover to view details">
<span matBadge="" matBadgeOverlap="false" matBadgeSize="small">
Status
</span>
</th>
<td
*matCellDef="let i"
[matTooltip]="i.error_message"
mat-cell
matTooltipPosition="left"
>
{{ i.status | enumKey : status }}
</td>
</ng-container>
<ng-container [matColumnDef]="cols.def.provider">
<th *matHeaderCellDef mat-header-cell>Provider</th>
<td *matCellDef="let i" mat-cell>
{{ i.provider_id }}
</td>
</ng-container>
<ng-container [matColumnDef]="cols.def.history">
<th *matHeaderCellDef mat-header-cell matTooltip="Hover to view details">
<span matBadge="" matBadgeOverlap="false" matBadgeSize="small"
>History</span
>
</th>
<td
*matCellDef="let i"
[matTooltip]="i.history?.length ? (i.history | json) : ''"
mat-cell
matTooltipPosition="left"
>
{{ i.history?.length || '' }}
</td>
</ng-container>
<tr *matHeaderRowDef="cols.list" mat-header-row></tr>
<tr *matRowDef="let row; columns: cols.list" mat-row></tr>
</table>
<cc-show-more-button
*ngIf="hasMore$ | async"
[inProgress]="inProgress$ | async"
(fetchMore)="fetchMore()"
></cc-show-more-button>
</mat-card>
</ng-container>
</div> </div>
</div> </div>

View File

@ -1,18 +1,26 @@
import { SelectionModel } from '@angular/cdk/collections'; import { Component, OnInit, TemplateRef } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { DateRange } from '@angular/material/datepicker'; import { DateRange } from '@angular/material/datepicker';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { BaseDialogResponseStatus, BaseDialogService, clean, splitIds } from '@vality/ng-core'; import { BaseDialogResponseStatus, BaseDialogService, clean, splitIds } from '@vality/ng-core';
import { repairer } from '@vality/repairer-proto'; import { repairer } from '@vality/repairer-proto';
import { Namespace, ProviderID, RepairStatus, Machine } from '@vality/repairer-proto/repairer'; import { Namespace, ProviderID, RepairStatus, Machine } from '@vality/repairer-proto/repairer';
import isNil from 'lodash-es/isNil';
import { Moment } from 'moment'; import { Moment } from 'moment';
import { filter, map, switchMap } from 'rxjs/operators'; import { BehaviorSubject } from 'rxjs';
import { filter, map, switchMap, shareReplay } from 'rxjs/operators';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error'; import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import { ConfirmActionDialogComponent } from '@cc/components/confirm-action-dialog';
import {
createGridColumns,
createDatetimeFormattedColumn,
createDescriptionFormattedColumn,
} from '@cc/components/simple-table';
import { createTooltipTemplateGridColumn } from '@cc/components/simple-table/components/simple-table-tooltip-cell-template.component';
import { getEnumKey } from '@cc/utils';
import { ConfirmActionDialogComponent } from '../../../components/confirm-action-dialog';
import { Columns, SELECT_COLUMN_NAME } from '../../../components/table';
import { RepairManagementService } from '../../api/repairer'; import { RepairManagementService } from '../../api/repairer';
import { QueryParamsService } from '../../shared/services'; import { QueryParamsService } from '../../shared/services';
import { NotificationService } from '../../shared/services/notification'; import { NotificationService } from '../../shared/services/notification';
@ -55,17 +63,41 @@ export class RepairingComponent implements OnInit {
error_message: null, error_message: null,
...this.qp.params, ...this.qp.params,
}); });
selection: SelectionModel<Machine>; selected$ = new BehaviorSubject<Machine[]>([]);
cols = new Columns(
SELECT_COLUMN_NAME,
'id',
'namespace',
'createdAt',
'provider',
'status',
'history'
);
status = repairer.RepairStatus; status = repairer.RepairStatus;
columns$ = this.domainStoreService.getObjects('provider').pipe(
map((providers) =>
createGridColumns<Machine>([
'id',
{ header: 'Namespace', field: 'ns' },
createDatetimeFormattedColumn('created_at'),
createDescriptionFormattedColumn<Machine>(
'provider',
(data) =>
providers.find((p) => String(p.ref.id) === data.provider_id)?.data?.name,
(data) => data.provider_id
),
createTooltipTemplateGridColumn(
{
field: 'status',
formatter: (data: Machine) =>
getEnumKey(repairer.RepairStatus, data.status),
},
(d) => d.error_message
),
createTooltipTemplateGridColumn(
{
field: 'history',
formatter: (data: Machine) =>
data.history?.length ? String(data.history.length) : '',
},
(d) => d.history
),
])
),
shareReplay({ refCount: true, bufferSize: 1 })
);
cellTemplate: Record<string, TemplateRef<any>> = {};
constructor( constructor(
private machinesService: MachinesService, private machinesService: MachinesService,
@ -74,7 +106,8 @@ export class RepairingComponent implements OnInit {
private baseDialogService: BaseDialogService, private baseDialogService: BaseDialogService,
private repairManagementService: RepairManagementService, private repairManagementService: RepairManagementService,
private notificationService: NotificationService, private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService private notificationErrorService: NotificationErrorService,
private domainStoreService: DomainStoreService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -90,7 +123,7 @@ export class RepairingComponent implements OnInit {
clean({ clean({
ids: splitIds(ids), ids: splitIds(ids),
ns, ns,
provider_id, provider_id: isNil(provider_id) ? null : String(provider_id),
status, status,
error_message, error_message,
timespan: timespan:
@ -107,8 +140,9 @@ export class RepairingComponent implements OnInit {
.subscribe((params) => this.machinesService.search(params)); .subscribe((params) => this.machinesService.search(params));
} }
update() { update(size: number) {
this.machinesService.refresh(); this.machinesService.refresh(size);
this.selected$.next([]);
} }
fetchMore() { fetchMore() {
@ -118,14 +152,14 @@ export class RepairingComponent implements OnInit {
repair() { repair() {
this.baseDialogService this.baseDialogService
.open(ConfirmActionDialogComponent, { .open(ConfirmActionDialogComponent, {
title: `Simple repair ${this.selection.selected.length} machines`, title: `Simple repair ${this.selected$.value.length} machines`,
}) })
.afterClosed() .afterClosed()
.pipe( .pipe(
filter(({ status }) => status === BaseDialogResponseStatus.Success), filter(({ status }) => status === BaseDialogResponseStatus.Success),
switchMap(() => switchMap(() =>
this.repairManagementService.SimpleRepairAll( this.repairManagementService.SimpleRepairAll(
this.selection.selected.map(({ id, ns }) => ({ id, ns })) this.selected$.value.map(({ id, ns }) => ({ id, ns }))
) )
), ),
untilDestroyed(this) untilDestroyed(this)
@ -140,9 +174,13 @@ export class RepairingComponent implements OnInit {
repairByScenario() { repairByScenario() {
this.baseDialogService this.baseDialogService
.open(RepairByScenarioDialogComponent, { machines: this.selection.selected }) .open(RepairByScenarioDialogComponent, { machines: this.selected$.value })
.afterClosed() .afterClosed()
.pipe(untilDestroyed(this)) .pipe(untilDestroyed(this))
.subscribe(); .subscribe();
} }
trackById(index: number, item: Machine) {
return item.id;
}
} }

View File

@ -15,8 +15,9 @@ import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { ActionsModule, BaseDialogModule } from '@vality/ng-core'; import { ActionsModule, BaseDialogModule } from '@vality/ng-core';
import { EnumKeyPipe, EnumKeysPipe } from '@cc/app/shared'; import { EnumKeyPipe, EnumKeysPipe, DomainObjectFieldComponent } from '@cc/app/shared';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form'; import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { SimpleTableModule } from '@cc/components/simple-table';
import { EmptySearchResultModule } from '../../../components/empty-search-result'; import { EmptySearchResultModule } from '../../../components/empty-search-result';
import { TableModule } from '../../../components/table'; import { TableModule } from '../../../components/table';
@ -50,6 +51,8 @@ import { RepairingComponent } from './repairing.component';
MatChipsModule, MatChipsModule,
EnumKeyPipe, EnumKeyPipe,
EnumKeysPipe, EnumKeysPipe,
DomainObjectFieldComponent,
SimpleTableModule,
], ],
declarations: [RepairingComponent, RepairByScenarioDialogComponent], declarations: [RepairingComponent, RepairByScenarioDialogComponent],
}) })

View File

@ -15,9 +15,9 @@ export class MachinesService extends PartialFetcher<Machine, SearchRequest> {
super(); super();
} }
protected fetch(params: SearchRequest, continuationToken: string) { protected fetch(params: SearchRequest, continuationToken: string, size: number) {
return this.repairManagementService return this.repairManagementService
.Search({ limit: 100, continuation_token: continuationToken, ...params }) .Search({ limit: size, continuation_token: continuationToken, ...params })
.pipe( .pipe(
map(({ machines, continuation_token }) => ({ map(({ machines, continuation_token }) => ({
result: machines, result: machines,

View File

@ -6,9 +6,9 @@
</cc-actions> </cc-actions>
</div> </div>
<cc-simple-table <cc-simple-table
[columns]="columns"
[data]="sources$ | async" [data]="sources$ | async"
[inProgress]="!!(progress$ | async)" [loading]="!!(progress$ | async)"
[schema]="schema"
noUpdate noUpdate
></cc-simple-table> ></cc-simple-table>
</div> </div>

View File

@ -1,8 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { StatSource } from '@vality/fistful-proto/internal/fistful_stat';
import { BaseDialogService } from '@vality/ng-core'; import { BaseDialogService } from '@vality/ng-core';
import { Schema } from '@cc/components/simple-table'; import {
createGridColumns,
createDescriptionFormattedColumn,
createDatetimeFormattedColumn,
} from '@cc/components/simple-table';
import { CreateSourceComponent } from './create-source/create-source.component'; import { CreateSourceComponent } from './create-source/create-source.component';
import { FetchSourcesService } from './fetch-sources.service'; import { FetchSourcesService } from './fetch-sources.service';
@ -21,11 +24,11 @@ import { FetchSourcesService } from './fetch-sources.service';
export class SourcesComponent { export class SourcesComponent {
sources$ = this.fetchSourcesService.sources$; sources$ = this.fetchSourcesService.sources$;
progress$ = this.fetchSourcesService.progress$; progress$ = this.fetchSourcesService.progress$;
schema = new Schema<StatSource>([ columns = createGridColumns([
{ value: 'name', description: 'id' }, createDescriptionFormattedColumn('name', 'id'),
'identity', 'identity',
'currency_symbolic_code', 'currency_symbolic_code',
'created_at', createDatetimeFormattedColumn('created_at'),
]); ]);
constructor( constructor(

View File

@ -16,10 +16,10 @@
</mat-card> </mat-card>
<cc-simple-table <cc-simple-table
[columns]="columns"
[data]="wallets$ | async" [data]="wallets$ | async"
[hasMore]="hasMore$ | async" [hasMore]="hasMore$ | async"
[inProgress]="inProgress$ | async" [loading]="inProgress$ | async"
[schema]="schema"
(fetchMore)="fetchMore()" (fetchMore)="fetchMore()"
(size)="search($event)" (size)="search($event)"
(update)="search($event.size)" (update)="search($event.size)"

View File

@ -1,13 +1,16 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy'; import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { StatWallet } from '@vality/fistful-proto/internal/fistful_stat';
import { clean } from '@vality/ng-core'; import { clean } from '@vality/ng-core';
import { startWith, map } from 'rxjs/operators'; import { startWith, map } from 'rxjs/operators';
import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet'; import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
import { QueryParamsService } from '@cc/app/shared/services'; import { QueryParamsService } from '@cc/app/shared/services';
import { Schema } from '@cc/components/simple-table'; import {
createDatetimeFormattedColumn,
createDescriptionFormattedColumn,
createGridColumns,
} from '@cc/components/simple-table';
import { FetchWalletsService } from './fetch-wallets.service'; import { FetchWalletsService } from './fetch-wallets.service';
@ -29,11 +32,11 @@ export class WalletsComponent implements OnInit {
wallets$ = this.fetchWalletsService.searchResult$; wallets$ = this.fetchWalletsService.searchResult$;
inProgress$ = this.fetchWalletsService.doAction$; inProgress$ = this.fetchWalletsService.doAction$;
hasMore$ = this.fetchWalletsService.hasMore$; hasMore$ = this.fetchWalletsService.hasMore$;
schema = new Schema<StatWallet>([ columns = createGridColumns([
{ value: 'name', description: 'id' }, createDescriptionFormattedColumn('name', 'id'),
'currency_symbolic_code', 'currency_symbolic_code',
'identity_id', 'identity_id',
{ value: 'created_at', type: 'datetime' }, createDatetimeFormattedColumn('created_at'),
]); ]);
filters = this.fb.group<WalletParams>({ filters = this.fb.group<WalletParams>({
party_id: null, party_id: null,

View File

@ -0,0 +1,6 @@
<cc-select-search-field
[formControl]="control"
[label]="name | titlecase"
[options]="options$ | async"
[progress]="isLoading$ | async"
></cc-select-search-field>

View File

@ -0,0 +1,60 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnChanges } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { DomainObject } from '@vality/domain-proto/internal/domain';
import { defer, switchMap, ReplaySubject } from 'rxjs';
import { shareReplay, map } from 'rxjs/operators';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
import { ComponentChanges } from '@cc/app/shared';
import {
DOMAIN_OBJECTS_TO_OPTIONS,
OtherDomainObjects,
defaultDomainObjectToOption,
} from '@cc/app/shared/services/domain-metadata-form-extensions/utils/domains-objects-to-options';
import { SelectSearchFieldModule } from '@cc/components/select-search-field';
import { ValidatedFormControlSuperclass, provideValueAccessor } from '@cc/utils';
@Component({
standalone: true,
selector: 'cc-domain-object-field',
templateUrl: './domain-object-field.component.html',
providers: [provideValueAccessor(() => DomainObjectFieldComponent)],
imports: [CommonModule, SelectSearchFieldModule, ReactiveFormsModule],
})
export class DomainObjectFieldComponent<T extends keyof DomainObject>
extends ValidatedFormControlSuperclass<DomainObject[T]>
implements OnChanges
{
@Input() name: T;
control = new FormControl<DomainObject[T]>(null);
options$ = defer(() => this.name$).pipe(
switchMap((name) => this.domainStoreService.getObjects(name)),
map((objs) => {
const domainObjectToOption =
this.name in DOMAIN_OBJECTS_TO_OPTIONS
? DOMAIN_OBJECTS_TO_OPTIONS[this.name as keyof OtherDomainObjects]
: defaultDomainObjectToOption;
return objs
.map(domainObjectToOption)
.map((o) => ({ ...o, description: `#${String(o.value)}` }));
}),
shareReplay({ bufferSize: 1, refCount: true })
);
isLoading$ = this.domainStoreService.isLoading$;
private name$ = new ReplaySubject<keyof DomainObject>(1);
constructor(private domainStoreService: DomainStoreService) {
super();
}
ngOnChanges(changes: ComponentChanges<DomainObjectFieldComponent<T>>) {
super.ngOnChanges(changes);
if (changes.name) {
this.name$.next(this.name);
}
}
}

View File

@ -0,0 +1 @@
export * from './domain-object-field.component';

View File

@ -8,3 +8,4 @@ export * from './shop-field';
export * from './shop-details'; export * from './shop-details';
export * from './payout-tool-details'; export * from './payout-tool-details';
export * from './payout-tool-field'; export * from './payout-tool-field';
export * from './domain-object-field';

View File

@ -95,8 +95,8 @@ export abstract class PartialFetcher<R, P> {
this.action$.next({ type: 'search', value, size }); this.action$.next({ type: 'search', value, size });
} }
refresh() { refresh(size?: number) {
this.action$.next({ type: 'search' }); this.action$.next({ type: 'search', size });
} }
fetchMore() { fetchMore() {

View File

@ -1,4 +1,5 @@
@use '@angular/material' as mat; @use '@angular/material' as mat;
@use '@ng-matero/extensions' as mtx;
@import '../../../components/components-themes'; @import '../../../components/components-themes';
@import '../../shared/components/shared-components-themes'; @import '../../shared/components/shared-components-themes';
@ -14,6 +15,7 @@
@include mat.core-theme($theme); @include mat.core-theme($theme);
@include mat.button-theme($theme); @include mat.button-theme($theme);
@include mat.all-legacy-component-themes($theme); @include mat.all-legacy-component-themes($theme);
@include mtx.all-component-themes($theme);
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);

View File

@ -21,7 +21,10 @@
*ngFor="let option of isExternalSearch ? options : (filteredOptions$ | async)" *ngFor="let option of isExternalSearch ? options : (filteredOptions$ | async)"
[value]="option.value" [value]="option.value"
> >
{{ option.label }} <div class="label">{{ option.label }}</div>
<div *ngIf="option.description" class="mat-caption cc-secondary-text">
{{ option.description }}
</div>
</mat-option> </mat-option>
<mat-option <mat-option
*ngIf=" *ngIf="
@ -32,11 +35,14 @@
" "
[value]="cachedOption.value" [value]="cachedOption.value"
> >
{{ cachedOption.label }} <div class="label">{{ cachedOption.label }}</div>
<div *ngIf="cachedOption.description" class="mat-caption cc-secondary-text">
{{ cachedOption.description }}
</div>
</mat-option> </mat-option>
</ng-container> </ng-container>
<ng-template #progressBar> <ng-template #progressBar>
<mat-option disabled> <mat-option disabled fxLayout="column" fxLayoutAlign="center none">
<mat-progress-bar mode="indeterminate"></mat-progress-bar> <mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-option> </mat-option>
</ng-template> </ng-template>

View File

@ -1,3 +1,9 @@
mat-form-field { mat-form-field {
width: 100%; width: 100%;
} }
.label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
@ -22,6 +23,7 @@ import { SelectSearchFieldComponent } from './select-search-field.component';
MatSelectModule, MatSelectModule,
NgxMatSelectSearchModule, NgxMatSelectSearchModule,
MatProgressBarModule, MatProgressBarModule,
FlexModule,
], ],
declarations: [SelectSearchFieldComponent], declarations: [SelectSearchFieldComponent],
exports: [SelectSearchFieldComponent], exports: [SelectSearchFieldComponent],

View File

@ -1,4 +1,5 @@
export interface Option<T> { export interface Option<T> {
value: T; value: T;
label: string; label: string;
description?: string;
} }

View File

@ -3,7 +3,10 @@ import { Option } from '../types';
const filterPredicate = const filterPredicate =
<T>(searchStr: string) => <T>(searchStr: string) =>
(option: Option<T>) => (option: Option<T>) =>
option.label.toLowerCase().includes(searchStr); option.label.toLowerCase().includes(searchStr) ||
(option.description && option.description.toLowerCase().includes(searchStr)) ||
(typeof option.value !== 'object' &&
String(option.value).toLowerCase().includes(searchStr));
export const filterOptions = <T>(options: Option<T>[], controlValue: unknown): Option<T>[] => export const filterOptions = <T>(options: Option<T>[], controlValue: unknown): Option<T>[] =>
controlValue && typeof controlValue === 'string' controlValue && typeof controlValue === 'string'

View File

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'cc-simple-table-actions',
template: `
<div fxLayout fxLayoutGap="16px">
<ng-content></ng-content>
</div>
`,
})
export class SimpleTableActionsComponent {}

View File

@ -0,0 +1,39 @@
import { Component, ViewChild, TemplateRef, Output, EventEmitter } from '@angular/core';
import { createGridColumn, GridColumn } from '@cc/components/simple-table';
@Component({
selector: 'cc-simple-table-tooltip-cell-template',
template: `
<ng-template #tpl let-col="colDef" let-index="index" let-row>
<div
*ngIf="col.formatter ? col.formatter(row, col) : row[col.field] as val"
[matTooltip]="col._data?.tooltip && (col._data.tooltip(row) | json)"
[ngClass]="{ dashed: !!col._data?.tooltip?.(row) }"
matTooltipPosition="right"
>
{{ val }}
</div>
</ng-template>
`,
styles: [
`
.dashed {
text-decoration: underline;
cursor: default;
text-decoration-style: dotted;
}
`,
],
})
export class SimpleTableTooltipCellTemplateComponent {
@Output() template = new EventEmitter<TemplateRef<any>>();
@ViewChild('tpl', { static: true }) set tpl(tpl: TemplateRef<any>) {
this.template.emit(tpl);
}
}
export function createTooltipTemplateGridColumn<T>(col: GridColumn<T>, tooltip: (data: T) => any) {
return { ...createGridColumn(col), _data: { tooltip } };
}

View File

@ -1,3 +1,3 @@
export * from './types/schema';
export * from './simple-table.component'; export * from './simple-table.component';
export * from './simple-table.module'; export * from './simple-table.module';
export * from './utils/create-grid-columns';

View File

@ -1,14 +1,14 @@
<div fxLayout="column" fxLayoutGap="32px"> <div fxLayout="column" fxLayoutGap="32px">
<ng-container *ngIf="data; else init"> <div *ngIf="!noUpdate || actions" fxLayout fxLayoutAlign="space-between" fxLayoutGap="8px">
<div *ngIf="!noUpdate" fxLayout fxLayoutGap="16px"> <div *ngIf="!noUpdate" fxLayout fxLayoutGap="16px">
<button <button
[disabled]="inProgress" [disabled]="loading"
mat-stroked-button mat-stroked-button
(click)="update.emit(this.size$.value ? { size: this.size$.value } : {})" (click)="update.emit(this.size$.value ? { size: this.size$.value } : {})"
> >
UPDATE UPDATE
</button> </button>
<button [disabled]="inProgress" [matMenuTriggerFor]="menu" mat-button> <button [disabled]="loading" [matMenuTriggerFor]="menu" mat-button>
{{ size$ | async }} <mat-icon>table_rows_narrow</mat-icon> {{ size$ | async }} <mat-icon>table_rows_narrow</mat-icon>
</button> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
@ -17,48 +17,29 @@
<button mat-menu-item (click)="size$.next(1000)">1000</button> <button mat-menu-item (click)="size$.next(1000)">1000</button>
</mat-menu> </mat-menu>
</div> </div>
<ng-content select="cc-simple-table-actions"></ng-content>
<mat-card *ngIf="data.length; else empty" class="table-card">
<table [dataSource]="data" mat-table>
<ng-container *ngFor="let p of schema.params" [matColumnDef]="p.def">
<th *matHeaderCellDef mat-header-cell>{{ p.label }}</th>
<td *matCellDef="let i" mat-cell>
<ng-container [ngSwitch]="p.type">
<div>
<ng-template ngSwitchCase="datetime">
{{ p.value(i) | date : 'dd.MM.yyyy HH:mm:ss' }}
</ng-template>
<ng-template ngSwitchDefault>
{{ p.value(i) }}
</ng-template>
</div> </div>
<div *ngIf="p.description" class="mat-caption cc-secondary-text">
{{ p.description(i) }}
</div>
</ng-container>
</td>
</ng-container>
<tr *matHeaderRowDef="schema.list" mat-header-row></tr>
<tr *matRowDef="let row; columns: schema.list" mat-row></tr>
</table>
<mat-card class="table-card">
<mtx-grid
[cellSelectable]="false"
[cellTemplate]="cellTemplate"
[columns]="columns"
[data]="data"
[loading]="loading || !columns"
[paginationTemplate]="footerTpl"
[rowSelectable]="rowSelectable"
[trackBy]="trackBy"
(rowSelectionChange)="rowSelectionChange.emit($event)"
></mtx-grid>
<ng-template #footerTpl>
<cc-show-more-button <cc-show-more-button
*ngIf="hasMore" *ngIf="hasMore && columns"
[inProgress]="inProgress" [inProgress]="loading"
class="show-more" class="show-more"
style="width: 100%" style="width: 100%"
(fetchMore)="fetchMore.emit(this.size$.value ? { size: this.size$.value } : {})" (fetchMore)="fetchMore.emit(this.size$.value ? { size: this.size$.value } : {})"
></cc-show-more-button> ></cc-show-more-button>
</mat-card>
<ng-template #empty
><cc-empty-search-result style="width: 100%"></cc-empty-search-result
></ng-template>
</ng-container>
<ng-template #init>
<div *ngIf="inProgress" fxFlex fxLayout="row" fxLayoutAlign="center center">
<mat-spinner></mat-spinner>
</div>
</ng-template> </ng-template>
</mat-card>
</div> </div>

View File

@ -1,6 +1,10 @@
.table-card { .table-card {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
& > * {
border: none;
}
} }
.show-more { .show-more {

View File

@ -1,9 +1,11 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit, ContentChild } from '@angular/core';
import { MtxGridColumn } from '@ng-matero/extensions/grid';
import { MtxGrid } from '@ng-matero/extensions/grid/grid';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy'; import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { coerceBoolean } from 'coerce-property'; import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { Schema } from './types/schema'; import { SimpleTableActionsComponent } from './components/simple-table-actions.component';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -13,16 +15,22 @@ import { Schema } from './types/schema';
}) })
export class SimpleTableComponent<T> implements OnInit { export class SimpleTableComponent<T> implements OnInit {
@Input() data: T[]; @Input() data: T[];
@Input() schema: Schema<T>; @Input() columns: MtxGridColumn[];
@Input() cellTemplate?: MtxGrid['cellTemplate'];
@Input() trackBy?: MtxGrid['trackBy'];
@Input() @coerceBoolean loading = false;
@Input() @coerceBoolean rowSelectable = false;
@Output() rowSelectionChange = new EventEmitter<T[]>();
@Input() @coerceBoolean hasMore = false; @Input() @coerceBoolean hasMore = false;
@Input() @coerceBoolean inProgress = false;
@Input() @coerceBoolean noUpdate = false; @Input() @coerceBoolean noUpdate = false;
@Output() size = new EventEmitter<number>(); @Output() size = new EventEmitter<number>();
@Output() update = new EventEmitter<{ size?: number }>(); @Output() update = new EventEmitter<{ size?: number }>();
@Output() fetchMore = new EventEmitter<{ size?: number }>(); @Output() fetchMore = new EventEmitter<{ size?: number }>();
@ContentChild(SimpleTableActionsComponent) actions: SimpleTableActionsComponent;
size$ = new BehaviorSubject<undefined | number>(25); size$ = new BehaviorSubject<undefined | number>(25);
ngOnInit() { ngOnInit() {

View File

@ -9,10 +9,15 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MtxGridModule } from '@ng-matero/extensions/grid';
import { ActionsModule } from '@vality/ng-core';
import { TableModule } from '@cc/components/table'; import { TableModule } from '@cc/components/table';
import { EmptySearchResultModule } from '../empty-search-result'; import { EmptySearchResultModule } from '../empty-search-result';
import { SimpleTableActionsComponent } from './components/simple-table-actions.component';
import { SimpleTableTooltipCellTemplateComponent } from './components/simple-table-tooltip-cell-template.component';
import { SimpleTableComponent } from './simple-table.component'; import { SimpleTableComponent } from './simple-table.component';
@NgModule({ @NgModule({
@ -29,8 +34,19 @@ import { SimpleTableComponent } from './simple-table.component';
MatIconModule, MatIconModule,
MatMenuModule, MatMenuModule,
MatButtonModule, MatButtonModule,
ActionsModule,
MatTooltipModule,
MtxGridModule,
],
declarations: [
SimpleTableComponent,
SimpleTableActionsComponent,
SimpleTableTooltipCellTemplateComponent,
],
exports: [
SimpleTableComponent,
SimpleTableActionsComponent,
SimpleTableTooltipCellTemplateComponent,
], ],
declarations: [SimpleTableComponent],
exports: [SimpleTableComponent],
}) })
export class SimpleTableModule {} export class SimpleTableModule {}

View File

@ -1,56 +0,0 @@
import startCase from 'lodash-es/startCase';
import { Overwrite } from 'utility-types';
export type Path<T> = ((p: T) => string) | keyof T;
export interface BaseParam<T> {
def: string;
label: string;
value: (p: T) => string;
description?: (p: T) => string;
type?: 'datetime';
}
export type Param<T> = Overwrite<
Omit<BaseParam<T>, 'def'>,
{
label?: string;
value: Path<T>;
description?: Path<T>;
}
>;
function createGetValueFn<T>(v: ((d: T) => string) | keyof T): (d: T) => string {
if (typeof v === 'function') return v;
return (d) => d[v as any];
}
function createLabel(value: unknown): string {
return startCase(String(value));
}
export class Schema<T> {
params: BaseParam<T>[];
get list() {
return this.params.map((p) => p.def);
}
constructor(params: (Param<T> | keyof T)[]) {
this.params = params.map((p) => {
if (typeof p === 'object')
return {
def: p.label ?? String(p.value),
label: p.label ?? createLabel(p.value),
value: createGetValueFn(p.value),
description: p.description ? createGetValueFn(p.description) : null,
type: p?.type,
};
return {
def: String(p),
label: createLabel(p),
value: createGetValueFn(p),
};
});
}
}

View File

@ -0,0 +1,56 @@
import { formatDate } from '@angular/common';
import { MtxGridColumn } from '@ng-matero/extensions/grid';
import isObject from 'lodash-es/isObject';
import startCase from 'lodash-es/startCase';
import { Overwrite } from 'utility-types';
export type GridColumn<T> =
| (Overwrite<
MtxGridColumn,
{
formatter?: (rowData: T, colDef?: MtxGridColumn) => string;
}
> & {
_data?: any;
})
| keyof T;
export function createGridColumn<T>(col: GridColumn<T>) {
if (!isObject(col))
col = {
field: col as string,
};
if (!col.header) col.header = startCase(String(col.field).toLowerCase());
return {
...col,
};
}
export function createGridColumns<T>(columns: GridColumn<T>[]): MtxGridColumn[] {
return columns.map((col) => createGridColumn(col));
}
export function createDescriptionFormattedColumn<T>(
field: string,
getDescriptionOrDescriptionField: ((data: T) => string) | string,
getValue?: (data: T) => string
): MtxGridColumn {
return {
field,
formatter: (data: T) => {
const desc =
typeof getDescriptionOrDescriptionField === 'function'
? getDescriptionOrDescriptionField(data)
: String(data[getDescriptionOrDescriptionField]);
const value = getValue ? getValue(data) : String(data[field]);
return value + (desc ? `<div class="mat-caption cc-secondary-text">${desc}</div>` : '');
},
};
}
export function createDatetimeFormattedColumn<T>(field: string): MtxGridColumn {
return {
field,
formatter: (data: T) => formatDate(data[field], 'dd.MM.yyyy HH:mm:ss', 'en'),
};
}