Commit c21d15b2 by Ooh-Ao

pivot

parent f0065caa
...@@ -1118,23 +1118,104 @@ export class WidgetConfigRegistryService { ...@@ -1118,23 +1118,104 @@ export class WidgetConfigRegistryService {
// ======================================== // ========================================
'SyncfusionPivotWidgetComponent': { 'SyncfusionPivotWidgetComponent': {
...baseConfig, ...baseConfig,
// Basic Configuration
expandAll: false, expandAll: false,
displayOptionView: 'Both', displayOptionView: 'Both',
chartType: 'Column',
dataSource: 'static',
apiEndpoint: '',
// Pivot Features
showToolbar: true, showToolbar: true,
showFieldList: true, showFieldList: true,
showGroupingBar: true,
showPager: true,
allowCalculatedField: false,
allowConditionalFormatting: false,
allowNumberFormatting: true,
allowFieldDragDrop: true,
allowSubTotal: true,
allowGrandTotal: true,
allowDrillThrough: false,
allowSorting: true,
allowExcelExport: true,
allowPdfExport: true,
allowCsvExport: true,
allowPrint: true,
aggregationType: 'sum',
numberFormat: 'N',
// Pivot Fields
rows: [], rows: [],
columns: [], columns: [],
values: [], values: [],
filters: [], filters: [],
enableDrillThrough: true,
enableSorting: true, // Enhanced UI Configuration
dataCount: 0,
lastUpdated: new Date(),
shadow: 'medium',
isLoading: false,
hasError: false,
errorMessage: '',
isEmpty: false,
// Enhanced Interaction Configuration
enableKeyboard: true,
enableFocus: true,
hoverEffect: true,
clickEffect: true,
enableTooltip: true,
enableSelection: true,
enableExport: true,
enableRefresh: true,
clickAction: 'none',
customClickHandler: '',
// Enhanced Layout Configuration
fullWidth: false,
fullHeight: false,
widthUnit: 'px',
heightUnit: 'px',
sizeOption: 'medium',
aspectRatio: 'auto',
// Enhanced Data Configuration
enableFiltering: true, enableFiltering: true,
enableSorting: true,
enableGrouping: true, enableGrouping: true,
enablePaging: true,
pageSize: 10,
currentPage: 1,
totalPages: 1,
// Enhanced Security Configuration
requireHttps: false,
allowedRoles: '',
dataEncryption: false,
auditLog: false,
rateLimit: 0,
// Enhanced Animation Configuration
enableAnimations: true,
animationType: 'fade',
animationDuration: 300,
animationDelay: 0,
// Enhanced Style Configuration
customCSS: '',
theme: 'light',
fontSize: 14,
fontWeight: 'normal',
fontFamily: 'system-ui, -apple-system, sans-serif',
padding: 16,
margin: 8,
// Additional Pivot Configuration
showGrandTotals: true, showGrandTotals: true,
showSubTotals: true, showSubTotals: true,
showRowTotals: true, showRowTotals: true,
showColumnTotals: true, showColumnTotals: true,
enableExport: true,
exportFormats: ['excel', 'pdf', 'csv'] exportFormats: ['excel', 'pdf', 'csv']
}, },
......
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>
...@@ -87,7 +90,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -87,7 +90,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<h3 class="section-title text-blue-600">Size Configuration</h3> <h3 class="section-title text-blue-600">Size Configuration</h3>
<div class="size-config"> <div class="size-config">
<div <div
*ngFor="let option of sizeOptions" *ngFor="let option of sizeOptions; trackBy: trackBySizeOption"
class="size-option" class="size-option"
[class.selected]="currentConfig.sizeOption === option.id" [class.selected]="currentConfig.sizeOption === option.id"
(click)="setSizeOption(option.id)"> (click)="setSizeOption(option.id)">
...@@ -231,7 +234,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -231,7 +234,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-icon>add</mat-icon> Add Row <mat-icon>add</mat-icon> Add Row
</button> </button>
</div> </div>
<div *ngFor="let row of currentConfig.rows; let i = index" class="field-config-item"> <div *ngFor="let row of currentConfig.rows; let i = index; trackBy: trackByRowIndex" class="field-config-item">
<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">
<mat-label>Row Field</mat-label> <mat-label>Row Field</mat-label>
...@@ -268,7 +271,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -268,7 +271,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-icon>add</mat-icon> Add Column <mat-icon>add</mat-icon> Add Column
</button> </button>
</div> </div>
<div *ngFor="let col of currentConfig.columns; let i = index" class="field-config-item"> <div *ngFor="let col of currentConfig.columns; let i = index; trackBy: trackByColumnIndex" class="field-config-item">
<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">
<mat-label>Column Field</mat-label> <mat-label>Column Field</mat-label>
...@@ -305,7 +308,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -305,7 +308,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-icon>add</mat-icon> Add Value <mat-icon>add</mat-icon> Add Value
</button> </button>
</div> </div>
<div *ngFor="let val of currentConfig.values; let i = index" class="field-config-item"> <div *ngFor="let val of currentConfig.values; let i = index; trackBy: trackByValueIndex" class="field-config-item">
<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>Value Field</mat-label> <mat-label>Value Field</mat-label>
...@@ -352,7 +355,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -352,7 +355,7 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-icon>add</mat-icon> Add Filter <mat-icon>add</mat-icon> Add Filter
</button> </button>
</div> </div>
<div *ngFor="let fil of currentConfig.filters; let i = index" class="field-config-item"> <div *ngFor="let fil of currentConfig.filters; let i = index; trackBy: trackByFilterIndex" class="field-config-item">
<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">
<mat-label>Filter Field</mat-label> <mat-label>Filter Field</mat-label>
...@@ -432,10 +435,191 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -432,10 +435,191 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
</div> </div>
</mat-tab> </mat-tab>
<!-- Enhanced UI Tab -->
<mat-tab label="UI">
<div class="config-section">
<h3 class="section-title text-indigo-600">
<i class="bi bi-palette-fill mr-2"></i>
Enhanced UI Configuration
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Shadow</mat-label>
<mat-select [(ngModel)]="currentConfig.shadow" name="shadow">
<mat-option value="none">None</mat-option>
<mat-option value="small">Small</mat-option>
<mat-option value="medium">Medium</mat-option>
<mat-option value="large">Large</mat-option>
</mat-select>
<mat-hint>Widget shadow effect</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Theme</mat-label>
<mat-select [(ngModel)]="currentConfig.theme" name="theme">
<mat-option value="light">Light</mat-option>
<mat-option value="dark">Dark</mat-option>
<mat-option value="auto">Auto</mat-option>
</mat-select>
<mat-hint>Color theme</mat-hint>
</mat-form-field>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<mat-form-field appearance="fill">
<mat-label>Font Size (px)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.fontSize" name="fontSize" min="10" max="24">
<mat-hint>Base font size</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Font Weight</mat-label>
<mat-select [(ngModel)]="currentConfig.fontWeight" name="fontWeight">
<mat-option value="normal">Normal</mat-option>
<mat-option value="bold">Bold</mat-option>
<mat-option value="lighter">Lighter</mat-option>
</mat-select>
<mat-hint>Font weight</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Font Family</mat-label>
<input matInput [(ngModel)]="currentConfig.fontFamily" name="fontFamily" placeholder="system-ui, sans-serif">
<mat-hint>Font family</mat-hint>
</mat-form-field>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Padding (px)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.padding" name="padding" min="0" max="50">
<mat-hint>Internal padding</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Margin (px)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.margin" name="margin" min="0" max="50">
<mat-hint>External margin</mat-hint>
</mat-form-field>
</div>
<div class="feature-group">
<h4 class="text-sm font-semibold text-gray-700 mb-3">Interaction Features</h4>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
<mat-checkbox [(ngModel)]="currentConfig.enableKeyboard" name="enableKeyboard">
Enable Keyboard
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.enableFocus" name="enableFocus">
Enable Focus
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.hoverEffect" name="hoverEffect">
Hover Effect
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.clickEffect" name="clickEffect">
Click Effect
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.enableTooltip" name="enableTooltip">
Enable Tooltip
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.enableSelection" name="enableSelection">
Enable Selection
</mat-checkbox>
</div>
</div>
</div>
</mat-tab>
<!-- Animation Tab -->
<mat-tab label="Animation">
<div class="config-section">
<h3 class="section-title text-pink-600">
<i class="bi bi-magic mr-2"></i>
Animation Configuration
</h3>
<div class="flex items-center space-x-4 mb-4">
<mat-checkbox [(ngModel)]="currentConfig.enableAnimations" name="enableAnimations">
Enable Animations
</mat-checkbox>
</div>
<div *ngIf="currentConfig.enableAnimations" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Animation Type</mat-label>
<mat-select [(ngModel)]="currentConfig.animationType" name="animationType">
<mat-option value="fade">Fade</mat-option>
<mat-option value="slide">Slide</mat-option>
<mat-option value="bounce">Bounce</mat-option>
<mat-option value="pulse">Pulse</mat-option>
<mat-option value="none">None</mat-option>
</mat-select>
<mat-hint>Animation type</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Animation Duration (ms)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.animationDuration" name="animationDuration" min="100" max="2000">
<mat-hint>Animation duration</mat-hint>
</mat-form-field>
</div>
<div *ngIf="currentConfig.enableAnimations" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Animation Delay (ms)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.animationDelay" name="animationDelay" min="0" max="1000">
<mat-hint>Animation delay</mat-hint>
</mat-form-field>
</div>
</div>
</mat-tab>
<!-- Security Tab -->
<mat-tab label="Security">
<div class="config-section">
<h3 class="section-title text-red-600">
<i class="bi bi-shield-fill mr-2"></i>
Security Configuration
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Allowed Roles</mat-label>
<input matInput [(ngModel)]="currentConfig.allowedRoles" name="allowedRoles" placeholder="admin,user,manager">
<mat-hint>Comma-separated roles</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Rate Limit (requests/hour)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.rateLimit" name="rateLimit" min="0" max="10000">
<mat-hint>0 = No limit</mat-hint>
</mat-form-field>
</div>
<div class="feature-group">
<h4 class="text-sm font-semibold text-gray-700 mb-3">Security Features</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-checkbox [(ngModel)]="currentConfig.requireHttps" name="requireHttps">
Require HTTPS
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.dataEncryption" name="dataEncryption">
Data Encryption
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.auditLog" name="auditLog">
Audit Logging
</mat-checkbox>
</div>
</div>
</div>
</mat-tab>
<!-- Data Tab --> <!-- Data 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">
...@@ -475,50 +659,88 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -475,50 +659,88 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
`, `,
styles: [` styles: [`
.config-container { .config-container {
padding: 20px; padding: 24px;
background: #f8fafc; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
border-radius: 12px; border-radius: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
} }
.config-section { .config-section {
margin-bottom: 32px; margin-bottom: 32px;
padding: 24px; padding: 28px;
background: white; background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border-radius: 8px; border-radius: 12px;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transform: translateY(-2px);
}
} }
.section-title { .section-title {
font-size: 1.375rem;
font-weight: 700;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 3px solid #e2e8f0;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: flex;
align-items: center;
gap: 8px;
i {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; color: #3b82f6;
margin-bottom: 20px; }
padding-bottom: 8px;
border-bottom: 2px solid #e2e8f0;
} }
.config-tabs { .config-tabs {
margin-top: 0; margin-top: 0;
background: white;
border-radius: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
overflow: hidden;
} }
.config-tabs .mat-tab-body-content { .config-tabs .mat-tab-body-content {
padding: 20px 0; padding: 24px 0;
} }
.config-tabs .mat-tab-header { .config-tabs .mat-tab-header {
background: white; background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
border-radius: 8px 8px 0 0; border-radius: 16px 16px 0 0;
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1);
} }
.config-tabs .mat-tab-label { .config-tabs .mat-tab-label {
min-width: 120px; min-width: 140px;
font-weight: 500; font-weight: 600;
font-size: 0.875rem;
padding: 16px 24px;
transition: all 0.3s ease;
color: #6b7280;
&:hover {
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
}
} }
.config-tabs .mat-tab-label-active { .config-tabs .mat-tab-label-active {
color: #3b82f6; color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
border-bottom: 3px solid #3b82f6;
}
.config-tabs .mat-ink-bar {
background: #3b82f6;
height: 3px;
} }
.mat-form-field { .mat-form-field {
...@@ -666,12 +888,49 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -666,12 +888,49 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
.text-purple-600 { color: #9333ea; } .text-purple-600 { color: #9333ea; }
.text-orange-600 { color: #ea580c; } .text-orange-600 { color: #ea580c; }
.text-emerald-600 { color: #059669; } .text-emerald-600 { color: #059669; }
.text-indigo-600 { color: #4f46e5; }
.text-pink-600 { color: #db2777; }
.text-red-600 { color: #dc2626; }
.text-cyan-600 { color: #0891b2; }
.text-yellow-600 { color: #ca8a04; }
`] `]
}) })
export class SyncfusionPivotConfigComponent extends BaseConfigComponent implements OnInit { export class SyncfusionPivotConfigComponent extends BaseConfigComponent implements OnInit, AfterViewInit {
constructor(override cdr: ChangeDetectorRef) {
super(cdr);
}
override ngOnInit(): void { override ngOnInit(): void {
// Initialize basic config first
this.initializeDefaultConfig(); this.initializeDefaultConfig();
}
override ngAfterViewInit(): void {
// Initialize other configs after view is ready
this.initializeColorDefaults(); this.initializeColorDefaults();
this.initializeAdvancedConfig();
this.cdr.detectChanges();
}
// TrackBy functions to prevent unnecessary re-renders
trackByRowIndex(index: number, item: any): number {
return index;
}
trackByColumnIndex(index: number, item: any): number {
return index;
}
trackByValueIndex(index: number, item: any): number {
return index;
}
trackByFilterIndex(index: number, item: any): number {
return index;
}
trackBySizeOption(index: number, item: any): string {
return item.id;
} }
override initializeDefaultConfig(): void { override initializeDefaultConfig(): void {
...@@ -734,6 +993,68 @@ export class SyncfusionPivotConfigComponent extends BaseConfigComponent implemen ...@@ -734,6 +993,68 @@ export class SyncfusionPivotConfigComponent extends BaseConfigComponent implemen
if (!this.currentConfig.borderColor) this.currentConfig.borderColor = '#E5E7EB'; if (!this.currentConfig.borderColor) this.currentConfig.borderColor = '#E5E7EB';
} }
private initializeAdvancedConfig() {
// Enhanced UI configuration
if (this.currentConfig.dataCount === undefined) this.currentConfig.dataCount = 0;
if (!this.currentConfig.lastUpdated) this.currentConfig.lastUpdated = new Date();
if (!this.currentConfig.shadow) this.currentConfig.shadow = 'medium';
if (this.currentConfig.isLoading === undefined) this.currentConfig.isLoading = false;
if (this.currentConfig.hasError === undefined) this.currentConfig.hasError = false;
if (!this.currentConfig.errorMessage) this.currentConfig.errorMessage = '';
if (this.currentConfig.isEmpty === undefined) this.currentConfig.isEmpty = false;
// Enhanced interaction configuration
if (this.currentConfig.enableKeyboard === undefined) this.currentConfig.enableKeyboard = true;
if (this.currentConfig.enableFocus === undefined) this.currentConfig.enableFocus = true;
if (this.currentConfig.hoverEffect === undefined) this.currentConfig.hoverEffect = true;
if (this.currentConfig.clickEffect === undefined) this.currentConfig.clickEffect = true;
if (this.currentConfig.enableTooltip === undefined) this.currentConfig.enableTooltip = true;
if (this.currentConfig.enableSelection === undefined) this.currentConfig.enableSelection = true;
if (this.currentConfig.enableExport === undefined) this.currentConfig.enableExport = true;
if (this.currentConfig.enableRefresh === undefined) this.currentConfig.enableRefresh = true;
if (!this.currentConfig.clickAction) this.currentConfig.clickAction = 'none';
if (!this.currentConfig.customClickHandler) this.currentConfig.customClickHandler = '';
// Enhanced layout configuration
if (this.currentConfig.fullWidth === undefined) this.currentConfig.fullWidth = false;
if (this.currentConfig.fullHeight === undefined) this.currentConfig.fullHeight = false;
if (!this.currentConfig.widthUnit) this.currentConfig.widthUnit = 'px';
if (!this.currentConfig.heightUnit) this.currentConfig.heightUnit = 'px';
if (!this.currentConfig.sizeOption) this.currentConfig.sizeOption = 'medium';
if (!this.currentConfig.aspectRatio) this.currentConfig.aspectRatio = 'auto';
// Enhanced data configuration
if (this.currentConfig.enableFiltering === undefined) this.currentConfig.enableFiltering = true;
if (this.currentConfig.enableSorting === undefined) this.currentConfig.enableSorting = true;
if (this.currentConfig.enableGrouping === undefined) this.currentConfig.enableGrouping = true;
if (this.currentConfig.enablePaging === undefined) this.currentConfig.enablePaging = true;
if (!this.currentConfig.pageSize) this.currentConfig.pageSize = 10;
if (!this.currentConfig.currentPage) this.currentConfig.currentPage = 1;
if (!this.currentConfig.totalPages) this.currentConfig.totalPages = 1;
// Enhanced security configuration
if (this.currentConfig.requireHttps === undefined) this.currentConfig.requireHttps = false;
if (!this.currentConfig.allowedRoles) this.currentConfig.allowedRoles = '';
if (this.currentConfig.dataEncryption === undefined) this.currentConfig.dataEncryption = false;
if (this.currentConfig.auditLog === undefined) this.currentConfig.auditLog = false;
if (!this.currentConfig.rateLimit) this.currentConfig.rateLimit = 0;
// Enhanced animation configuration
if (this.currentConfig.enableAnimations === undefined) this.currentConfig.enableAnimations = true;
if (!this.currentConfig.animationType) this.currentConfig.animationType = 'fade';
if (!this.currentConfig.animationDuration) this.currentConfig.animationDuration = 300;
if (!this.currentConfig.animationDelay) this.currentConfig.animationDelay = 0;
// Enhanced style configuration
if (!this.currentConfig.customCSS) this.currentConfig.customCSS = '';
if (!this.currentConfig.theme) this.currentConfig.theme = 'light';
if (!this.currentConfig.fontSize) this.currentConfig.fontSize = 14;
if (!this.currentConfig.fontWeight) this.currentConfig.fontWeight = 'normal';
if (!this.currentConfig.fontFamily) this.currentConfig.fontFamily = 'system-ui, -apple-system, sans-serif';
if (!this.currentConfig.padding) this.currentConfig.padding = 16;
if (!this.currentConfig.margin) this.currentConfig.margin = 8;
}
addPivotRow() { addPivotRow() {
this.addArrayItem('rows', { name: '', caption: '', visible: true, expanded: false }); this.addArrayItem('rows', { name: '', caption: '', visible: true, expanded: false });
} }
......
<div <div
class="syncfusion-pivot-widget" class="syncfusion-pivot-widget"
[ngStyle]="getAllStyles()" [ngStyle]="getAllStyles()"
[ngClass]="['custom-styled', getInteractionClasses()]" [ngClass]="['custom-styled', getInteractionClasses(), 'shadow-' + shadow]"
[attr.data-widget-id]="widgetId" [attr.data-widget-id]="widgetId"
[attr.data-source]="dataSource" [attr.data-source]="dataSource"
[title]="enableTooltip ? (title + ' - Pivot Table Widget') : null" [title]="enableTooltip ? (title + ' - Pivot Table Widget') : null"
...@@ -21,13 +21,24 @@ ...@@ -21,13 +21,24 @@
<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 class="data-count" *ngIf="dataCount > 0">
<i class="bi bi-database"></i>
<span>{{ dataCount }} records</span>
</div>
<div class="last-updated" *ngIf="lastUpdated">
<i class="bi bi-clock"></i>
<span>{{ lastUpdated | date:'short' }}</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="Export Data">
<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="Refresh Data">
<i class="bi bi-arrow-clockwise"></i> <i class="bi bi-arrow-clockwise" [class.rotating]="isLoading"></i>
</div>
<div *ngIf="enableFiltering" class="action-button filter-button" (click)="toggleFilter($event)" title="Toggle Filter">
<i class="bi bi-funnel"></i>
</div> </div>
</div> </div>
</div> </div>
...@@ -35,6 +46,10 @@ ...@@ -35,6 +46,10 @@
<i class="bi bi-info-circle"></i> <i class="bi bi-info-circle"></i>
<span>{{ getDataSourceInfo() }}</span> <span>{{ getDataSourceInfo() }}</span>
</div> </div>
<div *ngIf="requireHttps && !dataSource.includes('https')" class="security-warning">
<i class="bi bi-shield-exclamation"></i>
<span>HTTPS required for secure data transmission</span>
</div>
</div> </div>
<!-- Security Warning --> <!-- Security Warning -->
...@@ -47,18 +62,42 @@ ...@@ -47,18 +62,42 @@
<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">Loading pivot data...</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">Error Loading Data</h3>
<p class="error-message">{{ errorMessage }}</p>
<button class="retry-button" (click)="refreshData($event)">
<i class="bi bi-arrow-clockwise"></i>
Retry
</button>
</div>
<!-- Empty State -->
<div *ngIf="isEmpty && !isLoading && !hasError" class="empty-state">
<div class="empty-icon">
<i class="bi bi-table"></i>
</div>
<h3 class="empty-title">No Data Available</h3>
<p class="empty-message">There is no data to display in the pivot table.</p>
<button class="refresh-button" (click)="refreshData($event)">
<i class="bi bi-arrow-clockwise"></i>
Refresh
</button>
</div> </div>
<!-- Pivot View --> <!-- Pivot View -->
<ejs-pivotview #pivotview *ngIf="!isLoading && !hasError" <div *ngIf="!isLoading && !hasError && !isEmpty" class="pivot-container">
<ejs-pivotview #pivotview
[dataSourceSettings]="dataSourceSettings" [dataSourceSettings]="dataSourceSettings"
[allowCalculatedField]="allowCalculatedField" [allowCalculatedField]="allowCalculatedField"
[showFieldList]="showFieldList" [showFieldList]="showFieldList"
...@@ -73,6 +112,7 @@ ...@@ -73,6 +112,7 @@
(dataBound)="onDataBound($event)" (dataBound)="onDataBound($event)"
[height]="'100%'"> [height]="'100%'">
</ejs-pivotview> </ejs-pivotview>
</div>
</div> </div>
</div> </div>
...@@ -5,13 +5,36 @@ ...@@ -5,13 +5,36 @@
height: 100%; height: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
// Shadow variants
&.shadow-none {
box-shadow: none;
}
&.shadow-small {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
&.shadow-medium {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
&.shadow-large {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
// 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, #f8fafc 0%, #e2e8f0 100%);
border-radius: 12px 12px 0 0;
.header-content { .header-content {
display: flex; display: flex;
...@@ -20,14 +43,41 @@ ...@@ -20,14 +43,41 @@
.header-left { .header-left {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 8px; gap: 4px;
.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: 4px;
font-size: 12px;
color: #6b7280;
font-weight: 500;
i {
font-size: 10px;
}
}
.last-updated {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #9ca3af;
font-weight: 400;
i {
font-size: 9px;
}
} }
} }
...@@ -35,32 +85,39 @@ ...@@ -35,32 +85,39 @@
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 ease;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.8);
color: inherit; color: #374151;
opacity: 0.7; border: 1px solid rgba(229, 231, 235, 0.5);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
&: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 8px rgba(0, 0, 0, 0.15);
} }
&:active { &:active {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
} }
i { i {
font-size: 14px; font-size: 16px;
}
&.rotating i {
animation: spin 1s linear infinite;
} }
} }
} }
...@@ -120,6 +177,174 @@ ...@@ -120,6 +177,174 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: relative;
}
// Loading State
.loading-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
.loading-spinner {
margin-bottom: 16px;
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e2e8f0;
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, #fef2f2 0%, #fee2e2 100%);
text-align: center;
.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: #7f1d1d;
margin: 0 0 20px 0;
}
.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 8px rgba(239, 68, 68, 0.3);
}
&:active {
transform: translateY(0);
}
i {
font-size: 16px;
}
}
}
// Empty State
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
text-align: center;
.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;
margin: 0 0 20px 0;
}
.refresh-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: #3b82f6;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #2563eb;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}
&:active {
transform: translateY(0);
}
i {
font-size: 16px;
}
}
}
// Pivot Container
.pivot-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: white;
border-radius: 0 0 12px 12px;
} }
// Interaction States // Interaction States
...@@ -189,6 +414,15 @@ ...@@ -189,6 +414,15 @@
} }
// 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 +472,15 @@ ...@@ -238,6 +472,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;
......
import { Component, ViewChild, OnDestroy, OnInit, AfterViewInit } from '@angular/core'; import { Component, ViewChild, OnDestroy, OnInit, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { PivotViewModule, IDataSet, FieldListService, CalculatedFieldService, ToolbarService, GroupingBarService, ConditionalFormattingService, PivotViewComponent, PDFExportService, ExcelExportService, ToolbarItems, PivotChartService, DisplayOption } from '@syncfusion/ej2-angular-pivotview'; import { PivotViewModule, IDataSet, FieldListService, CalculatedFieldService, ToolbarService, GroupingBarService, ConditionalFormattingService, PivotViewComponent, PDFExportService, ExcelExportService, ToolbarItems, PivotChartService, DisplayOption } from '@syncfusion/ej2-angular-pivotview';
import { ChartSettingsModel } from '@syncfusion/ej2-pivotview/src/pivotview/model/chartsettings-model'; import { ChartSettingsModel } from '@syncfusion/ej2-pivotview/src/pivotview/model/chartsettings-model';
...@@ -21,6 +21,74 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -21,6 +21,74 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
public widgetId: string; public widgetId: string;
private isPerspectiveApplied = false; private isPerspectiveApplied = false;
// Enhanced UI properties
public dataCount: number = 0;
public lastUpdated: Date = new Date();
public shadow: string = 'medium';
public override isLoading: boolean = false;
public override hasError: boolean = false;
public override errorMessage: string = '';
public isEmpty: boolean = false;
// Enhanced interaction properties
public enableKeyboard: boolean = true;
public enableFocus: boolean = true;
public hoverEffect: boolean = true;
public clickEffect: boolean = true;
public enableTooltip: boolean = true;
public enableSelection: boolean = true;
public enableExport: boolean = true;
public enableRefresh: boolean = true;
public clickAction: string = 'none';
public customClickHandler: string = '';
// Enhanced layout properties
public fullWidth: boolean = false;
public fullHeight: boolean = false;
public widthUnit: string = 'px';
public heightUnit: string = 'px';
public sizeOption: string = 'medium';
public aspectRatio: string = 'auto';
// Enhanced data properties
public enableFiltering: boolean = true;
public enableSorting: boolean = true;
public enableGrouping: boolean = true;
public enablePaging: boolean = true;
public pageSize: number = 10;
public currentPage: number = 1;
public totalPages: number = 1;
// Enhanced security properties
public requireHttps: boolean = false;
public allowedRoles: string = '';
public dataEncryption: boolean = false;
public auditLog: boolean = false;
public rateLimit: number = 0;
// Enhanced style properties
public customCSS: string = '';
public theme: string = 'light';
public fontSize: number = 14;
public fontWeight: string = 'normal';
public fontFamily: string = 'system-ui, -apple-system, sans-serif';
public padding: number = 16;
public margin: number = 8;
// Animation properties
public enableAnimations: boolean = true;
public animationType: string = 'fade';
public animationDuration: number = 300;
public animationDelay: number = 0;
// Original data storage
public override originalData: any[] = [];
public dataSourceSettings: { public dataSourceSettings: {
dataSource: DataManager; dataSource: DataManager;
expandAll: boolean; expandAll: boolean;
...@@ -50,29 +118,11 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -50,29 +118,11 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
public accentColor: string = '#3B82F6'; public accentColor: string = '#3B82F6';
public borderRadius: number = 8; public borderRadius: number = 8;
public borderWidth: number = 1; public borderWidth: number = 1;
public padding: number = 16;
public margin: number = 8;
public fontSize: number = 14;
public fontWeight: string = 'normal';
public fontFamily: string = 'system-ui, -apple-system, sans-serif';
public customCSS: string = '';
// Animation properties
public enableAnimations: boolean = true;
public animationType: string = 'fade';
public animationDuration: number = 300;
public animationDelay: number = 0;
public hoverEffects: boolean = true; public hoverEffects: boolean = true;
// Interaction properties // Interaction properties
public enableTooltip: boolean = true;
public enableClick: boolean = true; public enableClick: boolean = true;
public enableHover: boolean = true; public enableHover: boolean = true;
public enableSelection: boolean = true;
public enableExport: boolean = true;
public enableRefresh: boolean = true;
public clickAction: string = 'none';
public customClickHandler: string = '';
// Layout properties // Layout properties
public width: number = 800; public width: number = 800;
...@@ -81,7 +131,6 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -81,7 +131,6 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
public minHeight: number = 400; public minHeight: number = 400;
public maxWidth: number = 1400; public maxWidth: number = 1400;
public maxHeight: number = 1000; public maxHeight: number = 1000;
public aspectRatio: string = 'auto';
public responsive: boolean = true; public responsive: boolean = true;
// Data properties // Data properties
...@@ -94,10 +143,6 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -94,10 +143,6 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
// Security properties // Security properties
public requireAuth: boolean = false; public requireAuth: boolean = false;
public allowedRoles: string = '';
public dataEncryption: boolean = false;
public auditLog: boolean = false;
public rateLimit: number = 0;
// Pivot-specific properties from SyncfusionPivotConfigComponent // Pivot-specific properties from SyncfusionPivotConfigComponent
public displayOptionView: string = 'Both'; public displayOptionView: string = 'Both';
...@@ -126,12 +171,12 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -126,12 +171,12 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
// Pivot-specific properties (moved to above section) // Pivot-specific properties (moved to above section)
public showChart: boolean = true; public showChart: boolean = true;
public showGrid: boolean = true; public showGrid: boolean = true;
public enableGrouping: boolean = true;
public allowFiltering: boolean = true; public allowFiltering: boolean = true;
constructor( constructor(
protected override dashboardStateService: DashboardStateService, protected override dashboardStateService: DashboardStateService,
private widgetStateService: WidgetStateService private widgetStateService: WidgetStateService,
private cdr: ChangeDetectorRef
) { ) {
super(dashboardStateService); super(dashboardStateService);
} }
...@@ -175,6 +220,66 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -175,6 +220,66 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
chartSettings: this.chartSettings, chartSettings: this.chartSettings,
}; };
// Enhanced UI configuration
this.dataCount = this.configObj.dataCount || 0;
this.lastUpdated = this.configObj.lastUpdated || new Date();
this.shadow = this.configObj.shadow || 'medium';
this.isLoading = this.configObj.isLoading || false;
this.hasError = this.configObj.hasError || false;
this.errorMessage = this.configObj.errorMessage || '';
this.isEmpty = this.configObj.isEmpty || false;
// Enhanced interaction configuration
this.enableKeyboard = this.configObj.enableKeyboard !== undefined ? this.configObj.enableKeyboard : true;
this.enableFocus = this.configObj.enableFocus !== undefined ? this.configObj.enableFocus : true;
this.hoverEffect = this.configObj.hoverEffect !== undefined ? this.configObj.hoverEffect : true;
this.clickEffect = this.configObj.clickEffect !== undefined ? this.configObj.clickEffect : true;
this.enableTooltip = this.configObj.enableTooltip !== undefined ? this.configObj.enableTooltip : true;
this.enableSelection = this.configObj.enableSelection !== undefined ? this.configObj.enableSelection : true;
this.enableExport = this.configObj.enableExport !== undefined ? this.configObj.enableExport : true;
this.enableRefresh = this.configObj.enableRefresh !== undefined ? this.configObj.enableRefresh : true;
this.clickAction = this.configObj.clickAction || 'none';
this.customClickHandler = this.configObj.customClickHandler || '';
// Enhanced layout configuration
this.fullWidth = this.configObj.fullWidth !== undefined ? this.configObj.fullWidth : false;
this.fullHeight = this.configObj.fullHeight !== undefined ? this.configObj.fullHeight : false;
this.widthUnit = this.configObj.widthUnit || 'px';
this.heightUnit = this.configObj.heightUnit || 'px';
this.sizeOption = this.configObj.sizeOption || 'medium';
this.aspectRatio = this.configObj.aspectRatio || 'auto';
// Enhanced data configuration
this.enableFiltering = this.configObj.enableFiltering !== undefined ? this.configObj.enableFiltering : true;
this.enableSorting = this.configObj.enableSorting !== undefined ? this.configObj.enableSorting : true;
this.enableGrouping = this.configObj.enableGrouping !== undefined ? this.configObj.enableGrouping : true;
this.enablePaging = this.configObj.enablePaging !== undefined ? this.configObj.enablePaging : true;
this.pageSize = this.configObj.pageSize || 10;
this.currentPage = this.configObj.currentPage || 1;
this.totalPages = this.configObj.totalPages || 1;
// Enhanced security configuration
this.requireHttps = this.configObj.requireHttps !== undefined ? this.configObj.requireHttps : false;
this.allowedRoles = this.configObj.allowedRoles || '';
this.dataEncryption = this.configObj.dataEncryption !== undefined ? this.configObj.dataEncryption : false;
this.auditLog = this.configObj.auditLog !== undefined ? this.configObj.auditLog : false;
this.rateLimit = this.configObj.rateLimit || 0;
// Enhanced animation configuration
this.enableAnimations = this.configObj.enableAnimations !== undefined ? this.configObj.enableAnimations : true;
this.animationType = this.configObj.animationType || 'fade';
this.animationDuration = this.configObj.animationDuration || 300;
this.animationDelay = this.configObj.animationDelay || 0;
// Enhanced style configuration
this.customCSS = this.configObj.customCSS || '';
this.theme = this.configObj.theme || 'light';
this.fontSize = this.configObj.fontSize || 14;
this.fontWeight = this.configObj.fontWeight || 'normal';
this.fontFamily = this.configObj.fontFamily || 'system-ui, -apple-system, sans-serif';
this.padding = this.configObj.padding || 16;
this.margin = this.configObj.margin || 8;
// Pivot features configuration // Pivot features configuration
this.showFieldList = this.configObj.showFieldList !== undefined ? this.configObj.showFieldList : true; this.showFieldList = this.configObj.showFieldList !== undefined ? this.configObj.showFieldList : true;
this.showToolbar = this.configObj.showToolbar !== undefined ? this.configObj.showToolbar : true; this.showToolbar = this.configObj.showToolbar !== undefined ? this.configObj.showToolbar : true;
...@@ -319,15 +424,47 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -319,15 +424,47 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
} }
onDataUpdate(data: IDataSet[]): void { onDataUpdate(data: IDataSet[]): void {
const transformedData = this.transformData(data); try {
// Store original data
this.originalData = data || [];
this.dataCount = this.originalData.length;
this.lastUpdated = new Date();
this.isEmpty = this.originalData.length === 0;
// Transform data if transform function is provided
let transformedData = this.transformData(data);
// Apply filtering if enabled
if (this.enableFiltering) {
transformedData = this.applyDataFilter(transformedData);
}
// Apply conditional formatting if enabled
if (this.allowConditionalFormatting) {
transformedData = this.applyConditionalFormatting(transformedData);
}
this.dataSourceSettings = { this.dataSourceSettings = {
...this.dataSourceSettings, ...this.dataSourceSettings,
dataSource: new DataManager(transformedData) dataSource: new DataManager(transformedData)
}; };
if (this.pivotview) { if (this.pivotview) {
this.pivotview.dataSourceSettings.dataSource = new DataManager(transformedData); this.pivotview.dataSourceSettings.dataSource = new DataManager(transformedData);
// The refresh is implicitly handled by the dataBound event now // The refresh is implicitly handled by the dataBound event now
} }
// Update loading and error states
this.isLoading = false;
this.hasError = false;
this.cdr.detectChanges();
} catch (error) {
console.error('Error updating pivot data:', error);
this.hasError = true;
this.errorMessage = 'Failed to update data';
this.isLoading = false;
this.cdr.detectChanges();
}
} }
onDataBound(args: any): void { onDataBound(args: any): void {
...@@ -684,6 +821,41 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -684,6 +821,41 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
} }
} }
// Method to apply data filtering
private applyDataFilter(data: any[]): any[] {
if (!this.enableFiltering || !data) {
return data;
}
try {
// Apply basic filtering logic here
// This can be extended based on specific filtering requirements
return data;
} catch (error) {
console.error('Error applying data filter:', error);
return data;
}
}
// Method to apply conditional formatting
private applyConditionalFormatting(data: any[]): any[] {
if (!this.allowConditionalFormatting || !data) {
return data;
}
try {
// Apply conditional formatting logic here
// This can be extended based on specific formatting requirements
return data;
} catch (error) {
console.error('Error applying conditional formatting:', error);
return data;
}
}
// Export data functionality // Export data functionality
exportData(event: Event): void { exportData(event: Event): void {
if (event) { if (event) {
...@@ -740,4 +912,20 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen ...@@ -740,4 +912,20 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
this.isLoading = false; this.isLoading = false;
}, 1000); }, 1000);
} }
// Toggle filter functionality
toggleFilter(event: Event): void {
if (event) {
event.stopPropagation();
}
this.enableFiltering = !this.enableFiltering;
// Apply or remove filtering
if (this.originalData && this.originalData.length > 0) {
this.onDataUpdate(this.originalData);
}
console.log('Filter toggled:', this.enableFiltering);
}
} }
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