Commit 9e7c3447 by Ooh-Ao

คลังวิดเจ็ท

parent 71f98397
...@@ -51,20 +51,20 @@ ...@@ -51,20 +51,20 @@
<!-- Widget List --> <!-- Widget List -->
<div class="widget-list space-y-2"> <div class="widget-list space-y-2">
<div <div
*ngFor="let widget of filteredAvailableWidgets" *ngFor="let menuItem of filteredAvailableWidgets"
(click)="addWidgetToDashboard(widget)" (click)="addWidgetToDashboard(menuItem)"
class="widget-item p-3 rounded-lg hover:bg-gray-100 cursor-pointer transition-colors duration-200" class="widget-item p-3 rounded-lg hover:bg-gray-100 cursor-pointer transition-colors duration-200"
> >
<p class="font-semibold text-gray-700">{{ widget.thName }}</p> <p class="font-semibold text-gray-700">{{ menuItem.widget.thName }}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
Size: {{ widget.cols }}x{{ widget.rows }} Size: {{ menuItem.widget.cols }}x{{ menuItem.widget.rows }}
</p> </p>
</div> </div>
<p <p
*ngIf="filteredAvailableWidgets.length === 0" *ngIf="filteredAvailableWidgets.length === 0"
class="text-gray-500 text-center mt-4" class="text-gray-500 text-center mt-4"
> >
No widgets found. No widgets found for this dataset.
</p> </p>
</div> </div>
</div> </div>
......
import { Component, OnInit, ViewChild, Type, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, ViewChild, Type } 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 { throwError } from 'rxjs';
import { map, switchMap, tap, catchError, take } from 'rxjs/operators'; import { map, switchMap, catchError, take } from 'rxjs/operators';
import { DashboardLayoutComponent, DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts'; // Import Syncfusion modules import { DashboardLayoutComponent, DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts';
import { MatDialog, MatDialogModule } from '@angular/material/dialog'; // Import MatDialog import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NotificationService } from '../../shared/services/notification.service'; import { NotificationService } from '../../shared/services/notification.service';
import { DashboardModel, WidgetModel, DatasetModel } from '../models/widgets.model'; import { DashboardModel, WidgetModel, DatasetModel } from '../models/widgets.model';
import { MenuItemsWidget } from '../models/m-menuitems-widget.model';
import { DashboardDataService } from '../services/dashboard-data.service'; import { DashboardDataService } from '../services/dashboard-data.service';
import { WidgetDataService } from '../services/widget-data.service'; // Import new service import { MMenuitemsWidgetService } from '../services/m-menuitems-widget.service';
import { WidgetService } from '../services/widgets.service'; // Import WidgetService import { DashboardStateService, SelectedDataset } from '../services/dashboard-state.service';
import { DashboardStateService, SelectedDataset } from '../services/dashboard-state.service'; // Import SelectedDataset
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 all the widget components // Import all the widget components
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component'; import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
import { CompanyInfoSubfolderWidgetComponent } from '../widgets/company-info-widget/company-info-widget.component'; // New import
import { HeadcountWidgetComponent } from '../widgets/headcount-widget.component'; import { HeadcountWidgetComponent } from '../widgets/headcount-widget.component';
import { AttendanceOverviewWidgetComponent } from '../widgets/attendance-overview-widget.component'; import { AttendanceOverviewWidgetComponent } from '../widgets/attendance-overview-widget.component';
import { PayrollSummaryWidgetComponent } from '../widgets/payroll-summary-widget.component'; import { PayrollSummaryWidgetComponent } from '../widgets/payroll-summary-widget.component';
import { EmployeeDirectoryWidgetComponent } from '../widgets/employee-directory-widget.component'; import { EmployeeDirectoryWidgetComponent } from '../widgets/employee-directory-widget.component';
import { KpiWidgetComponent } from '../widgets/kpi-widget/kpi-widget.component';
import { WelcomeWidgetComponent } from '../widgets/welcome-widget/welcome-widget.component'; import { WelcomeWidgetComponent } from '../widgets/welcome-widget/welcome-widget.component';
import { ChartWidgetComponent } from '../widgets/chart-widget/chart-widget.component';
import { QuickLinksWidgetComponent } from '../widgets/quick-links-widget/quick-links-widget.component'; import { QuickLinksWidgetComponent } from '../widgets/quick-links-widget/quick-links-widget.component';
import { SyncfusionDatagridWidgetComponent } from '../widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component'; import { SyncfusionDatagridWidgetComponent } from '../widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component';
import { SyncfusionPivotWidgetComponent } from '../widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component'; import { SyncfusionPivotWidgetComponent } from '../widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component';
import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widget/syncfusion-chart-widget.component'; import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widget/syncfusion-chart-widget.component';
import { DatasetPickerComponent } from './dataset-picker.component'; import { DatasetPickerComponent } from './dataset-picker.component';
import { DataTableWidgetComponent } from '../widgets/dynamic-widgets/data-table-widget.component'; // Import new widget
import { AccumulationChartAllModule, ChartAllModule } from '@syncfusion/ej2-angular-charts';
import { HttpClientModule } from '@angular/common/http';
import { AreaChartWidgetComponent } from '../widgets/area-chart-widget/area-chart-widget.component';
import { BarChartWidgetComponent } from '../widgets/bar-chart-widget/bar-chart-widget.component';
import { PieChartWidgetComponent } from '../widgets/pie-chart-widget/pie-chart-widget.component';
import { ScatterBubbleChartWidgetComponent } from '../widgets/scatter-bubble-chart-widget/scatter-bubble-chart-widget.component';
import { MultiRowCardWidgetComponent } from '../widgets/multi-row-card-widget/multi-row-card-widget.component';
import { ComboChartWidgetComponent } from '../widgets/combo-chart-widget/combo-chart-widget.component';
import { DoughnutChartWidgetComponent } from '../widgets/doughnut-chart-widget/doughnut-chart-widget.component';
import { FunnelChartWidgetComponent } from '../widgets/funnel-chart-widget/funnel-chart-widget.component';
import { GaugeChartWidgetComponent } from '../widgets/gauge-chart-widget/gauge-chart-widget.component';
import { SimpleKpiWidgetComponent } from '../widgets/simple-kpi-widget/simple-kpi-widget.component'; import { SimpleKpiWidgetComponent } from '../widgets/simple-kpi-widget/simple-kpi-widget.component';
import { FilledMapWidgetComponent } from '../widgets/filled-map-widget/filled-map-widget.component'; import { ChartAllModule, AccumulationChartAllModule } from '@syncfusion/ej2-angular-charts';
import { MatrixWidgetComponent } from '../widgets/matrix-widget/matrix-widget.component'; import { HttpClientModule } from '@angular/common/http';
import { SlicerWidgetComponent } from '../widgets/slicer-widget/slicer-widget.component';
import { SimpleTableWidgetComponent } from '../widgets/simple-table-widget/simple-table-widget.component';
import { WaterfallChartWidgetComponent } from '../widgets/waterfall-chart-widget/waterfall-chart-widget.component';
import { TreemapWidgetComponent } from '../widgets/treemap-widget/treemap-widget.component';
export interface DashboardPanel extends PanelModel { export interface DashboardPanel extends PanelModel {
componentType: Type<any>; componentType: Type<any>;
...@@ -75,27 +53,17 @@ export interface DashboardPanel extends PanelModel { ...@@ -75,27 +53,17 @@ export interface DashboardPanel extends PanelModel {
SyncfusionPivotWidgetComponent, SyncfusionPivotWidgetComponent,
SyncfusionChartWidgetComponent, SyncfusionChartWidgetComponent,
DatasetPickerComponent, DatasetPickerComponent,
DataTableWidgetComponent, // Add new widget to imports WelcomeWidgetComponent,
AreaChartWidgetComponent, QuickLinksWidgetComponent,
BarChartWidgetComponent, HeadcountWidgetComponent,
PieChartWidgetComponent, AttendanceOverviewWidgetComponent,
ScatterBubbleChartWidgetComponent, PayrollSummaryWidgetComponent,
MultiRowCardWidgetComponent, EmployeeDirectoryWidgetComponent,
ComboChartWidgetComponent,
DoughnutChartWidgetComponent,
FunnelChartWidgetComponent,
GaugeChartWidgetComponent,
SimpleKpiWidgetComponent, SimpleKpiWidgetComponent,
FilledMapWidgetComponent,
MatrixWidgetComponent,
SlicerWidgetComponent,
SimpleTableWidgetComponent,
WaterfallChartWidgetComponent,
TreemapWidgetComponent,
DashboardLayoutModule, DashboardLayoutModule,
ChartAllModule, ChartAllModule,
AccumulationChartAllModule, // Add Syncfusion DashboardLayoutModule AccumulationChartAllModule,
MatDialogModule // Add MatDialogModule MatDialogModule
], ],
templateUrl: './dashboard-management.component.html', templateUrl: './dashboard-management.component.html',
styleUrls: ['./dashboard-management.component.scss'], styleUrls: ['./dashboard-management.component.scss'],
...@@ -104,59 +72,35 @@ export class DashboardManagementComponent implements OnInit { ...@@ -104,59 +72,35 @@ export class DashboardManagementComponent implements OnInit {
@ViewChild('editLayout') public layout!: DashboardLayoutComponent; @ViewChild('editLayout') public layout!: DashboardLayoutComponent;
public panels: DashboardPanel[] = []; public panels: DashboardPanel[] = [];
public cellSpacing: number[] = [10, 10]; public cellSpacing: number[] = [10, 10];
public mediaQuery: string = 'max-width: 700px';
public availableWidgets: WidgetModel[] = []; public availableWidgets: MenuItemsWidget[] = [];
public filteredAvailableWidgets: WidgetModel[] = []; public filteredAvailableWidgets: MenuItemsWidget[] = [];
public widgetSearchTerm: string = ''; public widgetSearchTerm: string = '';
public dashboardData: DashboardModel | null = null; public dashboardData: DashboardModel | null = null;
public userDashboards: DashboardModel[] = []; public userDashboards: DashboardModel[] = [];
public selectedDashboardId: DashboardModel public selectedDashboardId: DashboardModel | null = null;
private widgetComponentMap: { [key: string]: Type<any> } = { private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent, CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent, HeadcountWidgetComponent,
'AttendanceOverviewWidgetComponent': AttendanceOverviewWidgetComponent, AttendanceOverviewWidgetComponent,
'PayrollSummaryWidgetComponent': PayrollSummaryWidgetComponent, PayrollSummaryWidgetComponent,
'EmployeeDirectoryWidgetComponent': EmployeeDirectoryWidgetComponent, EmployeeDirectoryWidgetComponent,
'KpiWidgetComponent': KpiWidgetComponent, WelcomeWidgetComponent,
'WelcomeWidgetComponent': WelcomeWidgetComponent, QuickLinksWidgetComponent,
'ChartWidgetComponent': ChartWidgetComponent, SyncfusionDatagridWidgetComponent,
'QuickLinksWidgetComponent': QuickLinksWidgetComponent, SyncfusionPivotWidgetComponent,
'SyncfusionDatagridWidgetComponent': SyncfusionDatagridWidgetComponent, SyncfusionChartWidgetComponent,
'SyncfusionPivotWidgetComponent': SyncfusionPivotWidgetComponent, SimpleKpiWidgetComponent,
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent,
'AreaChartWidgetComponent': AreaChartWidgetComponent,
'BarChartWidgetComponent': BarChartWidgetComponent,
'PieChartWidgetComponent': PieChartWidgetComponent,
'ScatterBubbleChartWidgetComponent': ScatterBubbleChartWidgetComponent,
'MultiRowCardWidgetComponent': MultiRowCardWidgetComponent,
'ComboChartWidgetComponent': ComboChartWidgetComponent,
'DoughnutChartWidgetComponent': DoughnutChartWidgetComponent,
'FunnelChartWidgetComponent': FunnelChartWidgetComponent,
'GaugeChartWidgetComponent': GaugeChartWidgetComponent,
'SimpleKpiWidgetComponent': SimpleKpiWidgetComponent,
'FilledMapWidgetComponent': FilledMapWidgetComponent,
'MatrixWidgetComponent': MatrixWidgetComponent,
'SlicerWidgetComponent': SlicerWidgetComponent,
'SimpleTableWidgetComponent': SimpleTableWidgetComponent,
'WaterfallChartWidgetComponent': WaterfallChartWidgetComponent,
'TreemapWidgetComponent': TreemapWidgetComponent,
'NewDataTableWidget': DataTableWidgetComponent, // Add new widget to map
'CompanyInfoSubfolderWidgetComponent': CompanyInfoSubfolderWidgetComponent // Add new widget to map
}; };
constructor( constructor(
private route: ActivatedRoute,
private dashboardDataService: DashboardDataService, private dashboardDataService: DashboardDataService,
private widgetDataService: WidgetDataService, // Inject new service private mMenuitemsWidgetService: MMenuitemsWidgetService,
private widgetService: WidgetService, // Inject WidgetService
private dashboardStateService: DashboardStateService, private dashboardStateService: DashboardStateService,
private datasetService: DatasetService, // Inject DatasetService
private dialog: MatDialog, private dialog: MatDialog,
private notificationService: NotificationService, private notificationService: NotificationService
private widgetConfigGenerator: WidgetConfigGeneratorService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
...@@ -168,27 +112,14 @@ export class DashboardManagementComponent implements OnInit { ...@@ -168,27 +112,14 @@ export class DashboardManagementComponent implements OnInit {
).subscribe(dashboards => { ).subscribe(dashboards => {
this.userDashboards = dashboards; this.userDashboards = dashboards;
}); });
this.widgetService.getListWidgets().pipe(
catchError(error => {
this.notificationService.error('Error', 'Failed to load available widgets.');
return throwError(() => error);
})
).subscribe(widgets => {
this.availableWidgets = [...widgets].map(widget => ({
...widget,
config: widget.config || {}
}));
this.filterWidgets();
});
} }
filterWidgets(): void { filterWidgets(): void {
if (!this.widgetSearchTerm) { if (!this.widgetSearchTerm) {
this.filteredAvailableWidgets = [...this.availableWidgets]; this.filteredAvailableWidgets = [...this.availableWidgets];
} else { } else {
this.filteredAvailableWidgets = this.availableWidgets.filter(widget => this.filteredAvailableWidgets = this.availableWidgets.filter(menuItem =>
widget.thName.toLowerCase().includes(this.widgetSearchTerm.toLowerCase()) menuItem.widget.thName.toLowerCase().includes(this.widgetSearchTerm.toLowerCase())
); );
} }
} }
...@@ -208,29 +139,28 @@ export class DashboardManagementComponent implements OnInit { ...@@ -208,29 +139,28 @@ 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) {
this.dashboardStateService.selectDataset(dashboard.datasetId); this.dashboardStateService.selectDataset(dashboard.datasetId);
this.loadWidgetsForDataset(dashboard.datasetId);
} else {
// Clear widget list if no dataset is selected
this.availableWidgets = [];
this.filterWidgets();
} }
// this.notificationService.success('Success', 'Dashboard loaded successfully!');
} }
}); });
} }
} }
loadWidgetsForDataset(datasetId: string): void {
this.mMenuitemsWidgetService.getWidgetsForDataset(datasetId).subscribe(widgets => {
this.availableWidgets = widgets;
this.filterWidgets();
});
}
createNewDashboard(): void { createNewDashboard(): void {
const newDashboardName = prompt('Enter a name for the new dashboard:'); const newDashboardName = prompt('Enter a name for the new dashboard:');
if (newDashboardName) { if (newDashboardName) {
...@@ -242,12 +172,8 @@ export class DashboardManagementComponent implements OnInit { ...@@ -242,12 +172,8 @@ export class DashboardManagementComponent implements OnInit {
}); });
this.dashboardDataService.saveDashboard(newDashboard).pipe( this.dashboardDataService.saveDashboard(newDashboard).pipe(
switchMap(addedDashboard => { switchMap(addedDashboard => {
// After saving, refetch the list of all dashboards
return this.dashboardDataService.getDashboards().pipe( return this.dashboardDataService.getDashboards().pipe(
map(dashboards => ({ map(dashboards => ({ addedDashboard, dashboards }))
addedDashboard,
dashboards
}))
); );
}), }),
catchError(error => { catchError(error => {
...@@ -255,9 +181,9 @@ export class DashboardManagementComponent implements OnInit { ...@@ -255,9 +181,9 @@ export class DashboardManagementComponent implements OnInit {
return throwError(() => error); return throwError(() => error);
}) })
).subscribe(({ addedDashboard, dashboards }) => { ).subscribe(({ addedDashboard, dashboards }) => {
this.userDashboards = dashboards; // Update the local list with the fresh list this.userDashboards = dashboards;
this.selectedDashboardId = addedDashboard; // Select the new dashboard this.selectedDashboardId = addedDashboard;
this.loadSelectedDashboard(); // Load the new dashboard this.loadSelectedDashboard();
this.notificationService.success('Success', 'New dashboard created successfully!'); this.notificationService.success('Success', 'New dashboard created successfully!');
}); });
} }
...@@ -271,34 +197,57 @@ export class DashboardManagementComponent implements OnInit { ...@@ -271,34 +197,57 @@ export class DashboardManagementComponent implements OnInit {
return throwError(() => error); return throwError(() => error);
}) })
).subscribe(() => { ).subscribe(() => {
// this.notificationService.success('Success', 'Dashboard name saved successfully!'); this.notificationService.success('Success', 'Dashboard name saved successfully!');
}); });
} }
} }
mapWidgetsToPanels(widgets: WidgetModel[]): DashboardPanel[] { deleteDashboard(): void {
return widgets.map(widget => { if (!this.selectedDashboardId) return;
return {
id: widget.widgetId, if (confirm('Are you sure you want to delete this dashboard?')) {
header: widget.thName, this.dashboardDataService.deleteDashboard(this.selectedDashboardId).pipe(
sizeX: widget.cols, switchMap(() => {
sizeY: widget.rows, // Refresh the list of dashboards after deletion
row: widget.y, return this.dashboardDataService.getDashboards();
col: widget.x, }),
componentType: this.widgetComponentMap[widget.component], catchError(error => {
componentInputs: { config: widget.config || {} }, this.notificationService.error('Error', 'Failed to delete dashboard.');
originalWidget: widget return throwError(() => error);
}; })
}); ).subscribe(dashboards => {
this.notificationService.success('Success', 'Dashboard deleted successfully!');
this.userDashboards = dashboards;
// Reset selection
this.selectedDashboardId = null;
this.dashboardData = null;
this.panels = [];
this.availableWidgets = [];
this.filterWidgets();
});
}
}
onPanelChange(args: any): void {
if (this.dashboardData && args.changedPanels) {
args.changedPanels.forEach((changedPanel: PanelModel) => {
const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === changedPanel.id);
if (widgetIndex > -1) {
const updatedWidget = { ...this.dashboardData!.widgets[widgetIndex] };
updatedWidget.cols = changedPanel.sizeX!;
updatedWidget.rows = changedPanel.sizeY!;
updatedWidget.x = changedPanel.col!;
updatedWidget.y = changedPanel.row!;
this.dashboardData!.widgets[widgetIndex] = updatedWidget;
}
});
}
} }
saveLayout(): void { saveLayout(): void {
if (!this.dashboardData || !this.layout) return; if (!this.dashboardData || !this.layout) return;
// Get the current layout state directly from the Syncfusion component
const currentPanels = this.layout.serialize(); const currentPanels = this.layout.serialize();
// Update the widgets array with the latest positions and sizes
currentPanels.forEach((panel: PanelModel) => { currentPanels.forEach((panel: PanelModel) => {
const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === panel.id); const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === panel.id);
if (widgetIndex > -1) { if (widgetIndex > -1) {
...@@ -309,16 +258,12 @@ export class DashboardManagementComponent implements OnInit { ...@@ -309,16 +258,12 @@ export class DashboardManagementComponent implements OnInit {
} }
}); });
// Now, prepare the data for saving (deep copy and stringify)
const dashboardToSave = JSON.parse(JSON.stringify(this.dashboardData)); const dashboardToSave = JSON.parse(JSON.stringify(this.dashboardData));
if (dashboardToSave.widgets) { if (dashboardToSave.widgets) {
dashboardToSave.widgets.forEach((widget: WidgetModel) => { dashboardToSave.widgets.forEach((widget: WidgetModel) => {
const keysToProcess: Array<keyof WidgetModel> = ['config', 'perspective', 'data']; if (widget.config && typeof widget.config === 'object') {
keysToProcess.forEach(key => { widget.config = JSON.stringify(widget.config);
if ((widget as any)[key] && typeof (widget as any)[key] === 'object') { }
(widget as any)[key] = JSON.stringify((widget as any)[key]);
}
});
}); });
} }
...@@ -328,59 +273,40 @@ export class DashboardManagementComponent implements OnInit { ...@@ -328,59 +273,40 @@ export class DashboardManagementComponent implements OnInit {
return throwError(() => error); return throwError(() => error);
}) })
).subscribe(() => { ).subscribe(() => {
// this.notificationService.success('Success', 'Dashboard layout saved successfully!'); this.notificationService.success('Success', 'Dashboard layout saved successfully!');
this.loadSelectedDashboard(); this.loadSelectedDashboard();
}); });
} }
addWidgetToDashboard(widget: WidgetModel): void { addWidgetToDashboard(menuItem: MenuItemsWidget): void {
if (!this.dashboardData) { if (!this.dashboardData) {
this.notificationService.warning('Warning', 'Please select or create a dashboard first.'); this.notificationService.warning('Warning', 'Please select or create a dashboard first.');
return; return;
} }
if (!this.dashboardData.datasetId) {
this.notificationService.warning('Warning', 'Please select a dataset for the dashboard first.');
return;
}
this.dashboardStateService.selectedDataset$.pipe( // Create a new widget instance from the menu item's widget template
take(1) const newWidgetInstance = new WidgetModel(menuItem.widget);
).subscribe(selectedDataset => {
if (!selectedDataset || !selectedDataset.columns || selectedDataset.columns.length === 0) {
this.notificationService.error('Error', 'The selected dataset has no columns available.');
return;
}
const newWidgetInstance = new WidgetModel(widget); // Ensure the config is an object
newWidgetInstance.config = this.widgetConfigGenerator.generateConfig(widget, selectedDataset.columns); if (typeof newWidgetInstance.config === 'string') {
try {
this.dashboardData!.widgets.push(newWidgetInstance); newWidgetInstance.config = JSON.parse(newWidgetInstance.config);
this.panels = this.mapWidgetsToPanels(this.dashboardData!.widgets); } catch (e) {
// this.notificationService.info('Info', `Added widget: ${widget.thName}`); console.error('Error parsing widget config string:', newWidgetInstance.config, e);
}); newWidgetInstance.config = {}; // Reset to empty on error
} }
deleteDashboard(): void {
if (this.selectedDashboardId && confirm('Are you sure you want to delete this dashboard?')) {
this.dashboardDataService.deleteDashboard(this.dashboardData!).pipe(
catchError(error => {
this.notificationService.error('Error', 'Failed to delete dashboard.');
return throwError(() => error);
})
).subscribe(result => {
console.log(result)
this.notificationService.success('Success', 'Dashboard deleted successfully!');
});
} }
this.dashboardData.widgets.push(newWidgetInstance);
this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
this.notificationService.info('Info', `Added widget: ${newWidgetInstance.thName}`);
} }
removeWidgetFromDashboard(panelId: string): void { removeWidgetFromDashboard(panelId: string): void {
if (!this.dashboardData) return; if (!this.dashboardData) return;
if (confirm('Are you sure you want to remove this widget?')) { if (confirm('Are you sure you want to remove this widget?')) {
const updatedDashboard = { ...this.dashboardData, widgets: this.dashboardData.widgets.filter(w => w.widgetId !== panelId) }; this.dashboardData.widgets = this.dashboardData.widgets.filter(w => w.widgetId !== panelId);
this.dashboardData = updatedDashboard; this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
this.panels = this.mapWidgetsToPanels(updatedDashboard.widgets);
// this.notificationService.info('Info', 'Widget removed from dashboard.');
} }
} }
...@@ -390,50 +316,58 @@ export class DashboardManagementComponent implements OnInit { ...@@ -390,50 +316,58 @@ export class DashboardManagementComponent implements OnInit {
this.dashboardData.templateId = dataset.templateId; this.dashboardData.templateId = dataset.templateId;
this.dashboardData.fileName = dataset.fileName; this.dashboardData.fileName = dataset.fileName;
this.dashboardStateService.selectDataset(dataset.itemId); this.dashboardStateService.selectDataset(dataset.itemId);
this.loadWidgetsForDataset(dataset.itemId);
} }
} }
openWidgetConfigDialog(panel: PanelModel & { componentType: Type<any>, componentInputs?: { [key: string]: any }, originalWidget: WidgetModel }): void { openWidgetConfigDialog(panel: DashboardPanel): void {
const widget = panel.originalWidget; const widget = panel.originalWidget;
this.dashboardStateService.selectedDataset$.pipe(take(1)).subscribe((selectedDataset: SelectedDataset | null) => { this.dashboardStateService.selectedDataset$.pipe(take(1)).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: '600px', width: '600px',
data: { data: { widget, availableColumns }
widget: widget, // Pass the whole widget
availableColumns: availableColumns
}
}); });
dialogRef.afterClosed().subscribe((result: any) => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result && this.dashboardData) {
const updatedWidget = { ...widget, config: result }; const widgetIndex = this.dashboardData.widgets.findIndex(w => w.widgetId === widget.widgetId);
if (this.dashboardData) { if (widgetIndex > -1) {
const widgetIndex = this.dashboardData.widgets.findIndex(w => w.widgetId === updatedWidget.widgetId); this.dashboardData.widgets[widgetIndex].config = result;
if (widgetIndex > -1) { this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
this.dashboardData.widgets[widgetIndex] = updatedWidget;
this.panels = this.mapWidgetsToPanels(this.dashboardData.widgets);
}
} }
} }
}); });
}); });
} }
onPanelChange(args: any): void { mapWidgetsToPanels(widgets: WidgetModel[]): DashboardPanel[] {
if (this.dashboardData && args.changedPanels) { return widgets.map(widget => {
args.changedPanels.forEach((changedPanel: PanelModel) => { // Ensure config is an object before passing to component
const widgetIndex = this.dashboardData!.widgets.findIndex(w => w.widgetId === changedPanel.id); let configObject = {};
if (widgetIndex > -1) { if (typeof widget.config === 'string') {
const updatedWidget = { ...this.dashboardData!.widgets[widgetIndex] }; try {
updatedWidget.cols = changedPanel.sizeX!; configObject = JSON.parse(widget.config);
updatedWidget.rows = changedPanel.sizeY!; } catch (e) {
updatedWidget.x = changedPanel.col!; console.error('Error parsing widget config string:', widget.config, e);
updatedWidget.y = changedPanel.row!;
this.dashboardData!.widgets[widgetIndex] = updatedWidget;
} }
}); } else if (typeof widget.config === 'object') {
} configObject = widget.config;
}
return {
id: widget.widgetId,
header: widget.thName,
sizeX: widget.cols,
sizeY: widget.rows,
row: widget.y,
col: widget.x,
componentType: this.widgetComponentMap[widget.component],
componentInputs: { config: configObject },
originalWidget: widget
};
});
} }
} }
import { WidgetModel } from "./widgets.model";
export class MenuItemsWidget {
companyId: string;
itemId: string;
widget: WidgetModel;
data?: string;
config?: string;
perspective?: string;
constructor(initialValues: Partial<MenuItemsWidget> = {}) {
if (initialValues) {
Object.assign(this, initialValues);
}
}
}
import { DatasetWidgetLinkerComponent } from './widget-management/dataset-widget-linker.component';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { DashboardManagementComponent } from './dashboard-management/dashboard-management.component'; import { DashboardManagementComponent } from './dashboard-management/dashboard-management.component';
...@@ -25,6 +26,12 @@ export const portalManageRoutes: Routes = [ ...@@ -25,6 +26,12 @@ export const portalManageRoutes: Routes = [
path: 'dashboard-viewer/:dashboardId', path: 'dashboard-viewer/:dashboardId',
component: DashboardViewerComponent component: DashboardViewerComponent
}, },
{
path: 'widget-management',
children: [
{ path: 'linker', component: DatasetWidgetLinkerComponent, title: 'Dataset Widget Linker' }
]
}
// Optional: A redirect for the base portal-manage path // Optional: A redirect for the base portal-manage path
// { // {
// path: '', // path: '',
......
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
import { MenuItemsWidget } from '../models/m-menuitems-widget.model';
@Injectable({
providedIn: 'root'
})
export class MMenuitemsWidgetService {
private dataUrl = 'assets/data/d-menuitems-widget.json';
private linkedWidgetsCache: MenuItemsWidget[] = []; // In-memory cache for simulation
constructor(private http: HttpClient) { }
/**
* Gets all widget menu items and filters them by the provided datasetId.
* In a real application, this filtering would ideally be done on the backend.
* @param datasetId The ID of the dataset to filter widgets for.
* @returns An Observable of MenuItemsWidget[] that are linked to the given datasetId.
*/
getWidgetsForDataset(datasetId: string): Observable<MenuItemsWidget[]> {
// Simulate fetching from backend or use cache if available
if (this.linkedWidgetsCache.length > 0) {
return of(this.linkedWidgetsCache.filter(item => (item as any).datasetId === datasetId));
} else {
return this.http.get<any[]>(this.dataUrl).pipe(
tap(items => {
// Populate cache on first load
this.linkedWidgetsCache = items.map(item => new MenuItemsWidget(item));
}),
map(items => {
const filteredItems = items.filter(item => item.datasetId === datasetId);
return filteredItems.map(item => new MenuItemsWidget(item));
}),
catchError(error => {
console.error('Error loading widget menu items:', error);
return of([]); // Return an empty array on error
})
);
}
}
/**
* SIMULATED: Saves a linked widget. In a real app, this would be a POST/PUT request.
* Updates the in-memory cache and logs the action.
* @param menuItem The MenuItemsWidget to save.
* @returns An Observable of the saved MenuItemsWidget.
*/
saveLinkedWidget(menuItem: MenuItemsWidget): Observable<MenuItemsWidget> {
// Check if it already exists (for update scenario)
const index = this.linkedWidgetsCache.findIndex(item => item.itemId === menuItem.itemId);
if (index > -1) {
this.linkedWidgetsCache[index] = menuItem;
console.log('Simulating update linked widget:', menuItem);
} else {
this.linkedWidgetsCache.push(menuItem);
console.log('Simulating save new linked widget:', menuItem);
}
// In a real app, the backend would return the saved item.
return of(menuItem);
}
/**
* SIMULATED: Deletes a linked widget by its itemId. In a real app, this would be a DELETE request.
* Updates the in-memory cache and logs the action.
* @param itemId The itemId of the MenuItemsWidget to delete.
* @returns An Observable indicating success.
*/
deleteLinkedWidget(itemId: string): Observable<any> {
this.linkedWidgetsCache = this.linkedWidgetsCache.filter(item => item.itemId !== itemId);
console.log('Simulating delete linked widget with ID:', itemId);
// In a real app, this would return a status or confirmation.
return of({ success: true });
}
}
<div class="p-4 sm:p-6 lg:p-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-xl font-semibold text-gray-900">Dataset Widget Linker</h1>
<p class="mt-2 text-sm text-gray-700">Link available widgets to specific datasets and configure their default settings.</p>
</div>
</div>
<div class="mt-6">
<label for="dataset-select" class="block text-sm font-medium text-gray-700">Select Dataset</label>
<select id="dataset-select" name="dataset-select" [(ngModel)]="selectedDataset" (change)="onDatasetSelected()" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
<option [ngValue]="null">-- Select a Dataset --</option>
<option *ngFor="let dataset of datasets" [ngValue]="dataset">{{ dataset.tdesc }} ({{ dataset.itemId }})</option>
</select>
</div>
<div class="mt-8 grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Available Master Widgets -->
<div>
<h2 class="text-lg font-semibold text-gray-900">Available Widgets</h2>
<p class="mt-2 text-sm text-gray-700">Widgets registered in the system.</p>
<div class="mt-4 border border-gray-200 rounded-lg overflow-hidden shadow-sm">
<ul role="list" class="divide-y divide-gray-200">
<li *ngFor="let widget of masterWidgets" class="p-4 flex items-center justify-between hover:bg-gray-50">
<div>
<p class="text-sm font-medium text-gray-900">{{ widget.thName }}</p>
<p class="text-sm text-gray-500">{{ widget.component }} ({{ widget.cols }}x{{ widget.rows }})</p>
</div>
<button (click)="linkWidget(widget)" type="button" class="ml-3 inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Link
</button>
</li>
<li *ngIf="masterWidgets.length === 0" class="p-4 text-center text-sm text-gray-500">
No master widgets found.
</li>
</ul>
</div>
</div>
<!-- Linked Widgets for Selected Dataset -->
<div>
<h2 class="text-lg font-semibold text-gray-900">Linked Widgets ({{ selectedDataset?.tdesc || 'No Dataset Selected' }})</h2>
<p class="mt-2 text-sm text-gray-700">Widgets configured for the selected dataset.</p>
<div class="mt-4 border border-gray-200 rounded-lg overflow-hidden shadow-sm">
<ul role="list" class="divide-y divide-gray-200">
<li *ngFor="let linkedWidget of linkedWidgets" class="p-4 flex items-center justify-between hover:bg-gray-50">
<div>
<p class="text-sm font-medium text-gray-900">{{ linkedWidget.widget.thName }}</p>
<p class="text-sm text-gray-500">{{ linkedWidget.widget.component }}</p>
<p *ngIf="linkedWidget.config" class="text-xs text-gray-400">Config: {{ linkedWidget.config | slice:0:50 }}...</p>
</div>
<button (click)="unlinkWidget(linkedWidget.itemId)" type="button" class="ml-3 inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
Unlink
</button>
</li>
<li *ngIf="linkedWidgets.length === 0" class="p-4 text-center text-sm text-gray-500">
No widgets linked to this dataset.
</li>
</ul>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { NotificationService } from '../../shared/services/notification.service';
import { WidgetService } from '../services/widgets.service';
import { MMenuitemsWidgetService } from '../services/m-menuitems-widget.service';
import { DatasetService } from '../services/dataset.service';
import { WidgetModel, DatasetModel } from '../models/widgets.model';
import { MenuItemsWidget } from '../models/m-menuitems-widget.model';
import { WidgetConfigComponent } from '../dashboard-management/widget-config/widget-config.component';
import { take } from 'rxjs/operators';
import { DashboardStateService } from '../services/dashboard-state.service';
@Component({
selector: 'app-dataset-widget-linker',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './dataset-widget-linker.component.html',
styleUrls: ['./dataset-widget-linker.component.scss']
})
export class DatasetWidgetLinkerComponent implements OnInit {
public datasets: DatasetModel[] = [];
public selectedDataset: DatasetModel | null = null;
public masterWidgets: WidgetModel[] = [];
public linkedWidgets: MenuItemsWidget[] = [];
constructor(
private widgetService: WidgetService,
private mMenuitemsWidgetService: MMenuitemsWidgetService,
private datasetService: DatasetService,
private notificationService: NotificationService,
private dialog: MatDialog,
private dashboardStateService: DashboardStateService // To get columns for config
) { }
ngOnInit(): void {
this.datasetService.getDatasets().subscribe(datasets => {
this.datasets = datasets;
});
this.widgetService.getListWidgets().subscribe(widgets => {
this.masterWidgets = widgets;
});
}
onDatasetSelected(): void {
if (this.selectedDataset) {
this.loadLinkedWidgetsForDataset(this.selectedDataset.itemId);
// Also select the dataset in the global state to get its columns
this.dashboardStateService.selectDataset(this.selectedDataset.itemId);
} else {
this.linkedWidgets = [];
this.dashboardStateService.selectDataset(null);
}
}
loadLinkedWidgetsForDataset(datasetId: string): void {
this.mMenuitemsWidgetService.getWidgetsForDataset(datasetId).subscribe(linked => {
this.linkedWidgets = linked;
});
}
linkWidget(widget: WidgetModel): void {
if (!this.selectedDataset) {
this.notificationService.warning('Warning', 'Please select a dataset first.');
return;
}
// Check if widget is already linked
if (this.linkedWidgets.some(lw => lw.widget.widgetId === widget.widgetId)) {
this.notificationService.info('Info', 'This widget is already linked to the selected dataset.');
return;
}
// Get available columns for the selected dataset to pass to config dialog
this.dashboardStateService.selectedDataset$.pipe(take(1)).subscribe(selectedDataset => {
const availableColumns = selectedDataset ? selectedDataset.columns : [];
const dialogRef = this.dialog.open(WidgetConfigComponent, {
width: '600px',
data: {
widget: widget, // Pass the master widget template
availableColumns: availableColumns
}
});
dialogRef.afterClosed().subscribe(resultConfig => {
if (resultConfig) {
const newLinkedWidget: MenuItemsWidget = {
companyId: '1', // Placeholder, ideally from user context
itemId: `link-${this.selectedDataset!.itemId}-${widget.widgetId}`,
widget: widget, // The base widget definition
config: JSON.stringify(resultConfig), // Save configured object as string
data: '', // Not used for linking, but part of model
perspective: '' // Not used for linking, but part of model
};
this.mMenuitemsWidgetService.saveLinkedWidget(newLinkedWidget).subscribe(() => {
this.notificationService.success('Success', 'Widget linked successfully!');
this.loadLinkedWidgetsForDataset(this.selectedDataset!.itemId);
});
}
});
});
}
unlinkWidget(itemId: string): void {
if (confirm('Are you sure you want to unlink this widget from the dataset?')) {
this.mMenuitemsWidgetService.deleteLinkedWidget(itemId).subscribe(() => {
this.notificationService.success('Success', 'Widget unlinked successfully!');
this.loadLinkedWidgetsForDataset(this.selectedDataset!.itemId);
});
}
}
}
<div class="p-4 sm:p-6 lg:p-8">
<div class="container mx-auto p-6"> <div class="sm:flex sm:items-center">
<div class="flex justify-between items-center mb-4"> <div class="sm:flex-auto">
<h1 class="text-2xl font-bold">Widget Warehouse for {{ appName | titlecase }}</h1> <h1 class="text-xl font-semibold text-gray-900">Widget Registry</h1>
<button (click)="addNewWidget()" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> <p class="mt-2 text-sm text-gray-700">A list of all base widgets registered in the system, fetched from the central API.</p>
Add New Widget </div>
</button> <!-- Add/Delete functionality removed as this now points to a real API -->
</div> </div>
<div class="bg-white shadow-md rounded my-6"> <!-- Registered Widgets List -->
<table class="min-w-full table-auto"> <div class="mt-8 flex flex-col">
<thead class="bg-gray-200"> <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<tr> <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Widget Name</th> <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Component</th> <table class="min-w-full divide-y divide-gray-300">
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Default Size (Cols x Rows)</th> <thead class="bg-gray-50">
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Preview</th> <tr>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Name</th>
</tr> <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Component</th>
</thead> <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Default Size</th>
<tbody class="bg-white divide-y divide-gray-200"> <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
<tr *ngFor="let widget of widgets$ | async"> <span class="sr-only">Actions</span>
<td class="px-6 py-4 whitespace-nowrap">{{ widget.thName }}</td> </th>
<td class="px-6 py-4 whitespace-nowrap">{{ widget.component }}</td> </tr>
<td class="px-6 py-4 whitespace-nowrap">{{ widget.cols }} x {{ widget.rows }}</td> </thead>
<td class="px-6 py-4"> <tbody class="divide-y divide-gray-200 bg-white">
<div class="w-48 h-32 border border-gray-300 rounded-lg overflow-hidden shadow-sm flex items-center justify-center bg-gray-50"> <tr *ngFor="let widget of registeredWidgets">
<ng-container *ngComponentOutlet="getComponentType(widget.component)"></ng-container> <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
</div> <div class="flex items-center">
</td> <div class="ml-4">
<td class="px-6 py-4 whitespace-nowrap text-right"> <div class="font-medium text-gray-900">{{ widget.thName }}</div>
<button (click)="editWidget(widget.widgetId)" class="text-indigo-600 hover:text-indigo-900">Edit</button> <div class="text-gray-500">{{ widget.engName }}</div>
</td> </div>
</tr> </div>
<!-- Show a message if there are no widgets --> </td>
<tr *ngIf="!(widgets$ | async)?.length"> <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<td colspan="5" class="text-center py-4">No widgets found.</td> <span class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800">{{ widget.component }}</span>
</tr> </td>
</tbody> <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{{ widget.cols }}x{{ widget.rows }}</td>
</table> <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
</div> <!-- Preview button can be added here later -->
</div> </td>
</tr>
<tr *ngIf="registeredWidgets.length === 0">
<td colspan="4" class="whitespace-nowrap px-3 py-4 text-sm text-center text-gray-500">No widgets registered yet.</td>
</tr>
</tbody>
\ No newline at end of file
import { Component, OnInit, Type } from '@angular/core'; import { Component, OnInit, Type } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common';
import { Observable } from 'rxjs'; import { FormsModule } from '@angular/forms';
import { CommonModule, TitleCasePipe } from '@angular/common'; import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { NgComponentOutlet } from '@angular/common'; import { NgComponentOutlet } from '@angular/common';
import { Store } from '@ngrx/store';
import { WidgetModel } from '../models/widgets.model';
// Import all the widget components // Import all widget components for preview
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component'; import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
import { HeadcountWidgetComponent } from '../widgets/headcount-widget.component'; import { HeadcountWidgetComponent } from '../widgets/headcount-widget.component';
import { AttendanceOverviewWidgetComponent } from '../widgets/attendance-overview-widget.component'; import { AttendanceOverviewWidgetComponent } from '../widgets/attendance-overview-widget.component';
import { PayrollSummaryWidgetComponent } from '../widgets/payroll-summary-widget.component'; import { PayrollSummaryWidgetComponent } from '../widgets/payroll-summary-widget.component';
import { EmployeeDirectoryWidgetComponent } from '../widgets/employee-directory-widget.component'; import { EmployeeDirectoryWidgetComponent } from '../widgets/employee-directory-widget.component';
import { KpiWidgetComponent } from '../widgets/kpi-widget/kpi-widget.component';
import { WelcomeWidgetComponent } from '../widgets/welcome-widget/welcome-widget.component'; import { WelcomeWidgetComponent } from '../widgets/welcome-widget/welcome-widget.component';
import { ChartWidgetComponent } from '../widgets/chart-widget/chart-widget.component';
import { QuickLinksWidgetComponent } from '../widgets/quick-links-widget/quick-links-widget.component'; import { QuickLinksWidgetComponent } from '../widgets/quick-links-widget/quick-links-widget.component';
import { SyncfusionDatagridWidgetComponent } from '../widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component'; import { SyncfusionDatagridWidgetComponent } from '../widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component';
import { SyncfusionPivotWidgetComponent } from '../widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component'; import { SyncfusionPivotWidgetComponent } from '../widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component';
import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widget/syncfusion-chart-widget.component'; import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widget/syncfusion-chart-widget.component';
import { NotificationService } from '../../shared/services/notification.service';
import { WidgetModel } from '../models/widgets.model';
import { WidgetService } from '../services/widgets.service';
import { SimpleKpiWidgetComponent } from '../widgets/simple-kpi-widget/simple-kpi-widget.component';
@Component({ @Component({
selector: 'app-widget-list', selector: 'app-widget-list',
standalone: true, standalone: true,
imports: [CommonModule, RouterModule, TitleCasePipe, NgComponentOutlet, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent], imports: [
templateUrl: './widget-list.component.html', CommonModule,
FormsModule,
MatDialogModule,
NgComponentOutlet,
// Import widgets to be available for NgComponentOutlet
CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, WelcomeWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent, SimpleKpiWidgetComponent
],
templateUrl: './widget-list.component.html'
}) })
export class WidgetListComponent implements OnInit { export class WidgetListComponent implements OnInit {
widgets$!: Observable<WidgetModel[]>;
appName: string = '';
// Map string names to actual component classes public registeredWidgets: WidgetModel[] = [];
// This map is crucial for mapping component string names to actual component types for previewing.
private widgetComponentMap: { [key: string]: Type<any> } = { private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent, CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent, HeadcountWidgetComponent,
'AttendanceOverviewWidgetComponent': AttendanceOverviewWidgetComponent, AttendanceOverviewWidgetComponent,
'PayrollSummaryWidgetComponent': PayrollSummaryWidgetComponent, PayrollSummaryWidgetComponent,
'EmployeeDirectoryWidgetComponent': EmployeeDirectoryWidgetComponent, EmployeeDirectoryWidgetComponent,
'KpiWidgetComponent': KpiWidgetComponent, WelcomeWidgetComponent,
'WelcomeWidgetComponent': WelcomeWidgetComponent, QuickLinksWidgetComponent,
'ChartWidgetComponent': ChartWidgetComponent, SyncfusionDatagridWidgetComponent,
'QuickLinksWidgetComponent': QuickLinksWidgetComponent, SyncfusionPivotWidgetComponent,
'SyncfusionDatagridWidgetComponent': SyncfusionDatagridWidgetComponent, SyncfusionChartWidgetComponent,
'SyncfusionPivotWidgetComponent': SyncfusionPivotWidgetComponent, SimpleKpiWidgetComponent,
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent
}; };
constructor( constructor(
private store: Store, private widgetService: WidgetService,
private route: ActivatedRoute, private notificationService: NotificationService,
private router: Router private dialog: MatDialog
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
// this.store.dispatch(DashboardActions.loadWidgets()); this.loadRegisteredWidgets();
// this.widgets$ = this.store.select(DashboardSelectors.selectAllWidgets);
}
getComponentType(componentName: string): Type<any> | null {
return this.widgetComponentMap[componentName] || null;
} }
editWidget(widgetId: string): void { loadRegisteredWidgets(): void {
this.router.navigate(['/portal-manage', this.appName, 'widget-warehouse', 'edit', widgetId]); this.widgetService.getListWidgets().subscribe(widgets => {
this.registeredWidgets = widgets;
});
} }
addNewWidget(): void { getComponentType(componentName: string): Type<any> {
this.router.navigate(['/portal-manage', this.appName, 'widget-warehouse', 'edit', 'new']); return this.widgetComponentMap[componentName];
} }
} }
[{"companyId": "1", "itemId": "dataset-1-headcount-bar", "datasetId": "people-analytics-dataset", "widget": {"widgetId": "widget-headcount-bar", "thName": "จำนวนพนักงาน (แผนภูมิแท่ง)", "engName": "Headcount (Bar Chart)", "component": "HeadcountWidgetComponent", "cols": 3, "rows": 2, "x": 0, "y": 0, "config": "{\"title\":\"Headcount by Department\",\"categoryField\":\"department\",\"chartType\":\"bar\"}"}}, {"companyId": "1", "itemId": "dataset-1-headcount-doughnut", "datasetId": "people-analytics-dataset", "widget": {"widgetId": "widget-headcount-doughnut", "thName": "สัดส่วนพนักงาน (แผนภูมิโดนัท)", "engName": "Headcount (Doughnut Chart)", "component": "HeadcountWidgetComponent", "cols": 2, "rows": 2, "x": 0, "y": 0, "config": "{\"title\":\"Headcount by Gender\",\"categoryField\":\"gender\",\"chartType\":\"doughnut\"}"}}, {"companyId": "1", "itemId": "dataset-1-emp-directory", "datasetId": "people-analytics-dataset", "widget": {"widgetId": "widget-emp-directory", "thName": "ทำเนียบพนักงาน", "engName": "Employee Directory", "component": "EmployeeDirectoryWidgetComponent", "cols": 2, "rows": 4, "x": 0, "y": 0, "config": "{\"title\":\"Our Employees\",\"nameField\":\"name\",\"positionField\":\"position\",\"departmentField\":\"department\",\"photoField\":\"photoUrl\"}"}}, {"companyId": "1", "itemId": "dataset-2-kpi", "datasetId": "financial-dataset", "widget": {"widgetId": "widget-revenue-kpi", "thName": "รายได้รวม", "engName": "Total Revenue", "component": "SimpleKpiWidgetComponent", "cols": 1, "rows": 1, "x": 0, "y": 0, "config": "{\"title\":\"Total Revenue\",\"valueField\":\"revenue\",\"aggregation\":\"sum\",\"unit\":\"THB\",\"icon\":\"cash-stack\",\"color\":\"#10b981\"}"}}]}
\ No newline at end of file
[
{
"widgetId": "widget-headcount",
"thName": "จำนวนพนักงาน",
"engName": "Headcount",
"component": "HeadcountWidgetComponent",
"cols": 3,
"rows": 2,
"x": 0,
"y": 0
},
{
"widgetId": "widget-simple-kpi",
"thName": "ตัวชี้วัดอย่างง่าย",
"engName": "Simple KPI",
"component": "SimpleKpiWidgetComponent",
"cols": 1,
"rows": 1,
"x": 0,
"y": 0
},
{
"widgetId": "widget-quick-links",
"thName": "ลิงก์ด่วน",
"engName": "Quick Links",
"component": "QuickLinksWidgetComponent",
"cols": 2,
"rows": 2,
"x": 0,
"y": 0
},
{
"widgetId": "widget-employee-directory",
"thName": "ทำเนียบพนักงาน",
"engName": "Employee Directory",
"component": "EmployeeDirectoryWidgetComponent",
"cols": 3,
"rows": 4,
"x": 0,
"y": 0
}
]
\ No newline at end of file
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