add generic table
This commit is contained in:
parent
1bfa4dec47
commit
ec44849b31
@ -22,10 +22,6 @@ export class GenericTableSearchForm {
|
|||||||
|
|
||||||
this.filterForm.valueChanges.pipe(
|
this.filterForm.valueChanges.pipe(
|
||||||
debounceTime(300),
|
debounceTime(300),
|
||||||
filter( value => {
|
|
||||||
console.info(value)
|
|
||||||
return value.term && value.term.length >= 3;
|
|
||||||
}),
|
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
).subscribe(values => {
|
).subscribe(values => {
|
||||||
const cleanFilter = Object.fromEntries(
|
const cleanFilter = Object.fromEntries(
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<div [ngClass]="config.tableCssClass" [class]="'overflow-x-auto'">
|
<div [ngClass]="config.tableCssClass" [class]="'overflow-x-auto'">
|
||||||
<app-generic-table-search-form (searchTermChanged)="searchTermChanged($event)" ></app-generic-table-search-form>
|
<app-generic-table-search-form (searchTermChanged)="searchTermChanged($event)" ></app-generic-table-search-form>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@if (data$ | async; as getDataResponse) {
|
@if (data$ | async; as getDataResponse) {
|
||||||
<table class="table w-full table-zebra">
|
<table class="table w-full table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
@ -51,7 +54,20 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
@if ((getDataResponse?.data?.meta?.totalPages ?? 1) > 1) {
|
@if ((getDataResponse?.data?.meta?.totalPages ?? 1) > 0) {
|
||||||
|
<div class="flex justify-end mt-4 items-center me-3 ">
|
||||||
|
Items: {{getDataResponse.data.data.length}}/{{getDataResponse.data.meta.totalItems}}
|
||||||
|
Page: {{getDataResponse.data.meta.currentPage}}/{{getDataResponse.data.meta.totalPages}}
|
||||||
|
PageSize:
|
||||||
|
<select class="select w-auto" #pageSize (change)="onPageSizeChange(pageSize.value)">
|
||||||
|
<option>10</option>
|
||||||
|
<option>20</option>
|
||||||
|
<option>50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
@if ((getDataResponse?.data?.meta?.totalPages ?? 1) > 1) {
|
||||||
<div class="flex justify-center mt-4">
|
<div class="flex justify-center mt-4">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -88,4 +88,9 @@ export class GenericTable<T> implements OnInit {
|
|||||||
this.config.page$.next(1);
|
this.config.page$.next(1);
|
||||||
this.config.filter$.next(searchTerm);
|
this.config.filter$.next(searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onPageSizeChange(value: any) {
|
||||||
|
this.config.page$.next(1);
|
||||||
|
this.config.limit$.next(+value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
// dvbooking-cli/src/templates/angular-generic/data-provider.service.ts.tpl
|
||||||
|
|
||||||
|
// Generated by the CLI
|
||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { DataProvider, GetDataOptions, GetDataResponse } from '../../../../components/generic-table/data-provider.interface';
|
||||||
|
import { Product } from '../../models/product.model';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { ProductService } from '../../services/product.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProductDataProvider implements DataProvider<Product> {
|
||||||
|
private productService = inject(ProductService);
|
||||||
|
|
||||||
|
getData(options?: GetDataOptions): Observable<GetDataResponse<Product>> {
|
||||||
|
const {q,page,limit} = options?.params ?? {};
|
||||||
|
// The generic table's params are compatible with our NestJS Query DTO
|
||||||
|
return this.productService.search(q ?? '',page,limit, ).pipe(
|
||||||
|
map((res) => {
|
||||||
|
// Adapt the paginated response to the GetDataResponse format
|
||||||
|
return { data: res };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<!-- dvbooking-cli/src/templates/angular-generic/table.component.html.tpl -->
|
||||||
|
|
||||||
|
<!-- Generated by the CLI -->
|
||||||
|
<div class="p-4 md:p-8 space-y-6">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="text-3xl font-bold">Products (Generic Table)</h1>
|
||||||
|
<a routerLink="/products/new" class="btn btn-primary">Create New</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-generic-table [config]="tableConfig"></app-generic-table>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
// dvbooking-cli/src/templates/angular-generic/table.component.ts.tpl
|
||||||
|
|
||||||
|
// Generated by the CLI
|
||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { Router, RouterModule } from '@angular/router';
|
||||||
|
import { Product } from '../../models/product.model';
|
||||||
|
import { ProductDataProvider } from './product-data-provider.service';
|
||||||
|
import { ColumnDefinition } from '../../../../components/generic-table/column-definition.interface';
|
||||||
|
import { GenericTable } from '../../../../components/generic-table/generic-table';
|
||||||
|
import { GenericTableConfig } from '../../../../components/generic-table/generic-table.config';
|
||||||
|
import {
|
||||||
|
ActionDefinition,
|
||||||
|
GenericActionColumn,
|
||||||
|
} from '../../../../components/generic-action-column/generic-action-column';
|
||||||
|
import { ProductService } from '../../services/product.service';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product-table',
|
||||||
|
standalone: true,
|
||||||
|
imports: [GenericTable, RouterModule],
|
||||||
|
templateUrl: './product-table.component.html',
|
||||||
|
})
|
||||||
|
export class ProductTableComponent implements OnInit {
|
||||||
|
|
||||||
|
private refresh$ = new BehaviorSubject<void>(undefined);
|
||||||
|
private filter$ = new BehaviorSubject<any>({});
|
||||||
|
private page$ = new BehaviorSubject<number>(1);
|
||||||
|
private limit$ = new BehaviorSubject<number>(10);
|
||||||
|
|
||||||
|
router = inject(Router);
|
||||||
|
tableConfig!: GenericTableConfig<Product>;
|
||||||
|
|
||||||
|
productDataProvider = inject(ProductDataProvider);
|
||||||
|
productService = inject(ProductService);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const actionHandler = (action: ActionDefinition<Product>, item: Product) => {
|
||||||
|
switch (action.action) {
|
||||||
|
case 'view':
|
||||||
|
this.router.navigate(['/products', item?.id]);
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
this.router.navigate(['/products', item?.id, 'edit']);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
this.deleteItem(item.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tableConfig = {
|
||||||
|
refresh$: this.refresh$,
|
||||||
|
filter$: this.filter$,
|
||||||
|
page$: this.page$,
|
||||||
|
limit$: this.limit$,
|
||||||
|
dataProvider: this.productDataProvider,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
attribute: 'name',
|
||||||
|
headerCell: true,
|
||||||
|
valueCell: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: 'price',
|
||||||
|
headerCell: true,
|
||||||
|
valueCell: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: 'is_available',
|
||||||
|
headerCell: true,
|
||||||
|
valueCell: {
|
||||||
|
value: item => (item as any)?.is_available ? 'yes' : 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: 'actions',
|
||||||
|
headerCell: { value: 'Actions' },
|
||||||
|
valueCell: {
|
||||||
|
component: GenericActionColumn,
|
||||||
|
componentInputs: item => ({
|
||||||
|
item: item,
|
||||||
|
actions: [
|
||||||
|
{ action: 'view', handler: actionHandler },
|
||||||
|
{ action: 'edit', handler: actionHandler },
|
||||||
|
{ action: 'delete', handler: actionHandler },
|
||||||
|
] as ActionDefinition<Product>[],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as ColumnDefinition<Product>[],
|
||||||
|
tableCssClass: 'product-table-container',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteItem(id: number): void {
|
||||||
|
if (confirm('Are you sure you want to delete this item?')) {
|
||||||
|
this.productService.remove(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
console.log(`Item with ID ${id} deleted successfully.`);
|
||||||
|
this.refresh$.next();
|
||||||
|
},
|
||||||
|
// --- THIS IS THE FIX ---
|
||||||
|
// Explicitly type 'err' to satisfy strict TypeScript rules.
|
||||||
|
error: (err: any) => {
|
||||||
|
console.error(`Error deleting item with ID ${id}:`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user