Commit 244ac3e1 by Ooh-Ao

widget

parent 7d32073b
import { Component, OnInit, ViewChild, Type, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, ViewChild, Type, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router'; import { ActivatedRoute, RouterModule } from '@angular/router';
import { CommonModule, TitleCasePipe } from '@angular/common'; import { CommonModule, TitleCasePipe } from '@angular/common';
import { NgComponentOutlet } from '@angular/common'; import { NgComponentOutlet } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Observable, of, forkJoin, throwError } from 'rxjs'; import { Observable, of, forkJoin, throwError } from 'rxjs';
import { map, switchMap, tap, catchError } from 'rxjs/operators'; import { map, switchMap, tap, catchError, take } from 'rxjs/operators';
import { DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts'; // Import Syncfusion modules import { DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts'; // Import Syncfusion modules
import { MatDialog, MatDialogModule } from '@angular/material/dialog'; // Import MatDialog import { MatDialog, MatDialogModule } from '@angular/material/dialog'; // Import MatDialog
import { NotificationService } from '../../shared/services/notification.service'; import { NotificationService } from '../../shared/services/notification.service';
...@@ -17,6 +15,7 @@ import { WidgetDataService } from '../services/widget-data.service'; // Import n ...@@ -17,6 +15,7 @@ import { WidgetDataService } from '../services/widget-data.service'; // Import n
import { WidgetService } from '../services/widgets.service'; // Import WidgetService import { WidgetService } from '../services/widgets.service'; // Import WidgetService
import { DashboardStateService, SelectedDataset } from '../services/dashboard-state.service'; // Import SelectedDataset import { DashboardStateService, SelectedDataset } from '../services/dashboard-state.service'; // Import SelectedDataset
import { DatasetService } from '../services/dataset.service'; import { DatasetService } from '../services/dataset.service';
import { WidgetConfigGeneratorService } from '../services/widget-config-generator.service';
import { WidgetConfigComponent } from './widget-config/widget-config.component'; // Import WidgetConfigComponent import { WidgetConfigComponent } from './widget-config/widget-config.component'; // Import WidgetConfigComponent
...@@ -115,398 +114,6 @@ export class DashboardManagementComponent implements OnInit { ...@@ -115,398 +114,6 @@ export class DashboardManagementComponent implements OnInit {
public userDashboards: DashboardModel[] = []; public userDashboards: DashboardModel[] = [];
public selectedDashboardId: DashboardModel public selectedDashboardId: DashboardModel
// private localWidgets: WidgetModel[] = [
// new WidgetModel({
// id: 'local-sync-pivot',
// name: 'Syncfusion Pivot Table (Local)',
// component: 'SyncfusionPivotWidgetComponent',
// cols: 3,
// rows: 3,
// config: {
// title: 'Employee Salary Pivot',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// rows: [{ name: 'department' }],
// columns: [{ name: 'gender' }],
// values: [{ name: 'salary', caption: 'Total Salary' }],
// }
// }),
// new WidgetModel({
// id: 'local-sync-grid',
// name: 'Syncfusion Data Grid (Local)',
// component: 'SyncfusionDatagridWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'All Employee Data',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// columns: [
// { field: 'id', headerText: 'ID', width: 70 },
// { field: 'name', headerText: 'Name', width: 120 },
// { field: 'department', headerText: 'Department', width: 120 },
// { field: 'salary', headerText: 'Salary', width: 100 },
// { field: 'performanceScore', headerText: 'Performance', width: 120 },
// ]
// }
// }),
// new WidgetModel({
// id: 'local-sync-chart',
// name: 'Syncfusion Chart (Local)',
// component: 'SyncfusionChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Performance',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yField: 'performanceScore',
// xAxisTitle: 'Employee',
// yAxisTitle: 'Score',
// }
// }),
// new WidgetModel({
// id: 'local-area-chart',
// name: 'Area Chart (Local)',
// component: 'AreaChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Monthly Sales by Employee',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yField: 'salesAmount',
// xAxisTitle: 'Employee',
// yAxisTitle: 'Sales Amount',
// }
// }),
// new WidgetModel({
// id: 'local-bar-chart',
// name: 'Bar Chart (Local)',
// component: 'BarChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Performance Score',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yField: 'performanceScore',
// xAxisTitle: 'Employee',
// yAxisTitle: 'Performance Score',
// type: 'Column'
// }
// }),
// new WidgetModel({
// id: 'local-pie-chart',
// name: 'Pie Chart (Local)',
// component: 'PieChartWidgetComponent',
// cols: 2,
// rows: 2,
// config: {
// title: 'Gender Distribution',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'gender',
// yField: 'id',
// aggregation: 'count',
// }
// }),
// new WidgetModel({
// id: 'local-scatter-bubble-chart',
// name: 'Scatter/Bubble Chart (Local)',
// component: 'ScatterBubbleChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Performance vs. Salary (Scatter)',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'performanceScore',
// yField: 'salary',
// xAxisTitle: 'Performance Score',
// yAxisTitle: 'Salary',
// type: 'Scatter'
// }
// }),
// new WidgetModel({
// id: 'local-multi-row-card',
// name: 'Multi-Row Card (Local)',
// component: 'MultiRowCardWidgetComponent',
// cols: 2,
// rows: 2,
// config: {
// title: 'Employee Overview',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// labelField: 'department',
// valueField: 'salary',
// unitField: 'currency' // Assuming 'currency' field exists in employee-data.json or can be added
// }
// }),
// new WidgetModel({
// id: 'local-combo-chart',
// name: 'Combo Chart (Local)',
// component: 'ComboChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Sales and Performance',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yFields: ['salesAmount', 'performanceScore'],
// xAxisTitle: 'Employee',
// yAxisTitle: 'Value',
// series: [
// { type: 'Column', xName: 'name', yName: 'salesAmount', name: 'Sales' },
// { type: 'Line', xName: 'name', yName: 'performanceScore', name: 'Performance' }
// ]
// }
// }),
// new WidgetModel({
// id: 'local-doughnut-chart',
// name: 'Doughnut Chart (Local)',
// component: 'DoughnutChartWidgetComponent',
// cols: 2,
// rows: 2,
// config: {
// title: 'Department Distribution',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'department',
// yField: 'id',
// aggregation: 'count',
// }
// }),
// new WidgetModel({
// id: 'local-funnel-chart',
// name: 'Funnel Chart (Local)',
// component: 'FunnelChartWidgetComponent',
// cols: 2,
// rows: 3,
// config: {
// title: 'Sales Funnel',
// xField: 'stage',
// yField: 'value',
// }
// }),
// new WidgetModel({
// id: 'local-gauge-chart',
// name: 'Gauge Chart (Local)',
// component: 'GaugeChartWidgetComponent',
// cols: 2,
// rows: 2,
// config: {
// title: 'Average Performance Score',
// valueField: 'performanceScore', // Assuming 'performanceScore' is a numeric field in employee-data.json
// ranges: [
// { start: 0, end: 60, color: '#E0B9B9' },
// { start: 60, end: 80, color: '#B9D7EA' },
// { start: 80, end: 100, color: '#B9EAB9' }
// ]
// }
// }),
// new WidgetModel({
// id: 'local-simple-kpi',
// name: 'Simple KPI (Local)',
// component: 'SimpleKpiWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Total Employees',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// valueField: 'id',
// aggregation: 'count',
// unit: 'persons',
// trend: 'up',
// trendValue: '+10%'
// }
// }),
// new WidgetModel({
// id: 'local-filled-map',
// name: 'Filled Map (Local)',
// component: 'FilledMapWidgetComponent',
// cols: 4,
// rows: 3,
// config: {
// title: 'Employee Distribution by Country',
// source: { type: 'url', url: 'assets/data/employee-data.json' }, // Assuming employee-data.json has country and value
// countryField: 'country', // Assuming a 'country' field in employee-data.json
// valueField: 'employeeCount', // Assuming an 'employeeCount' field in employee-data.json
// colorMapping: [
// { value: 0, color: '#C3E6CB' },
// { value: 50, color: '#FFECB5' },
// { value: 100, color: '#F5C6CB' }
// ]
// }
// }),
// new WidgetModel({
// id: 'local-matrix',
// name: 'Matrix (Local)',
// component: 'MatrixWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Skills Matrix',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// columns: [
// { field: 'name', headerText: 'Employee Name' },
// { field: 'department', headerText: 'Department' },
// { field: 'skill1', headerText: 'Skill 1' }, // Assuming these fields exist or can be added to employee-data.json
// { field: 'skill2', headerText: 'Skill 2' },
// { field: 'skill3', headerText: 'Skill 3' },
// ]
// }
// }),
// new WidgetModel({
// id: 'local-slicer',
// name: 'Slicer (Local)',
// component: 'SlicerWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Filter by Department',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// optionsField: 'department', // Assuming 'department' field exists in employee-data.json
// }
// }),
// new WidgetModel({
// id: 'local-simple-table',
// name: 'Simple Table (Local)',
// component: 'SimpleTableWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Details',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// columns: [
// { field: 'id', headerText: 'ID' },
// { field: 'name', headerText: 'Name' },
// { field: 'department', headerText: 'Department' },
// { field: 'salary', headerText: 'Salary' },
// ]
// }
// }),
// new WidgetModel({
// id: 'local-waterfall-chart',
// name: 'Waterfall Chart (Local)',
// component: 'WaterfallChartWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Salary Changes',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yField: 'salaryChange', // Assuming 'salaryChange' field exists in employee-data.json
// xAxisTitle: 'Employee',
// yAxisTitle: 'Salary Change',
// }
// }),
// new WidgetModel({
// id: 'local-treemap',
// name: 'Treemap (Local)',
// component: 'TreemapWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Salary Progression',
// source: { type: 'url', url: 'assets/data/employee-data.json' },
// xField: 'name',
// yField: 'salary',
// xAxisTitle: 'Employee',
// yAxisTitle: 'Salary',
// }
// }),
// new WidgetModel({
// id: 'local-attendance-overview',
// name: 'Attendance Overview (Local)',
// component: 'AttendanceOverviewWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Daily Attendance Summary',
// source: { type: 'url', url: 'assets/data/sample1.json' }, // Assuming sample1.json has attendance data
// presentField: 'present',
// onLeaveField: 'onLeave',
// absentField: 'absent',
// }
// }),
// new WidgetModel({
// id: 'local-company-info',
// name: 'Company Info (Local)',
// component: 'CompanyInfoWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Our Company Details',
// source: { type: 'url', url: 'assets/data/sample1.json' }, // Assuming sample1.json has company info
// companyNameField: 'companyName',
// addressField: 'address',
// contactField: 'contact',
// }
// }),
// new WidgetModel({
// id: 'local-employee-directory',
// name: 'Employee Directory (Local)',
// component: 'EmployeeDirectoryWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'All Employees',
// source: { type: 'url', url: 'assets/data/employee-data.json' }, // Assuming employee-data.json has employee info
// nameField: 'name',
// positionField: 'position',
// departmentField: 'department',
// }
// }),
// new WidgetModel({
// id: 'local-headcount',
// name: 'Employee Headcount (Local)',
// component: 'HeadcountWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Current Headcount',
// source: { type: 'url', url: 'assets/data/employee-data.json' }, // Assuming employee-data.json has employee data
// categoryField: 'department', // Field to categorize headcount by
// }
// }),
// new WidgetModel({
// id: 'local-payroll-summary',
// name: 'Payroll Summary (Local)',
// component: 'PayrollSummaryWidgetComponent',
// cols: 2,
// rows: 1,
// config: {
// title: 'Latest Payroll Overview',
// source: { type: 'url', url: 'assets/data/sample1.json' }, // Assuming sample1.json has payroll data
// totalPayrollField: 'totalPayroll',
// employeesPaidField: 'employeesPaid',
// }
// }),
// new WidgetModel({
// id: 'local-payroll-details',
// name: 'Payroll Details (Local)',
// component: 'PayrollWidgetComponent',
// cols: 3,
// rows: 2,
// config: {
// title: 'Employee Payroll Details',
// source: { type: 'url', url: 'assets/data/employee-data.json' }, // Assuming employee-data.json has payroll details
// employeeNameField: 'name',
// payPeriodField: 'payPeriod',
// netPayField: 'netPay',
// }
// }),
// new WidgetModel({
// id: 'local-company-info-subfolder',
// name: 'Company Info (Subfolder Local)',
// component: 'CompanyInfoSubfolderWidgetComponent', // Use the new component name
// cols: 2,
// rows: 1,
// config: {
// title: 'Our Company Details (Subfolder)',
// source: { type: 'url', url: 'assets/data/sample1.json' }, // Assuming sample1.json has company info
// companyNameField: 'companyName',
// addressField: 'address',
// contactField: 'contact',
// }
// }),
// ];
private widgetComponentMap: { [key: string]: Type<any> } = { private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent, 'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent, 'HeadcountWidgetComponent': HeadcountWidgetComponent,
...@@ -548,7 +155,8 @@ export class DashboardManagementComponent implements OnInit { ...@@ -548,7 +155,8 @@ export class DashboardManagementComponent implements OnInit {
private dashboardStateService: DashboardStateService, private dashboardStateService: DashboardStateService,
private datasetService: DatasetService, // Inject DatasetService private datasetService: DatasetService, // Inject DatasetService
private dialog: MatDialog, private dialog: MatDialog,
private notificationService: NotificationService private notificationService: NotificationService,
private widgetConfigGenerator: WidgetConfigGeneratorService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
...@@ -559,13 +167,8 @@ export class DashboardManagementComponent implements OnInit { ...@@ -559,13 +167,8 @@ export class DashboardManagementComponent implements OnInit {
}) })
).subscribe(dashboards => { ).subscribe(dashboards => {
this.userDashboards = dashboards; this.userDashboards = dashboards;
// if (this.userDashboards.length > 0) {
// this.selectedDashboardId = this.userDashboards[0];
// this.loadSelectedDashboard();
// }
}); });
// Populate availableWidgets from WidgetService
this.widgetService.getListWidgets().pipe( this.widgetService.getListWidgets().pipe(
catchError(error => { catchError(error => {
this.notificationService.error('Error', 'Failed to load available widgets.'); this.notificationService.error('Error', 'Failed to load available widgets.');
...@@ -574,9 +177,9 @@ export class DashboardManagementComponent implements OnInit { ...@@ -574,9 +177,9 @@ export class DashboardManagementComponent implements OnInit {
).subscribe(widgets => { ).subscribe(widgets => {
this.availableWidgets = [...widgets].map(widget => ({ this.availableWidgets = [...widgets].map(widget => ({
...widget, ...widget,
config: widget.config || {} // Ensure config property exists config: widget.config || {}
})); }));
this.filterWidgets(); // Initialize filtered list this.filterWidgets();
}); });
} }
...@@ -588,7 +191,6 @@ export class DashboardManagementComponent implements OnInit { ...@@ -588,7 +191,6 @@ export class DashboardManagementComponent implements OnInit {
widget.thName.toLowerCase().includes(this.widgetSearchTerm.toLowerCase()) widget.thName.toLowerCase().includes(this.widgetSearchTerm.toLowerCase())
); );
} }
console.log(this.filteredAvailableWidgets)
} }
clearSearch(): void { clearSearch(): void {
...@@ -597,7 +199,6 @@ export class DashboardManagementComponent implements OnInit { ...@@ -597,7 +199,6 @@ export class DashboardManagementComponent implements OnInit {
} }
loadSelectedDashboard(): void { loadSelectedDashboard(): void {
console.log(this.selectedDashboardId)
if (this.selectedDashboardId) { if (this.selectedDashboardId) {
this.notificationService.info('Info', 'Loading dashboard data...'); this.notificationService.info('Info', 'Loading dashboard data...');
this.dashboardDataService.getDashboardById(this.selectedDashboardId.dashboardId).pipe( this.dashboardDataService.getDashboardById(this.selectedDashboardId.dashboardId).pipe(
...@@ -607,6 +208,18 @@ export class DashboardManagementComponent implements OnInit { ...@@ -607,6 +208,18 @@ export class DashboardManagementComponent implements OnInit {
}) })
).subscribe(dashboard => { ).subscribe(dashboard => {
if (dashboard) { if (dashboard) {
if (dashboard.widgets) {
dashboard.widgets.forEach(widget => {
if (widget.config && typeof widget.config === 'string') {
try {
widget.config = JSON.parse(widget.config);
} catch (e) {
console.error('Error parsing widget config string:', widget.config, e);
widget.config = {};
}
}
});
}
this.dashboardData = dashboard; this.dashboardData = dashboard;
this.panels = this.mapWidgetsToPanels(dashboard.widgets || []); this.panels = this.mapWidgetsToPanels(dashboard.widgets || []);
if (dashboard.datasetId) { if (dashboard.datasetId) {
...@@ -656,18 +269,6 @@ export class DashboardManagementComponent implements OnInit { ...@@ -656,18 +269,6 @@ export class DashboardManagementComponent implements OnInit {
mapWidgetsToPanels(widgets: WidgetModel[]): DashboardPanel[] { mapWidgetsToPanels(widgets: WidgetModel[]): DashboardPanel[] {
return widgets.map(widget => { return widgets.map(widget => {
let widgetConfig: any = {};
if (typeof widget.config === 'string') {
try {
widgetConfig = JSON.parse(widget.config);
} catch (e) {
console.error('Error parsing widget config string:', widget.config, e);
widgetConfig = {}; // Default to empty object on error
}
} else {
widgetConfig = widget.config || {};
}
return { return {
id: widget.widgetId, id: widget.widgetId,
header: widget.thName, header: widget.thName,
...@@ -676,29 +277,25 @@ export class DashboardManagementComponent implements OnInit { ...@@ -676,29 +277,25 @@ export class DashboardManagementComponent implements OnInit {
row: widget.y, row: widget.y,
col: widget.x, col: widget.x,
componentType: this.widgetComponentMap[widget.component], componentType: this.widgetComponentMap[widget.component],
componentInputs: { config: widgetConfig }, // Pass the config object as 'config' input componentInputs: { config: widget.config || {} },
originalWidget: widget // Add the original widget here originalWidget: widget
}; };
}); });
} }
saveLayout(): void { saveLayout(): void {
if (!this.dashboardData) return; if (!this.dashboardData) return;
// Create a deep copy to avoid mutating the component's state
const dashboardToSave = JSON.parse(JSON.stringify(this.dashboardData)); const dashboardToSave = JSON.parse(JSON.stringify(this.dashboardData));
// Stringify all widget configs before saving
if (dashboardToSave.widgets) { if (dashboardToSave.widgets) {
dashboardToSave.widgets.forEach((widget: WidgetModel) => { dashboardToSave.widgets.forEach((widget: WidgetModel) => {
if (widget.config && typeof widget.config === 'object') { const keysToProcess: Array<keyof WidgetModel> = ['config', 'perspective', 'data'];
widget.config = JSON.stringify(widget.config); keysToProcess.forEach(key => {
} if ((widget as any)[key] && typeof (widget as any)[key] === 'object') {
(widget as any)[key] = JSON.stringify((widget as any)[key]);
}
});
}); });
} }
console.log('Saving dashboard with stringified configs:', dashboardToSave);
this.dashboardDataService.saveDashboard(dashboardToSave).pipe( this.dashboardDataService.saveDashboard(dashboardToSave).pipe(
catchError(error => { catchError(error => {
this.notificationService.error('Error', 'Failed to save dashboard layout.'); this.notificationService.error('Error', 'Failed to save dashboard layout.');
...@@ -706,54 +303,35 @@ export class DashboardManagementComponent implements OnInit { ...@@ -706,54 +303,35 @@ export class DashboardManagementComponent implements OnInit {
}) })
).subscribe(() => { ).subscribe(() => {
this.notificationService.success('Success', 'Dashboard layout saved successfully!'); this.notificationService.success('Success', 'Dashboard layout saved successfully!');
// Optionally, reload the dashboard to ensure UI is consistent with saved state
this.loadSelectedDashboard(); this.loadSelectedDashboard();
}); });
} }
// Kept for compatibility with the sidebar, which is not the focus of this refactor
addWidgetToDashboard(widget: WidgetModel): void { addWidgetToDashboard(widget: WidgetModel): void {
if (!this.dashboardData) return; if (!this.dashboardData) {
if (this.dashboardData.widgets.find(w => w.widgetId === widget.widgetId)) { this.notificationService.warning('Warning', 'Please select or create a dashboard first.');
alert('Widget already exists in the dashboard.'); return;
return; }
if (!this.dashboardData.datasetId) {
this.notificationService.warning('Warning', 'Please select a dataset for the dashboard first.');
return;
} }
this.dashboardData.widgets.push(widget);
this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
this.notificationService.info('Info', 'Widget added to dashboard.');
}
// Test method to add the new data-driven widget this.dashboardStateService.selectedDataset$.pipe(
addDataDrivenWidget(): void { take(1)
console.log(this.dashboardData) ).subscribe(selectedDataset => {
if (!this.dashboardData) return; if (!selectedDataset || !selectedDataset.columns || selectedDataset.columns.length === 0) {
this.notificationService.error('Error', 'The selected dataset has no columns available.');
return;
}
const newWidget = new WidgetModel({ const newWidgetInstance = new WidgetModel(widget);
widgetId: `widget-${Date.now()}`, newWidgetInstance.config = this.widgetConfigGenerator.generateConfig(widget, selectedDataset.columns);
thName: 'Employee Data (New Arch)',
component: 'NewDataTableWidget',
cols: 4,
rows: 3,
x: 0,
y: 0,
config: {
title: 'Employee List',
source: {
type: 'url',
url: 'assets/data/sample1.json' // Using the sample data from DatasetService
},
columns: [
{ field: 'EmployeeID', headerText: 'ID', width: 70 },
{ field: 'FirstName', headerText: 'First Name', width: 120 },
{ field: 'LastName', headerText: 'Last Name', width: 120 },
{ field: 'Position', headerText: 'Position', width: 150 },
{ field: 'Department', headerText: 'Department', width: 150 },
]
}
});
this.dashboardData.widgets.push(newWidget); this.dashboardData!.widgets.push(newWidgetInstance);
this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets); this.panels = this.mapWidgetsToPanels(this.dashboardData!.widgets);
this.notificationService.info('Info', `Added widget: ${widget.thName}`);
});
} }
deleteDashboard(): void { deleteDashboard(): void {
...@@ -777,17 +355,10 @@ export class DashboardManagementComponent implements OnInit { ...@@ -777,17 +355,10 @@ export class DashboardManagementComponent implements OnInit {
this.dashboardData = updatedDashboard; this.dashboardData = updatedDashboard;
this.panels = this.mapWidgetsToPanels(updatedDashboard.widgets); this.panels = this.mapWidgetsToPanels(updatedDashboard.widgets);
this.notificationService.info('Info', 'Widget removed from dashboard.'); this.notificationService.info('Info', 'Widget removed from dashboard.');
// const updatedDashboard = { ...this.dashboardData, widgets: this.dashboardData.widgets.filter(w => w.id !== panelId) };
// this.dashboardDataService.saveDashboard(updatedDashboard).subscribe(updated => {
// this.dashboardData = updated;
// this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
// });
} }
} }
onDatasetSelected(dataset: DatasetModel): void { onDatasetSelected(dataset: DatasetModel): void {
console.log('Dataset selected:', dataset); // Added for debugging/clarity
if (this.dashboardData) { if (this.dashboardData) {
this.dashboardData.datasetId = dataset.itemId; this.dashboardData.datasetId = dataset.itemId;
this.dashboardData.templateId = dataset.templateId; this.dashboardData.templateId = dataset.templateId;
...@@ -796,135 +367,27 @@ export class DashboardManagementComponent implements OnInit { ...@@ -796,135 +367,27 @@ export class DashboardManagementComponent implements OnInit {
} }
} }
// getDatasetByTemplate(dataset: DatasetModel): void {
// this.datasetService.getDatasetByTemplate(dataset.templateId, dataset.fileName).subscribe(dataset => {
// if (dataset && this.dashboardData) {
// // Update the config of each widget with the new data
// this.dashboardData.widgets.forEach(widget => {
// if (widget.config) {
// widget.config.source.data = dataset; // Assuming the dataset is the data source
// }
// });
// // Remap panels to reflect the changes
// this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
// }
// }, (error) => {
// console.error('Error fetching dataset by template:', error);
// let dataset = [
// {
// "id": "E001",
// "name": "Alice Smith",
// "department": "Sales",
// "salary": 60000,
// "hireDate": "2020-01-15",
// "performanceScore": 85,
// "gender": "Female",
// "age": 30,
// "salesAmount": 120000,
// "region": "North"
// },
// {
// "id": "E002",
// "name": "Bob Johnson",
// "department": "Marketing",
// "salary": 55000,
// "hireDate": "2019-03-20",
// "performanceScore": 92,
// "gender": "Male",
// "age": 35,
// "salesAmount": 0,
// "region": "East"
// },
// {
// "id": "E003",
// "name": "Charlie Brown",
// "department": "Sales",
// "salary": 62000,
// "hireDate": "2021-07-01",
// "performanceScore": 78,
// "gender": "Male",
// "age": 28,
// "salesAmount": 110000,
// "region": "West"
// },
// {
// "id": "E004",
// "name": "Diana Prince",
// "department": "HR",
// "salary": 70000,
// "hireDate": "2018-11-10",
// "performanceScore": 95,
// "gender": "Female",
// "age": 40,
// "salesAmount": 0,
// "region": "South"
// },
// {
// "id": "E005",
// "name": "Eve Adams",
// "department": "Marketing",
// "salary": 58000,
// "hireDate": "2022-05-25",
// "performanceScore": 88,
// "gender": "Female",
// "age": 25,
// "salesAmount": 0,
// "region": "North"
// },
// {
// "id": "E006",
// "name": "Frank White",
// "department": "Sales",
// "salary": 65000,
// "hireDate": "2019-09-01",
// "performanceScore": 90,
// "gender": "Male",
// "age": 32,
// "salesAmount": 130000,
// "region": "East"
// }
// ]
// if (dataset && this.dashboardData) {
// // Update the config of each widget with the new data
// this.dashboardData.widgets.forEach(widget => {
// if (widget.config) {
// widget.config.source.data = dataset; // Assuming the dataset is the data source
// }
// });
// // Remap panels to reflect the changes
// this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
// }
// });
// }
openWidgetConfigDialog(panel: PanelModel & { componentType: Type<any>, componentInputs?: { [key: string]: any }, originalWidget: WidgetModel }): void { openWidgetConfigDialog(panel: PanelModel & { componentType: Type<any>, componentInputs?: { [key: string]: any }, originalWidget: WidgetModel }): void {
const widget = panel.originalWidget; const widget = panel.originalWidget;
console.log('Opening config dialog for widget:', widget); this.dashboardStateService.selectedDataset$.pipe(take(1)).subscribe((selectedDataset: SelectedDataset | null) => {
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
const availableColumns = selectedDataset ? selectedDataset.columns : []; const availableColumns = selectedDataset ? selectedDataset.columns : [];
const dialogRef = this.dialog.open(WidgetConfigComponent, { const dialogRef = this.dialog.open(WidgetConfigComponent, {
width: '400px', width: '600px',
data: { data: {
config: widget.config, widget: widget, // Pass the whole widget
availableColumns: availableColumns, availableColumns: availableColumns
widgetType: widget.component
} }
}); });
dialogRef.afterClosed().subscribe((result: any) => { dialogRef.afterClosed().subscribe((result: any) => {
if (result) { if (result) {
// Update the widget's config with the new values
const updatedWidget = { ...widget, config: result }; const updatedWidget = { ...widget, config: result };
if (this.dashboardData) { if (this.dashboardData) {
const widgetIndex = this.dashboardData.widgets.findIndex(w => w.widgetId === updatedWidget.widgetId); const widgetIndex = this.dashboardData.widgets.findIndex(w => w.widgetId === updatedWidget.widgetId);
if (widgetIndex > -1) { if (widgetIndex > -1) {
this.dashboardData.widgets[widgetIndex] = updatedWidget; this.dashboardData.widgets[widgetIndex] = updatedWidget;
// Re-map panels to reflect changes
this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets); this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
// Save the updated dashboard
// this.dashboardDataService.saveDashboard(this.dashboardData).subscribe();
} }
} }
} }
...@@ -933,10 +396,8 @@ export class DashboardManagementComponent implements OnInit { ...@@ -933,10 +396,8 @@ export class DashboardManagementComponent implements OnInit {
} }
onPanelChange(args: any): void { onPanelChange(args: any): void {
console.log('onPanelChange triggered:', args);
if (this.dashboardData && args.changedPanels) { if (this.dashboardData && args.changedPanels) {
args.changedPanels.forEach((changedPanel: PanelModel) => { args.changedPanels.forEach((changedPanel: PanelModel) => {
console.log('Panel changed:', changedPanel);
const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === changedPanel.id); const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === changedPanel.id);
if (widgetIndex > -1) { if (widgetIndex > -1) {
const updatedWidget = { ...this.dashboardData!.widgets[widgetIndex] }; const updatedWidget = { ...this.dashboardData!.widgets[widgetIndex] };
...@@ -945,12 +406,8 @@ export class DashboardManagementComponent implements OnInit { ...@@ -945,12 +406,8 @@ export class DashboardManagementComponent implements OnInit {
updatedWidget.x = changedPanel.col!; updatedWidget.x = changedPanel.col!;
updatedWidget.y = changedPanel.row!; updatedWidget.y = changedPanel.row!;
this.dashboardData!.widgets[widgetIndex] = updatedWidget; this.dashboardData!.widgets[widgetIndex] = updatedWidget;
console.log('Updated widget data:', updatedWidget);
} }
}); });
console.log('Current dashboard data after change:', this.dashboardData);
// Save the updated dashboard after all panels have been processed
// this.dashboardDataService.saveDashboard(this.dashboardData!).subscribe();
} }
} }
} }
\ No newline at end of file
...@@ -652,6 +652,7 @@ ...@@ -652,6 +652,7 @@
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions align="end"> <mat-dialog-actions align="end">
<button mat-stroked-button (click)="resetConfig()" class="mr-auto">Reset to Default</button>
<button mat-button (click)="onCancel()">Cancel</button> <button mat-button (click)="onCancel()">Cancel</button>
<button mat-raised-button color="primary" (click)="onSave()">Save</button> <button mat-raised-button color="primary" (click)="onSave()">Save</button>
</mat-dialog-actions> </mat-dialog-actions>
...@@ -9,11 +9,12 @@ import { MatButtonModule } from '@angular/material/button'; ...@@ -9,11 +9,12 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { WidgetConfigGeneratorService } from '../../services/widget-config-generator.service';
import { WidgetModel } from '../../models/widgets.model';
export interface WidgetConfigDialogData { export interface WidgetConfigDialogData {
config: any; widget: WidgetModel;
availableColumns: string[]; availableColumns: string[];
widgetType: string;
} }
@Component({ @Component({
...@@ -41,27 +42,26 @@ export class WidgetConfigComponent implements OnInit { ...@@ -41,27 +42,26 @@ export class WidgetConfigComponent implements OnInit {
constructor( constructor(
public dialogRef: MatDialogRef<WidgetConfigComponent>, public dialogRef: MatDialogRef<WidgetConfigComponent>,
@Inject(MAT_DIALOG_DATA) public data: WidgetConfigDialogData @Inject(MAT_DIALOG_DATA) public data: WidgetConfigDialogData,
private widgetConfigGenerator: WidgetConfigGeneratorService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.availableColumns = this.data.availableColumns; this.availableColumns = this.data.availableColumns;
this.widgetType = this.data.widgetType; this.widgetType = this.data.widget.component;
// Handle config whether it is a string or an object // Handle config whether it is a string or an object
try { try {
if (typeof this.data.config === 'string') { if (typeof this.data.widget.config === 'string') {
this.currentConfig = JSON.parse(this.data.config); this.currentConfig = JSON.parse(this.data.widget.config);
} else { } else {
// Deep copy config to avoid direct mutation of the original object this.currentConfig = JSON.parse(JSON.stringify(this.data.widget.config || {}));
this.currentConfig = JSON.parse(JSON.stringify(this.data.config || {}));
} }
} catch (e) { } catch (e) {
console.error('Error parsing widget config:', e); console.error('Error parsing widget config:', e);
this.currentConfig = {}; // Default to empty object on error this.currentConfig = {};
} }
// Initialize arrays if they don't exist
const gridLikeWidgets = ['SyncfusionDatagridWidgetComponent', 'NewDataTableWidget', 'MatrixWidgetComponent']; const gridLikeWidgets = ['SyncfusionDatagridWidgetComponent', 'NewDataTableWidget', 'MatrixWidgetComponent'];
if (gridLikeWidgets.includes(this.widgetType) && !this.currentConfig.columns) { if (gridLikeWidgets.includes(this.widgetType) && !this.currentConfig.columns) {
this.currentConfig.columns = []; this.currentConfig.columns = [];
...@@ -81,6 +81,10 @@ export class WidgetConfigComponent implements OnInit { ...@@ -81,6 +81,10 @@ export class WidgetConfigComponent implements OnInit {
} }
} }
resetConfig(): void {
this.currentConfig = this.widgetConfigGenerator.generateConfig(this.data.widget, this.data.availableColumns);
}
// Methods for Grid-like Widgets // Methods for Grid-like Widgets
addGridColumn() { addGridColumn() {
this.currentConfig.columns.push({ field: '', headerText: '', width: 100, isPrimary: false }); this.currentConfig.columns.push({ field: '', headerText: '', width: 100, isPrimary: false });
...@@ -123,7 +127,6 @@ export class WidgetConfigComponent implements OnInit { ...@@ -123,7 +127,6 @@ export class WidgetConfigComponent implements OnInit {
onSave(): void { onSave(): void {
// No more JSON parsing needed for the refactored widgets
this.dialogRef.close(this.currentConfig); this.dialogRef.close(this.currentConfig);
} }
......
...@@ -114,6 +114,22 @@ export class DashboardViewerComponent implements OnInit { ...@@ -114,6 +114,22 @@ export class DashboardViewerComponent implements OnInit {
).subscribe({ ).subscribe({
next: dashboard => { next: dashboard => {
if (dashboard) { if (dashboard) {
if (dashboard.widgets) {
dashboard.widgets.forEach(widget => {
const keysToProcess: Array<keyof WidgetModel> = ['config', 'perspective', 'data'];
keysToProcess.forEach(key => {
if ((widget as any)[key] && typeof (widget as any)[key] === 'string') {
try {
(widget as any)[key] = JSON.parse((widget as any)[key] as string);
} catch (e) {
console.error(`Error parsing widget ${key} string:`, (widget as any)[key], e);
(widget as any)[key] = {};
}
}
});
});
}
this.dashboardData = dashboard; this.dashboardData = dashboard;
this.panels = this.mapWidgetsToPanels(dashboard.widgets || []); this.panels = this.mapWidgetsToPanels(dashboard.widgets || []);
if (dashboard.datasetId) { if (dashboard.datasetId) {
......
import { Injectable } from '@angular/core';
import { WidgetModel } from '../models/widgets.model';
@Injectable({
providedIn: 'root'
})
export class WidgetConfigGeneratorService {
constructor() { }
public generateConfig(widget: WidgetModel, columns: string[]): any {
let newConfig: any;
// Handle config whether it is a string or an object
try {
if (typeof widget.config === 'string') {
newConfig = JSON.parse(widget.config);
} else {
// Deep copy config to avoid direct mutation of the original object
newConfig = JSON.parse(JSON.stringify(widget.config || {}));
}
} catch (e) {
console.error('Error parsing widget config in generator service:', e);
newConfig = {}; // Default to empty object on error
}
const col1 = columns.length > 0 ? columns[0] : '';
const col2 = columns.length > 1 ? columns[1] : col1;
switch (widget.component) {
case 'SyncfusionDatagridWidgetComponent':
case 'MatrixWidgetComponent':
case 'SimpleTableWidgetComponent':
case 'NewDataTableWidget':
newConfig.title = widget.thName;
newConfig.columns = columns.map(c => ({ field: c, headerText: c, width: 150 }));
break;
case 'BarChartWidgetComponent':
case 'AreaChartWidgetComponent':
case 'PieChartWidgetComponent':
case 'DoughnutChartWidgetComponent':
case 'WaterfallChartWidgetComponent':
newConfig.title = widget.thName;
newConfig.xField = col1;
newConfig.yField = col2;
break;
case 'SimpleKpiWidgetComponent':
case 'GaugeChartWidgetComponent':
newConfig.title = widget.thName;
newConfig.valueField = col1;
break;
case 'SlicerWidgetComponent':
newConfig.title = widget.thName;
newConfig.optionsField = col1;
break;
default:
newConfig.title = widget.thName;
break;
}
return newConfig;
}
}
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<link rel="icon" type="image/x-icon" href="./assets/images/brand-logos/favicon.ico"> <link rel="icon" type="image/x-icon" href="./assets/images/brand-logos/favicon.ico">
<link rel="stylesheet" href="assets/JS/pace/themes/silver/pace-theme-flash.css" /> <link rel="stylesheet" href="assets/JS/pace/themes/silver/pace-theme-flash.css" />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100..900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- <link <!-- <link
href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700;800&display=swap"
rel="stylesheet" rel="stylesheet"
......
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