Commit cf761296 by Ooh-Ao

widgets

parent b0dd4799
...@@ -11,6 +11,87 @@ import { PayrollConfigComponent } from '../../widgets/configs/payroll-config/pay ...@@ -11,6 +11,87 @@ import { PayrollConfigComponent } from '../../widgets/configs/payroll-config/pay
import { BaseConfigComponent } from '../base-config/base-config.component'; import { BaseConfigComponent } from '../base-config/base-config.component';
/**
* Widget configuration metadata interface
*/
export interface WidgetMetadata {
name: string;
displayName: string;
description: string;
category: WidgetCategory;
icon: string;
version: string;
author: string;
tags: string[];
isDeprecated: boolean;
replacement?: string;
minWidth?: number;
minHeight?: number;
maxWidth?: number;
maxHeight?: number;
defaultWidth?: number;
defaultHeight?: number;
supportedDataSources: DataSourceType[];
features: WidgetFeature[];
complexity: 'simple' | 'intermediate' | 'advanced';
}
/**
* Widget categories for better organization
*/
export enum WidgetCategory {
CHARTS = 'charts',
TABLES = 'tables',
CARDS = 'cards',
KPI = 'kpi',
LAYOUT = 'layout',
UTILITY = 'utility',
BUSINESS = 'business',
DATA_GRID = 'data-grid',
PIVOT = 'pivot',
FORMS = 'forms',
MEDIA = 'media',
NAVIGATION = 'navigation'
}
/**
* Data source types supported by widgets
*/
export enum DataSourceType {
STATIC = 'static',
API = 'api',
DATASET = 'dataset',
WEBSOCKET = 'websocket',
FILE = 'file',
ODATA = 'odata',
WEBAPI = 'webapi'
}
/**
* Widget features for capability discovery
*/
export enum WidgetFeature {
EXPORT = 'export',
FILTER = 'filter',
SORT = 'sort',
PAGINATION = 'pagination',
SEARCH = 'search',
GROUPING = 'grouping',
EDITING = 'editing',
SELECTION = 'selection',
REFRESH = 'refresh',
ZOOM = 'zoom',
DRILL_DOWN = 'drill_down',
CUSTOMIZATION = 'customization',
RESPONSIVE = 'responsive',
ANIMATION = 'animation',
TOOLTIP = 'tooltip',
PRINT = 'print'
}
/**
* Widget configuration component interface
*/
export interface WidgetConfigComponent extends BaseConfigComponent { export interface WidgetConfigComponent extends BaseConfigComponent {
// All config components extend BaseConfigComponent which already has: // All config components extend BaseConfigComponent which already has:
// currentConfig: any; // currentConfig: any;
...@@ -23,288 +104,1306 @@ export interface WidgetConfigComponent extends BaseConfigComponent { ...@@ -23,288 +104,1306 @@ export interface WidgetConfigComponent extends BaseConfigComponent {
}) })
export class WidgetConfigRegistryService { export class WidgetConfigRegistryService {
private configComponents: Map<string, Type<WidgetConfigComponent>> = new Map(); private configComponents: Map<string, Type<WidgetConfigComponent>> = new Map();
private widgetMetadata: Map<string, WidgetMetadata> = new Map();
private categoryIndex: Map<WidgetCategory, string[]> = new Map();
constructor() { constructor() {
this.initializeCategories();
this.registerDefaultConfigs(); this.registerDefaultConfigs();
} }
/**
* Initialize widget categories
*/
private initializeCategories(): void {
Object.values(WidgetCategory).forEach(category => {
this.categoryIndex.set(category, []);
});
}
/**
* Register all default widget configurations with comprehensive metadata
*/
private registerDefaultConfigs(): void { private registerDefaultConfigs(): void {
// Register existing config components // ========================================
this.registerConfig('SimpleKpiWidgetComponent', SimpleKpiConfigComponent); // KPI WIDGETS
this.registerConfig('SyncfusionDatagridWidgetComponent', SyncfusionDatagridConfigComponent); // ========================================
this.registerWidgetWithMetadata('SimpleKpiWidgetComponent', SimpleKpiConfigComponent, {
// Register new config components name: 'SimpleKpiWidgetComponent',
this.registerConfig('SyncfusionPivotWidgetComponent', SyncfusionPivotConfigComponent); displayName: 'Simple KPI Widget',
this.registerConfig('ChartWidgetComponent', ChartConfigComponent); description: 'A simple key performance indicator widget for displaying single metrics',
// this.registerConfig('CompanyInfoWidgetComponent', CompanyInfoConfigComponent); category: WidgetCategory.KPI,
// this.registerConfig('CompanyInfoSubfolderWidgetComponent', CompanyInfoConfigComponent); icon: 'trending-up',
this.registerConfig('AttendanceOverviewWidgetComponent', AttendanceConfigComponent); version: '1.0.0',
this.registerConfig('PayrollSummaryWidgetComponent', PayrollConfigComponent); author: 'Portal Team',
this.registerConfig('PayrollWidgetComponent', PayrollConfigComponent); tags: ['kpi', 'metric', 'simple', 'dashboard'],
isDeprecated: false,
// Register chart widgets with ChartConfigComponent minWidth: 200,
this.registerConfig('SyncfusionChartWidgetComponent', ChartConfigComponent); minHeight: 150,
this.registerConfig('PieChartWidgetComponent', ChartConfigComponent); maxWidth: 600,
this.registerConfig('BarChartWidgetComponent', ChartConfigComponent); maxHeight: 400,
this.registerConfig('AreaChartWidgetComponent', ChartConfigComponent); defaultWidth: 300,
this.registerConfig('DoughnutChartWidgetComponent', ChartConfigComponent); defaultHeight: 200,
this.registerConfig('FunnelChartWidgetComponent', ChartConfigComponent); supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
this.registerConfig('ScatterBubbleChartWidgetComponent', ChartConfigComponent); features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.TOOLTIP],
this.registerConfig('GaugeChartWidgetComponent', ChartConfigComponent); complexity: 'simple'
this.registerConfig('TreemapWidgetComponent', ChartConfigComponent); });
this.registerConfig('WaterfallChartWidgetComponent', ChartConfigComponent);
this.registerConfig('ComboChartWidgetComponent', ChartConfigComponent); this.registerWidgetWithMetadata('KpiWidgetComponent', SimpleKpiConfigComponent, {
name: 'KpiWidgetComponent',
// Register table widgets with TableConfigComponent displayName: 'KPI Widget',
this.registerConfig('DataTableWidgetComponent', TableConfigComponent); description: 'Advanced KPI widget with multiple metrics and visualizations',
this.registerConfig('SimpleTableWidgetComponent', TableConfigComponent); category: WidgetCategory.KPI,
icon: 'bar-chart-2',
// Register card widgets with CardConfigComponent version: '1.0.0',
this.registerConfig('MultiRowCardWidgetComponent', CardConfigComponent); author: 'Portal Team',
this.registerConfig('NotificationWidgetComponent', CardConfigComponent); tags: ['kpi', 'metrics', 'dashboard', 'analytics'],
this.registerConfig('WelcomeWidgetComponent', CardConfigComponent); isDeprecated: false,
this.registerConfig('QuickLinksWidgetComponent', CardConfigComponent); minWidth: 250,
this.registerConfig('CompanyInfoWidgetComponent', CardConfigComponent); minHeight: 180,
this.registerConfig('CompanyInfoSubfolderWidgetComponent', CardConfigComponent); maxWidth: 800,
maxHeight: 500,
// Register KPI widgets with SimpleKpiConfigComponent defaultWidth: 350,
this.registerConfig('KpiWidgetComponent', SimpleKpiConfigComponent); defaultHeight: 250,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
// Register utility widgets with appropriate config components features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.TOOLTIP, WidgetFeature.EXPORT],
this.registerConfig('ClockWidgetComponent', SimpleKpiConfigComponent); complexity: 'intermediate'
this.registerConfig('WeatherWidgetComponent', SimpleKpiConfigComponent); });
this.registerConfig('CalendarWidgetComponent', SimpleKpiConfigComponent);
this.registerConfig('MatrixWidgetComponent', TableConfigComponent); // ========================================
this.registerConfig('SlicerWidgetComponent', SimpleKpiConfigComponent); // DATA GRID WIDGETS
this.registerConfig('FilledMapWidgetComponent', SimpleKpiConfigComponent); // ========================================
this.registerConfig('EmployeeDirectoryWidgetComponent', CardConfigComponent); this.registerWidgetWithMetadata('SyncfusionDatagridWidgetComponent', SyncfusionDatagridConfigComponent, {
this.registerConfig('HeadcountWidgetComponent', SimpleKpiConfigComponent); name: 'SyncfusionDatagridWidgetComponent',
displayName: 'Data Grid Widget',
description: 'Advanced data grid with sorting, filtering, paging, and export capabilities',
category: WidgetCategory.DATA_GRID,
icon: 'grid',
version: '1.0.0',
author: 'Portal Team',
tags: ['datagrid', 'table', 'data', 'syncfusion', 'advanced'],
isDeprecated: false,
minWidth: 400,
minHeight: 300,
maxWidth: 1200,
maxHeight: 800,
defaultWidth: 600,
defaultHeight: 400,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET, DataSourceType.ODATA, DataSourceType.WEBAPI],
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
],
complexity: 'advanced'
});
// Continue with other widget registrations...
this.registerRemainingWidgets();
}
/**
* Register remaining widgets with their configurations
*/
private registerRemainingWidgets(): void {
// ========================================
// PIVOT WIDGETS
// ========================================
this.registerWidgetWithMetadata('SyncfusionPivotWidgetComponent', SyncfusionPivotConfigComponent, {
name: 'SyncfusionPivotWidgetComponent',
displayName: 'Pivot Table Widget',
description: 'Interactive pivot table for data analysis and cross-tabulation',
category: WidgetCategory.PIVOT,
icon: 'layers',
version: '1.0.0',
author: 'Portal Team',
tags: ['pivot', 'analysis', 'cross-tab', 'syncfusion'],
isDeprecated: false,
minWidth: 500,
minHeight: 400,
maxWidth: 1200,
maxHeight: 800,
defaultWidth: 700,
defaultHeight: 500,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET, DataSourceType.ODATA],
features: [
WidgetFeature.EXPORT, WidgetFeature.FILTER, WidgetFeature.SORT, WidgetFeature.GROUPING,
WidgetFeature.REFRESH, WidgetFeature.CUSTOMIZATION, WidgetFeature.RESPONSIVE, WidgetFeature.DRILL_DOWN
],
complexity: 'advanced'
});
// ========================================
// CHART WIDGETS
// ========================================
const chartWidgets = [
{ name: 'SyncfusionChartWidgetComponent', displayName: 'Chart Widget', description: 'General purpose chart widget' },
{ name: 'PieChartWidgetComponent', displayName: 'Pie Chart', description: 'Circular chart for showing proportions' },
{ name: 'BarChartWidgetComponent', displayName: 'Bar Chart', description: 'Vertical bar chart for comparisons' },
{ name: 'AreaChartWidgetComponent', displayName: 'Area Chart', description: 'Filled area chart for trends' },
{ name: 'DoughnutChartWidgetComponent', displayName: 'Doughnut Chart', description: 'Ring chart with center space' },
{ name: 'FunnelChartWidgetComponent', displayName: 'Funnel Chart', description: 'Funnel visualization for processes' },
{ name: 'ScatterBubbleChartWidgetComponent', displayName: 'Scatter/Bubble Chart', description: 'Scatter plot with optional bubble sizing' },
{ name: 'GaugeChartWidgetComponent', displayName: 'Gauge Chart', description: 'Radial gauge for single values' },
{ name: 'TreemapWidgetComponent', displayName: 'Treemap', description: 'Hierarchical data visualization' },
{ name: 'WaterfallChartWidgetComponent', displayName: 'Waterfall Chart', description: 'Cumulative effect visualization' },
{ name: 'ComboChartWidgetComponent', displayName: 'Combo Chart', description: 'Combined chart types' }
];
chartWidgets.forEach(widget => {
this.registerWidgetWithMetadata(widget.name, ChartConfigComponent, {
name: widget.name,
displayName: widget.displayName,
description: widget.description,
category: WidgetCategory.CHARTS,
icon: 'bar-chart',
version: '1.0.0',
author: 'Portal Team',
tags: ['chart', 'visualization', 'data', 'graph'],
isDeprecated: false,
minWidth: 300,
minHeight: 200,
maxWidth: 1000,
maxHeight: 600,
defaultWidth: 400,
defaultHeight: 300,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [
WidgetFeature.EXPORT, WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH,
WidgetFeature.ZOOM, WidgetFeature.TOOLTIP, WidgetFeature.RESPONSIVE, WidgetFeature.ANIMATION
],
complexity: 'intermediate'
});
});
// ========================================
// TABLE WIDGETS
// ========================================
this.registerWidgetWithMetadata('DataTableWidgetComponent', TableConfigComponent, {
name: 'DataTableWidgetComponent',
displayName: 'Data Table',
description: 'Simple data table with basic functionality',
category: WidgetCategory.TABLES,
icon: 'table',
version: '1.0.0',
author: 'Portal Team',
tags: ['table', 'data', 'simple'],
isDeprecated: false,
minWidth: 300,
minHeight: 200,
maxWidth: 800,
maxHeight: 600,
defaultWidth: 500,
defaultHeight: 350,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.SORT, WidgetFeature.FILTER, WidgetFeature.EXPORT, WidgetFeature.REFRESH],
complexity: 'simple'
});
this.registerWidgetWithMetadata('SimpleTableWidgetComponent', TableConfigComponent, {
name: 'SimpleTableWidgetComponent',
displayName: 'Simple Table',
description: 'Basic table widget for simple data display',
category: WidgetCategory.TABLES,
icon: 'list',
version: '1.0.0',
author: 'Portal Team',
tags: ['table', 'simple', 'basic'],
isDeprecated: false,
minWidth: 250,
minHeight: 150,
maxWidth: 600,
maxHeight: 400,
defaultWidth: 350,
defaultHeight: 250,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH],
complexity: 'simple'
});
this.registerWidgetWithMetadata('MatrixWidgetComponent', TableConfigComponent, {
name: 'MatrixWidgetComponent',
displayName: 'Matrix Table',
description: 'Matrix-style table for cross-tabulated data',
category: WidgetCategory.TABLES,
icon: 'grid-3x3',
version: '1.0.0',
author: 'Portal Team',
tags: ['matrix', 'table', 'cross-tab'],
isDeprecated: false,
minWidth: 400,
minHeight: 300,
maxWidth: 1000,
maxHeight: 700,
defaultWidth: 600,
defaultHeight: 450,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.SORT, WidgetFeature.FILTER, WidgetFeature.EXPORT, WidgetFeature.CUSTOMIZATION],
complexity: 'intermediate'
});
// ========================================
// CARD WIDGETS
// ========================================
const cardWidgets = [
{ name: 'MultiRowCardWidgetComponent', displayName: 'Multi Row Card', description: 'Card widget with multiple data rows' },
{ name: 'NotificationWidgetComponent', displayName: 'Notification Widget', description: 'Widget for displaying notifications and alerts' },
{ name: 'WelcomeWidgetComponent', displayName: 'Welcome Widget', description: 'Welcome message and greeting widget' },
{ name: 'QuickLinksWidgetComponent', displayName: 'Quick Links', description: 'Widget with quick navigation links' },
{ name: 'CompanyInfoWidgetComponent', displayName: 'Company Information', description: 'Company details and information display' },
{ name: 'CompanyInfoSubfolderWidgetComponent', displayName: 'Company Info Subfolder', description: 'Company information with subfolder structure' },
{ name: 'EmployeeDirectoryWidgetComponent', displayName: 'Employee Directory', description: 'Employee listing and directory widget' }
];
cardWidgets.forEach(widget => {
this.registerWidgetWithMetadata(widget.name, CardConfigComponent, {
name: widget.name,
displayName: widget.displayName,
description: widget.description,
category: WidgetCategory.CARDS,
icon: 'credit-card',
version: '1.0.0',
author: 'Portal Team',
tags: ['card', 'display', 'information'],
isDeprecated: false,
minWidth: 200,
minHeight: 150,
maxWidth: 500,
maxHeight: 400,
defaultWidth: 300,
defaultHeight: 200,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.RESPONSIVE],
complexity: 'simple'
});
});
// ========================================
// BUSINESS WIDGETS
// ========================================
this.registerWidgetWithMetadata('AttendanceOverviewWidgetComponent', AttendanceConfigComponent, {
name: 'AttendanceOverviewWidgetComponent',
displayName: 'Attendance Overview',
description: 'Widget for displaying employee attendance statistics',
category: WidgetCategory.BUSINESS,
icon: 'users',
version: '1.0.0',
author: 'Portal Team',
tags: ['attendance', 'hr', 'business', 'employees'],
isDeprecated: false,
minWidth: 300,
minHeight: 200,
maxWidth: 600,
maxHeight: 400,
defaultWidth: 400,
defaultHeight: 300,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.EXPORT],
complexity: 'intermediate'
});
this.registerWidgetWithMetadata('PayrollSummaryWidgetComponent', PayrollConfigComponent, {
name: 'PayrollSummaryWidgetComponent',
displayName: 'Payroll Summary',
description: 'Summary widget for payroll information and statistics',
category: WidgetCategory.BUSINESS,
icon: 'dollar-sign',
version: '1.0.0',
author: 'Portal Team',
tags: ['payroll', 'hr', 'business', 'finance'],
isDeprecated: false,
minWidth: 300,
minHeight: 200,
maxWidth: 600,
maxHeight: 400,
defaultWidth: 400,
defaultHeight: 300,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.EXPORT],
complexity: 'intermediate'
});
this.registerWidgetWithMetadata('PayrollWidgetComponent', PayrollConfigComponent, {
name: 'PayrollWidgetComponent',
displayName: 'Payroll Widget',
description: 'Detailed payroll widget with comprehensive information',
category: WidgetCategory.BUSINESS,
icon: 'file-text',
version: '1.0.0',
author: 'Portal Team',
tags: ['payroll', 'hr', 'business', 'detailed'],
isDeprecated: false,
minWidth: 400,
minHeight: 300,
maxWidth: 800,
maxHeight: 600,
defaultWidth: 500,
defaultHeight: 400,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.EXPORT, WidgetFeature.FILTER],
complexity: 'advanced'
});
this.registerWidgetWithMetadata('HeadcountWidgetComponent', SimpleKpiConfigComponent, {
name: 'HeadcountWidgetComponent',
displayName: 'Headcount Widget',
description: 'Widget for displaying employee headcount statistics',
category: WidgetCategory.BUSINESS,
icon: 'user-check',
version: '1.0.0',
author: 'Portal Team',
tags: ['headcount', 'hr', 'business', 'employees'],
isDeprecated: false,
minWidth: 250,
minHeight: 150,
maxWidth: 500,
maxHeight: 350,
defaultWidth: 350,
defaultHeight: 200,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API, DataSourceType.DATASET],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.TOOLTIP],
complexity: 'simple'
});
// ========================================
// UTILITY WIDGETS
// ========================================
const utilityWidgets = [
{ name: 'ClockWidgetComponent', displayName: 'Clock Widget', description: 'Real-time clock and date display' },
{ name: 'WeatherWidgetComponent', displayName: 'Weather Widget', description: 'Current weather information display' },
{ name: 'CalendarWidgetComponent', displayName: 'Calendar Widget', description: 'Calendar and event display widget' },
{ name: 'SlicerWidgetComponent', displayName: 'Slicer Widget', description: 'Data filtering and slicing widget' },
{ name: 'FilledMapWidgetComponent', displayName: 'Map Widget', description: 'Geographic data visualization widget' }
];
utilityWidgets.forEach(widget => {
this.registerWidgetWithMetadata(widget.name, SimpleKpiConfigComponent, {
name: widget.name,
displayName: widget.displayName,
description: widget.description,
category: WidgetCategory.UTILITY,
icon: 'tool',
version: '1.0.0',
author: 'Portal Team',
tags: ['utility', 'tool', 'helper'],
isDeprecated: false,
minWidth: 200,
minHeight: 150,
maxWidth: 400,
maxHeight: 300,
defaultWidth: 300,
defaultHeight: 200,
supportedDataSources: [DataSourceType.STATIC, DataSourceType.API],
features: [WidgetFeature.CUSTOMIZATION, WidgetFeature.REFRESH, WidgetFeature.RESPONSIVE],
complexity: 'simple'
});
});
} }
/**
* Register a widget with comprehensive metadata
*/
registerWidgetWithMetadata(widgetType: string, configComponent: Type<WidgetConfigComponent>, metadata: WidgetMetadata): void {
this.configComponents.set(widgetType, configComponent);
this.widgetMetadata.set(widgetType, metadata);
// Add to category index
const categoryWidgets = this.categoryIndex.get(metadata.category) || [];
if (!categoryWidgets.includes(widgetType)) {
categoryWidgets.push(widgetType);
this.categoryIndex.set(metadata.category, categoryWidgets);
}
}
/**
* Legacy method for backward compatibility
*/
registerConfig(widgetType: string, configComponent: Type<WidgetConfigComponent>): void { registerConfig(widgetType: string, configComponent: Type<WidgetConfigComponent>): void {
this.configComponents.set(widgetType, configComponent); this.configComponents.set(widgetType, configComponent);
} }
/**
* Get configuration component for a widget type
*/
getConfigComponent(widgetType: string): Type<WidgetConfigComponent> | null { getConfigComponent(widgetType: string): Type<WidgetConfigComponent> | null {
return this.configComponents.get(widgetType) || null; return this.configComponents.get(widgetType) || null;
} }
/**
* Check if a widget type has a configuration component
*/
hasConfigComponent(widgetType: string): boolean { hasConfigComponent(widgetType: string): boolean {
return this.configComponents.has(widgetType); return this.configComponents.has(widgetType);
} }
/**
* Get all registered widget types
*/
getAllRegisteredWidgets(): string[] { getAllRegisteredWidgets(): string[] {
return Array.from(this.configComponents.keys()); return Array.from(this.configComponents.keys());
} }
// Helper method to get fallback config for widgets without specific config components /**
* Get widget metadata
*/
getWidgetMetadata(widgetType: string): WidgetMetadata | null {
return this.widgetMetadata.get(widgetType) || null;
}
/**
* Get all widget metadata
*/
getAllWidgetMetadata(): Map<string, WidgetMetadata> {
return new Map(this.widgetMetadata);
}
/**
* Get widgets by category
*/
getWidgetsByCategory(category: WidgetCategory): string[] {
return this.categoryIndex.get(category) || [];
}
/**
* Get all categories with their widget counts
*/
getCategoriesWithCounts(): Map<WidgetCategory, number> {
const categories = new Map<WidgetCategory, number>();
this.categoryIndex.forEach((widgets, category) => {
categories.set(category, widgets.length);
});
return categories;
}
/**
* Search widgets by name, description, or tags
*/
searchWidgets(query: string): string[] {
const searchTerm = query.toLowerCase();
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
const searchableText = [
metadata.name,
metadata.displayName,
metadata.description,
...metadata.tags
].join(' ').toLowerCase();
if (searchableText.includes(searchTerm)) {
results.push(widgetType);
}
});
return results;
}
/**
* Get widgets by complexity level
*/
getWidgetsByComplexity(complexity: 'simple' | 'intermediate' | 'advanced'): string[] {
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
if (metadata.complexity === complexity) {
results.push(widgetType);
}
});
return results;
}
/**
* Get widgets that support a specific data source
*/
getWidgetsByDataSource(dataSource: DataSourceType): string[] {
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
if (metadata.supportedDataSources.includes(dataSource)) {
results.push(widgetType);
}
});
return results;
}
/**
* Get widgets that have a specific feature
*/
getWidgetsByFeature(feature: WidgetFeature): string[] {
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
if (metadata.features.includes(feature)) {
results.push(widgetType);
}
});
return results;
}
/**
* Get deprecated widgets
*/
getDeprecatedWidgets(): string[] {
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
if (metadata.isDeprecated) {
results.push(widgetType);
}
});
return results;
}
/**
* Get widgets within size constraints
*/
getWidgetsBySize(minWidth: number, minHeight: number, maxWidth?: number, maxHeight?: number): string[] {
const results: string[] = [];
this.widgetMetadata.forEach((metadata, widgetType) => {
const fitsWidth = (metadata.minWidth || 0) <= minWidth && (!maxWidth || !metadata.maxWidth || metadata.maxWidth >= maxWidth);
const fitsHeight = (metadata.minHeight || 0) <= minHeight && (!maxHeight || !metadata.maxHeight || metadata.maxHeight >= maxHeight);
if (fitsWidth && fitsHeight) {
results.push(widgetType);
}
});
return results;
}
/**
* Validate widget configuration
*/
validateWidgetConfig(widgetType: string, config: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
const metadata = this.getWidgetMetadata(widgetType);
if (!metadata) {
return { isValid: false, errors: ['Widget type not found'] };
}
// Check required fields
if (!config.title) {
errors.push('Title is required');
}
// Check size constraints
if (config.width && metadata.minWidth && config.width < metadata.minWidth) {
errors.push(`Width must be at least ${metadata.minWidth}px`);
}
if (config.width && metadata.maxWidth && config.width > metadata.maxWidth) {
errors.push(`Width must be at most ${metadata.maxWidth}px`);
}
if (config.height && metadata.minHeight && config.height < metadata.minHeight) {
errors.push(`Height must be at least ${metadata.minHeight}px`);
}
if (config.height && metadata.maxHeight && config.height > metadata.maxHeight) {
errors.push(`Height must be at most ${metadata.maxHeight}px`);
}
return { isValid: errors.length === 0, errors };
}
/**
* Get recommended widgets based on criteria
*/
getRecommendedWidgets(criteria: {
category?: WidgetCategory;
complexity?: 'simple' | 'intermediate' | 'advanced';
dataSource?: DataSourceType;
features?: WidgetFeature[];
size?: { width: number; height: number };
}): string[] {
let candidates = Array.from(this.widgetMetadata.keys());
// Filter by category
if (criteria.category) {
candidates = candidates.filter(widgetType => {
const metadata = this.widgetMetadata.get(widgetType);
return metadata?.category === criteria.category;
});
}
// Filter by complexity
if (criteria.complexity) {
candidates = candidates.filter(widgetType => {
const metadata = this.widgetMetadata.get(widgetType);
return metadata?.complexity === criteria.complexity;
});
}
// Filter by data source
if (criteria.dataSource) {
candidates = candidates.filter(widgetType => {
const metadata = this.widgetMetadata.get(widgetType);
return metadata?.supportedDataSources.includes(criteria.dataSource!);
});
}
// Filter by features
if (criteria.features && criteria.features.length > 0) {
candidates = candidates.filter(widgetType => {
const metadata = this.widgetMetadata.get(widgetType);
return criteria.features!.every(feature => metadata?.features.includes(feature));
});
}
// Filter by size
if (criteria.size) {
candidates = candidates.filter(widgetType => {
const metadata = this.widgetMetadata.get(widgetType);
if (!metadata) return false;
const fitsWidth = (metadata.minWidth || 0) <= criteria.size!.width &&
(!metadata.maxWidth || metadata.maxWidth >= criteria.size!.width);
const fitsHeight = (metadata.minHeight || 0) <= criteria.size!.height &&
(!metadata.maxHeight || metadata.maxHeight >= criteria.size!.height);
return fitsWidth && fitsHeight;
});
}
return candidates;
}
/**
* Get comprehensive fallback configuration for widgets
*/
getFallbackConfig(widgetType: string): any { getFallbackConfig(widgetType: string): any {
const fallbackConfigs: { [key: string]: any } = { const metadata = this.getWidgetMetadata(widgetType);
'CompanyInfoWidgetComponent': { const baseConfig = {
title: 'Company Information', title: metadata?.displayName || 'Widget',
companyNameField: '', width: metadata?.defaultWidth || 300,
addressField: '', height: metadata?.defaultHeight || 200,
contactField: '' ...this.getBaseFallbackConfig()
}, };
'AttendanceOverviewWidgetComponent': {
title: 'Attendance Overview', const specificConfigs: { [key: string]: any } = {
presentField: '', // ========================================
onLeaveField: '', // KPI WIDGETS
absentField: '' // ========================================
'SimpleKpiWidgetComponent': {
...baseConfig,
valueField: 'value',
labelField: 'label',
aggregation: 'sum',
unit: '',
icon: 'trending-up',
decimalPlaces: 0,
showTrend: true,
trendField: 'trend',
color: '#3B82F6'
}, },
'EmployeeDirectoryWidgetComponent': { 'KpiWidgetComponent': {
title: 'Employee Directory', ...baseConfig,
nameField: '', valueField: 'value',
positionField: '', labelField: 'label',
departmentField: '', aggregation: 'sum',
photoField: '' unit: '',
icon: 'bar-chart-2',
decimalPlaces: 0,
showTrend: true,
trendField: 'trend',
color: '#3B82F6',
showComparison: true,
comparisonField: 'previousValue',
comparisonLabel: 'vs Previous'
}, },
'HeadcountWidgetComponent': { 'HeadcountWidgetComponent': {
title: 'Headcount', ...baseConfig,
categoryField: '', valueField: 'count',
chartType: 'bar' labelField: 'department',
}, aggregation: 'count',
'PayrollSummaryWidgetComponent': { unit: 'employees',
title: 'Payroll Summary', icon: 'user-check',
totalPayrollField: '', decimalPlaces: 0,
employeesPaidField: '' showTrend: true,
}, trendField: 'change',
'PayrollWidgetComponent': { color: '#10B981'
title: 'Payroll',
employeeNameField: '',
payPeriodField: '',
netPayField: ''
},
'WelcomeWidgetComponent': {
title: 'Welcome',
messageType: 'static',
staticMessage: 'Welcome!',
messageField: ''
},
'ChartWidgetComponent': {
title: 'Chart',
xField: '',
yAxisTitle: '',
yFields: []
},
'QuickLinksWidgetComponent': {
title: 'Quick Links',
nameField: '',
urlField: '',
iconField: ''
}, },
// ========================================
// DATA GRID WIDGETS
// ========================================
'SyncfusionDatagridWidgetComponent': { 'SyncfusionDatagridWidgetComponent': {
title: 'Data Grid', ...baseConfig,
columns: [], columns: [],
dataSource: '', dataSource: 'static',
allowPaging: true, allowPaging: true,
allowSorting: true, allowSorting: true,
allowFiltering: true allowFiltering: true,
allowGrouping: false,
allowReordering: true,
allowResizing: true,
allowSelection: false,
allowMultiSelection: false,
allowEditing: false,
allowAdding: false,
pageSize: 10,
pageSizes: [5, 10, 20, 50, 100],
showToolbar: true,
showHeader: true,
showFooter: false,
enableExport: true,
exportFormats: ['excel', 'pdf', 'csv'],
enableSearch: false,
enableVirtualization: false,
rowHeight: 40,
headerRowHeight: 40,
showAlternateRows: false,
alternateRowColor: '#F9FAFB',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151',
borderRadius: 8,
borderWidth: 1,
fontSize: 14,
fontFamily: 'system-ui, -apple-system, sans-serif'
}, },
// ========================================
// PIVOT WIDGETS
// ========================================
'SyncfusionPivotWidgetComponent': { 'SyncfusionPivotWidgetComponent': {
title: 'Pivot Table', ...baseConfig,
expandAll: false, expandAll: false,
displayOptionView: 'Both', displayOptionView: 'Both',
showToolbar: true, showToolbar: true,
showFieldList: true,
rows: [], rows: [],
columns: [], columns: [],
values: [], values: [],
filters: [] filters: [],
}, enableDrillThrough: true,
'SyncfusionChartWidgetComponent': { enableSorting: true,
title: 'Chart', enableFiltering: true,
xField: '', enableGrouping: true,
yField: '', showGrandTotals: true,
xAxisTitle: '', showSubTotals: true,
yAxisTitle: '' showRowTotals: true,
}, showColumnTotals: true,
'TimeTrackingWidgetComponent': { enableExport: true,
title: 'Time Tracking', exportFormats: ['excel', 'pdf', 'csv']
statusField: '',
hoursField: ''
}, },
'ScatterBubbleChartWidgetComponent': { // ========================================
title: 'Scatter/Bubble Chart', // CHART WIDGETS
// ========================================
'SyncfusionChartWidgetComponent': {
...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
xAxisTitle: '', xAxisTitle: '',
yAxisTitle: '', yAxisTitle: '',
type: 'Scatter' chartType: 'column',
}, showLegend: true,
'MultiRowCardWidgetComponent': { showDataLabels: false,
title: 'Multi Row Card', enableAnimation: true,
labelField: '', enableZoom: false,
valueField: '', enableTooltip: true,
unitField: '' colorScheme: 'Material',
}, backgroundColor: '#FFFFFF',
'ComboChartWidgetComponent': { textColor: '#374151',
title: 'Combo Chart', gridLines: true,
xField: '', axisLines: true
xAxisTitle: '',
yAxisTitle: '',
yFields: [],
series: []
},
'WaterfallChartWidgetComponent': {
title: 'Waterfall Chart',
xField: '',
yField: '',
xAxisTitle: '',
yAxisTitle: ''
}, },
'PieChartWidgetComponent': { 'PieChartWidgetComponent': {
title: 'Pie Chart', ...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
aggregation: 'none' aggregation: 'sum',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
innerRadius: 0,
startAngle: 0,
endAngle: 360,
colorScheme: 'Material'
}, },
'BarChartWidgetComponent': { 'BarChartWidgetComponent': {
title: 'Bar Chart', ...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
aggregation: 'none' aggregation: 'sum',
orientation: 'vertical',
showLegend: true,
showDataLabels: false,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
showGridLines: true
}, },
'AreaChartWidgetComponent': { 'AreaChartWidgetComponent': {
title: 'Area Chart', ...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
aggregation: 'none' aggregation: 'sum',
showLegend: true,
showDataLabels: false,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
fillOpacity: 0.6,
showGridLines: true
}, },
'DoughnutChartWidgetComponent': { 'DoughnutChartWidgetComponent': {
title: 'Doughnut Chart', ...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
aggregation: 'none' aggregation: 'sum',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
innerRadius: 50,
startAngle: 0,
endAngle: 360,
colorScheme: 'Material'
}, },
'FunnelChartWidgetComponent': { 'FunnelChartWidgetComponent': {
title: 'Funnel Chart', ...baseConfig,
xField: '', xField: '',
yField: '', yField: '',
aggregation: 'none' aggregation: 'sum',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
funnelType: 'funnel'
}, },
'GaugeChartWidgetComponent': { 'ScatterBubbleChartWidgetComponent': {
title: 'Gauge Chart', ...baseConfig,
valueField: '' xField: '',
yField: '',
bubbleSizeField: '',
xAxisTitle: '',
yAxisTitle: '',
showLegend: true,
showDataLabels: false,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
showGridLines: true
}, },
'SimpleTableWidgetComponent': { 'GaugeChartWidgetComponent': {
title: 'Simple Table' ...baseConfig,
valueField: '',
minValue: 0,
maxValue: 100,
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
gaugeType: 'circular',
startAngle: 0,
endAngle: 360
}, },
'TreemapWidgetComponent': { 'TreemapWidgetComponent': {
title: 'Treemap', ...baseConfig,
groupField: '', groupField: '',
valueField: '' valueField: '',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
layoutType: 'squarified'
},
'WaterfallChartWidgetComponent': {
...baseConfig,
xField: '',
yField: '',
xAxisTitle: '',
yAxisTitle: '',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
showGridLines: true
},
'ComboChartWidgetComponent': {
...baseConfig,
xField: '',
xAxisTitle: '',
yAxisTitle: '',
yFields: [],
series: [],
showLegend: true,
showDataLabels: false,
enableAnimation: true,
enableTooltip: true,
colorScheme: 'Material',
showGridLines: true
},
// ========================================
// TABLE WIDGETS
// ========================================
'DataTableWidgetComponent': {
...baseConfig,
columns: [],
dataSource: 'static',
allowSorting: true,
allowFiltering: true,
allowPaging: true,
pageSize: 10,
showHeader: true,
showFooter: false,
enableExport: true,
exportFormats: ['excel', 'csv'],
rowHeight: 40,
headerRowHeight: 40,
showAlternateRows: false,
alternateRowColor: '#F9FAFB',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151'
},
'SimpleTableWidgetComponent': {
...baseConfig,
columns: [],
dataSource: 'static',
showHeader: true,
showFooter: false,
rowHeight: 40,
headerRowHeight: 40,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151'
}, },
'MatrixWidgetComponent': { 'MatrixWidgetComponent': {
title: 'Matrix', ...baseConfig,
columns: [] columns: [],
rows: [],
values: [],
dataSource: 'static',
showHeader: true,
showFooter: true,
enableExport: true,
exportFormats: ['excel', 'csv'],
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
headerBackgroundColor: '#F9FAFB',
headerTextColor: '#374151'
},
// ========================================
// CARD WIDGETS
// ========================================
'MultiRowCardWidgetComponent': {
...baseConfig,
labelField: '',
valueField: '',
unitField: '',
dataSource: 'static',
cardStyle: 'default',
showIcon: true,
iconField: '',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16,
shadow: 'small'
},
'NotificationWidgetComponent': {
...baseConfig,
messageField: '',
typeField: '',
timestampField: '',
dataSource: 'static',
maxItems: 10,
showTimestamp: true,
showType: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
},
'WelcomeWidgetComponent': {
...baseConfig,
messageType: 'static',
staticMessage: 'Welcome!',
messageField: '',
showUserInfo: true,
userField: '',
backgroundColor: '#F0F9FF',
borderColor: '#0EA5E9',
textColor: '#0C4A6E',
borderRadius: 8,
borderWidth: 1,
padding: 20
},
'QuickLinksWidgetComponent': {
...baseConfig,
nameField: '',
urlField: '',
iconField: '',
dataSource: 'static',
layout: 'grid',
columns: 3,
showIcons: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
},
'CompanyInfoWidgetComponent': {
...baseConfig,
companyNameField: '',
addressField: '',
contactField: '',
logoField: '',
dataSource: 'static',
showLogo: true,
showAddress: true,
showContact: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 20
},
'CompanyInfoSubfolderWidgetComponent': {
...baseConfig,
companyNameField: '',
addressField: '',
contactField: '',
logoField: '',
subfolderField: '',
dataSource: 'static',
showLogo: true,
showAddress: true,
showContact: true,
showSubfolders: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 20
},
'EmployeeDirectoryWidgetComponent': {
...baseConfig,
nameField: '',
positionField: '',
departmentField: '',
photoField: '',
emailField: '',
phoneField: '',
dataSource: 'static',
showPhotos: true,
showContact: true,
layout: 'grid',
columns: 3,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
},
// ========================================
// BUSINESS WIDGETS
// ========================================
'AttendanceOverviewWidgetComponent': {
...baseConfig,
presentField: '',
onLeaveField: '',
absentField: '',
dataSource: 'static',
chartType: 'doughnut',
showLegend: true,
showDataLabels: true,
enableAnimation: true,
colorScheme: 'Material',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
}, },
'PayrollSummaryWidgetComponent': {
...baseConfig,
totalPayrollField: '',
employeesPaidField: '',
averagePayField: '',
dataSource: 'static',
showTrend: true,
trendField: 'change',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
},
'PayrollWidgetComponent': {
...baseConfig,
employeeNameField: '',
payPeriodField: '',
netPayField: '',
grossPayField: '',
deductionsField: '',
dataSource: 'static',
allowFiltering: true,
allowSorting: true,
allowPaging: true,
pageSize: 10,
showHeader: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
},
// ========================================
// UTILITY WIDGETS
// ========================================
'ClockWidgetComponent': { 'ClockWidgetComponent': {
title: 'Clock Widget', ...baseConfig,
timezone: 'local', timezone: 'local',
timeFormat: '12', timeFormat: '12',
dateFormat: 'MM/DD/YYYY', dateFormat: 'MM/DD/YYYY',
showSeconds: true, showSeconds: true,
showDate: true, showDate: true,
showDay: true showDay: true,
showTime: true,
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16,
fontSize: 18,
fontFamily: 'monospace'
}, },
'WeatherWidgetComponent': { 'WeatherWidgetComponent': {
title: 'Weather Widget', ...baseConfig,
location: 'Bangkok', location: 'Bangkok',
units: 'metric', units: 'metric',
showForecast: true, showForecast: true,
forecastDays: 5 forecastDays: 5,
showTemperature: true,
showHumidity: true,
showWind: true,
backgroundColor: '#F0F9FF',
borderColor: '#0EA5E9',
textColor: '#0C4A6E',
borderRadius: 8,
borderWidth: 1,
padding: 16
}, },
'CalendarWidgetComponent': { 'CalendarWidgetComponent': {
title: 'Calendar Widget', ...baseConfig,
viewMode: 'month', viewMode: 'month',
showWeekends: true, showWeekends: true,
showToday: true, showToday: true,
enableEvents: true enableEvents: true,
eventField: '',
dateField: '',
titleField: '',
dataSource: 'static',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
}, },
'SlicerWidgetComponent': { 'SlicerWidgetComponent': {
title: 'Slicer Widget', ...baseConfig,
slicerField: '', slicerField: '',
slicerType: 'dropdown', slicerType: 'dropdown',
multiSelect: false multiSelect: false,
dataSource: 'static',
showLabel: true,
labelText: 'Filter by:',
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
}, },
'FilledMapWidgetComponent': { 'FilledMapWidgetComponent': {
title: 'Map Widget', ...baseConfig,
mapType: 'world', mapType: 'world',
regionField: 'region', regionField: 'region',
valueField: 'value'
},
'KpiWidgetComponent': {
title: 'KPI Widget',
valueField: 'value', valueField: 'value',
labelField: 'label', dataSource: 'static',
aggregation: 'sum', showLegend: true,
unit: '', showTooltip: true,
icon: 'info', colorScheme: 'Material',
decimalPlaces: 0 backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16
} }
}; };
return fallbackConfigs[widgetType] || { title: 'Widget' }; return specificConfigs[widgetType] || baseConfig;
}
/**
* Get base fallback configuration common to all widgets
*/
private getBaseFallbackConfig(): any {
return {
// Common styling
backgroundColor: '#FFFFFF',
borderColor: '#E5E7EB',
textColor: '#374151',
borderRadius: 8,
borderWidth: 1,
padding: 16,
margin: 8,
fontSize: 14,
fontFamily: 'system-ui, -apple-system, sans-serif',
fontWeight: 'normal',
// Common layout
responsive: true,
aspectRatio: 'auto',
// Common data
dataSource: 'static',
refreshInterval: 0,
cacheEnabled: false,
cacheDuration: 300,
// Common features
enableAnimations: true,
enableTooltip: true,
enableRefresh: true,
enableExport: false,
// Common security
requireAuth: false,
allowedRoles: '',
permissionLevel: 'read',
dataEncryption: false,
auditLog: false,
rateLimit: 0,
sessionTimeout: 30
};
} }
} }
...@@ -58,6 +58,20 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -58,6 +58,20 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<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>Static Label</mat-label>
<input matInput [(ngModel)]="currentConfig.staticLabel" name="staticLabel" placeholder="Enter static label">
<mat-hint>Override label field with static text</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Static Value</mat-label>
<input matInput [(ngModel)]="currentConfig.staticValue" name="staticValue" placeholder="Enter static value">
<mat-hint>Override value field with static text</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>Value Format</mat-label> <mat-label>Value Format</mat-label>
<mat-select [(ngModel)]="currentConfig.valueFormat" name="valueFormat"> <mat-select [(ngModel)]="currentConfig.valueFormat" name="valueFormat">
<mat-option value="number">Number</mat-option> <mat-option value="number">Number</mat-option>
...@@ -137,28 +151,177 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -137,28 +151,177 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<!-- Size Configuration --> <!-- Size Configuration -->
<div class="config-section"> <div class="config-section">
<h3 class="text-blue-600">Size Configuration</h3> <h3 class="text-blue-600">Size Configuration</h3>
<div class="size-config">
<div <!-- Size Presets -->
*ngFor="let option of sizeOptions" <div class="size-presets">
class="size-option" <h4 class="text-sm font-medium text-gray-700 mb-3">Quick Presets</h4>
[class.selected]="currentConfig.sizeOption === option.id" <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
(click)="setSizeOption(option.id)"> <div
<h4>{{ option.label }}</h4> *ngFor="let option of sizeOptions"
<p>{{ option.description }}</p> class="size-preset-card"
[class.selected]="currentConfig.sizeOption === option.id"
(click)="setSizeOption(option.id)">
<div class="preset-icon">
<i [class]="getSizeIcon(option.id)"></i>
</div>
<div class="preset-info">
<h5>{{ option.label }}</h5>
<p>{{ option.description }}</p>
</div>
</div>
</div> </div>
</div> </div>
<div *ngIf="currentConfig.sizeOption === 'custom'" class="grid grid-cols-2 gap-4 mt-4"> <!-- Custom Size Configuration -->
<mat-form-field appearance="fill"> <div *ngIf="currentConfig.sizeOption === 'custom'" class="custom-size-config mt-6">
<mat-label>Width</mat-label> <h4 class="text-sm font-medium text-gray-700 mb-4">Custom Dimensions</h4>
<input matInput [(ngModel)]="currentConfig.width" name="width" placeholder="e.g., 300px, 50%, auto">
<mat-hint>Widget width</mat-hint> <!-- Width Configuration -->
</mat-form-field> <div class="dimension-group">
<mat-form-field appearance="fill"> <label class="dimension-label">Width</label>
<mat-label>Height</mat-label> <div class="dimension-controls">
<input matInput [(ngModel)]="currentConfig.height" name="height" placeholder="e.g., 200px, 50%, auto"> <mat-form-field appearance="outline" class="flex-1">
<mat-hint>Widget height</mat-hint> <mat-label>Width Value</mat-label>
</mat-form-field> <input matInput [(ngModel)]="currentConfig.width" name="width"
placeholder="e.g., 300px, 50%, 100%, auto">
<mat-hint>Width value with unit</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="w-32">
<mat-label>Unit</mat-label>
<mat-select [(ngModel)]="currentConfig.widthUnit" name="widthUnit">
<mat-option value="px">px</mat-option>
<mat-option value="%">%</mat-option>
<mat-option value="rem">rem</mat-option>
<mat-option value="em">em</mat-option>
<mat-option value="vh">vh</mat-option>
<mat-option value="vw">vw</mat-option>
<mat-option value="auto">auto</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<!-- Height Configuration -->
<div class="dimension-group">
<label class="dimension-label">Height</label>
<div class="dimension-controls">
<mat-form-field appearance="outline" class="flex-1">
<mat-label>Height Value</mat-label>
<input matInput [(ngModel)]="currentConfig.height" name="height"
placeholder="e.g., 200px, 50%, 100%, auto">
<mat-hint>Height value with unit</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="w-32">
<mat-label>Unit</mat-label>
<mat-select [(ngModel)]="currentConfig.heightUnit" name="heightUnit">
<mat-option value="px">px</mat-option>
<mat-option value="%">%</mat-option>
<mat-option value="rem">rem</mat-option>
<mat-option value="em">em</mat-option>
<mat-option value="vh">vh</mat-option>
<mat-option value="vw">vw</mat-option>
<mat-option value="auto">auto</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<!-- Full Panel Options -->
<div class="full-panel-options">
<h5 class="text-sm font-medium text-gray-600 mb-3">Full Panel Options</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="full-panel-card" (click)="setFullPanel('width')"
[class.selected]="currentConfig.fullWidth">
<div class="card-icon">
<i class="fas fa-arrows-alt-h"></i>
</div>
<div class="card-content">
<h6>Full Width</h6>
<p>100% width of container</p>
</div>
<mat-checkbox [(ngModel)]="currentConfig.fullWidth"
(change)="onFullWidthChange($event)"></mat-checkbox>
</div>
<div class="full-panel-card" (click)="setFullPanel('height')"
[class.selected]="currentConfig.fullHeight">
<div class="card-icon">
<i class="fas fa-arrows-alt-v"></i>
</div>
<div class="card-content">
<h6>Full Height</h6>
<p>100% height of container</p>
</div>
<mat-checkbox [(ngModel)]="currentConfig.fullHeight"
(change)="onFullHeightChange($event)"></mat-checkbox>
</div>
</div>
</div>
<!-- Responsive Settings -->
<div class="responsive-settings mt-6">
<h5 class="text-sm font-medium text-gray-600 mb-3">Responsive Settings</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="outline">
<mat-label>Min Width</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.minWidth"
name="minWidth" min="0" placeholder="200">
<mat-hint>Minimum width in pixels</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Min Height</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.minHeight"
name="minHeight" min="0" placeholder="150">
<mat-hint>Minimum height in pixels</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Width</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.maxWidth"
name="maxWidth" min="0" placeholder="800">
<mat-hint>Maximum width in pixels</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Max Height</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.maxHeight"
name="maxHeight" min="0" placeholder="600">
<mat-hint>Maximum height in pixels</mat-hint>
</mat-form-field>
</div>
</div>
<!-- Aspect Ratio -->
<div class="aspect-ratio-settings mt-6">
<h5 class="text-sm font-medium text-gray-600 mb-3">Aspect Ratio</h5>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<div class="aspect-ratio-option"
[class.selected]="currentConfig.aspectRatio === 'auto'"
(click)="setAspectRatio('auto')">
<div class="ratio-icon">📐</div>
<span>Auto</span>
</div>
<div class="aspect-ratio-option"
[class.selected]="currentConfig.aspectRatio === '16:9'"
(click)="setAspectRatio('16:9')">
<div class="ratio-icon">📺</div>
<span>16:9</span>
</div>
<div class="aspect-ratio-option"
[class.selected]="currentConfig.aspectRatio === '4:3'"
(click)="setAspectRatio('4:3')">
<div class="ratio-icon">📱</div>
<span>4:3</span>
</div>
<div class="aspect-ratio-option"
[class.selected]="currentConfig.aspectRatio === '1:1'"
(click)="setAspectRatio('1:1')">
<div class="ratio-icon">⬜</div>
<span>1:1</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -171,16 +334,95 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -171,16 +334,95 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<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>Background Type</mat-label>
<mat-select [(ngModel)]="currentConfig.backgroundType" name="backgroundType">
<mat-option value="solid">Solid Color</mat-option>
<mat-option value="gradient">Gradient</mat-option>
<mat-option value="image">Image</mat-option>
</mat-select>
<mat-hint>Type of background</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Background Color</mat-label> <mat-label>Background Color</mat-label>
<input matInput type="color" [(ngModel)]="currentConfig.backgroundColor" name="backgroundColor"> <input matInput type="color" [(ngModel)]="currentConfig.backgroundColor" name="backgroundColor">
<mat-hint>Widget background color</mat-hint> <mat-hint>Widget background color</mat-hint>
</mat-form-field> </mat-form-field>
</div>
<div *ngIf="currentConfig.backgroundType === 'gradient'" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Gradient Start Color</mat-label>
<input matInput type="color" [(ngModel)]="currentConfig.gradientStartColor" name="gradientStartColor">
<mat-hint>Gradient start color</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Gradient End Color</mat-label>
<input matInput type="color" [(ngModel)]="currentConfig.gradientEndColor" name="gradientEndColor">
<mat-hint>Gradient end color</mat-hint>
</mat-form-field>
</div>
<div *ngIf="currentConfig.backgroundType === 'gradient'" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Gradient Direction</mat-label>
<mat-select [(ngModel)]="currentConfig.gradientDirection" name="gradientDirection">
<mat-option value="to right">Left to Right</mat-option>
<mat-option value="to left">Right to Left</mat-option>
<mat-option value="to bottom">Top to Bottom</mat-option>
<mat-option value="to top">Bottom to Top</mat-option>
<mat-option value="to bottom right">Top Left to Bottom Right</mat-option>
<mat-option value="to top left">Bottom Right to Top Left</mat-option>
</mat-select>
<mat-hint>Gradient direction</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Gradient Type</mat-label>
<mat-select [(ngModel)]="currentConfig.gradientType" name="gradientType">
<mat-option value="linear">Linear</mat-option>
<mat-option value="radial">Radial</mat-option>
</mat-select>
<mat-hint>Gradient type</mat-hint>
</mat-form-field>
</div>
<div *ngIf="currentConfig.backgroundType === 'image'" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Background Image URL</mat-label>
<input matInput [(ngModel)]="currentConfig.backgroundImage" name="backgroundImage" placeholder="https://example.com/image.jpg">
<mat-hint>Background image URL</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Background Size</mat-label>
<mat-select [(ngModel)]="currentConfig.backgroundSize" name="backgroundSize">
<mat-option value="cover">Cover</mat-option>
<mat-option value="contain">Contain</mat-option>
<mat-option value="100% 100%">Stretch</mat-option>
</mat-select>
<mat-hint>How background image is sized</mat-hint>
</mat-form-field>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
<mat-label>Text Color</mat-label> <mat-label>Text Color</mat-label>
<input matInput type="color" [(ngModel)]="currentConfig.textColor" name="textColor"> <input matInput type="color" [(ngModel)]="currentConfig.textColor" name="textColor">
<mat-hint>Text color</mat-hint> <mat-hint>Text color</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Text Shadow</mat-label>
<mat-select [(ngModel)]="currentConfig.textShadow" name="textShadow">
<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>Text shadow effect</mat-hint>
</mat-form-field>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
...@@ -312,28 +554,24 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -312,28 +554,24 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<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>Icon</mat-label> <mat-label>Icon Library</mat-label>
<mat-select [(ngModel)]="currentConfig.icon" name="icon"> <mat-select [(ngModel)]="currentConfig.iconLibrary" name="iconLibrary">
<mat-option value="none">None</mat-option> <mat-option value="material">Material Icons</mat-option>
<mat-option value="trending_up">Trending Up</mat-option> <mat-option value="bootstrap">Bootstrap Icons</mat-option>
<mat-option value="trending_down">Trending Down</mat-option> <mat-option value="fontawesome">Font Awesome</mat-option>
<mat-option value="show_chart">Chart</mat-option> <mat-option value="feather">Feather Icons</mat-option>
<mat-option value="assessment">Assessment</mat-option>
<mat-option value="analytics">Analytics</mat-option>
<mat-option value="bar_chart">Bar Chart</mat-option>
<mat-option value="pie_chart">Pie Chart</mat-option>
<mat-option value="donut_large">Donut</mat-option>
<mat-option value="account_balance">Account Balance</mat-option>
<mat-option value="attach_money">Money</mat-option>
<mat-option value="people">People</mat-option>
<mat-option value="business">Business</mat-option>
<mat-option value="home">Home</mat-option>
<mat-option value="star">Star</mat-option>
<mat-option value="favorite">Favorite</mat-option>
</mat-select> </mat-select>
<mat-hint>Icon to display with KPI</mat-hint> <mat-hint>Icon library to use</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Icon</mat-label>
<input matInput [(ngModel)]="currentConfig.icon" name="icon" placeholder="e.g., trending-up, chart-bar">
<mat-hint>Icon name from selected library</mat-hint>
</mat-form-field> </mat-form-field>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
<mat-label>Icon Position</mat-label> <mat-label>Icon Position</mat-label>
<mat-select [(ngModel)]="currentConfig.iconPosition" name="iconPosition"> <mat-select [(ngModel)]="currentConfig.iconPosition" name="iconPosition">
...@@ -341,9 +579,20 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -341,9 +579,20 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-option value="right">Right</mat-option> <mat-option value="right">Right</mat-option>
<mat-option value="top">Top</mat-option> <mat-option value="top">Top</mat-option>
<mat-option value="bottom">Bottom</mat-option> <mat-option value="bottom">Bottom</mat-option>
<mat-option value="center">Center</mat-option>
</mat-select> </mat-select>
<mat-hint>Position of the icon</mat-hint> <mat-hint>Position of the icon</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Icon Style</mat-label>
<mat-select [(ngModel)]="currentConfig.iconStyle" name="iconStyle">
<mat-option value="outline">Outline</mat-option>
<mat-option value="filled">Filled</mat-option>
<mat-option value="duotone">Duotone</mat-option>
</mat-select>
<mat-hint>Icon style variant</mat-hint>
</mat-form-field>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
...@@ -359,6 +608,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -359,6 +608,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-hint>Icon color</mat-hint> <mat-hint>Icon color</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Icon Background</mat-label>
<input matInput type="color" [(ngModel)]="currentConfig.iconBackground" name="iconBackground">
<mat-hint>Icon background color</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Icon Border Radius (px)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.iconBorderRadius" name="iconBorderRadius" min="0" max="50">
<mat-hint>Icon background border radius</mat-hint>
</mat-form-field>
</div>
<div class="flex items-center space-x-4">
<mat-checkbox [(ngModel)]="currentConfig.showIconBackground" name="showIconBackground">
Show Icon Background
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.iconPulse" name="iconPulse">
Icon Pulse Animation
</mat-checkbox>
</div>
</div> </div>
</mat-tab> </mat-tab>
...@@ -486,6 +759,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -486,6 +759,10 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-option value="scale">Scale In</mat-option> <mat-option value="scale">Scale In</mat-option>
<mat-option value="bounce">Bounce</mat-option> <mat-option value="bounce">Bounce</mat-option>
<mat-option value="pulse">Pulse</mat-option> <mat-option value="pulse">Pulse</mat-option>
<mat-option value="flip">Flip</mat-option>
<mat-option value="zoom">Zoom</mat-option>
<mat-option value="rotate">Rotate</mat-option>
<mat-option value="shake">Shake</mat-option>
</mat-select> </mat-select>
<mat-hint>Type of animation effect</mat-hint> <mat-hint>Type of animation effect</mat-hint>
</mat-form-field> </mat-form-field>
...@@ -499,6 +776,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -499,6 +776,30 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<div *ngIf="currentConfig.enableAnimations" class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div *ngIf="currentConfig.enableAnimations" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill"> <mat-form-field appearance="fill">
<mat-label>Animation Direction</mat-label>
<mat-select [(ngModel)]="currentConfig.animationDirection" name="animationDirection">
<mat-option value="normal">Normal</mat-option>
<mat-option value="reverse">Reverse</mat-option>
<mat-option value="alternate">Alternate</mat-option>
<mat-option value="alternate-reverse">Alternate Reverse</mat-option>
</mat-select>
<mat-hint>Animation direction</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Animation Iteration Count</mat-label>
<mat-select [(ngModel)]="currentConfig.animationIteration" name="animationIteration">
<mat-option value="1">Once</mat-option>
<mat-option value="2">Twice</mat-option>
<mat-option value="3">Three Times</mat-option>
<mat-option value="infinite">Infinite</mat-option>
</mat-select>
<mat-hint>How many times to repeat animation</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> <mat-label>Animation Delay (ms)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.animationDelay" name="animationDelay" min="0" max="1000"> <input matInput type="number" [(ngModel)]="currentConfig.animationDelay" name="animationDelay" min="0" max="1000">
<mat-hint>Delay before animation starts</mat-hint> <mat-hint>Delay before animation starts</mat-hint>
...@@ -631,6 +932,34 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -631,6 +932,34 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
placeholder="data => data.map(item => ({ ...item, formattedValue: formatCurrency(item.value) }))"></textarea> placeholder="data => data.map(item => ({ ...item, formattedValue: formatCurrency(item.value) }))"></textarea>
<mat-hint>JavaScript function to transform data</mat-hint> <mat-hint>JavaScript function to transform data</mat-hint>
</mat-form-field> </mat-form-field>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Data Validation</mat-label>
<mat-select [(ngModel)]="currentConfig.dataValidation" name="dataValidation">
<mat-option value="none">None</mat-option>
<mat-option value="required">Required</mat-option>
<mat-option value="numeric">Numeric Only</mat-option>
<mat-option value="positive">Positive Numbers Only</mat-option>
</mat-select>
<mat-hint>Data validation rules</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Error Message</mat-label>
<input matInput [(ngModel)]="currentConfig.errorMessage" name="errorMessage" placeholder="Failed to load data">
<mat-hint>Error message to display</mat-hint>
</mat-form-field>
</div>
<div class="flex items-center space-x-4">
<mat-checkbox [(ngModel)]="currentConfig.showDataCount" name="showDataCount">
Show Data Count
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.showLastUpdated" name="showLastUpdated">
Show Last Updated
</mat-checkbox>
</div>
</div> </div>
</mat-tab> </mat-tab>
...@@ -685,6 +1014,35 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -685,6 +1014,35 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
placeholder="function(event) {&#10; console.log('Widget clicked:', event);&#10;}"></textarea> placeholder="function(event) {&#10; console.log('Widget clicked:', event);&#10;}"></textarea>
<mat-hint>Custom JavaScript function for click events</mat-hint> <mat-hint>Custom JavaScript function for click events</mat-hint>
</mat-form-field> </mat-form-field>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Hover Effect</mat-label>
<mat-select [(ngModel)]="currentConfig.hoverEffect" name="hoverEffect">
<mat-option value="none">None</mat-option>
<mat-option value="scale">Scale</mat-option>
<mat-option value="glow">Glow</mat-option>
<mat-option value="shadow">Shadow</mat-option>
<mat-option value="tilt">Tilt</mat-option>
</mat-select>
<mat-hint>Hover effect type</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Tooltip Content</mat-label>
<input matInput [(ngModel)]="currentConfig.tooltipContent" name="tooltipContent" placeholder="Custom tooltip text">
<mat-hint>Custom tooltip content</mat-hint>
</mat-form-field>
</div>
<div class="flex items-center space-x-4">
<mat-checkbox [(ngModel)]="currentConfig.enableKeyboard" name="enableKeyboard">
Enable Keyboard Navigation
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.enableFocus" name="enableFocus">
Enable Focus Indicator
</mat-checkbox>
</div>
</div> </div>
</mat-tab> </mat-tab>
...@@ -726,6 +1084,33 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -726,6 +1084,33 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<input matInput [(ngModel)]="currentConfig.allowedRoles" name="allowedRoles" placeholder="admin, analyst, manager"> <input matInput [(ngModel)]="currentConfig.allowedRoles" name="allowedRoles" placeholder="admin, analyst, manager">
<mat-hint>Roles that can access this widget</mat-hint> <mat-hint>Roles that can access this widget</mat-hint>
</mat-form-field> </mat-form-field>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>Permission Level</mat-label>
<mat-select [(ngModel)]="currentConfig.permissionLevel" name="permissionLevel">
<mat-option value="read">Read Only</mat-option>
<mat-option value="write">Read/Write</mat-option>
<mat-option value="admin">Full Access</mat-option>
</mat-select>
<mat-hint>Required permission level</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Session Timeout (minutes)</mat-label>
<input matInput type="number" [(ngModel)]="currentConfig.sessionTimeout" name="sessionTimeout" min="5" max="480">
<mat-hint>Session timeout in minutes</mat-hint>
</mat-form-field>
</div>
<div class="flex items-center space-x-4">
<mat-checkbox [(ngModel)]="currentConfig.requireHttps" name="requireHttps">
Require HTTPS
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.enableCors" name="enableCors">
Enable CORS
</mat-checkbox>
</div>
</div> </div>
</mat-tab> </mat-tab>
...@@ -783,6 +1168,29 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -783,6 +1168,29 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
<mat-hint>Color when condition is false</mat-hint> <mat-hint>Color when condition is false</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<mat-form-field appearance="fill">
<mat-label>True Icon</mat-label>
<input matInput [(ngModel)]="currentConfig.trueIcon" name="trueIcon" placeholder="e.g., check-circle">
<mat-hint>Icon when condition is true</mat-hint>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>False Icon</mat-label>
<input matInput [(ngModel)]="currentConfig.falseIcon" name="falseIcon" placeholder="e.g., x-circle">
<mat-hint>Icon when condition is false</mat-hint>
</mat-form-field>
</div>
<div class="flex items-center space-x-4">
<mat-checkbox [(ngModel)]="currentConfig.showConditionalIcon" name="showConditionalIcon">
Show Conditional Icon
</mat-checkbox>
<mat-checkbox [(ngModel)]="currentConfig.animateConditionalChange" name="animateConditionalChange">
Animate Conditional Change
</mat-checkbox>
</div>
</div> </div>
</div> </div>
</mat-tab> </mat-tab>
...@@ -899,6 +1307,209 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con ...@@ -899,6 +1307,209 @@ import { BaseConfigComponent } from '../../../widget-config/base-config/base-con
.text-teal-600 { color: #0d9488; } .text-teal-600 { color: #0d9488; }
.text-red-600 { color: #dc2626; } .text-red-600 { color: #dc2626; }
.text-gray-600 { color: #4b5563; } .text-gray-600 { color: #4b5563; }
/* Size Configuration Styles */
.size-presets {
margin-bottom: 24px;
}
.size-preset-card {
display: flex;
align-items: center;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.size-preset-card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
transform: translateY(-2px);
}
.size-preset-card.selected {
border-color: #3b82f6;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: white;
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3);
}
.preset-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(59, 130, 246, 0.1);
border-radius: 12px;
margin-right: 12px;
font-size: 20px;
color: #3b82f6;
}
.size-preset-card.selected .preset-icon {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.preset-info h5 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
}
.preset-info p {
margin: 0;
font-size: 12px;
opacity: 0.8;
}
.custom-size-config {
background: #f8fafc;
border-radius: 12px;
padding: 24px;
border: 1px solid #e2e8f0;
}
.dimension-group {
margin-bottom: 20px;
}
.dimension-label {
display: block;
font-size: 14px;
font-weight: 600;
color: #374151;
margin-bottom: 8px;
}
.dimension-controls {
display: flex;
gap: 12px;
align-items: flex-end;
}
.full-panel-options {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e5e7eb;
}
.full-panel-card {
display: flex;
align-items: center;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.full-panel-card:hover {
border-color: #10b981;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.15);
}
.full-panel-card.selected {
border-color: #10b981;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
}
.card-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(16, 185, 129, 0.1);
border-radius: 8px;
margin-right: 12px;
font-size: 16px;
color: #10b981;
}
.full-panel-card.selected .card-icon {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.card-content {
flex: 1;
}
.card-content h6 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
}
.card-content p {
margin: 0;
font-size: 12px;
opacity: 0.8;
}
.responsive-settings {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e5e7eb;
}
.aspect-ratio-settings {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #e5e7eb;
}
.aspect-ratio-option {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.aspect-ratio-option:hover {
border-color: #8b5cf6;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.15);
}
.aspect-ratio-option.selected {
border-color: #8b5cf6;
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
color: white;
}
.ratio-icon {
font-size: 24px;
margin-bottom: 8px;
}
.aspect-ratio-option span {
font-size: 12px;
font-weight: 600;
}
.flex-1 {
flex: 1;
}
.w-32 {
width: 8rem;
}
`] `]
}) })
export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnInit { export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnInit {
...@@ -906,6 +1517,9 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI ...@@ -906,6 +1517,9 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI
{ id: 'small', label: 'Small', description: '200x150px' }, { id: 'small', label: 'Small', description: '200x150px' },
{ id: 'medium', label: 'Medium', description: '300x200px' }, { id: 'medium', label: 'Medium', description: '300x200px' },
{ id: 'large', label: 'Large', description: '400x300px' }, { id: 'large', label: 'Large', description: '400x300px' },
{ id: 'full-width', label: 'Full Width', description: '100% width' },
{ id: 'full-height', label: 'Full Height', description: '100% height' },
{ id: 'full-panel', label: 'Full Panel', description: '100% width & height' },
{ id: 'custom', label: 'Custom', description: 'Custom size' } { id: 'custom', label: 'Custom', description: 'Custom size' }
]; ];
...@@ -915,9 +1529,12 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI ...@@ -915,9 +1529,12 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI
} }
override initializeDefaultConfig() { override initializeDefaultConfig() {
// Basic configuration
if (!this.currentConfig.title) this.currentConfig.title = 'KPI Widget'; if (!this.currentConfig.title) this.currentConfig.title = 'KPI Widget';
if (!this.currentConfig.valueField) this.currentConfig.valueField = 'value'; if (!this.currentConfig.valueField) this.currentConfig.valueField = 'value';
if (!this.currentConfig.labelField) this.currentConfig.labelField = 'label'; if (!this.currentConfig.labelField) this.currentConfig.labelField = 'label';
if (!this.currentConfig.staticLabel) this.currentConfig.staticLabel = '';
if (!this.currentConfig.staticValue) this.currentConfig.staticValue = '';
if (!this.currentConfig.aggregation) this.currentConfig.aggregation = 'sum'; if (!this.currentConfig.aggregation) this.currentConfig.aggregation = 'sum';
if (!this.currentConfig.unit) this.currentConfig.unit = ''; if (!this.currentConfig.unit) this.currentConfig.unit = '';
if (!this.currentConfig.icon) this.currentConfig.icon = 'info'; if (!this.currentConfig.icon) this.currentConfig.icon = 'info';
...@@ -925,6 +1542,55 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI ...@@ -925,6 +1542,55 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI
if (!this.currentConfig.sizeOption) this.currentConfig.sizeOption = 'medium'; if (!this.currentConfig.sizeOption) this.currentConfig.sizeOption = 'medium';
if (!this.currentConfig.width) this.currentConfig.width = '300px'; if (!this.currentConfig.width) this.currentConfig.width = '300px';
if (!this.currentConfig.height) this.currentConfig.height = '200px'; if (!this.currentConfig.height) this.currentConfig.height = '200px';
if (!this.currentConfig.widthUnit) this.currentConfig.widthUnit = 'px';
if (!this.currentConfig.heightUnit) this.currentConfig.heightUnit = 'px';
if (this.currentConfig.fullWidth === undefined) this.currentConfig.fullWidth = false;
if (this.currentConfig.fullHeight === undefined) this.currentConfig.fullHeight = false;
// Background configuration
if (!this.currentConfig.backgroundType) this.currentConfig.backgroundType = 'solid';
if (!this.currentConfig.gradientStartColor) this.currentConfig.gradientStartColor = '#3366FF';
if (!this.currentConfig.gradientEndColor) this.currentConfig.gradientEndColor = '#00CCFF';
if (!this.currentConfig.gradientDirection) this.currentConfig.gradientDirection = 'to bottom right';
if (!this.currentConfig.gradientType) this.currentConfig.gradientType = 'linear';
if (!this.currentConfig.backgroundImage) this.currentConfig.backgroundImage = '';
if (!this.currentConfig.backgroundSize) this.currentConfig.backgroundSize = 'cover';
// Icon configuration
if (!this.currentConfig.iconLibrary) this.currentConfig.iconLibrary = 'material';
if (!this.currentConfig.iconStyle) this.currentConfig.iconStyle = 'outline';
if (!this.currentConfig.iconBackground) this.currentConfig.iconBackground = '#FFFFFF';
if (!this.currentConfig.iconBorderRadius) this.currentConfig.iconBorderRadius = 0;
if (this.currentConfig.showIconBackground === undefined) this.currentConfig.showIconBackground = false;
if (this.currentConfig.iconPulse === undefined) this.currentConfig.iconPulse = false;
// Animation configuration
if (!this.currentConfig.animationDirection) this.currentConfig.animationDirection = 'normal';
if (!this.currentConfig.animationIteration) this.currentConfig.animationIteration = '1';
// Data configuration
if (!this.currentConfig.dataValidation) this.currentConfig.dataValidation = 'none';
if (!this.currentConfig.errorMessage) this.currentConfig.errorMessage = 'Failed to load data';
if (this.currentConfig.showDataCount === undefined) this.currentConfig.showDataCount = false;
if (this.currentConfig.showLastUpdated === undefined) this.currentConfig.showLastUpdated = false;
// Interaction configuration
if (!this.currentConfig.hoverEffect) this.currentConfig.hoverEffect = 'scale';
if (!this.currentConfig.tooltipContent) this.currentConfig.tooltipContent = '';
if (this.currentConfig.enableKeyboard === undefined) this.currentConfig.enableKeyboard = true;
if (this.currentConfig.enableFocus === undefined) this.currentConfig.enableFocus = true;
// Security configuration
if (!this.currentConfig.permissionLevel) this.currentConfig.permissionLevel = 'read';
if (!this.currentConfig.sessionTimeout) this.currentConfig.sessionTimeout = 30;
if (this.currentConfig.requireHttps === undefined) this.currentConfig.requireHttps = false;
if (this.currentConfig.enableCors === undefined) this.currentConfig.enableCors = true;
// Conditional formatting configuration
if (!this.currentConfig.trueIcon) this.currentConfig.trueIcon = 'check-circle';
if (!this.currentConfig.falseIcon) this.currentConfig.falseIcon = 'x-circle';
if (this.currentConfig.showConditionalIcon === undefined) this.currentConfig.showConditionalIcon = false;
if (this.currentConfig.animateConditionalChange === undefined) this.currentConfig.animateConditionalChange = true;
} }
private initializeColorDefaults() { private initializeColorDefaults() {
...@@ -937,6 +1603,7 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI ...@@ -937,6 +1603,7 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI
if (!this.currentConfig.accentColor) this.currentConfig.accentColor = '#3B82F6'; if (!this.currentConfig.accentColor) this.currentConfig.accentColor = '#3B82F6';
if (!this.currentConfig.trueColor) this.currentConfig.trueColor = '#10B981'; if (!this.currentConfig.trueColor) this.currentConfig.trueColor = '#10B981';
if (!this.currentConfig.falseColor) this.currentConfig.falseColor = '#EF4444'; if (!this.currentConfig.falseColor) this.currentConfig.falseColor = '#EF4444';
if (!this.currentConfig.textShadow) this.currentConfig.textShadow = 'none';
// Initialize new fields // Initialize new fields
if (!this.currentConfig.valueFormat) this.currentConfig.valueFormat = 'number'; if (!this.currentConfig.valueFormat) this.currentConfig.valueFormat = 'number';
...@@ -992,13 +1659,83 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI ...@@ -992,13 +1659,83 @@ export class SimpleKpiConfigComponent extends BaseConfigComponent implements OnI
if (optionId === 'small') { if (optionId === 'small') {
this.currentConfig.width = '200px'; this.currentConfig.width = '200px';
this.currentConfig.height = '150px'; this.currentConfig.height = '150px';
this.currentConfig.fullWidth = false;
this.currentConfig.fullHeight = false;
} else if (optionId === 'medium') { } else if (optionId === 'medium') {
this.currentConfig.width = '300px'; this.currentConfig.width = '300px';
this.currentConfig.height = '200px'; this.currentConfig.height = '200px';
this.currentConfig.fullWidth = false;
this.currentConfig.fullHeight = false;
} else if (optionId === 'large') { } else if (optionId === 'large') {
this.currentConfig.width = '400px'; this.currentConfig.width = '400px';
this.currentConfig.height = '300px'; this.currentConfig.height = '300px';
this.currentConfig.fullWidth = false;
this.currentConfig.fullHeight = false;
} else if (optionId === 'full-width') {
this.currentConfig.width = '100%';
this.currentConfig.height = '200px';
this.currentConfig.fullWidth = true;
this.currentConfig.fullHeight = false;
} else if (optionId === 'full-height') {
this.currentConfig.width = '300px';
this.currentConfig.height = '100%';
this.currentConfig.fullWidth = false;
this.currentConfig.fullHeight = true;
} else if (optionId === 'full-panel') {
this.currentConfig.width = '100%';
this.currentConfig.height = '100%';
this.currentConfig.fullWidth = true;
this.currentConfig.fullHeight = true;
}
this.configChange.emit(this.currentConfig);
}
getSizeIcon(optionId: string): string {
const iconMap: { [key: string]: string } = {
'small': 'fas fa-square',
'medium': 'fas fa-expand-arrows-alt',
'large': 'fas fa-expand',
'full-width': 'fas fa-arrows-alt-h',
'full-height': 'fas fa-arrows-alt-v',
'full-panel': 'fas fa-expand-arrows-alt',
'custom': 'fas fa-cog'
};
return iconMap[optionId] || 'fas fa-square';
}
setFullPanel(type: 'width' | 'height') {
if (type === 'width') {
this.currentConfig.fullWidth = !this.currentConfig.fullWidth;
if (this.currentConfig.fullWidth) {
this.currentConfig.width = '100%';
}
} else {
this.currentConfig.fullHeight = !this.currentConfig.fullHeight;
if (this.currentConfig.fullHeight) {
this.currentConfig.height = '100%';
}
} }
this.configChange.emit(this.currentConfig); this.configChange.emit(this.currentConfig);
} }
onFullWidthChange(event: any) {
this.currentConfig.fullWidth = event.checked;
if (this.currentConfig.fullWidth) {
this.currentConfig.width = '100%';
}
this.configChange.emit(this.currentConfig);
}
onFullHeightChange(event: any) {
this.currentConfig.fullHeight = event.checked;
if (this.currentConfig.fullHeight) {
this.currentConfig.height = '100%';
}
this.configChange.emit(this.currentConfig);
}
setAspectRatio(ratio: string) {
this.currentConfig.aspectRatio = ratio;
this.configChange.emit(this.currentConfig);
}
} }
...@@ -33,6 +33,24 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -33,6 +33,24 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public borderWidth: number = 1; public borderWidth: number = 1;
public shadow: string = 'medium'; public shadow: string = 'medium';
// New display properties
public staticLabel: string = '';
public staticValue: string = '';
public backgroundType: string = 'solid';
public gradientStartColor: string = '#3366FF';
public gradientEndColor: string = '#00CCFF';
public gradientDirection: string = 'to bottom right';
public gradientType: string = 'linear';
public backgroundImage: string = '';
public backgroundSize: string = 'cover';
public textShadow: string = 'none';
public iconLibrary: string = 'material';
public iconStyle: string = 'outline';
public iconBackground: string = '#FFFFFF';
public iconBorderRadius: number = 0;
public showIconBackground: boolean = false;
public iconPulse: boolean = false;
// Trend properties // Trend properties
public showTrend: boolean = false; public showTrend: boolean = false;
public trendValue: string = ''; public trendValue: string = '';
...@@ -66,6 +84,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -66,6 +84,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public animationEasing: string = 'ease'; public animationEasing: string = 'ease';
public hoverEffects: boolean = true; public hoverEffects: boolean = true;
public autoRefresh: boolean = false; public autoRefresh: boolean = false;
public animationDirection: string = 'normal';
public animationIteration: string = '1';
// Interaction properties // Interaction properties
public enableTooltip: boolean = true; public enableTooltip: boolean = true;
...@@ -76,6 +96,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -76,6 +96,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public enableRefresh: boolean = true; public enableRefresh: boolean = true;
public clickAction: string = 'none'; public clickAction: string = 'none';
public customClickHandler: string = ''; public customClickHandler: string = '';
public hoverEffect: string = 'scale';
public tooltipContent: string = '';
public enableKeyboard: boolean = true;
public enableFocus: boolean = true;
// Layout properties // Layout properties
public width: number = 300; public width: number = 300;
...@@ -86,6 +110,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -86,6 +110,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public maxHeight: number = 400; public maxHeight: number = 400;
public aspectRatio: string = 'auto'; public aspectRatio: string = 'auto';
public responsive: boolean = true; public responsive: boolean = true;
public widthUnit: string = 'px';
public heightUnit: string = 'px';
public fullWidth: boolean = false;
public fullHeight: boolean = false;
// Data properties // Data properties
public dataSource: string = 'static'; public dataSource: string = 'static';
...@@ -94,6 +122,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -94,6 +122,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public cacheEnabled: boolean = false; public cacheEnabled: boolean = false;
public cacheDuration: number = 300; public cacheDuration: number = 300;
public dataTransform: string = ''; public dataTransform: string = '';
public dataValidation: string = 'none';
public override errorMessage: string = 'Failed to load data';
public showDataCount: boolean = false;
public showLastUpdated: boolean = false;
// Filter properties // Filter properties
public enableFilter: boolean = false; public enableFilter: boolean = false;
...@@ -109,6 +141,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -109,6 +141,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public conditionValue: string = ''; public conditionValue: string = '';
public trueColor: string = '#10B981'; public trueColor: string = '#10B981';
public falseColor: string = '#EF4444'; public falseColor: string = '#EF4444';
public trueIcon: string = 'check-circle';
public falseIcon: string = 'x-circle';
public showConditionalIcon: boolean = false;
public animateConditionalChange: boolean = true;
// Security properties // Security properties
public requireAuth: boolean = false; public requireAuth: boolean = false;
...@@ -116,12 +152,16 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -116,12 +152,16 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public dataEncryption: boolean = false; public dataEncryption: boolean = false;
public auditLog: boolean = false; public auditLog: boolean = false;
public rateLimit: number = 0; public rateLimit: number = 0;
public permissionLevel: string = 'read';
public sessionTimeout: number = 30;
public requireHttps: boolean = false;
public enableCors: boolean = true;
constructor(protected override dashboardStateService: DashboardStateService) { constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService); super(dashboardStateService);
} }
applyInitialConfig(): void { override applyInitialConfig(): void {
// Basic configuration // Basic configuration
this.title = this.configObj.title || 'KPI'; this.title = this.configObj.title || 'KPI';
this.label = this.configObj.labelField ? '' : 'KPI'; this.label = this.configObj.labelField ? '' : 'KPI';
...@@ -131,6 +171,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -131,6 +171,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.iconSize = this.configObj.iconSize || 24; this.iconSize = this.configObj.iconSize || 24;
this.valueField = this.configObj.valueField || ''; this.valueField = this.configObj.valueField || '';
this.labelField = this.configObj.labelField || ''; this.labelField = this.configObj.labelField || '';
this.staticLabel = this.configObj.staticLabel || '';
this.staticValue = this.configObj.staticValue || '';
this.valueFormat = this.configObj.valueFormat || 'number'; this.valueFormat = this.configObj.valueFormat || 'number';
this.decimalPlaces = this.configObj.decimalPlaces || 0; this.decimalPlaces = this.configObj.decimalPlaces || 0;
this.aggregation = this.configObj.aggregation || 'sum'; this.aggregation = this.configObj.aggregation || 'sum';
...@@ -147,6 +189,24 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -147,6 +189,24 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.shadow = this.configObj.shadow || 'medium'; this.shadow = this.configObj.shadow || 'medium';
this.iconColor = this.configObj.iconColor || '#FFFFFF'; this.iconColor = this.configObj.iconColor || '#FFFFFF';
// Background configuration
this.backgroundType = this.configObj.backgroundType || 'solid';
this.gradientStartColor = this.configObj.gradientStartColor || '#3366FF';
this.gradientEndColor = this.configObj.gradientEndColor || '#00CCFF';
this.gradientDirection = this.configObj.gradientDirection || 'to bottom right';
this.gradientType = this.configObj.gradientType || 'linear';
this.backgroundImage = this.configObj.backgroundImage || '';
this.backgroundSize = this.configObj.backgroundSize || 'cover';
this.textShadow = this.configObj.textShadow || 'none';
// Icon configuration
this.iconLibrary = this.configObj.iconLibrary || 'material';
this.iconStyle = this.configObj.iconStyle || 'outline';
this.iconBackground = this.configObj.iconBackground || '#FFFFFF';
this.iconBorderRadius = this.configObj.iconBorderRadius || 0;
this.showIconBackground = this.configObj.showIconBackground !== undefined ? this.configObj.showIconBackground : false;
this.iconPulse = this.configObj.iconPulse !== undefined ? this.configObj.iconPulse : false;
// Typography configuration // Typography configuration
this.fontSize = this.configObj.fontSize || 16; this.fontSize = this.configObj.fontSize || 16;
this.valueFontSize = this.configObj.valueFontSize || 32; this.valueFontSize = this.configObj.valueFontSize || 32;
...@@ -176,6 +236,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -176,6 +236,8 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.animationEasing = this.configObj.animationEasing || 'ease'; this.animationEasing = this.configObj.animationEasing || 'ease';
this.hoverEffects = this.configObj.hoverEffects !== undefined ? this.configObj.hoverEffects : true; this.hoverEffects = this.configObj.hoverEffects !== undefined ? this.configObj.hoverEffects : true;
this.autoRefresh = this.configObj.autoRefresh !== undefined ? this.configObj.autoRefresh : false; this.autoRefresh = this.configObj.autoRefresh !== undefined ? this.configObj.autoRefresh : false;
this.animationDirection = this.configObj.animationDirection || 'normal';
this.animationIteration = this.configObj.animationIteration || '1';
// Interaction configuration // Interaction configuration
this.enableTooltip = this.configObj.enableTooltip !== undefined ? this.configObj.enableTooltip : true; this.enableTooltip = this.configObj.enableTooltip !== undefined ? this.configObj.enableTooltip : true;
...@@ -186,6 +248,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -186,6 +248,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.enableRefresh = this.configObj.enableRefresh !== undefined ? this.configObj.enableRefresh : true; this.enableRefresh = this.configObj.enableRefresh !== undefined ? this.configObj.enableRefresh : true;
this.clickAction = this.configObj.clickAction || 'none'; this.clickAction = this.configObj.clickAction || 'none';
this.customClickHandler = this.configObj.customClickHandler || ''; this.customClickHandler = this.configObj.customClickHandler || '';
this.hoverEffect = this.configObj.hoverEffect || 'scale';
this.tooltipContent = this.configObj.tooltipContent || '';
this.enableKeyboard = this.configObj.enableKeyboard !== undefined ? this.configObj.enableKeyboard : true;
this.enableFocus = this.configObj.enableFocus !== undefined ? this.configObj.enableFocus : true;
// Layout configuration // Layout configuration
this.width = this.configObj.width || 300; this.width = this.configObj.width || 300;
...@@ -196,6 +262,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -196,6 +262,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.maxHeight = this.configObj.maxHeight || 400; this.maxHeight = this.configObj.maxHeight || 400;
this.aspectRatio = this.configObj.aspectRatio || 'auto'; this.aspectRatio = this.configObj.aspectRatio || 'auto';
this.responsive = this.configObj.responsive !== undefined ? this.configObj.responsive : true; this.responsive = this.configObj.responsive !== undefined ? this.configObj.responsive : true;
this.widthUnit = this.configObj.widthUnit || 'px';
this.heightUnit = this.configObj.heightUnit || 'px';
this.fullWidth = this.configObj.fullWidth !== undefined ? this.configObj.fullWidth : false;
this.fullHeight = this.configObj.fullHeight !== undefined ? this.configObj.fullHeight : false;
// Data configuration // Data configuration
this.dataSource = this.configObj.dataSource || 'static'; this.dataSource = this.configObj.dataSource || 'static';
...@@ -204,6 +274,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -204,6 +274,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.cacheEnabled = this.configObj.cacheEnabled !== undefined ? this.configObj.cacheEnabled : false; this.cacheEnabled = this.configObj.cacheEnabled !== undefined ? this.configObj.cacheEnabled : false;
this.cacheDuration = this.configObj.cacheDuration || 300; this.cacheDuration = this.configObj.cacheDuration || 300;
this.dataTransform = this.configObj.dataTransform || ''; this.dataTransform = this.configObj.dataTransform || '';
this.dataValidation = this.configObj.dataValidation || 'none';
this.errorMessage = this.configObj.errorMessage || 'Failed to load data';
this.showDataCount = this.configObj.showDataCount !== undefined ? this.configObj.showDataCount : false;
this.showLastUpdated = this.configObj.showLastUpdated !== undefined ? this.configObj.showLastUpdated : false;
// Filter configuration // Filter configuration
this.enableFilter = this.configObj.enableFilter || false; this.enableFilter = this.configObj.enableFilter || false;
...@@ -219,6 +293,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -219,6 +293,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.conditionValue = this.configObj.conditionValue || ''; this.conditionValue = this.configObj.conditionValue || '';
this.trueColor = this.configObj.trueColor || '#10B981'; this.trueColor = this.configObj.trueColor || '#10B981';
this.falseColor = this.configObj.falseColor || '#EF4444'; this.falseColor = this.configObj.falseColor || '#EF4444';
this.trueIcon = this.configObj.trueIcon || 'check-circle';
this.falseIcon = this.configObj.falseIcon || 'x-circle';
this.showConditionalIcon = this.configObj.showConditionalIcon !== undefined ? this.configObj.showConditionalIcon : false;
this.animateConditionalChange = this.configObj.animateConditionalChange !== undefined ? this.configObj.animateConditionalChange : true;
// Security configuration // Security configuration
this.requireAuth = this.configObj.requireAuth !== undefined ? this.configObj.requireAuth : false; this.requireAuth = this.configObj.requireAuth !== undefined ? this.configObj.requireAuth : false;
...@@ -226,6 +304,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -226,6 +304,10 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
this.dataEncryption = this.configObj.dataEncryption !== undefined ? this.configObj.dataEncryption : false; this.dataEncryption = this.configObj.dataEncryption !== undefined ? this.configObj.dataEncryption : false;
this.auditLog = this.configObj.auditLog !== undefined ? this.configObj.auditLog : false; this.auditLog = this.configObj.auditLog !== undefined ? this.configObj.auditLog : false;
this.rateLimit = this.configObj.rateLimit || 0; this.rateLimit = this.configObj.rateLimit || 0;
this.permissionLevel = this.configObj.permissionLevel || 'read';
this.sessionTimeout = this.configObj.sessionTimeout || 30;
this.requireHttps = this.configObj.requireHttps !== undefined ? this.configObj.requireHttps : false;
this.enableCors = this.configObj.enableCors !== undefined ? this.configObj.enableCors : true;
// Handle gradient background for hex colors // Handle gradient background for hex colors
if (this.backgroundColor.startsWith('#')) { if (this.backgroundColor.startsWith('#')) {
...@@ -236,6 +318,13 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -236,6 +318,13 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
} }
override onDataUpdate(data: any[]): void { override onDataUpdate(data: any[]): void {
// Check if using static values
if (this.staticValue) {
this.value = this.staticValue;
this.label = this.staticLabel || this.label;
return;
}
// Transform data if transform function is provided // Transform data if transform function is provided
let transformedData = this.transformData(data); let transformedData = this.transformData(data);
...@@ -370,7 +459,7 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -370,7 +459,7 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
} }
} }
onReset(): void { override onReset(): void {
// Reset to default values // Reset to default values
this.title = 'KPI (Default)'; this.title = 'KPI (Default)';
this.value = '123,456'; this.value = '123,456';
...@@ -448,8 +537,7 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -448,8 +537,7 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
// Helper method to get computed styles for dynamic styling // Helper method to get computed styles for dynamic styling
getWidgetStyles(): { [key: string]: string } { getWidgetStyles(): { [key: string]: string } {
return { const styles: { [key: string]: string } = {
'background': this.backgroundColor,
'color': this.textColor, 'color': this.textColor,
'border-color': this.borderColor, 'border-color': this.borderColor,
'border-radius': `${this.borderRadius}px`, 'border-radius': `${this.borderRadius}px`,
...@@ -460,6 +548,32 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -460,6 +548,32 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
'font-weight': this.fontWeight, 'font-weight': this.fontWeight,
'font-family': this.fontFamily 'font-family': this.fontFamily
}; };
// Handle different background types
if (this.backgroundType === 'gradient') {
const gradientType = this.gradientType === 'radial' ? 'radial-gradient' : 'linear-gradient';
const direction = this.gradientType === 'radial' ? 'circle' : this.gradientDirection;
styles['background'] = `${gradientType}(${direction}, ${this.gradientStartColor}, ${this.gradientEndColor})`;
} else if (this.backgroundType === 'image') {
styles['background-image'] = `url(${this.backgroundImage})`;
styles['background-size'] = this.backgroundSize;
styles['background-position'] = 'center';
styles['background-repeat'] = 'no-repeat';
} else {
styles['background'] = this.backgroundColor;
}
// Add text shadow
if (this.textShadow !== 'none') {
const shadowMap: { [key: string]: string } = {
'small': '0 1px 2px rgba(0,0,0,0.1)',
'medium': '0 2px 4px rgba(0,0,0,0.2)',
'large': '0 4px 8px rgba(0,0,0,0.3)'
};
styles['text-shadow'] = shadowMap[this.textShadow] || 'none';
}
return styles;
} }
// Helper method to combine all styles // Helper method to combine all styles
...@@ -495,34 +609,73 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -495,34 +609,73 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
'slide': 'slideInUp', 'slide': 'slideInUp',
'bounce': 'bounceIn', 'bounce': 'bounceIn',
'pulse': 'pulse', 'pulse': 'pulse',
'flip': 'flipInX',
'zoom': 'zoomIn',
'rotate': 'rotateIn',
'shake': 'shake',
'none': 'none' 'none': 'none'
}; };
return { const styles: { [key: string]: string } = {
'animation': `${animationMap[this.animationType]} ${this.animationDuration}ms ${this.animationEasing}`, 'animation': `${animationMap[this.animationType]} ${this.animationDuration}ms ${this.animationEasing}`,
'animation-delay': `${this.animationDelay}ms`, 'animation-delay': `${this.animationDelay}ms`,
'animation-fill-mode': 'both' 'animation-fill-mode': 'both'
}; };
// Add animation direction and iteration
if (this.animationDirection !== 'normal') {
styles['animation-direction'] = this.animationDirection;
}
if (this.animationIteration !== '1') {
styles['animation-iteration-count'] = this.animationIteration;
}
return styles;
} }
// Helper method to get layout styles // Helper method to get layout styles
getLayoutStyles(): { [key: string]: string } { getLayoutStyles(): { [key: string]: string } {
const styles: { [key: string]: string } = { const styles: { [key: string]: string } = {};
'width': `${this.width}px`,
'height': `${this.height}px`, // Handle width
'min-width': `${this.minWidth}px`, if (this.fullWidth) {
'min-height': `${this.minHeight}px`, styles['width'] = '100%';
'max-width': `${this.maxWidth}px`, } else {
'max-height': `${this.maxHeight}px` styles['width'] = `${this.width}${this.widthUnit}`;
}; }
// Handle height
if (this.fullHeight) {
styles['height'] = '100%';
} else {
styles['height'] = `${this.height}${this.heightUnit}`;
}
// Handle min/max dimensions
if (this.minWidth > 0) {
styles['min-width'] = `${this.minWidth}px`;
}
if (this.minHeight > 0) {
styles['min-height'] = `${this.minHeight}px`;
}
if (this.maxWidth > 0) {
styles['max-width'] = `${this.maxWidth}px`;
}
if (this.maxHeight > 0) {
styles['max-height'] = `${this.maxHeight}px`;
}
// Handle aspect ratio // Handle aspect ratio
if (this.aspectRatio !== 'auto') { if (this.aspectRatio !== 'auto') {
const [width, height] = this.aspectRatio.split(':');
const ratio = parseFloat(height) / parseFloat(width);
styles['aspect-ratio'] = this.aspectRatio; styles['aspect-ratio'] = this.aspectRatio;
} }
// Handle responsive
if (this.responsive) {
styles['box-sizing'] = 'border-box';
}
return styles; return styles;
} }
...@@ -546,6 +699,21 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -546,6 +699,21 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
classes.push('responsive'); classes.push('responsive');
} }
// Add hover effect classes
if (this.hoverEffect !== 'none') {
classes.push(`hover-${this.hoverEffect}`);
}
// Add keyboard navigation classes
if (this.enableKeyboard) {
classes.push('keyboard-enabled');
}
// Add focus indicator classes
if (this.enableFocus) {
classes.push('focus-enabled');
}
return classes.join(' '); return classes.join(' ');
} }
...@@ -643,16 +811,37 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -643,16 +811,37 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
// Helper method to get data source info // Helper method to get data source info
getDataSourceInfo(): string { getDataSourceInfo(): string {
let info = '';
switch (this.dataSource) { switch (this.dataSource) {
case 'api': case 'api':
return `API: ${this.apiEndpoint}`; info = `API: ${this.apiEndpoint}`;
break;
case 'websocket': case 'websocket':
return 'WebSocket Connection'; info = 'WebSocket Connection';
break;
case 'file': case 'file':
return 'File Upload'; info = 'File Upload';
break;
default: default:
return 'Static Data'; info = 'Static Data';
}
// Add validation info
if (this.dataValidation !== 'none') {
info += ` (${this.dataValidation})`;
}
// Add data count if enabled
if (this.showDataCount && this.originalData) {
info += ` - ${this.originalData.length} records`;
} }
// Add last updated if enabled
if (this.showLastUpdated) {
info += ` - Updated: ${new Date().toLocaleTimeString()}`;
}
return info;
} }
// Export data functionality // Export data functionality
...@@ -793,6 +982,27 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -793,6 +982,27 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
} else { } else {
this.valueColor = this.falseColor; this.valueColor = this.falseColor;
} }
// Apply conditional icons if enabled
if (this.showConditionalIcon) {
if (conditionMet) {
this.icon = this.trueIcon;
} else {
this.icon = this.falseIcon;
}
}
// Apply conditional animation if enabled
if (this.animateConditionalChange) {
// Add a temporary class for animation
const element = document.querySelector(`[data-widget-id="${this.widgetId}"]`);
if (element) {
element.classList.add('conditional-change');
setTimeout(() => {
element.classList.remove('conditional-change');
}, 500);
}
}
} }
// Method to get shadow styles // Method to get shadow styles
...@@ -808,12 +1018,29 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent { ...@@ -808,12 +1018,29 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
// Method to get icon styles // Method to get icon styles
getIconStyles(): { [key: string]: string } { getIconStyles(): { [key: string]: string } {
return { const styles: { [key: string]: string } = {
'font-size': `${this.iconSize}px`, 'font-size': `${this.iconSize}px`,
'color': this.iconColor, 'color': this.iconColor,
'width': `${this.iconSize}px`, 'width': `${this.iconSize}px`,
'height': `${this.iconSize}px` 'height': `${this.iconSize}px`
}; };
// Add icon background if enabled
if (this.showIconBackground) {
styles['background-color'] = this.iconBackground;
styles['border-radius'] = `${this.iconBorderRadius}px`;
styles['padding'] = '8px';
styles['display'] = 'inline-flex';
styles['align-items'] = 'center';
styles['justify-content'] = 'center';
}
// Add pulse animation if enabled
if (this.iconPulse) {
styles['animation'] = 'pulse 2s infinite';
}
return styles;
} }
// Method to get value styles // Method to get value styles
......
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