Commit 01ab61b4 by Ooh-Ao

linker

parent 0e1eb15e
...@@ -64,13 +64,31 @@ ...@@ -64,13 +64,31 @@
</div> </div>
</div> </div>
<div *ngIf="widgetToPreview"> <div *ngIf="widgetToPreview">
<div class="mb-4 p-4 border rounded-lg"> <!-- Dynamic Widget Rendering Area -->
<h3 class="text-md font-bold mb-2">{{ widgetToPreview.widget.thName }}</h3> <div class="p-4 border rounded-lg mb-4">
<p class="text-sm text-gray-600">{{ widgetToPreview.widget.component }}</p> <div *ngIf="!previewPanel" class="text-center text-red-500">
Preview not available for this widget type.
</div>
<ng-container *ngIf="previewPanel" [ngComponentOutlet]="previewPanel.componentType" [ngComponentOutletInputs]="previewPanel.componentInputs"></ng-container>
</div> </div>
<!-- Editable Configuration Form -->
<div class="p-4 border rounded-lg bg-gray-50"> <div class="p-4 border rounded-lg bg-gray-50">
<h3 class="text-md font-bold mb-2">Configuration</h3> <h3 class="text-md font-bold mb-2">Configuration</h3>
<pre class="bg-gray-900 text-white p-2 rounded-md text-xs">{{ widgetToPreview.config | json }}</pre> <form #configForm="ngForm" (ngSubmit)="saveConfiguration()">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div *ngFor="let key of getObjectKeys(configAsObject)" class="flex flex-col">
<label [for]="key" class="text-sm font-medium text-gray-600 mb-1 capitalize">{{ key }}</label>
<input type="text" [id]="key" [name]="key" [(ngModel)]="configAsObject[key]" class="p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
<div *ngIf="getObjectKeys(configAsObject).length === 0" class="text-center text-gray-500">
This widget has no configurable properties.
</div>
<div class="mt-4 flex justify-end" *ngIf="getObjectKeys(configAsObject).length > 0">
<button ejs-button type="submit" cssClass="e-primary">Save Configuration</button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
......
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild, Type } from '@angular/core';
import { CommonModule, JsonPipe } from '@angular/common'; import { CommonModule, JsonPipe } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
...@@ -7,7 +7,7 @@ import { MatDialog } from '@angular/material/dialog'; ...@@ -7,7 +7,7 @@ import { MatDialog } from '@angular/material/dialog';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns'; import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
import { DialogComponent, DialogModule } from '@syncfusion/ej2-angular-popups'; import { DialogComponent, DialogModule } from '@syncfusion/ej2-angular-popups';
import { GridComponent, GridModule, PageService, SelectionService } from '@syncfusion/ej2-angular-grids'; import { GridComponent, GridModule, PageService, SelectionService } from '@syncfusion/ej2-angular-grids';
import { ButtonComponent, ButtonModule } from '@syncfusion/ej2-angular-buttons'; import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { NotificationService } from '../../shared/services/notification.service'; import { NotificationService } from '../../shared/services/notification.service';
import { WidgetService } from '../services/widgets.service'; import { WidgetService } from '../services/widgets.service';
...@@ -19,17 +19,54 @@ import { WidgetConfigComponent } from '../dashboard-management/widget-config/wid ...@@ -19,17 +19,54 @@ import { WidgetConfigComponent } from '../dashboard-management/widget-config/wid
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { DashboardStateService } from '../services/dashboard-state.service'; import { DashboardStateService } from '../services/dashboard-state.service';
// --- Copied from DashboardViewerComponent for dynamic rendering ---
import { NgComponentOutlet } from '@angular/common';
import { PanelModel } from '@syncfusion/ej2-angular-layouts';
// Import all widget components
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
import { HeadcountWidgetComponent } from '../widgets/headcount-widget.component';
import { AttendanceOverviewWidgetComponent } from '../widgets/attendance-overview-widget.component';
import { PayrollSummaryWidgetComponent } from '../widgets/payroll-summary-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 { ChartWidgetComponent } from '../widgets/chart-widget/chart-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 { SyncfusionPivotWidgetComponent } from '../widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component';
import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widget/syncfusion-chart-widget.component';
import { DataTableWidgetComponent } from '../widgets/dynamic-widgets/data-table-widget.component';
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 { FilledMapWidgetComponent } from '../widgets/filled-map-widget/filled-map-widget.component';
import { MatrixWidgetComponent } from '../widgets/matrix-widget/matrix-widget.component';
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';
// Interface for the panel object used by ngComponentOutlet
export interface DashboardPanel extends PanelModel {
componentType: Type<any>;
componentInputs?: { [key: string]: any };
}
// ----------------------------------------------------------------
@Component({ @Component({
selector: 'app-dataset-widget-linker', selector: 'app-dataset-widget-linker',
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule, FormsModule, JsonPipe, DropDownListModule, DialogModule, GridModule, ButtonModule, NgComponentOutlet,
FormsModule, // Add all widget components here to make them available for NgComponentOutlet
JsonPipe, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent, DataTableWidgetComponent, AreaChartWidgetComponent, BarChartWidgetComponent, PieChartWidgetComponent, ScatterBubbleChartWidgetComponent, MultiRowCardWidgetComponent, ComboChartWidgetComponent, DoughnutChartWidgetComponent, FunnelChartWidgetComponent, GaugeChartWidgetComponent, SimpleKpiWidgetComponent, FilledMapWidgetComponent, MatrixWidgetComponent, SlicerWidgetComponent, SimpleTableWidgetComponent, WaterfallChartWidgetComponent, TreemapWidgetComponent
DropDownListModule,
DialogModule,
GridModule,
ButtonModule
], ],
providers: [PageService, SelectionService], providers: [PageService, SelectionService],
templateUrl: './dataset-widget-linker.component.html', templateUrl: './dataset-widget-linker.component.html',
...@@ -37,20 +74,54 @@ import { DashboardStateService } from '../services/dashboard-state.service'; ...@@ -37,20 +74,54 @@ import { DashboardStateService } from '../services/dashboard-state.service';
}) })
export class DatasetWidgetLinkerComponent implements OnInit { export class DatasetWidgetLinkerComponent implements OnInit {
// Syncfusion Component References
@ViewChild('addWidgetDialog') addWidgetDialog!: DialogComponent; @ViewChild('addWidgetDialog') addWidgetDialog!: DialogComponent;
@ViewChild('grid') grid!: GridComponent; @ViewChild('grid') grid!: GridComponent;
// Data lists
public datasets: DatasetModel[] = []; public datasets: DatasetModel[] = [];
public masterWidgets: WidgetModel[] = []; public masterWidgets: WidgetModel[] = [];
public linkedWidgets: MenuItemsWidget[] = []; public linkedWidgets: MenuItemsWidget[] = [];
// UI State
public selectedDatasetId: string | null = null; public selectedDatasetId: string | null = null;
public widgetToPreview: MenuItemsWidget | null = null;
public isModalVisible = false; public isModalVisible = false;
// --- State for the right panel ---
public widgetToPreview: MenuItemsWidget | null = null;
public configAsObject: any = {};
public previewPanel: DashboardPanel | null = null;
// --------------------------------
private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent,
'AttendanceOverviewWidgetComponent': AttendanceOverviewWidgetComponent,
'PayrollSummaryWidgetComponent': PayrollSummaryWidgetComponent,
'EmployeeDirectoryWidgetComponent': EmployeeDirectoryWidgetComponent,
'KpiWidgetComponent': KpiWidgetComponent,
'WelcomeWidgetComponent': WelcomeWidgetComponent,
'ChartWidgetComponent': ChartWidgetComponent,
'QuickLinksWidgetComponent': QuickLinksWidgetComponent,
'SyncfusionDatagridWidgetComponent': SyncfusionDatagridWidgetComponent,
'SyncfusionPivotWidgetComponent': SyncfusionPivotWidgetComponent,
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent,
'NewDataTableWidget': DataTableWidgetComponent,
'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,
};
constructor( constructor(
private widgetService: WidgetService, private widgetService: WidgetService,
private mMenuitemsWidgetService: MMenuitemsWidgetService, private mMenuitemsWidgetService: MMenuitemsWidgetService,
...@@ -61,17 +132,16 @@ export class DatasetWidgetLinkerComponent implements OnInit { ...@@ -61,17 +132,16 @@ export class DatasetWidgetLinkerComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.datasetService.getDatasets().subscribe(datasets => { this.datasetService.getDatasets().subscribe(datasets => { this.datasets = datasets; });
this.datasets = datasets; this.widgetService.getListWidgets().subscribe(widgets => { this.masterWidgets = widgets; });
}); }
this.widgetService.getListWidgets().subscribe(widgets => { getObjectKeys(obj: any): string[] {
this.masterWidgets = widgets; return Object.keys(obj);
});
} }
onDatasetChange(): void { onDatasetChange(): void {
this.widgetToPreview = null; this.clearPreview();
if (this.selectedDatasetId) { if (this.selectedDatasetId) {
this.loadLinkedWidgetsForDataset(this.selectedDatasetId); this.loadLinkedWidgetsForDataset(this.selectedDatasetId);
this.dashboardStateService.selectDataset(this.selectedDatasetId); this.dashboardStateService.selectDataset(this.selectedDatasetId);
...@@ -82,22 +152,50 @@ export class DatasetWidgetLinkerComponent implements OnInit { ...@@ -82,22 +152,50 @@ export class DatasetWidgetLinkerComponent implements OnInit {
} }
loadLinkedWidgetsForDataset(datasetId: string): void { loadLinkedWidgetsForDataset(datasetId: string): void {
this.mMenuitemsWidgetService.getWidgetsForDataset(datasetId).subscribe(linked => { this.mMenuitemsWidgetService.getWidgetsForDataset(datasetId).subscribe(linked => { this.linkedWidgets = linked; });
this.linkedWidgets = linked;
});
} }
viewWidget(widget: MenuItemsWidget): void { viewWidget(widget: MenuItemsWidget): void {
this.widgetToPreview = widget; this.widgetToPreview = widget;
try {
this.configAsObject = JSON.parse(widget.config || '{}');
} catch (e) {
this.notificationService.error('Error', 'Failed to parse widget config.');
this.configAsObject = {};
}
const componentType = this.widgetComponentMap[widget.widget.component];
if (componentType) {
this.previewPanel = {
id: widget.widget.widgetId,
componentType: componentType,
componentInputs: { config: this.configAsObject },
row: 0, col: 0, sizeX: 1, sizeY: 1 // Dummy values, not used for layout here
};
} else {
this.previewPanel = null;
this.notificationService.error('Error', `Preview not available for widget type: ${widget.widget.component}`);
}
}
saveConfiguration(): void {
if (!this.widgetToPreview) return;
this.widgetToPreview.config = JSON.stringify(this.configAsObject);
this.mMenuitemsWidgetService.saveLinkedWidget(this.widgetToPreview).subscribe(() => {
this.notificationService.success('Success', 'Configuration saved successfully!');
// Refresh the preview to apply the new config
this.viewWidget(this.widgetToPreview!);
});
} }
removeWidget(widget: MenuItemsWidget, event: MouseEvent): void { removeWidget(widget: MenuItemsWidget, event: MouseEvent): void {
event.stopPropagation(); // Prevent row selection when clicking the button event.stopPropagation();
if (confirm('Are you sure you want to unlink this widget?')) { if (confirm('Are you sure you want to unlink this widget?')) {
this.mMenuitemsWidgetService.deleteLinkedWidget(widget.itemId).subscribe(() => { this.mMenuitemsWidgetService.deleteLinkedWidget(widget.itemId).subscribe(() => {
this.notificationService.success('Success', 'Widget unlinked successfully!'); this.notificationService.success('Success', 'Widget unlinked successfully!');
if (this.widgetToPreview?.itemId === widget.itemId) { if (this.widgetToPreview?.itemId === widget.itemId) {
this.widgetToPreview = null; this.clearPreview();
} }
this.loadLinkedWidgetsForDataset(this.selectedDatasetId!); this.loadLinkedWidgetsForDataset(this.selectedDatasetId!);
}); });
...@@ -112,19 +210,19 @@ export class DatasetWidgetLinkerComponent implements OnInit { ...@@ -112,19 +210,19 @@ export class DatasetWidgetLinkerComponent implements OnInit {
addSelectedWidgets(): void { addSelectedWidgets(): void {
const selectedWidgets = this.grid.getSelectedRecords() as WidgetModel[]; const selectedWidgets = this.grid.getSelectedRecords() as WidgetModel[];
if (selectedWidgets.length > 0) { if (selectedWidgets.length > 0) {
selectedWidgets.forEach(widgetModel => { selectedWidgets.forEach(widgetModel => { this.linkWidget(widgetModel); });
this.linkWidget(widgetModel);
});
} }
this.addWidgetDialog.hide(); this.addWidgetDialog.hide();
} }
private linkWidget(widget: WidgetModel): void { private clearPreview(): void {
if (!this.selectedDatasetId) { this.widgetToPreview = null;
this.notificationService.warning('Warning', 'Something went wrong. No dataset selected.'); this.previewPanel = null;
return; this.configAsObject = {};
} }
private linkWidget(widget: WidgetModel): void {
if (!this.selectedDatasetId) return;
if (this.linkedWidgets.some(lw => lw.widget.widgetId === widget.widgetId)) { if (this.linkedWidgets.some(lw => lw.widget.widgetId === widget.widgetId)) {
this.notificationService.info('Info', `'${widget.thName}' is already linked.`); this.notificationService.info('Info', `'${widget.thName}' is already linked.`);
return; return;
...@@ -140,14 +238,9 @@ export class DatasetWidgetLinkerComponent implements OnInit { ...@@ -140,14 +238,9 @@ export class DatasetWidgetLinkerComponent implements OnInit {
dialogRef.afterClosed().subscribe(resultConfig => { dialogRef.afterClosed().subscribe(resultConfig => {
if (resultConfig) { if (resultConfig) {
const newLinkedWidget: MenuItemsWidget = { const newLinkedWidget: MenuItemsWidget = {
companyId: 'DEMO', // Placeholder companyId: 'DEMO', itemId: this.selectedDatasetId!, widget: widget,
itemId: this.selectedDatasetId!, config: JSON.stringify(resultConfig), data: '', perspective: ''
widget: widget,
config: JSON.stringify(resultConfig),
data: '',
perspective: ''
}; };
this.mMenuitemsWidgetService.saveLinkedWidget(newLinkedWidget).subscribe(() => { this.mMenuitemsWidgetService.saveLinkedWidget(newLinkedWidget).subscribe(() => {
this.notificationService.success('Success', `'${widget.thName}' linked successfully!`); this.notificationService.success('Success', `'${widget.thName}' linked successfully!`);
this.loadLinkedWidgetsForDataset(this.selectedDatasetId!); this.loadLinkedWidgetsForDataset(this.selectedDatasetId!);
......
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