Commit f0065caa by Ooh-Ao

datagrid

parent 05002a9d
......@@ -175,13 +175,13 @@ export class WidgetConfigRegistryService {
// ========================================
this.registerWidgetWithMetadata('SyncfusionDatagridWidgetComponent', SyncfusionDatagridConfigComponent, {
name: 'SyncfusionDatagridWidgetComponent',
displayName: 'Data Grid Widget',
description: 'Advanced data grid with sorting, filtering, paging, and export capabilities',
displayName: 'Advanced Data Grid',
description: 'Comprehensive data grid with advanced features including sorting, filtering, paging, grouping, editing, export capabilities, and real-time data management',
category: WidgetCategory.DATA_GRID,
icon: 'grid',
version: '1.0.0',
version: '2.0.0',
author: 'Portal Team',
tags: ['datagrid', 'table', 'data', 'syncfusion', 'advanced'],
tags: ['datagrid', 'table', 'data', 'syncfusion', 'advanced', 'enterprise', 'grid', 'spreadsheet'],
isDeprecated: false,
minWidth: 400,
minHeight: 300,
......@@ -189,11 +189,12 @@ export class WidgetConfigRegistryService {
maxHeight: 800,
defaultWidth: 600,
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: [
WidgetFeature.EXPORT, WidgetFeature.FILTER, WidgetFeature.SORT, WidgetFeature.PAGINATION,
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'
});
......@@ -1004,8 +1005,14 @@ export class WidgetConfigRegistryService {
allowMultiSelection: false,
allowEditing: false,
allowAdding: false,
allowPageSize: true,
allowPageNavigation: true,
allowMultiSorting: false,
allowAdvancedFiltering: false,
allowSearching: false,
pageSize: 10,
pageSizes: [5, 10, 20, 50, 100],
showPageInfo: true,
// UI Settings
showToolbar: true,
......@@ -1015,6 +1022,16 @@ export class WidgetConfigRegistryService {
alternateRowColor: '#F9FAFB',
rowHeight: 40,
headerRowHeight: 40,
headerTextColor: '#374151',
// Filter Settings
showFilterBar: false,
filterType: 'menu',
filterBarHeight: 40,
// Aggregate Settings
showAggregate: false,
aggregateColumns: [],
// Export Settings
enableExport: true,
......@@ -1023,17 +1040,16 @@ export class WidgetConfigRegistryService {
includeHeaders: true,
includeFilters: false,
// Search Settings
enableSearch: false,
searchSettings: {
fields: [],
operator: 'contains',
key: '',
ignoreCase: true
},
// Selection & Editing
selectionMode: 'none',
editMode: 'none',
// Virtualization
// Data Management
dataSourceType: 'local',
enableVirtualization: false,
enableInfiniteScroll: false,
enableLazyLoad: false,
cacheDuration: 300,
// Style Settings
backgroundType: 'solid',
......@@ -1041,7 +1057,6 @@ export class WidgetConfigRegistryService {
borderColor: '#E5E7EB',
textColor: '#374151',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151',
borderRadius: 8,
borderWidth: 1,
fontSize: 14,
......@@ -1064,11 +1079,8 @@ export class WidgetConfigRegistryService {
dataValidation: 'basic',
showDataCount: true,
showLastUpdated: true,
enableFilter: false,
filterField: '',
filterOperator: 'contains',
filterValue: '',
filterLabel: 'Filter',
dataTransform: '',
errorMessage: 'Failed to load data',
// Security Settings
requireAuth: false,
......
......@@ -9,9 +9,10 @@
// Widget Header
.widget-header {
flex-shrink: 0;
padding: 12px 16px;
padding: 16px 20px;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
position: relative;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(249, 250, 251, 0.9));
.header-content {
display: flex;
......@@ -21,13 +22,30 @@
.header-left {
display: flex;
align-items: center;
gap: 8px;
gap: 12px;
.widget-title {
font-size: 16px;
font-weight: 600;
font-size: 18px;
font-weight: 700;
margin: 0;
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 @@
display: flex;
gap: 8px;
.export-button,
.refresh-button {
width: 32px;
height: 32px;
border-radius: 6px;
.action-button {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
background: rgba(255, 255, 255, 0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(255, 255, 255, 0.8);
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 {
opacity: 1;
background: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.3);
color: #3B82F6;
transform: translateY(-2px);
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);
}
i {
font-size: 14px;
}
&.rotating i {
animation: spin 1s linear infinite;
}
}
}
}
......@@ -69,21 +94,35 @@
.data-source-info {
position: absolute;
top: 100%;
left: 16px;
right: 16px;
background: rgba(59, 130, 246, 0.1);
left: 20px;
right: 20px;
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-radius: 4px;
padding: 4px 8px;
font-size: 11px;
border-radius: 8px;
padding: 8px 12px;
font-size: 12px;
color: #3B82F6;
display: flex;
align-items: center;
gap: 4px;
margin-top: 4px;
justify-content: space-between;
margin-top: 8px;
backdrop-filter: blur(10px);
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 @@
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
&.hover-enabled:hover {
transform: translateY(-2px);
......@@ -189,6 +369,11 @@
}
// Animation Keyframes
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fadeIn {
from {
opacity: 0;
......@@ -238,6 +423,15 @@
}
}
@keyframes shimmer {
0% {
background-position: -200px 0;
}
100% {
background-position: calc(200px + 100%) 0;
}
}
// Aspect Ratio Constraints
&.aspect-ratio-16-9 {
aspect-ratio: 16/9;
......@@ -312,38 +506,50 @@
}
.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);
padding: 8px 16px;
padding: 12px 20px;
backdrop-filter: blur(10px);
.e-pagercontainer {
.e-pagercontrol {
color: inherit;
font-size: 12px;
color: #6B7280;
font-size: 13px;
font-weight: 500;
}
}
}
.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);
padding: 8px 16px;
padding: 12px 20px;
backdrop-filter: blur(10px);
.e-toolbaritems {
.e-toolbaritem {
.e-btn {
background-color: transparent;
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(229, 231, 235, 0.5);
color: inherit;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.2s ease;
color: #6B7280;
font-size: 13px;
font-weight: 500;
padding: 8px 16px;
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 {
background-color: rgba(59, 130, 246, 0.1);
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.3);
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 { 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 { ClickEventArgs } from '@syncfusion/ej2-navigations';
import { DataManager, Query } from '@syncfusion/ej2-data';
......@@ -53,7 +53,28 @@ L10n.load({
selector: 'app-syncfusion-datagrid-widget',
standalone: true,
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',
styleUrls: ['./syncfusion-datagrid-widget.component.scss']
})
......@@ -209,7 +230,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public selectedRowColor: string = '#DBEAFE';
public hoverRowColor: string = '#F3F4F6';
// New configuration properties from SyncfusionDatagridConfigComponent
// Enhanced configuration properties from SyncfusionDatagridConfigComponent
public allowPaging: boolean = true;
public allowPageSize: boolean = true;
public showPageInfo: boolean = true;
......@@ -243,6 +264,14 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public includeHeaders: boolean = true;
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
public sizeOptions = [
{ id: 'small', label: 'Small', description: '400x300px' },
......@@ -445,7 +474,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.selectedRowColor = this.configObj.selectedRowColor || '#DBEAFE';
this.hoverRowColor = this.configObj.hoverRowColor || '#F3F4F6';
// New configuration properties
// Enhanced configuration properties
this.allowPaging = this.configObj.allowPaging !== undefined ? this.configObj.allowPaging : true;
this.allowPageSize = this.configObj.allowPageSize !== undefined ? this.configObj.allowPageSize : true;
this.showPageInfo = this.configObj.showPageInfo !== undefined ? this.configObj.showPageInfo : true;
......@@ -481,6 +510,15 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.exportFilename = this.configObj.exportFilename || 'data-export';
this.includeHeaders = this.configObj.includeHeaders !== undefined ? this.configObj.includeHeaders : true;
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.allowedRoles = this.configObj.allowedRoles || '';
this.permissionLevel = this.configObj.permissionLevel || 'read';
......@@ -522,6 +560,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
}
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
let transformedData = this.transformData(data);
......@@ -543,17 +586,30 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.columns = Object.keys(transformedData[0]).map(key => ({
field: 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 {
// Apply perspective after data is loaded and rendered, but only once.
if (this.perspective && !this.isPerspectiveApplied && this.grid) {
setTimeout(() => {
this.setWidgetState(this.perspective as string);
}, 50); // Small delay to ensure rendering is complete
try {
setTimeout(() => {
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
toolbarClick(args: ClickEventArgs): void {
if (!this.grid || !args?.item?.id) return;
const fileName = `${this.title}.xlsx`;
if (args.item.id.includes('_excelexport')) {
let exportProperties: ExcelExportProperties = {
columns: this.columns.map(col => ({
field: col.field,
headerText: col.headerText
})) as Column[]
};
this.grid.excelExport(exportProperties);
} else if (args.item.id.includes('_pdfexport')) {
this.grid.pdfExport({ fileName: `${this.title}.pdf` });
} else if (args.item.id.includes('_csvexport')) {
let exportColumns = this.columns.map(col => ({
field: col.field!,
headerText: col.headerText!
}));
this.grid.csvExport({ columns: exportColumns as Column[] });
} else if (args.item.id.includes('_print')) {
this.grid.print();
try {
const fileName = `${this.title}.xlsx`;
if (args.item.id.includes('_excelexport')) {
let exportProperties: ExcelExportProperties = {
columns: this.columns.map(col => ({
field: col.field,
headerText: col.headerText
})) as Column[]
};
this.grid.excelExport(exportProperties);
} else if (args.item.id.includes('_pdfexport')) {
this.grid.pdfExport({ fileName: `${this.title}.pdf` });
} else if (args.item.id.includes('_csvexport')) {
let exportColumns = this.columns.map(col => ({
field: col.field!,
headerText: col.headerText!
}));
this.grid.csvExport({ columns: exportColumns as Column[] });
} 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
onColumnMenuClick(args: MenuEventArgs): void {
if (!args.item?.id || !this.grid) { return; }
if (args.item.id.startsWith('aggregate_')) {
const colField = (args as any)?.column?.field;
if (!colField) { return; }
try {
if (args.item.id.startsWith('aggregate_')) {
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 (this.aggregatesSum.find(a => a.field === colField)) {
......@@ -761,8 +824,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
});
}
}
this.grid?.refreshColumns(); // Refresh columns to show/hide aggregates
this.grid?.refresh(); // Refresh grid to recalculate aggregates
this.grid?.refreshColumns(); // Refresh columns to show/hide 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