Compare commits
3 Commits
79d474f9f5
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
aa4f9b4f8d
|
|||
|
860d48005e
|
|||
|
8fefc1a6df
|
+5
-2
@@ -3,8 +3,10 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"up-md": "ng serve md-demo",
|
"up-md": "ng serve md-demo",
|
||||||
|
"build-core": "ng build core --configuration production",
|
||||||
|
"publish-core": "npm publish dist/openharbor/core --access public --tag next",
|
||||||
"build-md": "ng build md-ui --configuration production",
|
"build-md": "ng build md-ui --configuration production",
|
||||||
"publish-md": "npm publish md-ui --access-public"
|
"publish-md": "npm publish dist/openharbor/md-ui --access public --tag next"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -18,7 +20,8 @@
|
|||||||
"@angular/platform-browser": "^18.0.0",
|
"@angular/platform-browser": "^18.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||||
"@angular/router": "^18.0.0",
|
"@angular/router": "^18.0.0",
|
||||||
"@openharbor/data": "^1.0.0-alpha.1",
|
"@openharbor/data": "1.0.0-alpha.4",
|
||||||
|
"@openharbor/ngx-data-ui-core": "^18.0.0-alpha.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.3"
|
"zone.js": "~0.14.3"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
"dest": "../../dist/core",
|
"dest": "../../dist/openharbor/core",
|
||||||
"lib": {
|
"lib": {
|
||||||
"entryFile": "src/public-api.ts"
|
"entryFile": "src/public-api.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@openharbor/ngx-data-ui-core",
|
"name": "@openharbor/ngx-data-ui-core",
|
||||||
"version": "18.0.0-alpha.1",
|
"version": "18.0.0-alpha.15",
|
||||||
|
"repository": "https://git.openharbor.io/Open-Harbor/ngx-data-ui",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "^18.2.0",
|
"@angular/common": "^18.2.0",
|
||||||
"@angular/core": "^18.2.0",
|
"@angular/core": "^18.2.0",
|
||||||
|
|||||||
+19
-2
@@ -1,4 +1,6 @@
|
|||||||
import {Observable} from "rxjs";
|
import {Observable} from "rxjs";
|
||||||
|
import {TemplateRef} from "@angular/core";
|
||||||
|
import {AbstractControl, FormGroup} from "@angular/forms";
|
||||||
|
|
||||||
export interface IConfirmOptions {
|
export interface IConfirmOptions {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -7,6 +9,21 @@ export interface IConfirmOptions {
|
|||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ICommandDirectiveService<TConfirmOptions extends IConfirmOptions = IConfirmOptions> {
|
export interface IConfirmEvents {
|
||||||
abstract confirm(options: TConfirmOptions): Observable<boolean>;
|
success: Observable<any>;
|
||||||
|
failure: Observable<any>;
|
||||||
|
loading: Observable<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConfirmForm<TControl extends FormType<TControl> = any> {
|
||||||
|
formTemplate?: TemplateRef<any>;
|
||||||
|
form?: FormGroup<TControl>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormType<TControl = any> = { [K in keyof TControl]: AbstractControl<any>; }
|
||||||
|
|
||||||
|
export type ConfirmOptions<TConfirmOptions extends IConfirmOptions> = TConfirmOptions & IConfirmEvents & IConfirmForm;
|
||||||
|
|
||||||
|
export interface ICommandDirectiveService<TConfirmOptions extends IConfirmOptions = IConfirmOptions> {
|
||||||
|
confirm(options?: ConfirmOptions<TConfirmOptions>): Observable<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Directive, inject, Input, TemplateRef} from '@angular/core';
|
||||||
|
import {FormGroup} from "@angular/forms";
|
||||||
|
import {FormType} from "../abstractions/command-directive-service.abstraction";
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[duiCommandForm]',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class CommandFormDirective<TForm extends FormType<TForm>> {
|
||||||
|
public readonly template = inject(TemplateRef<any>);
|
||||||
|
|
||||||
|
@Input({ alias: 'commandForm', required: true })
|
||||||
|
public form!: FormGroup<TForm>;
|
||||||
|
}
|
||||||
@@ -1,44 +1,66 @@
|
|||||||
import {Directive, EventEmitter, HostListener, Inject, Input, Optional, Output} from '@angular/core';
|
import {AfterContentInit, ContentChild, Directive, EventEmitter, HostListener, Input, Output} from '@angular/core';
|
||||||
import {IDataSource} from '@poweredsoft/data';
|
import {IDataSource} from '@openharbor/data';
|
||||||
import {finalize} from "rxjs";
|
import {finalize} from "rxjs";
|
||||||
import {ICommandDirectiveService, IConfirmOptions} from "../abstractions/command-directive-service.abstraction";
|
import {
|
||||||
|
ConfirmOptions, FormType,
|
||||||
|
ICommandDirectiveService,
|
||||||
|
IConfirmOptions
|
||||||
|
} from "../abstractions/command-directive-service.abstraction";
|
||||||
|
import {CommandFormDirective} from "./command-form.directive";
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[duiCommand]',
|
selector: '[duiCommand]',
|
||||||
standalone: true
|
standalone: true
|
||||||
})
|
})
|
||||||
export class CommandDirective<TModel extends {}, TConfirmOptions extends IConfirmOptions> {
|
export class CommandDirective<TModel extends {}, TConfirmOptions extends IConfirmOptions, TForm extends FormType<TForm> = any> implements AfterContentInit {
|
||||||
|
@ContentChild(CommandFormDirective) commandFormTemplate?: CommandFormDirective<TForm>;
|
||||||
|
|
||||||
@Input() confirm: boolean = false;
|
@Input() confirm: boolean = false;
|
||||||
@Input() confirmOptions?: TConfirmOptions;
|
@Input() confirmOptions?: TConfirmOptions;
|
||||||
@Input() refreshOnSuccess: boolean = false;
|
@Input() refresh: boolean = true;
|
||||||
@Input() params: any;
|
@Input() params?: any;
|
||||||
|
|
||||||
@Input() dataSource!: IDataSource<TModel>;
|
@Input({ required: true }) dataSource!: IDataSource<TModel>;
|
||||||
@Input() command!: string;
|
@Input({ required: true }) command!: string;
|
||||||
@Input() model!: TModel;
|
@Input() model: object = {};
|
||||||
|
@Input() service?: ICommandDirectiveService;
|
||||||
|
|
||||||
@Output() success: EventEmitter<any> = new EventEmitter<any>();
|
@Output() success: EventEmitter<any> = new EventEmitter<any>();
|
||||||
@Output() failure: EventEmitter<any> = new EventEmitter<any>();
|
@Output() failure: EventEmitter<any> = new EventEmitter<any>();
|
||||||
@Output() loading: EventEmitter<boolean> = new EventEmitter<boolean>();
|
@Output() loading: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||||
|
|
||||||
constructor(@Optional() private service?: ICommandDirectiveService<TConfirmOptions>) {
|
ngAfterContentInit(): void {
|
||||||
|
console.log('init');
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('click')
|
@HostListener('click')
|
||||||
handleClick() {
|
handleClick() {
|
||||||
if (this.confirm) {
|
if (this.confirm) {
|
||||||
if (!this.service) {
|
if (!this.service) {
|
||||||
const error = new Error(`NullInjectorError: No implementation provider for CommandDirectiveService Abstraction!`);
|
const error = new Error(`No service provided to directive for CommandDirectiveService!`);
|
||||||
error.name = 'NullInjectorError';
|
error.name = 'NullInjectorError';
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.service.confirm(this.confirmOptions)
|
const options = {
|
||||||
|
...this.confirmOptions,
|
||||||
|
...{
|
||||||
|
success: this.success.asObservable(),
|
||||||
|
failure: this.failure.asObservable(),
|
||||||
|
loading: this.loading.asObservable(),
|
||||||
|
},
|
||||||
|
...{
|
||||||
|
formTemplate: this.commandFormTemplate?.template,
|
||||||
|
form: this.commandFormTemplate?.form
|
||||||
|
}
|
||||||
|
} as ConfirmOptions<TConfirmOptions>;
|
||||||
|
|
||||||
|
this.service.confirm(options)
|
||||||
.subscribe(result => {
|
.subscribe(result => {
|
||||||
|
const model = { ...this.model, ...options.form?.getRawValue() };
|
||||||
|
console.log(options.form?.getRawValue());
|
||||||
if (result)
|
if (result)
|
||||||
this.executeCommand();
|
this.executeCommand(model);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -46,10 +68,10 @@ export class CommandDirective<TModel extends {}, TConfirmOptions extends IConfir
|
|||||||
this.executeCommand();
|
this.executeCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeCommand() {
|
private executeCommand(model?: any) {
|
||||||
this.dataSource.resolveCommandModelByName<TModel>({
|
this.dataSource.resolveCommandModelByName<TModel>({
|
||||||
command: this.command,
|
command: this.command,
|
||||||
model: this.model,
|
model: model,
|
||||||
params: this.params
|
params: this.params
|
||||||
})
|
})
|
||||||
.subscribe({ next: commandModel => {
|
.subscribe({ next: commandModel => {
|
||||||
@@ -61,7 +83,7 @@ export class CommandDirective<TModel extends {}, TConfirmOptions extends IConfir
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: commandResult =>
|
next: commandResult =>
|
||||||
{
|
{
|
||||||
if (this.refreshOnSuccess)
|
if (this.refresh)
|
||||||
this.dataSource.refresh();
|
this.dataSource.refresh();
|
||||||
|
|
||||||
this.success.emit(commandResult);
|
this.success.emit(commandResult);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>data-grid works!</p>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DataGridComponent } from './data-grid.component';
|
||||||
|
|
||||||
|
describe('DataGridComponent', () => {
|
||||||
|
let component: DataGridComponent;
|
||||||
|
let fixture: ComponentFixture<DataGridComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DataGridComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DataGridComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dui-data-grid',
|
||||||
|
standalone: true,
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './data-grid.component.html',
|
||||||
|
styleUrl: './data-grid.component.css'
|
||||||
|
})
|
||||||
|
export class DataGridComponent<TModel> {
|
||||||
|
|
||||||
|
@Input() dataSource: IDataSource<TModel>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {Directive, HostListener, Input} from '@angular/core';
|
||||||
|
import {FilterType, ICompositeFilter, IDataSource, IFilter, ISimpleFilter} from "@openharbor/data";
|
||||||
|
|
||||||
|
type FilterTypeString = `${FilterType}`;
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[duiSearch]',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class SearchDirective<TModel> {
|
||||||
|
@Input({ required: true }) dataSource!: IDataSource<TModel>;
|
||||||
|
@Input({ required: true }) filterPaths!: string[];
|
||||||
|
@Input() filterType: FilterTypeString = FilterType.CONTAINS;
|
||||||
|
|
||||||
|
filterValue?: string;
|
||||||
|
lastUsedFilter?: IFilter;
|
||||||
|
|
||||||
|
@HostListener('search', ['$event']) onSearch(event: Event): void {
|
||||||
|
console.log('Search event triggered', event);
|
||||||
|
this.filterValue = (event.target as any)?.value ?? '';
|
||||||
|
this.applySearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
applySearch() {
|
||||||
|
const existingFilters = this.dataSource.filters;
|
||||||
|
|
||||||
|
// adapt current filters to remove the previous one if exist
|
||||||
|
// and replace with new one.
|
||||||
|
const finalNewFilters = existingFilters
|
||||||
|
.filter(t => t !== this.lastUsedFilter);
|
||||||
|
|
||||||
|
if (this.filterValue) {
|
||||||
|
const newFilter: ICompositeFilter = {
|
||||||
|
and: true,
|
||||||
|
type: FilterType.COMPOSITE,
|
||||||
|
filters: this.filterPaths.map(filterPath => (<ISimpleFilter>{
|
||||||
|
path: filterPath,
|
||||||
|
type: this.filterType,
|
||||||
|
value: this.filterValue,
|
||||||
|
and: false
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
finalNewFilters.push(newFilter);
|
||||||
|
|
||||||
|
// update last used filter to replace it if changed.
|
||||||
|
this.lastUsedFilter = newFilter;
|
||||||
|
} else {
|
||||||
|
this.lastUsedFilter = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute search.
|
||||||
|
this.dataSource.query({
|
||||||
|
page: 1,
|
||||||
|
filters: finalNewFilters
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,5 @@
|
|||||||
|
|
||||||
export * from './lib/command/directives/command.directive';
|
export * from './lib/command/directives/command.directive';
|
||||||
export * from './lib/command/abstractions/command-directive-service.abstraction';
|
export * from './lib/command/abstractions/command-directive-service.abstraction';
|
||||||
|
export * from './lib/command/directives/command-form.directive';
|
||||||
|
export * from './lib/search/directives/search.directive';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
"dest": "../../dist/md-ui",
|
"dest": "../../dist/openharbor/md-ui",
|
||||||
"lib": {
|
"lib": {
|
||||||
"entryFile": "src/public-api.ts"
|
"entryFile": "src/public-api.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@openharbor/ngx-data-ui-md",
|
"name": "@openharbor/ngx-data-ui-md",
|
||||||
"version": "18.0.0-alpha.1",
|
"version": "18.0.0-alpha.24",
|
||||||
|
"repository": "https://git.openharbor.io/Open-Harbor/ngx-data-ui",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "^18.0.0",
|
"@angular/common": "^18.0.0",
|
||||||
"@angular/core": "^18.0.0",
|
"@angular/core": "^18.0.0",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {inject, Injectable} from '@angular/core';
|
import {inject, Injectable} from '@angular/core';
|
||||||
import {ICommandDirectiveService, IConfirmOptions} from "@openharbor/ngx-data-ui-core";
|
import {ConfirmOptions, ICommandDirectiveService, IConfirmOptions} from "@openharbor/ngx-data-ui-core";
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {MatDialog, MatDialogConfig} from "@angular/material/dialog";
|
import {MatDialog, MatDialogConfig} from "@angular/material/dialog";
|
||||||
import {
|
import {
|
||||||
@@ -12,14 +12,13 @@ export interface IMDCommandDirectiveServiceOptions extends IConfirmOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root'
|
||||||
useExisting: ICommandDirectiveService
|
|
||||||
})
|
})
|
||||||
export class CommandDirectiveService extends ICommandDirectiveService<IMDCommandDirectiveServiceOptions> {
|
export class CommandDirectiveService implements ICommandDirectiveService<IMDCommandDirectiveServiceOptions> {
|
||||||
readonly dialog = inject(MatDialog);
|
readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
confirm(options: IMDCommandDirectiveServiceOptions): Observable<boolean> {
|
confirm(options: ConfirmOptions<IMDCommandDirectiveServiceOptions>): Observable<boolean> {
|
||||||
const defaultOptions: Partial<IConfirmOptions> = {
|
const defaultOptions: Partial<IMDCommandDirectiveServiceOptions> = {
|
||||||
confirmText: 'Confirm',
|
confirmText: 'Confirm',
|
||||||
cancelText: 'Cancel'
|
cancelText: 'Cancel'
|
||||||
};
|
};
|
||||||
@@ -38,13 +37,25 @@ export class CommandDirectiveService extends ICommandDirectiveService<IMDCommand
|
|||||||
message: finalOptions.message,
|
message: finalOptions.message,
|
||||||
confirmText: finalOptions.confirmText,
|
confirmText: finalOptions.confirmText,
|
||||||
cancelText: finalOptions.cancelText,
|
cancelText: finalOptions.cancelText,
|
||||||
|
success: options.success,
|
||||||
|
loading: options.loading,
|
||||||
|
failure: options.failure,
|
||||||
|
formTemplate: options.formTemplate,
|
||||||
|
form: options.form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dialog.open<ConfirmDialogDefaultComponent, IConfirmDialogDefaultOptions>(ConfirmDialogDefaultComponent, dialogOptions);
|
const dialogRef = this.dialog.open<ConfirmDialogDefaultComponent, IConfirmDialogDefaultOptions>(ConfirmDialogDefaultComponent, dialogOptions);
|
||||||
|
const onConfirmSub = dialogRef.componentInstance.onConfirm
|
||||||
|
.subscribe(_ => {
|
||||||
|
subscriber.next(true);
|
||||||
|
});
|
||||||
|
|
||||||
subscriber.next(true);
|
dialogRef.afterClosed()
|
||||||
|
.subscribe(_ => {
|
||||||
|
onConfirmSub.unsubscribe();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -1,8 +1,19 @@
|
|||||||
<h2 mat-dialog-title>{{ title }}</h2>
|
<h2 mat-dialog-title>{{ title }}</h2>
|
||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
|
|
||||||
|
@if (formTemplate) {
|
||||||
|
<!-- [ngTemplateOutletContext]="{ $implicit: model, loading: loading, dataSource: dataSource }" -->
|
||||||
|
<ng-container [ngTemplateOutlet]="formTemplate"></ng-container>
|
||||||
|
}
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
<mat-dialog-actions>
|
<mat-dialog-actions>
|
||||||
<button mat-button mat-dialog-close>{{ cancelText ?? 'Cancel' }}</button>
|
<button mat-button mat-dialog-close cdkFocusInitial [disabled]="isLoading">{{ cancelText ?? 'Cancel' }}</button>
|
||||||
<button mat-button mat-dialog-close cdkFocusInitial>{{ confirmText ?? 'Confirm' }}</button>
|
<button mat-button (click)="onConfirmed()" [disabled]="isLoading">
|
||||||
|
@if(isLoading) {
|
||||||
|
<mat-spinner diameter="18"></mat-spinner>
|
||||||
|
} @else {
|
||||||
|
{{ confirmText ?? 'Confirm' }}
|
||||||
|
}
|
||||||
|
</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
|
|||||||
+68
-6
@@ -1,38 +1,100 @@
|
|||||||
import {Component, inject, Input} from '@angular/core';
|
import {Component, EventEmitter, inject, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
MAT_DIALOG_DATA,
|
MAT_DIALOG_DATA,
|
||||||
MatDialogActions,
|
MatDialogActions,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
MatDialogContent,
|
MatDialogContent,
|
||||||
|
MatDialogRef,
|
||||||
MatDialogTitle
|
MatDialogTitle
|
||||||
} from "@angular/material/dialog";
|
} from "@angular/material/dialog";
|
||||||
import {MatButton} from "@angular/material/button";
|
import {MatButton} from "@angular/material/button";
|
||||||
|
import {AsyncPipe, NgTemplateOutlet} from "@angular/common";
|
||||||
|
import {MatProgressSpinner} from "@angular/material/progress-spinner";
|
||||||
|
import {Observable, Subscription} from "rxjs";
|
||||||
|
import {FormGroup} from "@angular/forms";
|
||||||
|
import {FormType} from "@openharbor/ngx-data-ui-core";
|
||||||
|
|
||||||
export interface IConfirmDialogDefaultOptions {
|
export interface IConfirmDialogDefaultOptions<TControl extends FormType<TControl> = any> {
|
||||||
title: string;
|
title: string;
|
||||||
message: string;
|
message: string;
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
success: Observable<any>;
|
||||||
|
failure: Observable<any>;
|
||||||
|
loading: Observable<boolean>;
|
||||||
|
formTemplate?: TemplateRef<any>;
|
||||||
|
form?: FormGroup<TControl>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mdx-confirm-dialog-default',
|
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
MatDialogContent,
|
MatDialogContent,
|
||||||
MatDialogActions,
|
MatDialogActions,
|
||||||
MatButton,
|
MatButton,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
MatDialogTitle
|
MatDialogTitle,
|
||||||
|
AsyncPipe,
|
||||||
|
MatProgressSpinner,
|
||||||
|
NgTemplateOutlet
|
||||||
],
|
],
|
||||||
templateUrl: './confirm-dialog-default.component.html',
|
templateUrl: './confirm-dialog-default.component.html',
|
||||||
styleUrl: './confirm-dialog-default.component.css'
|
styleUrl: './confirm-dialog-default.component.css'
|
||||||
})
|
})
|
||||||
export class ConfirmDialogDefaultComponent {
|
export class ConfirmDialogDefaultComponent<TControl extends FormType<TControl> = any> implements OnInit, OnDestroy {
|
||||||
readonly data = inject<IConfirmDialogDefaultOptions>(MAT_DIALOG_DATA)
|
readonly data = inject<IConfirmDialogDefaultOptions<TControl>>(MAT_DIALOG_DATA);
|
||||||
|
readonly ref = inject(MatDialogRef<ConfirmDialogDefaultComponent>);
|
||||||
|
|
||||||
readonly title: string = this.data.title;
|
readonly title: string = this.data.title;
|
||||||
readonly message: string = this.data.message;
|
readonly message: string = this.data.message;
|
||||||
readonly confirmText?: string = this.data.confirmText;
|
readonly confirmText?: string = this.data.confirmText;
|
||||||
readonly cancelText?: string = this.data.cancelText;
|
readonly cancelText?: string = this.data.cancelText;
|
||||||
|
readonly $loading: Observable<boolean> = this.data.loading;
|
||||||
|
readonly success: Observable<any> = this.data.success;
|
||||||
|
readonly formTemplate?: TemplateRef<any> = this.data.formTemplate;
|
||||||
|
readonly form?: FormGroup<TControl>;
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
// todo: error messaging?
|
||||||
|
|
||||||
|
private subscriptions: Subscription[] = [];
|
||||||
|
private _onConfirm = new EventEmitter<void>;
|
||||||
|
private _disableCloseOriginalValue?: boolean;
|
||||||
|
|
||||||
|
get onConfirm() {
|
||||||
|
return this._onConfirm.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this._disableCloseOriginalValue = this.ref.disableClose;
|
||||||
|
this.subscriptions.push(this.$loading.subscribe(isLoading => {
|
||||||
|
this.isLoading = isLoading;
|
||||||
|
this.ref.disableClose = isLoading ? true : this._disableCloseOriginalValue;
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.subscriptions.push(this.success.subscribe(_ => {
|
||||||
|
this.ref.close();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
for (let subscription of this.subscriptions)
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmed() {
|
||||||
|
if (!this.formTemplate || !this.form) {
|
||||||
|
this._onConfirm.emit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.form.clearValidators();
|
||||||
|
|
||||||
|
if (!this.form.valid) {
|
||||||
|
this.form.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onConfirm.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
@if ($data) {
|
||||||
|
<table mat-table [dataSource]="$data" matSort
|
||||||
|
(matSortChange)="onSortChanged($event)">
|
||||||
|
@for (column of _options.columns; track column.column) {
|
||||||
|
<ng-container [matColumnDef]="column.column">
|
||||||
|
<th mat-header-cell *matHeaderCellDef
|
||||||
|
[mat-sort-header]="column.sorting && column.sorting.path ? column.sorting.path : column.column"
|
||||||
|
[disabled]="!(column.sorting?.enable)"
|
||||||
|
[sortActionDescription]="column.sorting && column.sorting.description ? (column.sorting.description(column.column) | async) ?? '' : 'Sort by ' + column.column">
|
||||||
|
@if (column.title) {
|
||||||
|
{{ column.title(column.column) | async }}
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
{{ column.column | titlecase }}
|
||||||
|
}
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
@if (column.value) {
|
||||||
|
{{ column.value(element[column.column]) | async }}
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
{{ element[column.column] }}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="getColumns()"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: getColumns();"></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@if(_options.pagination.enable) {
|
||||||
|
<mat-paginator
|
||||||
|
[disabled]="isLoading"
|
||||||
|
[hidePageSize]="_options.pagination.hidePageSize"
|
||||||
|
[pageSizeOptions]="_options.pagination.pageSizeOptions"
|
||||||
|
(page)="onPaginationEvent($event)"
|
||||||
|
[length]="totalRecords"
|
||||||
|
showFirstLastButtons
|
||||||
|
aria-label="Select page of periodic elements">
|
||||||
|
</mat-paginator>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (isLoading) {
|
||||||
|
<mat-progress-bar mode="query"></mat-progress-bar>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DataGridComponent } from './data-grid.component';
|
||||||
|
|
||||||
|
describe('DataGridComponent', () => {
|
||||||
|
let component: DataGridComponent;
|
||||||
|
let fixture: ComponentFixture<DataGridComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DataGridComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DataGridComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {IDataSource} from "@openharbor/data";
|
||||||
|
import {
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatTable
|
||||||
|
} from "@angular/material/table";
|
||||||
|
import {MatPaginator, PageEvent} from "@angular/material/paginator";
|
||||||
|
import {map, Observable, Subscription, tap} from "rxjs";
|
||||||
|
import {MatProgressBar} from "@angular/material/progress-bar";
|
||||||
|
import {AsyncPipe, JsonPipe, TitleCasePipe} from "@angular/common";
|
||||||
|
import {MatSort, MatSortHeader, Sort} from "@angular/material/sort";
|
||||||
|
|
||||||
|
export interface IDataGridOptions<TModel extends object> {
|
||||||
|
columns: IColumnDefinition<TModel, Extract<keyof TModel, string>>[];
|
||||||
|
actions?: [];
|
||||||
|
pagination: {
|
||||||
|
enable: boolean;
|
||||||
|
pageSize: number;
|
||||||
|
hidePageSize: boolean;
|
||||||
|
pageSizeOptions: number[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IColumnDefinition<TModel extends object, TColumn extends Extract<keyof TModel, string>> {
|
||||||
|
column: TColumn;
|
||||||
|
sorting?: {
|
||||||
|
enable: boolean;
|
||||||
|
description?: (element: TColumn) => Observable<string>;
|
||||||
|
path?: string;
|
||||||
|
},
|
||||||
|
title?: (element: TColumn) => Observable<string>;
|
||||||
|
value?: (element: TModel[TColumn]) => Observable<string|number|boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRowAction<TModel extends object> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mdx-data-grid',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCell,
|
||||||
|
MatPaginator,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatProgressBar,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatCellDef,
|
||||||
|
TitleCasePipe,
|
||||||
|
JsonPipe,
|
||||||
|
AsyncPipe,
|
||||||
|
MatSort,
|
||||||
|
MatSortHeader
|
||||||
|
],
|
||||||
|
templateUrl: './data-grid.component.html',
|
||||||
|
styleUrl: './data-grid.component.css'
|
||||||
|
})
|
||||||
|
export class DataGridComponent<TModel extends object> implements OnInit, OnDestroy {
|
||||||
|
@Input({ required: true }) dataSource!: IDataSource<TModel>;
|
||||||
|
@Input() options?: Partial<IDataGridOptions<TModel>>;
|
||||||
|
|
||||||
|
private subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
$data?: Observable<TModel[]>;
|
||||||
|
totalRecords: number = 0;
|
||||||
|
isLoading: boolean = false;
|
||||||
|
|
||||||
|
_options: IDataGridOptions<TModel> = {
|
||||||
|
columns: [],
|
||||||
|
pagination: {
|
||||||
|
enable: true,
|
||||||
|
hidePageSize: false,
|
||||||
|
pageSizeOptions: [10, 25, 50, 100],
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
return this._options.columns.map(options => {
|
||||||
|
return options.column;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this._options = {
|
||||||
|
...this._options,
|
||||||
|
...this.options
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setPageSize(this._options.pagination.pageSize);
|
||||||
|
|
||||||
|
this.subscriptions.push(this.dataSource.loading$
|
||||||
|
.subscribe(loading => this.isLoading = loading)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$data = this.dataSource.data$
|
||||||
|
.pipe(
|
||||||
|
tap((result => {
|
||||||
|
this.totalRecords = result?.totalRecords ?? 0;
|
||||||
|
})),
|
||||||
|
map((result) => {
|
||||||
|
return result?.data ?? [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
for (let subscription of this.subscriptions)
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPaginationEvent(ev: PageEvent) {
|
||||||
|
this.setPageSize(ev.pageSize);
|
||||||
|
this.setPage(ev.pageIndex);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
setPage(index: number) {
|
||||||
|
this.dataSource.page = index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageSize(size: number) {
|
||||||
|
this._options.pagination.pageSize = size;
|
||||||
|
this.dataSource.pageSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.dataSource.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSortChanged(state: Sort) {
|
||||||
|
if (state.direction === '') {
|
||||||
|
this.dataSource.sorts = this.dataSource.sorts.filter(sort => sort.path !== state.active);
|
||||||
|
} else {
|
||||||
|
let field = this.dataSource.sorts.find(field => field.path === state.active);
|
||||||
|
|
||||||
|
if (field === undefined) {
|
||||||
|
field = {
|
||||||
|
path: state.active
|
||||||
|
};
|
||||||
|
this.dataSource.sorts.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
field.ascending = state.direction === 'asc';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,4 @@
|
|||||||
|
|
||||||
export * from './lib/command/services/command-directive.service';
|
export * from './lib/command/services/command-directive.service';
|
||||||
export * from './lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component';
|
export * from './lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component';
|
||||||
|
export * from './lib/data-grid/data-grid/data-grid.component';
|
||||||
|
|||||||
@@ -11,10 +11,5 @@
|
|||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/*.spec.ts"
|
"**/*.spec.ts"
|
||||||
],
|
]
|
||||||
/*"paths": {
|
|
||||||
"@openharbor/ngx-data-ui-core": [
|
|
||||||
"../core/src/public-api",
|
|
||||||
]
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@openharbor/ngx-data-ui-core": [
|
"@openharbor/ngx-data-ui-core": [
|
||||||
"./projects/core/src/public-api"
|
"./dist/openharbor/core"
|
||||||
],
|
],
|
||||||
"@openharbor/ngx-data-ui-md": [
|
"@openharbor/ngx-data-ui-md": [
|
||||||
"./projects/md-ui/src/public-api"
|
"./projects/md-ui/src/public-api"
|
||||||
|
|||||||
@@ -1996,10 +1996,17 @@
|
|||||||
proc-log "^4.0.0"
|
proc-log "^4.0.0"
|
||||||
which "^4.0.0"
|
which "^4.0.0"
|
||||||
|
|
||||||
"@openharbor/data@^1.0.0-alpha.1":
|
"@openharbor/data@1.0.0-alpha.4":
|
||||||
version "1.0.0-alpha.1"
|
version "1.0.0-alpha.4"
|
||||||
resolved "https://registry.yarnpkg.com/@openharbor/data/-/data-1.0.0-alpha.1.tgz#69c2bf7a0e1bac6e54459d0123fd006dce86553a"
|
resolved "https://registry.yarnpkg.com/@openharbor/data/-/data-1.0.0-alpha.4.tgz#6ae6161c6fe3b504f32d7c65ab0899164560abaa"
|
||||||
integrity sha512-MLcfYd8ZFLxcFi13z8PnRiKLnt2Hn1FbHiXts9KBix/R8FIOSeIRZwrOFIBSrYRqk+muBvif9IGYx+uxHQlk8Q==
|
integrity sha512-WdlCmHETvwId3HmsGAeCNEcsJ4Rtydq5x/poM62wdAgBZJgu/I2XQ33Q5rFbctZeii2Cx5KHxsq+n9BrgA2/8A==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.3.0"
|
||||||
|
|
||||||
|
"@openharbor/ngx-data-ui-core@^18.0.0-alpha.1":
|
||||||
|
version "18.0.0-alpha.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@openharbor/ngx-data-ui-core/-/ngx-data-ui-core-18.0.0-alpha.1.tgz#a75ca3e98d4a4081736091706efa94c358de3e58"
|
||||||
|
integrity sha512-b8ullfXgOqWKHFh8+XZxihg7cT6NMZx7it1Zfpa1LHeU1VJh6lXWhi+lXzOmZPxm856ZJ+yWRTOQaxwmIqRoUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.3.0"
|
tslib "^2.3.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user