Commit f0065caa by Ooh-Ao

datagrid

parent 05002a9d
...@@ -175,13 +175,13 @@ export class WidgetConfigRegistryService { ...@@ -175,13 +175,13 @@ export class WidgetConfigRegistryService {
// ======================================== // ========================================
this.registerWidgetWithMetadata('SyncfusionDatagridWidgetComponent', SyncfusionDatagridConfigComponent, { this.registerWidgetWithMetadata('SyncfusionDatagridWidgetComponent', SyncfusionDatagridConfigComponent, {
name: 'SyncfusionDatagridWidgetComponent', name: 'SyncfusionDatagridWidgetComponent',
displayName: 'Data Grid Widget', displayName: 'Advanced Data Grid',
description: 'Advanced data grid with sorting, filtering, paging, and export capabilities', description: 'Comprehensive data grid with advanced features including sorting, filtering, paging, grouping, editing, export capabilities, and real-time data management',
category: WidgetCategory.DATA_GRID, category: WidgetCategory.DATA_GRID,
icon: 'grid', icon: 'grid',
version: '1.0.0', version: '2.0.0',
author: 'Portal Team', author: 'Portal Team',
tags: ['datagrid', 'table', 'data', 'syncfusion', 'advanced'], tags: ['datagrid', 'table', 'data', 'syncfusion', 'advanced', 'enterprise', 'grid', 'spreadsheet'],
isDeprecated: false, isDeprecated: false,
minWidth: 400, minWidth: 400,
minHeight: 300, minHeight: 300,
...@@ -189,11 +189,12 @@ export class WidgetConfigRegistryService { ...@@ -189,11 +189,12 @@ export class WidgetConfigRegistryService {
maxHeight: 800, maxHeight: 800,
defaultWidth: 600, defaultWidth: 600,
defaultHeight: 400, defaultHeight: 400,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET, DataSourceType.ODATA, DataSourceType.WEBAPI], supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET, DataSourceType.ODATA, DataSourceType.WEBAPI, DataSourceType.WEBSOCKET],
features: [ features: [
WidgetFeature.EXPORT, WidgetFeature.FILTER, WidgetFeature.SORT, WidgetFeature.PAGINATION, WidgetFeature.EXPORT, WidgetFeature.FILTER, WidgetFeature.SORT, WidgetFeature.PAGINATION,
WidgetFeature.SEARCH, WidgetFeature.GROUPING, WidgetFeature.EDITING, WidgetFeature.SELECTION, WidgetFeature.SEARCH, WidgetFeature.GROUPING, WidgetFeature.EDITING, WidgetFeature.SELECTION,
WidgetFeature.REFRESH, WidgetFeature.CUSTOMIZATION, WidgetFeature.RESPONSIVE, WidgetFeature.PRINT WidgetFeature.REFRESH, WidgetFeature.CUSTOMIZATION, WidgetFeature.RESPONSIVE, WidgetFeature.PRINT,
WidgetFeature.ANIMATION, WidgetFeature.TOOLTIP, WidgetFeature.ZOOM
], ],
complexity: 'advanced' complexity: 'advanced'
}); });
...@@ -1004,8 +1005,14 @@ export class WidgetConfigRegistryService { ...@@ -1004,8 +1005,14 @@ export class WidgetConfigRegistryService {
allowMultiSelection: false, allowMultiSelection: false,
allowEditing: false, allowEditing: false,
allowAdding: false, allowAdding: false,
allowPageSize: true,
allowPageNavigation: true,
allowMultiSorting: false,
allowAdvancedFiltering: false,
allowSearching: false,
pageSize: 10, pageSize: 10,
pageSizes: [5, 10, 20, 50, 100], pageSizes: [5, 10, 20, 50, 100],
showPageInfo: true,
// UI Settings // UI Settings
showToolbar: true, showToolbar: true,
...@@ -1015,6 +1022,16 @@ export class WidgetConfigRegistryService { ...@@ -1015,6 +1022,16 @@ export class WidgetConfigRegistryService {
alternateRowColor: '#F9FAFB', alternateRowColor: '#F9FAFB',
rowHeight: 40, rowHeight: 40,
headerRowHeight: 40, headerRowHeight: 40,
headerTextColor: '#374151',
// Filter Settings
showFilterBar: false,
filterType: 'menu',
filterBarHeight: 40,
// Aggregate Settings
showAggregate: false,
aggregateColumns: [],
// Export Settings // Export Settings
enableExport: true, enableExport: true,
...@@ -1023,17 +1040,16 @@ export class WidgetConfigRegistryService { ...@@ -1023,17 +1040,16 @@ export class WidgetConfigRegistryService {
includeHeaders: true, includeHeaders: true,
includeFilters: false, includeFilters: false,
// Search Settings // Selection & Editing
enableSearch: false, selectionMode: 'none',
searchSettings: { editMode: 'none',
fields: [],
operator: 'contains',
key: '',
ignoreCase: true
},
// Virtualization // Data Management
dataSourceType: 'local',
enableVirtualization: false, enableVirtualization: false,
enableInfiniteScroll: false,
enableLazyLoad: false,
cacheDuration: 300,
// Style Settings // Style Settings
backgroundType: 'solid', backgroundType: 'solid',
...@@ -1041,7 +1057,6 @@ export class WidgetConfigRegistryService { ...@@ -1041,7 +1057,6 @@ export class WidgetConfigRegistryService {
borderColor: '#E5E7EB', borderColor: '#E5E7EB',
textColor: '#374151', textColor: '#374151',
headerBackgroundColor: '#F9FAFB', headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151',
borderRadius: 8, borderRadius: 8,
borderWidth: 1, borderWidth: 1,
fontSize: 14, fontSize: 14,
...@@ -1064,11 +1079,8 @@ export class WidgetConfigRegistryService { ...@@ -1064,11 +1079,8 @@ export class WidgetConfigRegistryService {
dataValidation: 'basic', dataValidation: 'basic',
showDataCount: true, showDataCount: true,
showLastUpdated: true, showLastUpdated: true,
enableFilter: false, dataTransform: '',
filterField: '', errorMessage: 'Failed to load data',
filterOperator: 'contains',
filterValue: '',
filterLabel: 'Filter',
// Security Settings // Security Settings
requireAuth: false, requireAuth: false,
......
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnInit, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
...@@ -31,7 +31,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -31,7 +31,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Basic Configuration Tab --> <!-- Basic Configuration Tab -->
<mat-tab label="Basic"> <mat-tab label="Basic">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-blue-600">Basic Configuration</h3> <h3 class="section-title text-blue-600">
<i class="bi bi-gear-fill mr-2"></i>
Basic Configuration
</h3>
<mat-form-field appearance="fill" class="w-full"> <mat-form-field appearance="fill" class="w-full">
<mat-label>Title</mat-label> <mat-label>Title</mat-label>
...@@ -73,14 +76,17 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -73,14 +76,17 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Size Configuration --> <!-- Size Configuration -->
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-blue-600">Size Configuration</h3> <h3 class="section-title text-blue-600">
<i class="bi bi-arrows-fullscreen mr-2"></i>
Size Configuration
</h3>
<!-- Size Presets --> <!-- Size Presets -->
<div class="size-presets"> <div class="size-presets">
<h4 class="text-sm font-medium text-gray-700 mb-3">Quick Presets</h4> <h4 class="text-sm font-medium text-gray-700 mb-3">Quick Presets</h4>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3"> <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<div <div
*ngFor="let option of sizeOptions" *ngFor="let option of sizeOptions; trackBy: trackBySizeOption"
class="size-preset-card" class="size-preset-card"
[class.selected]="currentConfig.sizeOption === option.id" [class.selected]="currentConfig.sizeOption === option.id"
(click)="setSizeOption(option.id)"> (click)="setSizeOption(option.id)">
...@@ -253,7 +259,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -253,7 +259,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Columns Tab --> <!-- Columns Tab -->
<mat-tab label="Columns"> <mat-tab label="Columns">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-green-600">Column Configuration</h3> <h3 class="section-title text-green-600">
<i class="bi bi-columns-gap mr-2"></i>
Column Configuration
</h3>
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<h4>Available Columns</h4> <h4>Available Columns</h4>
...@@ -263,7 +272,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -263,7 +272,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
</button> </button>
</div> </div>
<div *ngFor="let column of currentConfig.columns; let i = index" class="column-config-item p-4 mb-4"> <div *ngFor="let column of currentConfig.columns; let i = index; trackBy: trackByColumnIndex" class="column-config-item p-4 mb-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
<mat-label>Field Name</mat-label> <mat-label>Field Name</mat-label>
...@@ -329,7 +338,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -329,7 +338,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Rows Tab --> <!-- Rows Tab -->
<mat-tab label="Rows"> <mat-tab label="Rows">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-purple-600">Row Configuration</h3> <h3 class="section-title text-purple-600">
<i class="bi bi-list-ul mr-2"></i>
Row Configuration
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
...@@ -373,7 +385,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -373,7 +385,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Style Tab --> <!-- Style Tab -->
<mat-tab label="Style"> <mat-tab label="Style">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-orange-600">Style Configuration</h3> <h3 class="section-title text-orange-600">
<i class="bi bi-palette-fill mr-2"></i>
Style Configuration
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
...@@ -427,7 +442,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -427,7 +442,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Filter Tab --> <!-- Filter Tab -->
<mat-tab label="Filter"> <mat-tab label="Filter">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-teal-600">Filter Configuration</h3> <h3 class="section-title text-teal-600">
<i class="bi bi-funnel-fill mr-2"></i>
Filter Configuration
</h3>
<div class="flex items-center space-x-4 mb-4"> <div class="flex items-center space-x-4 mb-4">
<mat-checkbox [(ngModel)]="currentConfig.allowFiltering" name="allowFiltering"> <mat-checkbox [(ngModel)]="currentConfig.allowFiltering" name="allowFiltering">
...@@ -461,7 +479,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -461,7 +479,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Aggregate Tab --> <!-- Aggregate Tab -->
<mat-tab label="Aggregate"> <mat-tab label="Aggregate">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-red-600">Aggregate Configuration</h3> <h3 class="section-title text-red-600">
<i class="bi bi-calculator mr-2"></i>
Aggregate Configuration
</h3>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<mat-checkbox [(ngModel)]="currentConfig.showAggregate" name="showAggregate"> <mat-checkbox [(ngModel)]="currentConfig.showAggregate" name="showAggregate">
...@@ -527,7 +548,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -527,7 +548,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Export Tab --> <!-- Export Tab -->
<mat-tab label="Export"> <mat-tab label="Export">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-indigo-600">Export Configuration</h3> <h3 class="section-title text-indigo-600">
<i class="bi bi-download mr-2"></i>
Export Configuration
</h3>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<mat-checkbox [(ngModel)]="currentConfig.enableExport" name="enableExport"> <mat-checkbox [(ngModel)]="currentConfig.enableExport" name="enableExport">
...@@ -568,7 +592,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -568,7 +592,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Features Tab --> <!-- Features Tab -->
<mat-tab label="Features"> <mat-tab label="Features">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-cyan-600">Grid Features</h3> <h3 class="section-title text-cyan-600">
<i class="bi bi-grid-3x3-gap-fill mr-2"></i>
Grid Features
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="feature-group"> <div class="feature-group">
...@@ -672,7 +699,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -672,7 +699,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Data Management Tab --> <!-- Data Management Tab -->
<mat-tab label="Data"> <mat-tab label="Data">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-emerald-600">Data Management</h3> <h3 class="section-title text-emerald-600">
<i class="bi bi-database-fill mr-2"></i>
Data Management
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
...@@ -739,7 +769,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -739,7 +769,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Security Tab --> <!-- Security Tab -->
<mat-tab label="Security"> <mat-tab label="Security">
<div class="config-section"> <div class="config-section">
<h3 class="section-title text-gray-600">Security Configuration</h3> <h3 class="section-title text-gray-600">
<i class="bi bi-shield-lock-fill mr-2"></i>
Security Configuration
</h3>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<mat-checkbox [(ngModel)]="currentConfig.requireAuth" name="requireAuth"> <mat-checkbox [(ngModel)]="currentConfig.requireAuth" name="requireAuth">
...@@ -811,14 +844,69 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -811,14 +844,69 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
.section-title { .section-title {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 700;
margin-bottom: 20px; margin-bottom: 1.5rem;
padding-bottom: 8px; padding-bottom: 0.75rem;
border-bottom: 2px solid #e2e8f0; border-bottom: 3px solid currentColor;
display: flex;
align-items: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
i {
font-size: 1.125rem;
opacity: 0.8;
}
} }
.config-tabs { .config-tabs {
margin-top: 0; margin-top: 0;
::ng-deep .mat-tab-group {
.mat-tab-header {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px 12px 0 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.mat-tab-label {
font-weight: 600;
color: #64748b;
transition: all 0.3s ease;
&:hover {
color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
}
&.mat-tab-label-active {
color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
}
}
.mat-ink-bar {
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
height: 3px;
border-radius: 2px;
}
}
}
.config-section {
padding: 24px;
background: linear-gradient(135deg, #ffffff, #f8fafc);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
margin-bottom: 16px;
border: 1px solid rgba(226, 232, 240, 0.5);
}
.feature-group {
background: rgba(255, 255, 255, 0.8);
padding: 16px;
border-radius: 8px;
border: 1px solid rgba(226, 232, 240, 0.5);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
} }
.config-tabs .mat-tab-body-content { .config-tabs .mat-tab-body-content {
...@@ -991,11 +1079,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -991,11 +1079,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
.text-emerald-600 { color: #059669; } .text-emerald-600 { color: #059669; }
`] `]
}) })
export class SyncfusionDatagridConfigComponent extends BaseConfigComponent implements OnInit { export class SyncfusionDatagridConfigComponent extends BaseConfigComponent implements OnInit, AfterViewInit {
constructor(override cdr: ChangeDetectorRef) {
super(cdr);
}
override ngOnInit() { override ngOnInit() {
// Initialize basic config first
this.initializeDefaultConfig(); this.initializeDefaultConfig();
}
override ngAfterViewInit() {
// Initialize other configs after view is ready
this.initializeColumns(); this.initializeColumns();
this.initializeColorDefaults(); this.initializeColorDefaults();
this.cdr.detectChanges();
}
// TrackBy functions to prevent unnecessary re-renders
trackByColumnIndex(index: number, item: any): number {
return index;
}
trackBySizeOption(index: number, item: any): string {
return item.id;
} }
override initializeDefaultConfig() { override initializeDefaultConfig() {
......
...@@ -21,19 +21,30 @@ ...@@ -21,19 +21,30 @@
<div class="header-content"> <div class="header-content">
<div class="header-left"> <div class="header-left">
<h4 class="widget-title">{{ title }}</h4> <h4 class="widget-title">{{ title }}</h4>
<div *ngIf="showDataCount" class="data-count">
<i class="bi bi-list-ul"></i>
<span>{{ dataCount }} รายการ</span>
</div>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<div *ngIf="enableExport" class="export-button" (click)="exportData($event)"> <div *ngIf="enableExport" class="action-button export-button" (click)="exportData($event)" title="ส่งออกข้อมูล">
<i class="bi bi-download"></i> <i class="bi bi-download"></i>
</div> </div>
<div *ngIf="enableRefresh && dataSource !== 'static'" class="refresh-button" (click)="refreshData($event)"> <div *ngIf="enableRefresh && dataSource !== 'static'" class="action-button refresh-button" (click)="refreshData($event)" title="รีเฟรชข้อมูล">
<i class="bi bi-arrow-clockwise"></i> <i class="bi bi-arrow-clockwise" [class.rotating]="isLoading"></i>
</div>
<div *ngIf="showFilterBar" class="action-button filter-button" title="กรองข้อมูล">
<i class="bi bi-funnel"></i>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="dataSource !== 'static'" class="data-source-info"> <div *ngIf="dataSource !== 'static'" class="data-source-info">
<i class="bi bi-info-circle"></i> <i class="bi bi-info-circle"></i>
<span>{{ getDataSourceInfo() }}</span> <span>{{ getDataSourceInfo() }}</span>
<span *ngIf="showLastUpdated" class="last-updated">
<i class="bi bi-clock"></i>
อัปเดตล่าสุด: {{ lastUpdated | date:'dd/MM/yyyy HH:mm' }}
</span>
</div> </div>
</div> </div>
...@@ -47,94 +58,156 @@ ...@@ -47,94 +58,156 @@
<div *ngIf="hasRequiredRole()" class="widget-body"> <div *ngIf="hasRequiredRole()" class="widget-body">
<!-- Loading State --> <!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full"> <div *ngIf="isLoading" class="loading-state">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div> <div class="loading-spinner">
<div class="spinner"></div>
</div>
<p class="loading-text">กำลังโหลดข้อมูล...</p>
</div> </div>
<!-- Error State --> <!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500"> <div *ngIf="hasError" class="error-state">
<i class="bi bi-exclamation-circle-fill text-4xl"></i> <div class="error-icon">
<p class="mt-2 font-semibold">{{ errorMessage }}</p> <i class="bi bi-exclamation-circle-fill"></i>
</div>
<h3 class="error-title">เกิดข้อผิดพลาด</h3>
<p class="error-message">{{ errorMessage }}</p>
<button class="retry-button" (click)="refreshData($event)">
<i class="bi bi-arrow-clockwise"></i>
ลองใหม่
</button>
</div>
<!-- Empty State -->
<div *ngIf="!isLoading && !hasError && dataCount === 0" class="empty-state">
<div class="empty-icon">
<i class="bi bi-table"></i>
</div>
<h3 class="empty-title">ไม่มีข้อมูล</h3>
<p class="empty-message">ไม่พบข้อมูลที่จะแสดงในตาราง</p>
</div> </div>
<!-- Grid --> <!-- Grid -->
<ejs-grid #grid *ngIf="!isLoading && !hasError" <div *ngIf="!isLoading && !hasError && dataCount > 0" class="grid-container">
[dataSource]="gridData" <ejs-grid #grid
[allowPaging]="enablePaging" [dataSource]="gridData"
[pageSettings]="pageSettings" [allowPaging]="allowPaging"
[allowSorting]="enableSorting" [pageSettings]="pageSettings"
[allowFiltering]="enableFiltering" [allowSorting]="allowSorting"
[allowGrouping]="enableGrouping" [allowFiltering]="allowAdvancedFiltering"
[toolbar]="showToolbar ? toolbar : []" [allowGrouping]="allowGrouping"
(toolbarClick)="toolbarClick($event)" [toolbar]="showToolbar ? toolbar : []"
[allowExcelExport]="enableExport" (toolbarClick)="toolbarClick($event)"
[allowPdfExport]="enableExport" [allowExcelExport]="enableExport"
[searchSettings]="enableSearch ? searchSettings : null" [allowPdfExport]="enableExport"
[groupSettings]="groupSettings" [searchSettings]="allowSearching ? searchSettings : null"
[filterSettings]="filterSettings" [groupSettings]="groupSettings"
[editSettings]="enableEditing ? editSettings : { allowEditing: false }" [filterSettings]="filterSettings"
[selectionSettings]="enableRowSelection ? selectionOptions : { type: 'None' }" [editSettings]="allowEditing ? editSettings : { allowEditing: false }"
[loadingIndicator]="loadingIndicator" [selectionSettings]="allowSelection ? selectionOptions : { type: 'None' }"
[query]="query" [loadingIndicator]="loadingIndicator"
[showColumnMenu]="showColumnMenu" [query]="query"
[allowReordering]="allowReordering" [showColumnMenu]="showColumnMenu"
[allowMultiSorting]="allowMultiSorting" [allowReordering]="allowReordering"
[columnMenuItems]="columnMenuItems" [allowMultiSorting]="allowMultiSorting"
(columnMenuClick)="onColumnMenuClick($event)" [columnMenuItems]="columnMenuItems"
(actionComplete)="actionComplete($event)" (columnMenuClick)="onColumnMenuClick($event)"
(dataBound)="onDataBound($event)" (actionComplete)="actionComplete($event)"
[height]="'100%'" (dataBound)="onDataBound($event)"
[allowResizing]="enableColumnResizing"> [height]="'100%'"
<e-columns> [allowResizing]="allowResizing"
<e-column *ngFor="let col of columns" [field]="col.field" [headerText]="col.headerText" [width]="col.width" [rowHeight]="rowHeight">
[format]="col.format" [textAlign]="col.textAlign" [isPrimaryKey]="col.isPrimaryKey" [editType]="col.editType"
[validationRules]="col.validationRules" [allowEditing]="col.allowEditing" [allowSorting]="col.allowSorting" <e-columns>
[allowFiltering]="col.allowFiltering" [visible]="col.visible" [type]="col.type"> <e-column *ngFor="let col of columns"
<ng-template #headerTemplate let-data> [field]="col.field"
<span class="font-size-12px font-weight-700">{{ col.headerText }}</span> [headerText]="col.headerText"
</ng-template> [width]="col.width"
</e-column> [format]="col.format"
</e-columns> [textAlign]="col.textAlign"
<e-aggregates> [isPrimaryKey]="col.isPrimaryKey"
<e-aggregate> [editType]="col.editType"
<e-columns> [validationRules]="col.validationRules"
<e-column *ngFor="let col of aggregatesSum" [field]="col.field" [type]="'Sum'" [footerTemplate]="'Sum: ${Sum}'" [allowEditing]="col.allowEditing"
[groupFooterTemplate]="'Sum: ${Sum}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format"> [allowSorting]="col.allowSorting"
</e-column> [allowFiltering]="col.allowFiltering"
</e-columns> [visible]="col.visible"
</e-aggregate> [type]="col.type">
<e-aggregate> <ng-template #headerTemplate let-data>
<e-columns> <div class="header-cell">
<e-column *ngFor="let col of aggregatesCount" [field]="col.field" [type]="'Count'" <span class="header-text">{{ col.headerText }}</span>
[footerTemplate]="'Count: ${Count}'" [groupFooterTemplate]="'Count: ${Count}'" <div *ngIf="col.sortable" class="sort-indicator">
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format"> <i class="bi bi-arrow-up-down"></i>
</e-column> </div>
</e-columns> </div>
</e-aggregate> </ng-template>
<e-aggregate> </e-column>
<e-columns> </e-columns>
<e-column *ngFor="let col of aggregatesAvg" [field]="col.field" [type]="'Average'"
[footerTemplate]="'Average: ${Average}'" [groupFooterTemplate]="'Average: ${Average}'" <e-aggregates *ngIf="showAggregate">
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format"> <e-aggregate>
</e-column> <e-columns>
</e-columns> <e-column *ngFor="let col of aggregatesSum"
</e-aggregate> [field]="col.field"
<e-aggregate> [type]="'Sum'"
<e-columns> [footerTemplate]="'รวม: ${Sum}'"
<e-column *ngFor="let col of aggregatesMin" [field]="col.field" [type]="'Min'" [footerTemplate]="'Min: ${Min}'" [groupFooterTemplate]="'รวม: ${Sum}'"
[groupFooterTemplate]="'Min: ${Min}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format"> [groupCaptionTemplate]="col.groupCaptionTemplate"
</e-column> [format]="col.format">
</e-columns> </e-column>
</e-aggregate> </e-columns>
<e-aggregate> </e-aggregate>
<e-columns> <e-aggregate>
<e-column *ngFor="let col of aggregatesMax" [field]="col.field" [type]="'Max'" [footerTemplate]="'Max: ${Max}'" <e-columns>
[groupFooterTemplate]="'Max: ${Max}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format"> <e-column *ngFor="let col of aggregatesCount"
</e-column> [field]="col.field"
</e-columns> [type]="'Count'"
</e-aggregate> [footerTemplate]="'จำนวน: ${Count}'"
</e-aggregates> [groupFooterTemplate]="'จำนวน: ${Count}'"
</ejs-grid> [groupCaptionTemplate]="col.groupCaptionTemplate"
[format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesAvg"
[field]="col.field"
[type]="'Average'"
[footerTemplate]="'เฉลี่ย: ${Average}'"
[groupFooterTemplate]="'เฉลี่ย: ${Average}'"
[groupCaptionTemplate]="col.groupCaptionTemplate"
[format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMin"
[field]="col.field"
[type]="'Min'"
[footerTemplate]="'ต่ำสุด: ${Min}'"
[groupFooterTemplate]="'ต่ำสุด: ${Min}'"
[groupCaptionTemplate]="col.groupCaptionTemplate"
[format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMax"
[field]="col.field"
[type]="'Max'"
[footerTemplate]="'สูงสุด: ${Max}'"
[groupFooterTemplate]="'สูงสุด: ${Max}'"
[groupCaptionTemplate]="col.groupCaptionTemplate"
[format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
</div>
</div> </div>
</div> </div>
...@@ -9,9 +9,10 @@ ...@@ -9,9 +9,10 @@
// Widget Header // Widget Header
.widget-header { .widget-header {
flex-shrink: 0; flex-shrink: 0;
padding: 12px 16px; padding: 16px 20px;
border-bottom: 1px solid rgba(229, 231, 235, 0.5); border-bottom: 1px solid rgba(229, 231, 235, 0.5);
position: relative; position: relative;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(249, 250, 251, 0.9));
.header-content { .header-content {
display: flex; display: flex;
...@@ -21,13 +22,30 @@ ...@@ -21,13 +22,30 @@
.header-left { .header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 12px;
.widget-title { .widget-title {
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 700;
margin: 0; margin: 0;
color: inherit; color: inherit;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.data-count {
display: flex;
align-items: center;
gap: 6px;
background: rgba(59, 130, 246, 0.1);
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
color: #3B82F6;
i {
font-size: 10px;
}
} }
} }
...@@ -35,33 +53,40 @@ ...@@ -35,33 +53,40 @@
display: flex; display: flex;
gap: 8px; gap: 8px;
.export-button, .action-button {
.refresh-button { width: 36px;
width: 32px; height: 36px;
height: 32px; border-radius: 8px;
border-radius: 6px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.8);
color: inherit; color: inherit;
opacity: 0.7; border: 1px solid rgba(229, 231, 235, 0.5);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
&:hover { &:hover {
opacity: 1; background: rgba(59, 130, 246, 0.1);
background: rgba(255, 255, 255, 0.2); border-color: rgba(59, 130, 246, 0.3);
transform: translateY(-1px); color: #3B82F6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
} }
&:active { &:active {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
i { i {
font-size: 14px; font-size: 14px;
} }
&.rotating i {
animation: spin 1s linear infinite;
}
} }
} }
} }
...@@ -69,21 +94,35 @@ ...@@ -69,21 +94,35 @@
.data-source-info { .data-source-info {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 16px; left: 20px;
right: 16px; right: 20px;
background: rgba(59, 130, 246, 0.1); background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(37, 99, 235, 0.1));
border: 1px solid rgba(59, 130, 246, 0.2); border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 4px; border-radius: 8px;
padding: 4px 8px; padding: 8px 12px;
font-size: 11px; font-size: 12px;
color: #3B82F6; color: #3B82F6;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; justify-content: space-between;
margin-top: 4px; margin-top: 8px;
backdrop-filter: blur(10px);
i { i {
font-size: 10px; font-size: 12px;
margin-right: 6px;
}
.last-updated {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
opacity: 0.8;
i {
font-size: 10px;
}
} }
} }
} }
...@@ -122,6 +161,147 @@ ...@@ -122,6 +161,147 @@
overflow: hidden; overflow: hidden;
} }
// Loading State
.loading-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background: linear-gradient(135deg, rgba(249, 250, 251, 0.8), rgba(243, 244, 246, 0.8));
.loading-spinner {
margin-bottom: 16px;
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(59, 130, 246, 0.1);
border-top: 4px solid #3B82F6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
}
.loading-text {
font-size: 14px;
color: #6B7280;
font-weight: 500;
margin: 0;
}
}
// Error State
.error-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background: linear-gradient(135deg, rgba(254, 242, 242, 0.8), rgba(252, 231, 243, 0.8));
.error-icon {
margin-bottom: 16px;
i {
font-size: 48px;
color: #EF4444;
}
}
.error-title {
font-size: 18px;
font-weight: 600;
color: #DC2626;
margin: 0 0 8px 0;
}
.error-message {
font-size: 14px;
color: #6B7280;
text-align: center;
margin: 0 0 20px 0;
max-width: 300px;
}
.retry-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #EF4444;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #DC2626;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
&:active {
transform: translateY(0);
}
i {
font-size: 12px;
}
}
}
// Empty State
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background: linear-gradient(135deg, rgba(249, 250, 251, 0.8), rgba(243, 244, 246, 0.8));
.empty-icon {
margin-bottom: 16px;
i {
font-size: 48px;
color: #9CA3AF;
}
}
.empty-title {
font-size: 18px;
font-weight: 600;
color: #374151;
margin: 0 0 8px 0;
}
.empty-message {
font-size: 14px;
color: #6B7280;
text-align: center;
margin: 0;
max-width: 300px;
}
}
// Grid Container
.grid-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
// Interaction States // Interaction States
&.hover-enabled:hover { &.hover-enabled:hover {
transform: translateY(-2px); transform: translateY(-2px);
...@@ -189,6 +369,11 @@ ...@@ -189,6 +369,11 @@
} }
// Animation Keyframes // Animation Keyframes
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
...@@ -238,6 +423,15 @@ ...@@ -238,6 +423,15 @@
} }
} }
@keyframes shimmer {
0% {
background-position: -200px 0;
}
100% {
background-position: calc(200px + 100%) 0;
}
}
// Aspect Ratio Constraints // Aspect Ratio Constraints
&.aspect-ratio-16-9 { &.aspect-ratio-16-9 {
aspect-ratio: 16/9; aspect-ratio: 16/9;
...@@ -312,38 +506,50 @@ ...@@ -312,38 +506,50 @@
} }
.e-pager { .e-pager {
background-color: rgba(249, 250, 251, 0.8); background: linear-gradient(135deg, rgba(249, 250, 251, 0.9), rgba(243, 244, 246, 0.9));
border-top: 1px solid rgba(229, 231, 235, 0.5); border-top: 1px solid rgba(229, 231, 235, 0.5);
padding: 8px 16px; padding: 12px 20px;
backdrop-filter: blur(10px);
.e-pagercontainer { .e-pagercontainer {
.e-pagercontrol { .e-pagercontrol {
color: inherit; color: #6B7280;
font-size: 12px; font-size: 13px;
font-weight: 500;
} }
} }
} }
.e-toolbar { .e-toolbar {
background-color: rgba(249, 250, 251, 0.8); background: linear-gradient(135deg, rgba(249, 250, 251, 0.9), rgba(243, 244, 246, 0.9));
border-bottom: 1px solid rgba(229, 231, 235, 0.5); border-bottom: 1px solid rgba(229, 231, 235, 0.5);
padding: 8px 16px; padding: 12px 20px;
backdrop-filter: blur(10px);
.e-toolbaritems { .e-toolbaritems {
.e-toolbaritem { .e-toolbaritem {
.e-btn { .e-btn {
background-color: transparent; background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(229, 231, 235, 0.5); border: 1px solid rgba(229, 231, 235, 0.5);
color: inherit; color: #6B7280;
font-size: 12px; font-size: 13px;
padding: 4px 8px; font-weight: 500;
border-radius: 4px; padding: 8px 16px;
transition: all 0.2s ease; border-radius: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
&:hover { &:hover {
background-color: rgba(59, 130, 246, 0.1); background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.3); border-color: rgba(59, 130, 246, 0.3);
color: #3B82F6; color: #3B82F6;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
&:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
} }
} }
......
import { Component, ViewChild, OnInit, OnDestroy, AfterViewInit, Input } from '@angular/core'; import { Component, ViewChild, OnInit, OnDestroy, AfterViewInit, Input } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { GridModule, PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, GridComponent, ToolbarItems, SearchSettingsModel, GroupSettingsModel, FilterSettingsModel, SelectionSettingsModel, AggregateService, ColumnMenuService, DetailRowService, ReorderService, EditService, PdfExportProperties, ExcelExportProperties, LoadingIndicatorModel, Column, SearchService, ResizeService } from '@syncfusion/ej2-angular-grids'; import { GridModule, PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, GridComponent, ToolbarItems, SearchSettingsModel, GroupSettingsModel, FilterSettingsModel, SelectionSettingsModel, AggregateService, ColumnMenuService, DetailRowService, ReorderService, EditService, PdfExportProperties, ExcelExportProperties, LoadingIndicatorModel, Column, SearchService, ResizeService, FreezeService, VirtualScrollService, InfiniteScrollService, LazyLoadGroupService, RowDDService, ContextMenuService } from '@syncfusion/ej2-angular-grids';
import { MenuEventArgs } from '@syncfusion/ej2-navigations'; import { MenuEventArgs } from '@syncfusion/ej2-navigations';
import { ClickEventArgs } from '@syncfusion/ej2-navigations'; import { ClickEventArgs } from '@syncfusion/ej2-navigations';
import { DataManager, Query } from '@syncfusion/ej2-data'; import { DataManager, Query } from '@syncfusion/ej2-data';
...@@ -53,7 +53,28 @@ L10n.load({ ...@@ -53,7 +53,28 @@ L10n.load({
selector: 'app-syncfusion-datagrid-widget', selector: 'app-syncfusion-datagrid-widget',
standalone: true, standalone: true,
imports: [CommonModule, GridModule], imports: [CommonModule, GridModule],
providers: [PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, AggregateService, ColumnMenuService, DetailRowService, ReorderService, EditService, SearchService, ResizeService], providers: [
PageService,
SortService,
FilterService,
GroupService,
ToolbarService,
ExcelExportService,
PdfExportService,
AggregateService,
ColumnMenuService,
DetailRowService,
ReorderService,
EditService,
SearchService,
ResizeService,
FreezeService,
VirtualScrollService,
InfiniteScrollService,
LazyLoadGroupService,
RowDDService,
ContextMenuService
],
templateUrl: './syncfusion-datagrid-widget.component.html', templateUrl: './syncfusion-datagrid-widget.component.html',
styleUrls: ['./syncfusion-datagrid-widget.component.scss'] styleUrls: ['./syncfusion-datagrid-widget.component.scss']
}) })
...@@ -209,7 +230,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -209,7 +230,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public selectedRowColor: string = '#DBEAFE'; public selectedRowColor: string = '#DBEAFE';
public hoverRowColor: string = '#F3F4F6'; public hoverRowColor: string = '#F3F4F6';
// New configuration properties from SyncfusionDatagridConfigComponent // Enhanced configuration properties from SyncfusionDatagridConfigComponent
public allowPaging: boolean = true; public allowPaging: boolean = true;
public allowPageSize: boolean = true; public allowPageSize: boolean = true;
public showPageInfo: boolean = true; public showPageInfo: boolean = true;
...@@ -243,6 +264,14 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -243,6 +264,14 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public includeHeaders: boolean = true; public includeHeaders: boolean = true;
public includeFilters: boolean = false; public includeFilters: boolean = false;
// Enhanced UI properties
public dataCount: number = 0;
public lastUpdated: Date = new Date();
public override originalData: any[] = [];
// Enhanced styling properties
public shadow: string = 'medium';
// Size options for configuration // Size options for configuration
public sizeOptions = [ public sizeOptions = [
{ id: 'small', label: 'Small', description: '400x300px' }, { id: 'small', label: 'Small', description: '400x300px' },
...@@ -445,7 +474,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -445,7 +474,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.selectedRowColor = this.configObj.selectedRowColor || '#DBEAFE'; this.selectedRowColor = this.configObj.selectedRowColor || '#DBEAFE';
this.hoverRowColor = this.configObj.hoverRowColor || '#F3F4F6'; this.hoverRowColor = this.configObj.hoverRowColor || '#F3F4F6';
// New configuration properties // Enhanced configuration properties
this.allowPaging = this.configObj.allowPaging !== undefined ? this.configObj.allowPaging : true; this.allowPaging = this.configObj.allowPaging !== undefined ? this.configObj.allowPaging : true;
this.allowPageSize = this.configObj.allowPageSize !== undefined ? this.configObj.allowPageSize : true; this.allowPageSize = this.configObj.allowPageSize !== undefined ? this.configObj.allowPageSize : true;
this.showPageInfo = this.configObj.showPageInfo !== undefined ? this.configObj.showPageInfo : true; this.showPageInfo = this.configObj.showPageInfo !== undefined ? this.configObj.showPageInfo : true;
...@@ -481,6 +510,15 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -481,6 +510,15 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.exportFilename = this.configObj.exportFilename || 'data-export'; this.exportFilename = this.configObj.exportFilename || 'data-export';
this.includeHeaders = this.configObj.includeHeaders !== undefined ? this.configObj.includeHeaders : true; this.includeHeaders = this.configObj.includeHeaders !== undefined ? this.configObj.includeHeaders : true;
this.includeFilters = this.configObj.includeFilters !== undefined ? this.configObj.includeFilters : false; this.includeFilters = this.configObj.includeFilters !== undefined ? this.configObj.includeFilters : false;
// Enhanced UI properties
this.dataCount = this.configObj.dataCount || 0;
this.lastUpdated = this.configObj.lastUpdated ? new Date(this.configObj.lastUpdated) : new Date();
// Enhanced styling properties
this.shadow = this.configObj.shadow || 'medium';
// Security configuration
this.requireAuth = this.configObj.requireAuth !== undefined ? this.configObj.requireAuth : false; this.requireAuth = this.configObj.requireAuth !== undefined ? this.configObj.requireAuth : false;
this.allowedRoles = this.configObj.allowedRoles || ''; this.allowedRoles = this.configObj.allowedRoles || '';
this.permissionLevel = this.configObj.permissionLevel || 'read'; this.permissionLevel = this.configObj.permissionLevel || 'read';
...@@ -522,6 +560,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -522,6 +560,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
} }
onDataUpdate(data: any[]): void { onDataUpdate(data: any[]): void {
// Store original data
this.originalData = data || [];
this.dataCount = this.originalData.length;
this.lastUpdated = new Date();
// Transform data if transform function is provided // Transform data if transform function is provided
let transformedData = this.transformData(data); let transformedData = this.transformData(data);
...@@ -543,17 +586,30 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -543,17 +586,30 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.columns = Object.keys(transformedData[0]).map(key => ({ this.columns = Object.keys(transformedData[0]).map(key => ({
field: key, field: key,
headerText: this.formatHeader(key), headerText: this.formatHeader(key),
width: 150 width: 150,
textAlign: 'left',
format: 'none',
visible: true,
sortable: this.allowSorting,
filterable: this.allowAdvancedFiltering
})); }));
} }
// Update loading and error states
this.isLoading = false;
this.hasError = false;
} }
onDataBound(args: any): void { onDataBound(args: any): void {
// Apply perspective after data is loaded and rendered, but only once. // Apply perspective after data is loaded and rendered, but only once.
if (this.perspective && !this.isPerspectiveApplied && this.grid) { if (this.perspective && !this.isPerspectiveApplied && this.grid) {
setTimeout(() => { try {
this.setWidgetState(this.perspective as string); setTimeout(() => {
}, 50); // Small delay to ensure rendering is complete this.setWidgetState(this.perspective as string);
}, 50); // Small delay to ensure rendering is complete
} catch (error) {
console.warn('Error applying perspective to grid:', error);
}
} }
} }
...@@ -673,25 +729,31 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -673,25 +729,31 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
toolbarClick(args: ClickEventArgs): void { toolbarClick(args: ClickEventArgs): void {
if (!this.grid || !args?.item?.id) return; if (!this.grid || !args?.item?.id) return;
const fileName = `${this.title}.xlsx`; try {
if (args.item.id.includes('_excelexport')) { const fileName = `${this.title}.xlsx`;
let exportProperties: ExcelExportProperties = { if (args.item.id.includes('_excelexport')) {
columns: this.columns.map(col => ({ let exportProperties: ExcelExportProperties = {
field: col.field, columns: this.columns.map(col => ({
headerText: col.headerText field: col.field,
})) as Column[] headerText: col.headerText
}; })) as Column[]
this.grid.excelExport(exportProperties); };
} else if (args.item.id.includes('_pdfexport')) { this.grid.excelExport(exportProperties);
this.grid.pdfExport({ fileName: `${this.title}.pdf` }); } else if (args.item.id.includes('_pdfexport')) {
} else if (args.item.id.includes('_csvexport')) { this.grid.pdfExport({ fileName: `${this.title}.pdf` });
let exportColumns = this.columns.map(col => ({ } else if (args.item.id.includes('_csvexport')) {
field: col.field!, let exportColumns = this.columns.map(col => ({
headerText: col.headerText! field: col.field!,
})); headerText: col.headerText!
this.grid.csvExport({ columns: exportColumns as Column[] }); }));
} else if (args.item.id.includes('_print')) { this.grid.csvExport({ columns: exportColumns as Column[] });
this.grid.print(); } else if (args.item.id.includes('_print')) {
this.grid.print();
}
} catch (error) {
console.error('Error in toolbar click:', error);
this.hasError = true;
this.errorMessage = 'Export failed. Please try again.';
} }
} }
...@@ -704,11 +766,12 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -704,11 +766,12 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
onColumnMenuClick(args: MenuEventArgs): void { onColumnMenuClick(args: MenuEventArgs): void {
if (!args.item?.id || !this.grid) { return; } if (!args.item?.id || !this.grid) { return; }
if (args.item.id.startsWith('aggregate_')) { try {
const colField = (args as any)?.column?.field; if (args.item.id.startsWith('aggregate_')) {
if (!colField) { return; } const colField = (args as any)?.column?.field;
if (!colField) { return; }
const selectedAgg = args.item.id.split('_')[1]; const selectedAgg = args.item.id.split('_')[1];
if (selectedAgg === 'sum') { if (selectedAgg === 'sum') {
if (this.aggregatesSum.find(a => a.field === colField)) { if (this.aggregatesSum.find(a => a.field === colField)) {
...@@ -761,8 +824,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple ...@@ -761,8 +824,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
}); });
} }
} }
this.grid?.refreshColumns(); // Refresh columns to show/hide aggregates this.grid?.refreshColumns(); // Refresh columns to show/hide aggregates
this.grid?.refresh(); // Refresh grid to recalculate aggregates this.grid?.refresh(); // Refresh grid to recalculate aggregates
}
} catch (error) {
console.error('Error in column menu click:', error);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment