From 860d48005edc7bdc81a5ee41c9b8d897628cecb3 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Thu, 5 Sep 2024 23:59:36 -0400 Subject: [PATCH] added command-form directive and logistic --- projects/core/package.json | 2 +- .../command-directive-service.abstraction.ts | 15 +- .../directives/command-form.directive.ts | 14 ++ .../command/directives/command.directive.ts | 37 ++-- .../data-grid/data-grid.component.css | 0 .../data-grid/data-grid.component.html | 1 + .../data-grid/data-grid.component.spec.ts | 23 +++ .../data-grid/data-grid.component.ts | 13 ++ projects/core/src/public-api.ts | 1 + projects/md-ui/package.json | 2 +- .../services/command-directive.service.ts | 8 +- .../confirm-dialog-default.component.html | 5 + .../confirm-dialog-default.component.ts | 36 +++- .../data-grid/data-grid.component.css | 0 .../data-grid/data-grid.component.html | 50 ++++++ .../data-grid/data-grid.component.spec.ts | 23 +++ .../data-grid/data-grid.component.ts | 161 ++++++++++++++++++ projects/md-ui/src/public-api.ts | 1 + 18 files changed, 363 insertions(+), 29 deletions(-) create mode 100644 projects/core/src/lib/command/directives/command-form.directive.ts create mode 100644 projects/core/src/lib/data-grid/data-grid/data-grid.component.css create mode 100644 projects/core/src/lib/data-grid/data-grid/data-grid.component.html create mode 100644 projects/core/src/lib/data-grid/data-grid/data-grid.component.spec.ts create mode 100644 projects/core/src/lib/data-grid/data-grid/data-grid.component.ts create mode 100644 projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.css create mode 100644 projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.html create mode 100644 projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.spec.ts create mode 100644 projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.ts diff --git a/projects/core/package.json b/projects/core/package.json index 9900068..ed29989 100644 --- a/projects/core/package.json +++ b/projects/core/package.json @@ -1,6 +1,6 @@ { "name": "@openharbor/ngx-data-ui-core", - "version": "18.0.0-alpha.7", + "version": "18.0.0-alpha.15", "repository": "https://git.openharbor.io/Open-Harbor/ngx-data-ui", "license": "MIT", "peerDependencies": { diff --git a/projects/core/src/lib/command/abstractions/command-directive-service.abstraction.ts b/projects/core/src/lib/command/abstractions/command-directive-service.abstraction.ts index c641907..d72c9b1 100644 --- a/projects/core/src/lib/command/abstractions/command-directive-service.abstraction.ts +++ b/projects/core/src/lib/command/abstractions/command-directive-service.abstraction.ts @@ -1,4 +1,6 @@ import {Observable} from "rxjs"; +import {TemplateRef} from "@angular/core"; +import {AbstractControl, FormGroup} from "@angular/forms"; export interface IConfirmOptions { title: string; @@ -13,6 +15,15 @@ export interface IConfirmEvents { loading: Observable; } -export interface ICommandDirectiveService { - confirm(options?: TConfirmOptions & IConfirmEvents): Observable; +export interface IConfirmForm = any> { + formTemplate?: TemplateRef; + form?: FormGroup +} + +export type FormType = { [K in keyof TControl]: AbstractControl; } + +export type ConfirmOptions = TConfirmOptions & IConfirmEvents & IConfirmForm; + +export interface ICommandDirectiveService { + confirm(options?: ConfirmOptions): Observable; } diff --git a/projects/core/src/lib/command/directives/command-form.directive.ts b/projects/core/src/lib/command/directives/command-form.directive.ts new file mode 100644 index 0000000..b3b46d3 --- /dev/null +++ b/projects/core/src/lib/command/directives/command-form.directive.ts @@ -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> { + public readonly template = inject(TemplateRef); + + @Input({ alias: 'commandForm', required: true }) + public form!: FormGroup; +} diff --git a/projects/core/src/lib/command/directives/command.directive.ts b/projects/core/src/lib/command/directives/command.directive.ts index 51e27db..8ae2e26 100644 --- a/projects/core/src/lib/command/directives/command.directive.ts +++ b/projects/core/src/lib/command/directives/command.directive.ts @@ -1,33 +1,36 @@ -import {Directive, EventEmitter, HostListener, Input, Output} from '@angular/core'; +import {AfterContentInit, ContentChild, Directive, EventEmitter, HostListener, Input, Output} from '@angular/core'; import {IDataSource} from '@openharbor/data'; import {finalize} from "rxjs"; import { + ConfirmOptions, FormType, ICommandDirectiveService, - IConfirmEvents, IConfirmOptions } from "../abstractions/command-directive-service.abstraction"; +import {CommandFormDirective} from "./command-form.directive"; @Directive({ selector: '[duiCommand]', standalone: true }) -export class CommandDirective { +export class CommandDirective = any> implements AfterContentInit { + @ContentChild(CommandFormDirective) commandFormTemplate?: CommandFormDirective; + @Input() confirm: boolean = false; @Input() confirmOptions?: TConfirmOptions; @Input() refresh: boolean = true; - @Input() params: any; + @Input() params?: any; - @Input() dataSource!: IDataSource; - @Input() command!: string; - @Input() model!: object; + @Input({ required: true }) dataSource!: IDataSource; + @Input({ required: true }) command!: string; + @Input() model: object = {}; @Input() service?: ICommandDirectiveService; @Output() success: EventEmitter = new EventEmitter(); @Output() failure: EventEmitter = new EventEmitter(); @Output() loading: EventEmitter = new EventEmitter(); - constructor() { - + ngAfterContentInit(): void { + console.log('init'); } @HostListener('click') @@ -44,14 +47,20 @@ export class CommandDirective; this.service.confirm(options) .subscribe(result => { + const model = { ...this.model, ...options.form?.getRawValue() }; + console.log(options.form?.getRawValue()); if (result) - this.executeCommand(); + this.executeCommand(model); }); return; } @@ -59,10 +68,10 @@ export class CommandDirective({ command: this.command, - model: this.model, + model: model, params: this.params }) .subscribe({ next: commandModel => { diff --git a/projects/core/src/lib/data-grid/data-grid/data-grid.component.css b/projects/core/src/lib/data-grid/data-grid/data-grid.component.css new file mode 100644 index 0000000..e69de29 diff --git a/projects/core/src/lib/data-grid/data-grid/data-grid.component.html b/projects/core/src/lib/data-grid/data-grid/data-grid.component.html new file mode 100644 index 0000000..6fbeeed --- /dev/null +++ b/projects/core/src/lib/data-grid/data-grid/data-grid.component.html @@ -0,0 +1 @@ +

data-grid works!

diff --git a/projects/core/src/lib/data-grid/data-grid/data-grid.component.spec.ts b/projects/core/src/lib/data-grid/data-grid/data-grid.component.spec.ts new file mode 100644 index 0000000..0d07268 --- /dev/null +++ b/projects/core/src/lib/data-grid/data-grid/data-grid.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DataGridComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DataGridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/core/src/lib/data-grid/data-grid/data-grid.component.ts b/projects/core/src/lib/data-grid/data-grid/data-grid.component.ts new file mode 100644 index 0000000..78f0a3a --- /dev/null +++ b/projects/core/src/lib/data-grid/data-grid/data-grid.component.ts @@ -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 { + + @Input() dataSource: IDataSource; +} diff --git a/projects/core/src/public-api.ts b/projects/core/src/public-api.ts index 9d98b07..234e08d 100644 --- a/projects/core/src/public-api.ts +++ b/projects/core/src/public-api.ts @@ -4,3 +4,4 @@ export * from './lib/command/directives/command.directive'; export * from './lib/command/abstractions/command-directive-service.abstraction'; +export * from './lib/command/directives/command-form.directive'; diff --git a/projects/md-ui/package.json b/projects/md-ui/package.json index d9442e0..f14bd44 100644 --- a/projects/md-ui/package.json +++ b/projects/md-ui/package.json @@ -1,6 +1,6 @@ { "name": "@openharbor/ngx-data-ui-md", - "version": "18.0.0-alpha.8", + "version": "18.0.0-alpha.24", "repository": "https://git.openharbor.io/Open-Harbor/ngx-data-ui", "license": "MIT", "peerDependencies": { diff --git a/projects/md-ui/src/lib/command/services/command-directive.service.ts b/projects/md-ui/src/lib/command/services/command-directive.service.ts index 633cd14..1787aec 100644 --- a/projects/md-ui/src/lib/command/services/command-directive.service.ts +++ b/projects/md-ui/src/lib/command/services/command-directive.service.ts @@ -1,5 +1,5 @@ import {inject, Injectable} from '@angular/core'; -import {ICommandDirectiveService, IConfirmEvents, IConfirmOptions} from "@openharbor/ngx-data-ui-core"; +import {ConfirmOptions, ICommandDirectiveService, IConfirmOptions} from "@openharbor/ngx-data-ui-core"; import {Observable} from 'rxjs'; import {MatDialog, MatDialogConfig} from "@angular/material/dialog"; import { @@ -17,7 +17,7 @@ export interface IMDCommandDirectiveServiceOptions extends IConfirmOptions { export class CommandDirectiveService implements ICommandDirectiveService { readonly dialog = inject(MatDialog); - confirm(options: IMDCommandDirectiveServiceOptions & IConfirmEvents): Observable { + confirm(options: ConfirmOptions): Observable { const defaultOptions: Partial = { confirmText: 'Confirm', cancelText: 'Cancel' @@ -39,7 +39,9 @@ export class CommandDirectiveService implements ICommandDirectiveService{{ title }} {{ message }} + + @if (formTemplate) { + + + } diff --git a/projects/md-ui/src/lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component.ts b/projects/md-ui/src/lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component.ts index 4cd4d88..d5b00d3 100644 --- a/projects/md-ui/src/lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component.ts +++ b/projects/md-ui/src/lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component.ts @@ -1,17 +1,20 @@ -import {Component, EventEmitter, inject, OnDestroy, OnInit} from '@angular/core'; +import {Component, EventEmitter, inject, OnDestroy, OnInit, TemplateRef} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, MatDialogClose, - MatDialogContent, MatDialogRef, + MatDialogContent, + MatDialogRef, MatDialogTitle } from "@angular/material/dialog"; import {MatButton} from "@angular/material/button"; -import {AsyncPipe} from "@angular/common"; +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 = any> { title: string; message: string; confirmText?: string; @@ -19,10 +22,11 @@ export interface IConfirmDialogDefaultOptions { success: Observable; failure: Observable; loading: Observable; + formTemplate?: TemplateRef; + form?: FormGroup; } @Component({ - selector: 'mdx-confirm-dialog-default', standalone: true, imports: [ MatDialogContent, @@ -31,13 +35,14 @@ export interface IConfirmDialogDefaultOptions { MatDialogClose, MatDialogTitle, AsyncPipe, - MatProgressSpinner + MatProgressSpinner, + NgTemplateOutlet ], templateUrl: './confirm-dialog-default.component.html', styleUrl: './confirm-dialog-default.component.css' }) -export class ConfirmDialogDefaultComponent implements OnInit, OnDestroy { - readonly data = inject(MAT_DIALOG_DATA); +export class ConfirmDialogDefaultComponent = any> implements OnInit, OnDestroy { + readonly data = inject>(MAT_DIALOG_DATA); readonly ref = inject(MatDialogRef); readonly title: string = this.data.title; @@ -46,6 +51,9 @@ export class ConfirmDialogDefaultComponent implements OnInit, OnDestroy { readonly cancelText?: string = this.data.cancelText; readonly $loading: Observable = this.data.loading; readonly success: Observable = this.data.success; + readonly formTemplate?: TemplateRef = this.data.formTemplate; + readonly form?: FormGroup; + isLoading = false; // todo: error messaging? @@ -75,6 +83,18 @@ export class ConfirmDialogDefaultComponent implements OnInit, OnDestroy { } onConfirmed() { + if (!this.formTemplate || !this.form) { + this._onConfirm.emit(); + return; + } + + this.form.clearValidators(); + + if (!this.form.valid) { + this.form.markAllAsTouched(); + return; + } + this._onConfirm.emit(); } } diff --git a/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.css b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.css new file mode 100644 index 0000000..e69de29 diff --git a/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.html b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.html new file mode 100644 index 0000000..42a02d6 --- /dev/null +++ b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.html @@ -0,0 +1,50 @@ +
+ @if ($data) { + + @for (column of _options.columns; track column.column) { + + + + + + } + + + +
+ @if (column.title) { + {{ column.title(column.column) | async }} + } + @else { + {{ column.column | titlecase }} + } + + @if (column.value) { + {{ column.value(element[column.column]) | async }} + } + @else { + {{ element[column.column] }} + } +
+ + @if(_options.pagination.enable) { + + + } + + @if (isLoading) { + + } + } +
diff --git a/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.spec.ts b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.spec.ts new file mode 100644 index 0000000..0d07268 --- /dev/null +++ b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DataGridComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DataGridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.ts b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.ts new file mode 100644 index 0000000..d89a160 --- /dev/null +++ b/projects/md-ui/src/lib/data-grid/data-grid/data-grid.component.ts @@ -0,0 +1,161 @@ +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 { + columns: IColumnDefinition>[]; + actions?: []; + pagination: { + enable: boolean; + pageSize: number; + hidePageSize: boolean; + pageSizeOptions: number[]; + } +} + +export interface IColumnDefinition> { + column: TColumn; + sorting?: { + enable: boolean; + description?: (element: TColumn) => Observable; + path?: string; + }, + title?: (element: TColumn) => Observable; + value?: (element: TModel[TColumn]) => Observable; +} + +export interface IRowAction { + +} + +@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 implements OnInit, OnDestroy { + @Input({ required: true }) dataSource!: IDataSource; + @Input() options?: Partial>; + + private subscriptions: Subscription[] = []; + + $data?: Observable; + totalRecords: number = 0; + isLoading: boolean = false; + + _options: IDataGridOptions = { + columns: [], + pagination: { + enable: true, + hidePageSize: false, + pageSizeOptions: [10, 25, 50, 100], + pageSize: 25 + } + }; + + getColumns() { + return this._options.columns.map(options => { + return options.column; + }); + } + + ngOnInit() { + this._options = { + ...this._options, + ...this.options + }; + + this.$data = this.dataSource.data$ + .pipe( + tap((result => { + this.totalRecords = result?.totalRecords ?? 0; + })), + map((result) => { + return result?.data ?? []; + }) + ); + + this.subscriptions.push(this.dataSource.loading$ + .subscribe(loading => this.isLoading = loading) + ); + } + + 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(); + } +} diff --git a/projects/md-ui/src/public-api.ts b/projects/md-ui/src/public-api.ts index 3c2ed45..9507104 100644 --- a/projects/md-ui/src/public-api.ts +++ b/projects/md-ui/src/public-api.ts @@ -4,3 +4,4 @@ export * from './lib/command/services/command-directive.service'; export * from './lib/confirm-dialog/confirm-dialog-default/confirm-dialog-default.component'; +export * from './lib/data-grid/data-grid/data-grid.component';