Commit 6b8b4df6 by Ooh-Ao

widget

parent a25087d6
......@@ -732,6 +732,7 @@ export class DashboardManagementComponent implements OnInit {
}
onDatasetSelected(datasetId: string): void {
console.log('Dataset selected:', datasetId); // Added for debugging/clarity
if (this.dashboardData) {
this.dashboardData.datasetId = datasetId;
this.dashboardStateService.selectDataset(datasetId);
......
......@@ -26,10 +26,13 @@ export class DashboardStateService {
if (dataset && dataset.url) {
return this.http.get<any[]>(dataset.url).pipe(
map(data => {
console.log('Fetched data for dataset:', data); // Log fetched data
if (data && data.length > 0) {
const columns = Object.keys(data[0]);
console.log('Derived columns:', columns); // Log derived columns
return { data, columns };
} else {
console.log('Fetched data is empty or invalid.'); // Log empty data
return { data: [], columns: [] };
}
})
......
<ejs-chart [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-chart *ngIf="!isLoading && !hasError" [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<e-series-collection>
<e-series [dataSource]="chartData" type="Area" xName="x" yName="y" name="Sales"></e-series>
</e-series-collection>
......
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartModule, AreaSeriesService, CategoryService, LegendService, TooltipService, DataLabelService } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-area-chart-widget',
......@@ -10,47 +11,29 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [AreaSeriesService, CategoryService, LegendService, TooltipService, DataLabelService],
templateUrl: './area-chart-widget.component.html',
})
export class AreaChartWidgetComponent implements OnInit {
@Input() config: any;
export class AreaChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public title: string = 'Area Chart';
public primaryXAxis: Object;
public primaryYAxis: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.primaryXAxis = { valueType: 'Category', title: 'Month' };
this.primaryYAxis = { title: 'Sales' };
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.xField && this.config.yField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.xAxisTitle) {
this.primaryXAxis = { ...this.primaryXAxis, title: this.config.xAxisTitle, valueType: 'Category' };
}
if (this.config.yAxisTitle) {
this.primaryYAxis = { ...this.primaryYAxis, title: this.config.yAxisTitle };
}
this.primaryXAxis = { valueType: 'Category', title: this.config.xAxisTitle || '' };
this.primaryYAxis = { title: this.config.yAxisTitle || '' };
}
resetChart(): void {
onReset(): void {
this.title = 'Area Chart (Default)';
this.chartData = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: 28 },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: 30 },
{ x: 'Jan', y: 35 }, { x: 'Feb', y: 28 },
{ x: 'Mar', y: 34 }, { x: 'Apr', y: 32 },
{ x: 'May', y: 40 }, { x: 'Jun', y: 30 },
];
this.primaryXAxis = { valueType: 'Category', title: 'Month' };
this.primaryYAxis = { title: 'Sales' };
}
}
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../models/widgets.model';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-attendance-overview-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-yellow-50">
<h3 class="font-bold text-yellow-800">{{ title }}</h3>
<p class="text-sm text-yellow-600">Present: {{ present }}</p>
<p class="text-sm text-yellow-600">On Leave: {{ onLeave }}</p>
<p class="text-sm text-yellow-600">Absent: {{ absent }}</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-yellow-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-yellow-600">Present: {{ present }}</p>
<p class="text-sm text-yellow-600">On Leave: {{ onLeave }}</p>
<p class="text-sm text-yellow-600">Absent: {{ absent }}</p>
</div>
</div>
`
})
export class AttendanceOverviewWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Today\'s Attendance';
present: number = 0;
onLeave: number = 0;
absent: number = 0;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
export class AttendanceOverviewWidgetComponent extends BaseWidgetComponent {
public present: number = 0;
public onLeave: number = 0;
public absent: number = 0;
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
const firstItem = data[0];
this.present = firstItem[this.config.presentField] || 0;
this.onLeave = firstItem[this.config.onLeaveField] || 0;
this.absent = firstItem[this.config.absentField] || 0;
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
if (data.length > 0) {
const firstItem = data[0];
this.present = firstItem[this.config.presentField] || 0;
this.onLeave = firstItem[this.config.onLeaveField] || 0;
this.absent = firstItem[this.config.absentField] || 0;
}
}
resetData(): void {
onReset(): void {
this.title = 'Attendance (Default)';
this.present = 150;
this.onLeave = 10;
this.absent = 5;
......
<ejs-chart [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-chart *ngIf="!isLoading && !hasError" [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<e-series-collection>
<e-series [dataSource]="chartData" [type]="type" xName="x" yName="y" name="Data"></e-series>
</e-series-collection>
......
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartModule, BarSeriesService, ColumnSeriesService, CategoryService, LegendService, TooltipService, DataLabelService } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-bar-chart-widget',
......@@ -10,51 +11,33 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [BarSeriesService, ColumnSeriesService, CategoryService, LegendService, TooltipService, DataLabelService],
templateUrl: './bar-chart-widget.component.html',
})
export class BarChartWidgetComponent implements OnInit {
@Input() config: any;
@Input() chartData: Object[];
@Input() title: string = 'Bar Chart';
@Input() type: 'Bar' | 'Column' = 'Column';
export class BarChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public type: 'Bar' | 'Column' = 'Column';
public primaryXAxis: Object;
public primaryYAxis: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.primaryXAxis = { valueType: 'Category', title: 'Country' };
this.primaryYAxis = { title: 'Sales' };
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.xField && this.config.yField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.xAxisTitle) {
this.primaryXAxis = { ...this.primaryXAxis, title: this.config.xAxisTitle };
}
if (this.config.yAxisTitle) {
this.primaryYAxis = { ...this.primaryYAxis, title: this.config.yAxisTitle };
}
if (this.config.type) {
this.type = this.config.type;
}
this.primaryXAxis = { valueType: 'Category', title: this.config.xAxisTitle || '' };
this.primaryYAxis = { title: this.config.yAxisTitle || '' };
this.type = this.config.type || 'Column';
}
resetChart(): void {
onReset(): void {
this.title = 'Bar Chart (Default)';
this.chartData = [
{ country: 'USA', sales: 20 },
{ country: 'China', sales: 25 },
{ country: 'Japan', sales: 18 },
{ country: 'Germany', sales: 15 },
{ x: 'USA', y: 20 },
{ x: 'China', y: 25 },
{ x: 'Japan', y: 18 },
{ x: 'Germany', y: 15 },
];
this.primaryXAxis = { valueType: 'Category', title: 'Country' };
this.primaryYAxis = { title: 'Sales' };
}
}
import { Input, OnInit, OnDestroy, Directive } from '@angular/core';
import { Subscription } from 'rxjs';
import { DashboardStateService, SelectedDataset } from '../services/dashboard-state.service';
@Directive() // Use @Directive() for base classes without their own template
export abstract class BaseWidgetComponent implements OnInit, OnDestroy {
@Input() config: any;
public title: string;
public isLoading = true;
public hasError = false;
public errorMessage: string | null = null;
protected subscription: Subscription = new Subscription();
constructor(protected dashboardStateService: DashboardStateService) {}
ngOnInit(): void {
this.title = this.config?.title || 'Widget';
const datasetSub = this.dashboardStateService.selectedDataset$.subscribe({
next: (selectedDataset: SelectedDataset | null) => {
this.isLoading = true;
this.hasError = false;
if (selectedDataset && selectedDataset.data) {
try {
this.onDataUpdate(selectedDataset.data);
this.isLoading = false;
} catch (error) {
this.handleError(error);
}
} else {
this.onReset();
this.isLoading = false;
}
},
error: (err) => {
this.handleError(err);
}
});
this.subscription.add(datasetSub);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
private handleError(error: any): void {
this.isLoading = false;
this.hasError = true;
this.errorMessage = 'An error occurred while loading widget data.';
console.error('Widget Error:', error);
this.onReset();
}
/**
* Abstract method to be implemented by child components.
* This method is called when new data is available from the selected dataset.
* @param data The data from the selected dataset.
*/
abstract onDataUpdate(data: any[]): void;
/**
* Abstract method to be implemented by child components.
* This method is called when there is no dataset selected or data is not available.
*/
abstract onReset(): void;
}
<div class="bg-white p-6 rounded-lg shadow-md h-full flex flex-col">
<h3 class="text-lg font-semibold text-gray-500 mb-4">Monthly Activity</h3>
<div #chart class="h-full w-full"></div>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<div *ngIf="!isLoading && !hasError" #chart class="h-full w-full"></div>
</div>
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, Input, SimpleChanges, OnChanges } from '@angular/core';
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MockDataService } from '../../common/mock-data.service';
import { DatasetModel } from '../../models/widgets.model';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient } from '@angular/common/http';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
// Declare ApexCharts globally if it's loaded via a script tag
declare var ApexCharts: any;
......@@ -14,21 +12,56 @@ declare var ApexCharts: any;
imports: [CommonModule],
templateUrl: './chart-widget.component.html',
})
export class ChartWidgetComponent implements OnInit, AfterViewInit, OnChanges {
export class ChartWidgetComponent extends BaseWidgetComponent implements AfterViewInit {
@ViewChild('chart') chartElement!: ElementRef;
@Input() config: any;
chartOptions: any;
private chartInstance: any; // To store the ApexCharts instance
constructor(private datasetService: DatasetService, private http: HttpClient) { }
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngAfterViewInit(): void {
if (typeof ApexCharts !== 'undefined' && this.chartElement) {
// Initial chart options are set in onReset
this.chartInstance = new ApexCharts(this.chartElement.nativeElement, this.getChartOptions([], []));
this.chartInstance.render();
// The subscription in BaseWidget will trigger onDataUpdate or onReset, which will update the chart.
} else {
console.error('ApexCharts is not loaded or chart element is not available.');
}
}
onDataUpdate(data: any[]): void {
const categories = data.map(item => item[this.config.xField]);
const series = this.config.yFields.map((yField: any) => ({
name: yField.name || yField.field,
data: data.map(item => item[yField.field])
}));
if (this.chartInstance) {
this.chartInstance.updateOptions(this.getChartOptions(series, categories));
}
}
ngOnInit(): void {
// Initial chart options, will be updated with fetched data
this.chartOptions = {
series: [],
onReset(): void {
this.title = 'Chart (Default)';
const defaultSeries = [{
name: 'Sample Series',
data: [10, 41, 35, 51, 49, 62, 69, 91, 148]
}];
const defaultCategories = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'];
if (this.chartInstance) {
this.chartInstance.updateOptions(this.getChartOptions(defaultSeries, defaultCategories));
}
}
private getChartOptions(series: any[], categories: any[]): any {
return {
series: series,
chart: {
type: 'bar',
type: this.config?.type || 'bar',
height: 250,
toolbar: {
show: false
......@@ -50,11 +83,11 @@ export class ChartWidgetComponent implements OnInit, AfterViewInit, OnChanges {
colors: ['transparent']
},
xaxis: {
categories: [],
categories: categories,
},
yaxis: {
title: {
text: 'Number'
text: this.config?.yAxisTitle || 'Number'
}
},
fill: {
......@@ -66,69 +99,10 @@ export class ChartWidgetComponent implements OnInit, AfterViewInit, OnChanges {
return val + " units"
}
}
},
title: {
text: this.title
}
};
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
// Reset chart if config or datasetId is removed
this.chartOptions.series = [];
this.chartOptions.xaxis.categories = [];
if (this.chartInstance) {
this.chartInstance.updateOptions(this.chartOptions);
}
}
}
ngAfterViewInit(): void {
if (typeof ApexCharts !== 'undefined' && this.chartElement) {
this.chartInstance = new ApexCharts(this.chartElement.nativeElement, this.chartOptions);
this.chartInstance.render();
} else {
console.error('ApexCharts is not loaded or chart element is not available.');
}
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
// Transform data for ApexCharts
// Assuming config.xField for categories and config.yFields for series
const categories = data.map(item => item[this.config.xField]);
const series = this.config.yFields.map((yField: any) => ({
name: yField.name || yField.field,
data: data.map(item => item[yField.field])
}));
const newOptions = {
series: series,
xaxis: {
categories: categories,
},
title: {
text: this.config.title || 'Chart'
},
yaxis: {
title: {
text: this.config.yAxisTitle || 'Number'
}
}
};
if (this.chartInstance) {
this.chartInstance.updateOptions(newOptions);
} else {
// If chartInstance is not yet created (e.g., first load), update chartOptions directly
this.chartOptions = { ...this.chartOptions, ...newOptions };
}
});
}
});
}
}
}
<ejs-chart [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-chart *ngIf="!isLoading && !hasError && config?.series" [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<e-series-collection>
<e-series *ngFor="let yField of config.yFields" [dataSource]="chartData" [type]="yField.type || 'Column'" xName="x" [yName]="yField.field" [name]="yField.name"></e-series>
<e-series *ngFor="let series of config.series"
[dataSource]="chartData"
[type]="series.type || 'Column'"
[xName]="series.xName"
[yName]="series.yName"
[name]="series.name">
</e-series>
</e-series-collection>
</ejs-chart>
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartModule, ColumnSeriesService, LineSeriesService, CategoryService, LegendService, TooltipService, DataLabelService } from '@syncfusion/ej2-angular-charts';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-combo-chart-widget',
standalone: true,
imports: [CommonModule, ChartModule, HttpClientModule],
providers: [ColumnSeriesService, LineSeriesService, CategoryService, LegendService, TooltipService, DataLabelService, DatasetService, HttpClient],
imports: [CommonModule, ChartModule],
providers: [ColumnSeriesService, LineSeriesService, CategoryService, LegendService, TooltipService, DataLabelService],
templateUrl: './combo-chart-widget.component.html',
})
export class ComboChartWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
@Input() chartData: Object[];
@Input() title: string = 'Combo Chart';
export class ComboChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public primaryXAxis: Object;
public primaryYAxis: Object;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
this.primaryXAxis = { valueType: 'Category', title: 'Month' };
this.primaryYAxis = { title: 'Value' };
if (!this.config || !this.config.datasetId) {
this.chartData = [
{ month: 'Jan', sales: 35, profit: 10 }, { month: 'Feb', sales: 28, profit: 12 },
{ month: 'Mar', sales: 34, profit: 8 }, { month: 'Apr', sales: 32, profit: 15 },
{ month: 'May', sales: 40, profit: 11 }, { month: 'Jun', sales: 30, profit: 13 },
];
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.chartData = [
{ month: 'Jan', sales: 35, profit: 10 }, { month: 'Feb', sales: 28, profit: 12 },
{ month: 'Mar', sales: 34, profit: 8 }, { month: 'Apr', sales: 32, profit: 15 },
{ month: 'May', sales: 40, profit: 11 }, { month: 'Jun', sales: 30, profit: 13 },
];
}
onDataUpdate(data: any[]): void {
this.chartData = data;
this.primaryXAxis = { valueType: 'Category', title: this.config.xAxisTitle || '' };
this.primaryYAxis = { title: this.config.yAxisTitle || '' };
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
// Transform data for combo chart
// Assuming config.xField and config.yFields (array of y-fields)
this.chartData = data.map(item => {
const transformedItem: any = { x: item[this.config.xField] };
this.config.yFields.forEach((field: string) => {
transformedItem[field] = item[field];
});
return transformedItem;
});
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.xAxisTitle) {
this.primaryXAxis = { ...this.primaryXAxis, title: this.config.xAxisTitle };
}
if (this.config.yAxisTitle) {
this.primaryYAxis = { ...this.primaryYAxis, title: this.config.yAxisTitle };
}
});
}
});
onReset(): void {
this.title = 'Combo Chart (Default)';
this.chartData = [
{ x: 'Jan', y1: 35, y2: 10 }, { x: 'Feb', y1: 28, y2: 12 },
{ x: 'Mar', y1: 34, y2: 8 }, { x: 'Apr', y1: 32, y2: 15 },
{ x: 'May', y1: 40, y2: 11 }, { x: 'Jun', y1: 30, y2: 13 },
];
this.primaryXAxis = { valueType: 'Category', title: 'Month' };
this.primaryYAxis = { title: 'Value' };
// Provide a default series config for the reset state
if (!this.config) {
this.config = {};
}
this.config.series = [
{ type: 'Column', xName: 'x', yName: 'y1', name: 'Sales' },
{ type: 'Line', xName: 'x', yName: 'y2', name: 'Profit' }
];
}
}
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../models/widgets.model';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-company-info-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-blue-50">
<h3 class="font-bold text-blue-800">{{ title }}</h3>
<p class="text-sm text-blue-600">Company Name: {{ companyName }}</p>
<p class="text-sm text-blue-600">Address: {{ address }}</p>
<p class="text-sm text-blue-600">Contact: {{ contact }}</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-blue-600">Company Name: {{ companyName }}</p>
<p class="text-sm text-blue-600">Address: {{ address }}</p>
<p class="text-sm text-blue-600">Contact: {{ contact }}</p>
</div>
</div>
`
})
export class CompanyInfoWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Company Information';
companyName: string = '';
address: string = '';
contact: string = '';
export class CompanyInfoWidgetComponent extends BaseWidgetComponent {
public companyName: string = '';
public address: string = '';
public contact: string = '';
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
const firstItem = data[0];
this.companyName = firstItem[this.config.companyNameField] || '';
this.address = firstItem[this.config.addressField] || '';
this.contact = firstItem[this.config.contactField] || '';
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
if (data.length > 0) {
const firstItem = data[0];
this.companyName = firstItem[this.config.companyNameField] || '';
this.address = firstItem[this.config.addressField] || '';
this.contact = firstItem[this.config.contactField] || '';
}
}
resetData(): void {
onReset(): void {
this.title = 'Company Info (Default)';
this.companyName = 'My Company Inc.';
this.address = '123 Main St, Anytown USA';
this.contact = 'info@mycompany.com';
......
......@@ -4,8 +4,24 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<p>Company Name: {{ companyName }}</p>
<p>Address: {{ address }}</p>
<p>Contact: {{ contact }}</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p>Company Name: {{ companyName }}</p>
<p>Address: {{ address }}</p>
<p>Contact: {{ contact }}</p>
</div>
</div>
</div>
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-company-info-subfolder-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
templateUrl: './company-info-widget.component.html',
styleUrls: ['./company-info-widget.component.scss']
})
export class CompanyInfoSubfolderWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Company Information';
companyName: string = '';
address: string = '';
contact: string = '';
export class CompanyInfoSubfolderWidgetComponent extends BaseWidgetComponent {
public companyName: string = '';
public address: string = '';
public contact: string = '';
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
const firstItem = data[0];
this.companyName = firstItem[this.config.companyNameField] || '';
this.address = firstItem[this.config.addressField] || '';
this.contact = firstItem[this.config.contactField] || '';
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
if (data.length > 0) {
const firstItem = data[0];
this.companyName = firstItem[this.config.companyNameField] || '';
this.address = firstItem[this.config.addressField] || '';
this.contact = firstItem[this.config.contactField] || '';
}
}
resetData(): void {
onReset(): void {
this.title = 'Company Info (Default)';
this.companyName = 'My Company Inc. (Subfolder)';
this.address = '456 Subfolder Ave, Anytown USA';
this.contact = 'info@subfolder.com';
......
<ejs-accumulationchart [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-accumulationchart *ngIf="!isLoading && !hasError" [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<e-accumulation-series-collection>
<e-accumulation-series [dataSource]="chartData" type="Doughnut" xName="x" yName="y" [dataLabel]="{ visible: true, name: 'text', position: 'Inside' }" innerRadius="40%"></e-accumulation-series>
</e-accumulation-series-collection>
......
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AccumulationChartModule, AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-doughnut-chart-widget',
......@@ -10,27 +11,18 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService],
templateUrl: './doughnut-chart-widget.component.html',
})
export class DoughnutChartWidgetComponent implements OnInit {
@Input() chartData: Object[];
@Input() title: string = 'Doughnut Chart';
@Input() legendSettings: Object;
@Input() config: any;
export class DoughnutChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public legendSettings: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.legendSettings = { visible: true };
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.xField && this.config.yField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.legendSettings = { visible: true };
let transformedData = data;
if (this.config.aggregation === 'count') {
const counts = transformedData.reduce((acc, item) => {
const key = item[this.config.xField];
......@@ -42,19 +34,18 @@ export class DoughnutChartWidgetComponent implements OnInit {
transformedData = transformedData.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
}
this.chartData = transformedData;
if (this.config.title) {
this.title = this.config.title;
}
}
resetChart(): void {
onReset(): void {
this.title = 'Doughnut Chart (Default)';
this.legendSettings = { visible: true };
this.chartData = [
{ browser: 'Chrome', users: 37 },
{ browser: 'Firefox', users: 17 },
{ browser: 'Internet Explorer', users: 19 },
{ browser: 'Edge', users: 4 },
{ browser: 'Safari', users: 11 },
{ browser: 'Other', users: 12 },
{ x: 'Chrome', y: 37 },
{ x: 'Firefox', y: 17 },
{ x: 'Internet Explorer', y: 19 },
{ x: 'Edge', y: 4 },
{ x: 'Safari', y: 11 },
{ x: 'Other', y: 12 },
];
}
}
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../models/widgets.model';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-employee-directory-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-indigo-50">
<h3 class="font-bold text-indigo-800">{{ title }}</h3>
<div *ngIf="employees.length > 0">
<div *ngFor="let employee of employees" class="mb-2">
<p class="text-sm text-indigo-600">{{ employee.name }} - {{ employee.position }} ({{ employee.department }})</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<div *ngIf="employees.length > 0">
<div *ngFor="let employee of employees" class="mb-2">
<p class="text-sm text-indigo-600">{{ employee.name }} - {{ employee.position }} ({{ employee.department }})</p>
</div>
</div>
<p *ngIf="employees.length === 0" class="text-sm text-indigo-600">No employees found.</p>
</div>
<p *ngIf="employees.length === 0" class="text-sm text-indigo-600">No employees found.</p>
</div>
`
})
export class EmployeeDirectoryWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Employee Directory';
employees: any[] = [];
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
export class EmployeeDirectoryWidgetComponent extends BaseWidgetComponent {
public employees: any[] = [];
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
this.employees = data.map(item => ({
name: item[this.config.nameField] || '',
position: item[this.config.positionField] || '',
department: item[this.config.departmentField] || '',
}));
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
}
onDataUpdate(data: any[]): void {
this.employees = data.map(item => ({
name: item[this.config.nameField] || '',
position: item[this.config.positionField] || '',
department: item[this.config.departmentField] || '',
}));
}
resetData(): void {
onReset(): void {
this.title = 'Employee Directory (Default)';
this.employees = [
{ name: 'John Doe', position: 'Software Engineer', department: 'IT' },
{ name: 'Jane Smith', position: 'Project Manager', department: 'Operations' },
......
<ejs-maps [titleSettings]="{ text: title }" [zoomSettings]="zoomSettings" [layers]="layers">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-maps *ngIf="!isLoading && !hasError" [titleSettings]="{ text: title }" [zoomSettings]="zoomSettings" [layers]="layers">
</ejs-maps>
import { Component, Input, OnInit, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MapsModule, MarkerService, ZoomService, DataLabelService, LegendService, MapsTooltipService } from '@syncfusion/ej2-angular-maps';
import { DatasetModel } from '../../models/widgets.model';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient } from '@angular/common/http';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-filled-map-widget',
......@@ -12,90 +11,46 @@ import { HttpClient } from '@angular/common/http';
providers: [MarkerService, ZoomService, DataLabelService, LegendService, MapsTooltipService],
templateUrl: './filled-map-widget.component.html',
})
export class FilledMapWidgetComponent implements OnInit {
@Input() config: any;
@Input() title: string = 'Filled Map';
@Input() mapData: Object[];
export class FilledMapWidgetComponent extends BaseWidgetComponent {
public zoomSettings: Object;
public layers: Object[];
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
this.zoomSettings = { enable: true };
if (!this.config || !this.config.datasetId) {
this.mapData = [
{ country: 'United States', value: 80 },
{ country: 'Canada', value: 60 },
{ country: 'Mexico', value: 40 },
];
}
this.layers = [
{
shapeData: new Object({ data: 'https://cdn.syncfusion.com/maps/map-data/world-map.json' }),
shapeDataPath: 'name',
shapePropertyPath: 'name',
dataSource: this.mapData,
tooltipSettings: { visible: true, valuePath: 'value' },
shapeSettings: {
fill: '#E5EEF6',
colorValuePath: 'value',
colorMapping: [
{ value: 0, color: '#C3E6CB' },
{ value: 50, color: '#FFECB5' },
{ value: 100, color: '#F5C6CB' }
]
}
}
];
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.mapData = [
{ country: 'United States', value: 80 },
{ country: 'Canada', value: 60 },
{ country: 'Mexico', value: 40 },
];
this.updateLayers();
}
onDataUpdate(data: any[]): void {
this.zoomSettings = { enable: true };
const mapData = data.map(item => ({
country: item[this.config.countryField],
value: item[this.config.valueField]
}));
this.updateLayers(mapData);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
this.mapData = data.map(item => ({
country: item[this.config.countryField],
value: item[this.config.valueField]
}));
if (this.config.title) {
this.title = this.config.title;
}
this.updateLayers();
});
}
});
}
onReset(): void {
this.title = 'Filled Map (Default)';
this.zoomSettings = { enable: true };
const defaultMapData = [
{ country: 'United States', value: 80 },
{ country: 'Canada', value: 60 },
{ country: 'Mexico', value: 40 },
];
this.updateLayers(defaultMapData);
}
updateLayers(): void {
private updateLayers(dataSource: any[]): void {
this.layers = [
{
shapeData: new Object({ data: 'https://cdn.syncfusion.com/maps/map-data/world-map.json' }),
shapeDataPath: 'name',
shapePropertyPath: 'name',
dataSource: this.mapData,
dataSource: dataSource,
tooltipSettings: { visible: true, valuePath: 'value' },
shapeSettings: {
fill: '#E5EEF6',
colorValuePath: 'value',
colorMapping: [
colorMapping: this.config?.colorMapping || [
{ value: 0, color: '#C3E6CB' },
{ value: 50, color: '#FFECB5' },
{ value: 100, color: '#F5C6CB' }
......
<ejs-accumulationchart [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-accumulationchart *ngIf="!isLoading && !hasError" [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<e-accumulation-series-collection>
<e-accumulation-series [dataSource]="chartData" type="Funnel" xName="x" yName="y" [dataLabel]="{ visible: true, name: 'text', position: 'Inside' }"></e-accumulation-series>
</e-accumulation-series-collection>
......
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AccumulationChartModule, FunnelSeriesService, AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-funnel-chart-widget',
......@@ -10,31 +11,22 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [FunnelSeriesService, AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService],
templateUrl: './funnel-chart-widget.component.html',
})
export class FunnelChartWidgetComponent implements OnInit {
@Input() config: any;
@Input() chartData: Object[];
@Input() title: string = 'Funnel Chart';
@Input() legendSettings: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.legendSettings = { visible: true };
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.xField && this.config.yField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
export class FunnelChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public legendSettings: Object;
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.legendSettings = { visible: true };
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
if (this.config.title) {
this.title = this.config.title;
}
}
resetChart(): void {
onReset(): void {
this.title = 'Funnel Chart (Default)';
this.legendSettings = { visible: true };
this.chartData = [
{ x: 'Website Visitors', y: 10000 },
{ x: 'Leads', y: 8000 },
......
<ejs-circulargauge [title]="title" [axes]="axes">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-circulargauge *ngIf="!isLoading && !hasError" [title]="title" [axes]="axes">
</ejs-circulargauge>
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CircularGaugeModule, GaugeTooltipService, AnnotationsService } from '@syncfusion/ej2-angular-circulargauge';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-gauge-chart-widget',
......@@ -10,55 +11,48 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [GaugeTooltipService, AnnotationsService],
templateUrl: './gauge-chart-widget.component.html',
})
export class GaugeChartWidgetComponent implements OnInit {
@Input() config: any;
@Input() value: number = 70;
@Input() title: string = 'Gauge Chart';
export class GaugeChartWidgetComponent extends BaseWidgetComponent {
public axes: Object[];
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.resetChart(); // Set initial state
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.valueField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
let value = 0;
if (data.length > 0) {
this.value = data[0][this.config.valueField]; // Assuming single value for gauge
}
if (this.config.title) {
this.title = this.config.title;
// Assuming gauge shows a single value, either aggregated or the first item's value
if (this.config.aggregation === 'sum') {
value = data.reduce((sum, item) => sum + (item[this.config.valueField] || 0), 0);
} else if (this.config.aggregation === 'avg') {
const sum = data.reduce((sum, item) => sum + (item[this.config.valueField] || 0), 0);
value = sum / data.length;
} else {
value = data[0][this.config.valueField];
}
}
this.setAxes();
this.setAxes(value);
}
resetChart(): void {
this.value = 70;
this.setAxes();
onReset(): void {
this.title = 'Gauge (Default)';
this.setAxes(70);
}
setAxes(): void {
private setAxes(value: number): void {
this.axes = [{
line: { width: 0 },
labelStyle: { font: { size: '0px' } },
majorTicks: { height: 0 },
minorTicks: { height: 0 },
pointers: [{
value: this.value,
value: value,
radius: '80%',
pointerWidth: 8,
cap: { radius: 7 },
needleTail: { length: '18%' }
}],
ranges: [
ranges: this.config?.ranges || [
{ start: 0, end: 50, color: '#E0B9B9' },
{ start: 50, end: 75, color: '#B9D7EA' },
{ start: 75, end: 100, color: '#B9EAB9' }
......
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetService } from '../services/dataset.service';
import { DatasetModel } from '../models/widgets.model';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-headcount-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-green-50">
<h3 class="font-bold text-green-800">{{ title }}</h3>
<p class="text-sm text-green-600">Total Headcount: {{ totalHeadcount }}</p>
<div *ngIf="breakdown.length > 0">
<p class="text-sm text-green-600">By {{ config?.categoryField || 'Category' }}:</p>
<ul>
<li *ngFor="let item of breakdown" class="text-xs text-green-600">
{{ item.category }}: {{ item.count }}
</li>
</ul>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-green-600">Total Headcount: {{ totalHeadcount }}</p>
<div *ngIf="breakdown.length > 0">
<p class="text-sm text-green-600">By {{ config?.categoryField || 'Category' }}:</p>
<ul>
<li *ngFor="let item of breakdown" class="text-xs text-green-600">
{{ item.category }}: {{ item.count }}
</li>
</ul>
</div>
</div>
</div>
`
})
export class HeadcountWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Employee Headcount';
totalHeadcount: number = 0;
breakdown: { category: string, count: number }[] = [];
export class HeadcountWidgetComponent extends BaseWidgetComponent {
public totalHeadcount: number = 0;
public breakdown: { category: string, count: number }[] = [];
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
this.totalHeadcount = data.length;
if (this.config.categoryField) {
const counts = data.reduce((acc, item) => {
const category = item[this.config.categoryField];
acc[category] = (acc[category] || 0) + 1;
return acc;
}, {});
this.breakdown = Object.keys(counts).map(key => ({ category: key, count: counts[key] }));
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
this.totalHeadcount = data.length;
if (this.config.categoryField) {
const counts = data.reduce((acc, item) => {
const category = item[this.config.categoryField];
acc[category] = (acc[category] || 0) + 1;
return acc;
}, {});
this.breakdown = Object.keys(counts).map(key => ({ category: key, count: counts[key] }));
}
}
resetData(): void {
onReset(): void {
this.title = 'Headcount (Default)';
this.totalHeadcount = 200;
this.breakdown = [
{ category: 'IT', count: 50 },
......
......@@ -3,21 +3,42 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th *ngFor="let header of headers" scope="col" class="px-6 py-3">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data" class="bg-white border-b">
<td *ngFor="let cell of row" class="px-6 py-4">
{{ cell }}
</td>
</tr>
</tbody>
</table>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Data Table -->
<div *ngIf="!isLoading && !hasError">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th *ngFor="let header of headers" scope="col" class="px-6 py-3">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data" class="bg-white border-b">
<td *ngFor="let cell of row" class="px-6 py-4">
{{ cell }}
</td>
</tr>
<tr *ngIf="data.length === 0">
<td [attr.colspan]="headers.length" class="px-6 py-4 text-center text-gray-500">
No data available.
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-matrix-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
templateUrl: './matrix-widget.component.html',
styleUrls: ['./matrix-widget.component.scss']
})
export class MatrixWidgetComponent implements OnInit, OnChanges {
@Input() title: string = 'Matrix';
@Input() headers: string[] = [];
@Input() data: any[][] = [];
@Input() config: any;
export class MatrixWidgetComponent extends BaseWidgetComponent {
public headers: string[] = [];
public data: any[][] = [];
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (!this.config || !this.config.datasetId) {
this.headers = ['Category', 'Q1', 'Q2', 'Q3', 'Q4'];
this.data = [
['Product A', 100, 120, 150, 130],
['Product B', 80, 90, 110, 100],
['Product C', 150, 130, 160, 140],
];
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.headers = ['Category', 'Q1', 'Q2', 'Q3', 'Q4'];
this.data = [
['Product A', 100, 120, 150, 130],
['Product B', 80, 90, 110, 100],
['Product C', 150, 130, 160, 140],
];
onDataUpdate(data: any[]): void {
if (this.config?.columns && data?.length > 0) {
this.headers = this.config.columns.map((col: any) => col.headerText);
this.data = data.map(row => this.config.columns.map((col: any) => row[col.field]));
} else {
this.onReset();
}
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (this.config.columns && data.length > 0) {
this.headers = this.config.columns.map((col: any) => col.headerText);
this.data = data.map(row => this.config.columns.map((col: any) => row[col.field]));
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
}
onReset(): void {
this.title = 'Matrix (Default)';
this.headers = ['Category', 'Q1', 'Q2', 'Q3', 'Q4'];
this.data = [
['Product A', 100, 120, 150, 130],
['Product B', 80, 90, 110, 100],
['Product C', 150, 130, 160, 140],
];
}
}
......@@ -3,9 +3,28 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<div *ngFor="let item of cardData" class="flex justify-between items-center py-2 border-b last:border-b-0">
<span class="text-gray-600">{{ item.label }}</span>
<span class="font-bold text-lg">{{ item.value }} <span class="text-sm text-gray-500">{{ item.unit }}</span></span>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<div *ngFor="let item of cardData" class="flex justify-between items-center py-2 border-b last:border-b-0">
<span class="text-gray-600">{{ item.label }}</span>
<span class="font-bold text-lg">{{ item.value }} <span class="text-sm text-gray-500">{{ item.unit }}</span></span>
</div>
<div *ngIf="!cardData || cardData.length === 0" class="text-center text-gray-500 py-4">
No data available.
</div>
</div>
</div>
</div>
import { Component, Input, OnInit, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetModel } from '../../models/widgets.model';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient } from '@angular/common/http';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-multi-row-card-widget',
......@@ -11,49 +10,27 @@ import { HttpClient } from '@angular/common/http';
templateUrl: './multi-row-card-widget.component.html',
styleUrls: ['./multi-row-card-widget.component.scss']
})
export class MultiRowCardWidgetComponent implements OnInit {
@Input() config: any;
@Input() cardData: any[];
@Input() title: string = 'Multi-Row Card';
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (!this.config || !this.config.datasetId) {
this.cardData = [
{ label: 'Total Sales', value: '1,234,567', unit: 'USD' },
{ label: 'New Customers', value: '5,432', unit: '' },
{ label: 'Conversion Rate', value: '12.34', unit: '%' },
];
}
export class MultiRowCardWidgetComponent extends BaseWidgetComponent {
public cardData: any[];
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.cardData = [
{ label: 'Total Sales', value: '1,234,567', unit: 'USD' },
{ label: 'New Customers', value: '5,432', unit: '' },
{ label: 'Conversion Rate', value: '12.34', unit: '%' },
];
}
onDataUpdate(data: any[]): void {
this.cardData = data.map(item => ({
label: item[this.config.labelField],
value: item[this.config.valueField],
unit: item[this.config.unitField] || ''
}));
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
this.cardData = data.map(item => ({
label: item[this.config.labelField],
value: item[this.config.valueField],
unit: item[this.config.unitField] || ''
}));
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
}
onReset(): void {
this.title = 'Multi-Row Card (Default)';
this.cardData = [
{ label: 'Total Sales', value: '1,234,567', unit: 'USD' },
{ label: 'New Customers', value: '5,432', unit: '' },
{ label: 'Conversion Rate', value: '12.34', unit: '%' },
];
}
}
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetService } from '../services/dataset.service';
import { DatasetModel } from '../models/widgets.model';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-payroll-summary-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-red-50">
<h3 class="font-bold text-red-800">{{ title }}</h3>
<p class="text-sm text-red-600">Total Payroll: {{ totalPayroll | currency }}</p>
<p class="text-sm text-red-600">Employees Paid: {{ employeesPaid }}</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-red-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-red-600">Total Payroll: {{ totalPayroll | currency }}</p>
<p class="text-sm text-red-600">Employees Paid: {{ employeesPaid }}</p>
</div>
</div>
`
})
export class PayrollSummaryWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Payroll Summary';
totalPayroll: number = 0;
employeesPaid: number = 0;
export class PayrollSummaryWidgetComponent extends BaseWidgetComponent {
public totalPayroll: number = 0;
public employeesPaid: number = 0;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
const firstItem = data[0];
this.totalPayroll = firstItem[this.config.totalPayrollField] || 0;
this.employeesPaid = firstItem[this.config.employeesPaidField] || 0;
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
if (data.length > 0) {
const firstItem = data[0];
this.totalPayroll = firstItem[this.config.totalPayrollField] || 0;
this.employeesPaid = firstItem[this.config.employeesPaidField] || 0;
}
}
resetData(): void {
onReset(): void {
this.title = 'Payroll Summary (Default)';
this.totalPayroll = 1500000;
this.employeesPaid = 180;
}
......
......@@ -4,8 +4,24 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<p>Employee: {{ employeeName }}</p>
<p>Pay Period: {{ payPeriod }}</p>
<p>Net Pay: {{ netPay | currency }}</p>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p>Employee: {{ employeeName }}</p>
<p>Pay Period: {{ payPeriod }}</p>
<p>Net Pay: {{ netPay | currency }}</p>
</div>
</div>
</div>
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-payroll-widget',
standalone: true,
imports: [CommonModule, HttpClientModule],
providers: [DatasetService, HttpClient],
imports: [CommonModule],
templateUrl: './payroll-widget.component.html',
styleUrls: ['./payroll-widget.component.scss']
})
export class PayrollWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
title: string = 'Payroll Details';
employeeName: string = '';
payPeriod: string = '';
netPay: number = 0;
export class PayrollWidgetComponent extends BaseWidgetComponent {
public employeeName: string = '';
public payPeriod: string = '';
public netPay: number = 0;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (this.config && this.config.datasetId) {
this.loadData();
} else {
this.resetData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.resetData();
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
const firstItem = data[0];
this.employeeName = firstItem[this.config.employeeNameField] || '';
this.payPeriod = firstItem[this.config.payPeriodField] || '';
this.netPay = firstItem[this.config.netPayField] || 0;
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
onDataUpdate(data: any[]): void {
if (data.length > 0) {
const firstItem = data[0];
this.employeeName = firstItem[this.config.employeeNameField] || '';
this.payPeriod = firstItem[this.config.payPeriodField] || '';
this.netPay = firstItem[this.config.netPayField] || 0;
}
}
resetData(): void {
onReset(): void {
this.title = 'Payroll (Default)';
this.employeeName = 'N/A';
this.payPeriod = 'N/A';
this.netPay = 0;
......
<ejs-accumulationchart [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-accumulationchart *ngIf="!isLoading && !hasError" [title]="title" [legendSettings]="legendSettings" [enableSmartLabels]="true">
<e-accumulation-series-collection>
<e-accumulation-series [dataSource]="chartData" type="Pie" xName="x" yName="y" [dataLabel]="{ visible: true, name: 'text', position: 'Inside' }"></e-accumulation-series>
</e-accumulation-series-collection>
......
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AccumulationChartModule, PieSeriesService, AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-pie-chart-widget',
......@@ -10,27 +11,18 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [PieSeriesService, AccumulationDataLabelService, AccumulationLegendService, AccumulationTooltipService],
templateUrl: './pie-chart-widget.component.html',
})
export class PieChartWidgetComponent implements OnInit {
@Input() chartData: Object[];
@Input() title: string = 'Pie Chart';
@Input() legendSettings: Object;
@Input() config: any;
export class PieChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public legendSettings: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.legendSettings = { visible: true };
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.xField && this.config.yField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.legendSettings = { visible: true };
let transformedData = data;
if (this.config.aggregation === 'count') {
const counts = transformedData.reduce((acc, item) => {
const key = item[this.config.xField];
......@@ -42,19 +34,18 @@ export class PieChartWidgetComponent implements OnInit {
transformedData = transformedData.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
}
this.chartData = transformedData;
if (this.config.title) {
this.title = this.config.title;
}
}
resetChart(): void {
onReset(): void {
this.title = 'Pie Chart (Default)';
this.legendSettings = { visible: true };
this.chartData = [
{ browser: 'Chrome', users: 37 },
{ browser: 'Firefox', users: 17 },
{ browser: 'Internet Explorer', users: 19 },
{ browser: 'Edge', users: 4 },
{ browser: 'Safari', users: 11 },
{ browser: 'Other', users: 12 },
{ x: 'Chrome', y: 37 },
{ x: 'Firefox', y: 17 },
{ x: 'Internet Explorer', y: 19 },
{ x: 'Edge', y: 4 },
{ x: 'Safari', y: 11 },
{ x: 'Other', y: 12 },
];
}
}
<ejs-chart [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-chart *ngIf="!isLoading && !hasError" [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<e-series-collection>
<e-series [dataSource]="chartData" [type]="type" xName="x" yName="y" [size]="config?.sizeField" name="Data"></e-series>
</e-series-collection>
......
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartModule, ScatterSeriesService, BubbleSeriesService, CategoryService, LegendService, TooltipService, DataLabelService } from '@syncfusion/ej2-angular-charts';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-scatter-bubble-chart-widget',
standalone: true,
imports: [CommonModule, ChartModule, HttpClientModule],
providers: [ScatterSeriesService, BubbleSeriesService, CategoryService, LegendService, TooltipService, DataLabelService, DatasetService, HttpClient],
imports: [CommonModule, ChartModule],
providers: [ScatterSeriesService, BubbleSeriesService, CategoryService, LegendService, TooltipService, DataLabelService],
templateUrl: './scatter-bubble-chart-widget.component.html',
})
export class ScatterBubbleChartWidgetComponent implements OnInit, OnChanges {
@Input() chartData: Object[];
@Input() title: string = 'Scatter & Bubble Chart';
@Input() type: 'Scatter' | 'Bubble' = 'Scatter';
@Input() config: any;
export class ScatterBubbleChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public type: 'Scatter' | 'Bubble' = 'Scatter';
public primaryXAxis: Object;
public primaryYAxis: Object;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnInit(): void {
this.primaryXAxis = { title: 'X-Value' };
this.primaryYAxis = { title: 'Y-Value' };
if (!this.config || !this.config.datasetId) {
if (this.type === 'Scatter') {
this.chartData = [
{ x: 10, y: 35 }, { x: 15, y: 28 },
{ x: 20, y: 34 }, { x: 25, y: 32 },
{ x: 30, y: 40 }, { x: 35, y: 30 },
];
} else { // Bubble
onDataUpdate(data: any[]): void {
this.type = this.config.type || 'Scatter';
if (this.type === 'Bubble') {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField], size: item[this.config.sizeField] }));
} else {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
}
this.primaryXAxis = { title: this.config.xAxisTitle || '' };
this.primaryYAxis = { title: this.config.yAxisTitle || '' };
}
onReset(): void {
this.title = 'Scatter Chart (Default)';
this.type = this.config?.type || 'Scatter';
if (this.type === 'Bubble') {
this.chartData = [
{ x: 10, y: 35, size: 5 }, { x: 15, y: 28, size: 8 },
{ x: 20, y: 34, size: 6 }, { x: 25, y: 32, size: 10 },
{ x: 30, y: 40, size: 7 }, { x: 35, y: 30, size: 9 },
];
}
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
if (this.type === 'Scatter') {
} else {
this.chartData = [
{ x: 10, y: 35 }, { x: 15, y: 28 },
{ x: 20, y: 34 }, { x: 25, y: 32 },
{ x: 30, y: 40 }, { x: 35, y: 30 },
];
} else { // Bubble
this.chartData = [
{ x: 10, y: 35, size: 5 }, { x: 15, y: 28, size: 8 },
{ x: 20, y: 34, size: 6 }, { x: 25, y: 32, size: 10 },
{ x: 30, y: 40, size: 7 }, { x: 35, y: 30, size: 9 },
];
}
}
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (this.config.type === 'Bubble') {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField], size: item[this.config.sizeField] }));
} else {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
}
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.xAxisTitle) {
this.primaryXAxis = { ...this.primaryXAxis, title: this.config.xAxisTitle };
}
if (this.config.yAxisTitle) {
this.primaryYAxis = { ...this.primaryYAxis, title: this.config.yAxisTitle };
}
if (this.config.type) {
this.type = this.config.type;
}
});
}
});
}
this.primaryXAxis = { title: 'X-Value' };
this.primaryYAxis = { title: 'Y-Value' };
}
}
<div class="card h-100">
<div class="card-body flex flex-col justify-center items-center">
<h5 class="text-lg font-semibold text-gray-600 mb-2">{{ title }}</h5>
<div class="text-5xl font-bold text-gray-800 mb-2">
{{ value }}<span *ngIf="unit" class="text-2xl ml-1">{{ unit }}</span>
<div class="card-body">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<div *ngIf="trendValue" class="flex items-center text-sm">
<span *ngIf="trend === 'up'" class="text-green-500 flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
{{ trendValue }}
</span>
<span *ngIf="trend === 'down'" class="text-red-500 flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
{{ trendValue }}
</span>
<span *ngIf="trend === 'neutral' && trendValue" class="text-gray-500">
{{ trendValue }}
</span>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="flex flex-col justify-center items-center h-full">
<h5 class="text-lg font-semibold text-gray-600 mb-2">{{ title }}</h5>
<div class="text-5xl font-bold text-gray-800 mb-2">
{{ value }}<span *ngIf="unit" class="text-2xl ml-1">{{ unit }}</span>
</div>
<div *ngIf="trendValue" class="flex items-center text-sm">
<span *ngIf="trend === 'up'" class="text-green-500 flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
{{ trendValue }}
</span>
<span *ngIf="trend === 'down'" class="text-red-500 flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
{{ trendValue }}
</span>
<span *ngIf="trend === 'neutral' && trendValue" class="text-gray-500">
{{ trendValue }}
</span>
</div>
</div>
</div>
</div>
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-simple-kpi-widget',
......@@ -10,47 +11,38 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
templateUrl: './simple-kpi-widget.component.html',
styleUrls: ['./simple-kpi-widget.component.scss']
})
export class SimpleKpiWidgetComponent implements OnInit {
@Input() title: string = 'Key Performance Indicator';
@Input() value: string = '123,456';
@Input() unit: string = '';
@Input() trend: 'up' | 'down' | 'neutral' = 'neutral';
@Input() trendValue: string = '';
@Input() config: any;
export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
public value: string = '0';
public unit: string = '';
public trend: 'up' | 'down' | 'neutral' = 'neutral';
public trendValue: string = '';
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.valueField) {
this.updateWidget(selectedDataset.data);
} else {
this.resetWidget();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateWidget(data: any[]): void {
onDataUpdate(data: any[]): void {
if (data.length > 0) {
let kpiValue = data[0][this.config.valueField];
let kpiValue = 0;
if (this.config.aggregation === 'count') {
kpiValue = data.length;
} else if (this.config.aggregation === 'sum') {
kpiValue = data.reduce((sum, item) => sum + (item[this.config.valueField] || 0), 0);
} else {
// Default to first value if no aggregation
kpiValue = data[0][this.config.valueField];
}
this.value = kpiValue.toLocaleString();
this.unit = this.config.unit || '';
this.trend = this.config.trend || 'neutral';
this.trendValue = this.config.trendValue || '';
}
if (this.config.title) {
this.title = this.config.title;
}
}
resetWidget(): void {
onReset(): void {
this.title = 'KPI (Default)';
this.value = '123,456';
this.unit = '';
this.unit = '#';
this.trend = 'neutral';
this.trendValue = '';
}
......
......@@ -3,21 +3,42 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th *ngFor="let header of headers" scope="col" class="px-6 py-3">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data" class="bg-white border-b">
<td *ngFor="let cell of row" class="px-6 py-4">
{{ cell }}
</td>
</tr>
</tbody>
</table>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Data Table -->
<div *ngIf="!isLoading && !hasError">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th *ngFor="let header of headers" scope="col" class="px-6 py-3">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data" class="bg-white border-b">
<td *ngFor="let cell of row" class="px-6 py-4">
{{ cell }}
</td>
</tr>
<tr *ngIf="data.length === 0">
<td [attr.colspan]="headers.length" class="px-6 py-4 text-center text-gray-500">
No data available.
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-simple-table-widget',
......@@ -10,36 +11,25 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
templateUrl: './simple-table-widget.component.html',
styleUrls: ['./simple-table-widget.component.scss']
})
export class SimpleTableWidgetComponent implements OnInit {
@Input() config: any;
@Input() title: string = 'Table';
@Input() headers: string[] = [];
@Input() data: any[][] = [];
export class SimpleTableWidgetComponent extends BaseWidgetComponent {
public headers: string[] = [];
public data: any[][] = [];
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.resetTable();
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.columns) {
this.updateTable(selectedDataset.data);
} else {
this.resetTable();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateTable(data: any[]): void {
if (data.length > 0) {
onDataUpdate(data: any[]): void {
if (data && data.length > 0 && this.config?.columns) {
this.headers = this.config.columns.map((col: any) => col.headerText);
this.data = data.map(row => this.config.columns.map((col: any) => row[col.field]));
}
if (this.config.title) {
this.title = this.config.title;
} else {
this.onReset();
}
}
resetTable(): void {
onReset(): void {
// Display placeholder data or an empty state
this.headers = ['ID', 'Name', 'Status'];
this.data = [
[1, 'Item A', 'Active'],
......
......@@ -3,7 +3,20 @@
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
<select
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1">{{ errorMessage }}</p>
</div>
<!-- Content -->
<select *ngIf="!isLoading && !hasError"
[(ngModel)]="selectedValue"
(change)="onSelectionChange()"
class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white"
......@@ -12,5 +25,6 @@
{{ option }}
</option>
</select>
</div>
</div>
import { Component, Input, OnInit, Output, EventEmitter, SimpleChanges, OnChanges } from '@angular/core';
import { Component, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DatasetModel } from '../../models/widgets.model';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient } from '@angular/common/http';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-slicer-widget',
standalone: true,
......@@ -11,52 +11,32 @@ import { HttpClient } from '@angular/common/http';
templateUrl: './slicer-widget.component.html',
styleUrls: ['./slicer-widget.component.scss']
})
export class SlicerWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
public title: string = 'Slicer';
export class SlicerWidgetComponent extends BaseWidgetComponent {
public options: string[] = [];
public selectedValue: string = '';
@Output() selectionChange = new EventEmitter<string>();
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (!this.config || !this.config.datasetId) {
this.options = ['All', 'Option 1', 'Option 2', 'Option 3'];
this.selectedValue = this.options[0];
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.options = ['All', 'Option 1', 'Option 2', 'Option 3'];
onDataUpdate(data: any[]): void {
if (this.config.optionsField) {
const uniqueOptions = [...new Set(data.map((item: any) => item[this.config.optionsField]))];
this.options = ['All', ...uniqueOptions.map(String)];
this.selectedValue = this.options[0];
}
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe((data: any[]) => {
if (this.config.optionsField) {
const uniqueOptions = [...new Set(data.map((item: any) => item[this.config.optionsField]))];
this.options = ['All', ...uniqueOptions.map(String)]; // Ensure options are strings
this.selectedValue = this.options[0];
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
}
onReset(): void {
this.title = 'Slicer (Default)';
this.options = ['All', 'Option 1', 'Option 2', 'Option 3'];
this.selectedValue = this.options[0];
}
onSelectionChange(): void {
// Note: In a real scenario, this should probably update the global state
// via DashboardStateService so other widgets can react to the filter.
this.selectionChange.emit(this.selectedValue);
}
}
......@@ -3,8 +3,24 @@
<div class="card-header">
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body flex flex-col justify-center items-center">
<p class="text-xl font-semibold">Status: {{ status }}</p>
<p class="text-lg">Hours Today: {{ hours }}</p>
<div class="card-body">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="flex flex-col justify-center items-center h-full">
<p class="text-xl font-semibold">Status: {{ status }}</p>
<p class="text-lg">Hours Today: {{ hours }}</p>
</div>
</div>
</div>
import { Component, Input, OnInit, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient } from '@angular/common/http';
import { DatasetModel } from '../../models/widgets.model';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-time-tracking-widget',
......@@ -12,47 +11,28 @@ import { DatasetModel } from '../../models/widgets.model';
templateUrl: './time-tracking-widget.component.html',
styleUrls: ['./time-tracking-widget.component.scss']
})
export class TimeTrackingWidgetComponent implements OnInit {
@Input() data: any;
@Input() config: any;
@Input() title: string = 'Time Tracking';
status: string = '';
hours: string = '';
export class TimeTrackingWidgetComponent extends BaseWidgetComponent {
public status: string = '';
public hours: string = '';
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
if (!this.config || !this.config.datasetId) {
this.status = 'Clocked In';
this.hours = '8.0';
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.status = 'Clocked In';
this.hours = '8.0';
onDataUpdate(data: any[]): void {
if (data.length > 0) {
// Assuming time tracking data is for a single user, so take the first item
const timeEntry = data[0];
this.status = this.config.statusField ? timeEntry[this.config.statusField] : 'N/A';
this.hours = this.config.hoursField ? timeEntry[this.config.hoursField] : 'N/A';
} else {
this.onReset();
}
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
if (data.length > 0) {
this.status = this.config.statusField ? data[0][this.config.statusField] : 'N/A';
this.hours = this.config.hoursField ? data[0][this.config.hoursField] : 'N/A';
}
if (this.config.title) {
this.title = this.config.title;
}
});
}
});
}
onReset(): void {
this.title = 'Time Tracking (Default)';
this.status = 'Clocked In';
this.hours = '8.0';
}
}
<ejs-treemap [titleSettings]="{ text: title }" [dataSource]="dataSource" [weightValuePath]="weightValuePath" [leafItemSettings]="leafItemSettings">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-treemap *ngIf="!isLoading && !hasError" [titleSettings]="{ text: title }" [dataSource]="dataSource" [weightValuePath]="weightValuePath" [leafItemSettings]="leafItemSettings">
</ejs-treemap>
import { Component, Input, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TreeMapModule, TreeMapTooltipService, TreeMapLegendService, TreeMapHighlightService, TreeMapSelectionService } from '@syncfusion/ej2-angular-treemap';
import { DashboardStateService, SelectedDataset } from '../../services/dashboard-state.service';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-treemap-widget',
......@@ -10,50 +11,38 @@ import { DashboardStateService, SelectedDataset } from '../../services/dashboard
providers: [TreeMapTooltipService, TreeMapLegendService, TreeMapHighlightService, TreeMapSelectionService],
templateUrl: './treemap-widget.component.html',
})
export class TreemapWidgetComponent implements OnInit {
@Input() config: any;
@Input() title: string = 'Treemap';
export class TreemapWidgetComponent extends BaseWidgetComponent {
public dataSource: Object[];
public weightValuePath: string;
public leafItemSettings: Object;
constructor(private dashboardStateService: DashboardStateService) { }
ngOnInit(): void {
this.resetChart();
this.dashboardStateService.selectedDataset$.subscribe((selectedDataset: SelectedDataset | null) => {
if (selectedDataset && selectedDataset.data && this.config && this.config.valueField) {
this.updateChart(selectedDataset.data);
} else {
this.resetChart();
}
});
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
updateChart(data: any[]): void {
onDataUpdate(data: any[]): void {
this.dataSource = data;
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.groupField) {
this.leafItemSettings = { labelPath: this.config.groupField };
}
if (this.config.valueField) {
this.weightValuePath = this.config.valueField;
}
this.weightValuePath = this.config.valueField;
this.leafItemSettings = {
labelPath: this.config.groupField,
showLabels: true
};
}
resetChart(): void {
onReset(): void {
this.title = 'Treemap (Default)';
this.dataSource = [
{ Continent: 'Asia', Countries: 'China', population: 1400 },
{ Continent: 'Asia', Countries: 'India', population: 1350 },
{ Continent: 'Europe', Countries: 'Germany', population: 83 },
{ Continent: 'Europe', Countries: 'France', population: 67 },
{ Continent: 'North America', Countries: 'USA', population: 330 },
{ Continent: 'North America', Countries: 'Canada', population: 38 },
{ group: 'Asia', country: 'China', value: 1400 },
{ group: 'Asia', country: 'India', value: 1350 },
{ group: 'Europe', country: 'Germany', value: 83 },
{ group: 'Europe', country: 'France', value: 67 },
{ group: 'North America', country: 'USA', value: 330 },
{ group: 'North America', country: 'Canada', value: 38 },
];
this.weightValuePath = 'population';
this.leafItemSettings = { labelPath: 'Countries' };
this.weightValuePath = 'value';
this.leafItemSettings = {
labelPath: 'country',
showLabels: true
};
}
}
<ejs-chart [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
</div>
<!-- Chart -->
<ejs-chart *ngIf="!isLoading && !hasError" [title]="title" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis">
<e-series-collection>
<e-series [dataSource]="chartData" type="Waterfall" xName="x" yName="y" name="Amount"></e-series>
</e-series-collection>
......
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartModule, WaterfallSeriesService, CategoryService, LegendService, TooltipService, DataLabelService } from '@syncfusion/ej2-angular-charts';
import { DatasetModel } from '../../models/widgets.model';
import { DatasetService } from '../../services/dataset.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component';
@Component({
selector: 'app-waterfall-chart-widget',
standalone: true,
imports: [CommonModule, ChartModule, HttpClientModule],
providers: [WaterfallSeriesService, CategoryService, LegendService, TooltipService, DataLabelService, DatasetService, HttpClient],
imports: [CommonModule, ChartModule],
providers: [WaterfallSeriesService, CategoryService, LegendService, TooltipService, DataLabelService],
templateUrl: './waterfall-chart-widget.component.html',
})
export class WaterfallChartWidgetComponent implements OnInit, OnChanges {
@Input() config: any;
export class WaterfallChartWidgetComponent extends BaseWidgetComponent {
public chartData: Object[];
public title: string = 'Waterfall Chart';
public primaryXAxis: Object;
public primaryYAxis: Object;
constructor(private datasetService: DatasetService, private http: HttpClient) { }
ngOnInit(): void {
this.primaryXAxis = { valueType: 'Category', title: 'Category' };
this.primaryYAxis = { title: 'Amount' };
if (!this.config || !this.config.datasetId) {
this.chartData = [
{ category: 'Starting Balance', amount: 10000 },
{ category: 'Revenue', amount: 5000 },
{ category: 'Expenses', amount: -2000 },
{ category: 'Investments', amount: 3000 },
{ category: 'Ending Balance', amount: 16000 },
];
}
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['config'] && this.config && this.config.datasetId) {
this.loadData();
} else if (changes['config'] && (!this.config || !this.config.datasetId)) {
this.chartData = [
{ category: 'Starting Balance', amount: 10000 },
{ category: 'Revenue', amount: 5000 },
{ category: 'Expenses', amount: -2000 },
{ category: 'Investments', amount: 3000 },
{ category: 'Ending Balance', amount: 16000 },
];
}
onDataUpdate(data: any[]): void {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
this.primaryXAxis = { valueType: 'Category', title: this.config.xAxisTitle || '' };
this.primaryYAxis = { title: this.config.yAxisTitle || '' };
}
loadData(): void {
if (this.config.datasetId) {
this.datasetService.getDatasetById(this.config.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<any[]>(dataset.url).subscribe(data => {
this.chartData = data.map(item => ({ x: item[this.config.xField], y: item[this.config.yField] }));
if (this.config.title) {
this.title = this.config.title;
}
if (this.config.xAxisTitle) {
this.primaryXAxis = { ...this.primaryXAxis, title: this.config.xAxisTitle };
}
if (this.config.yAxisTitle) {
this.primaryYAxis = { ...this.primaryYAxis, title: this.config.yAxisTitle };
}
});
}
});
}
onReset(): void {
this.title = 'Waterfall Chart (Default)';
this.chartData = [
{ x: 'Initial', y: 100 },
{ x: 'Sales', y: 50 },
{ x: 'Expenses', y: -20 },
{ x: 'Refunds', y: -15 },
{ x: 'Final', isSum: true }
];
this.primaryXAxis = { valueType: 'Category', title: 'Category' };
this.primaryYAxis = { title: 'Amount' };
}
}
ข้อเสนอแนะเพื่อการปรับปรุง
ข้อเสนอแนะเพื่อการปรับปรุง
โค้ดโดยรวมเขียนได้ดีครับ แต่มีบางจุดที่สามารถปรับปรุงเพื่อให้ดูแลรักษาง่ายและขยายความสามารถในอนาคตได้ดียิ่งขึ้นครับ
2. ปรับปรุงหน้าจอตั้งค่า Widget (`widget-config.component`):
* ปัญหา: ไฟล์ widget-config.component.html มี *ngIf จำนวนมากเพื่อแสดงฟอร์มสำหรับวิดเจ็ตแต่ละประเภท ซึ่งจะทำให้ไฟล์นี้ใหญ่และซับซ้อนขึ้นเรื่อยๆ เมื่อมีวิดเจ็ตใหม่ๆ เพิ่มเข้ามา
* ข้อเสนอแนะ: อาจจะแยกฟอร์มการตั้งค่าของแต่ละวิดเจ็ตออกเป็นคอมโพเนนต์ของตัวเอง แล้วให้ widget-config.component ทำหน้าที่แค่โหลดคอมโพเนนต์ฟอร์มนั้นๆ แบบไดนามิก (คล้ายกับวิธีที่แดชบอร์ดโหลดวิดเจ็ต)
3. แยกการลงทะเบียน Widget (`widgetComponentMap`):
* ปัญหา: ปัจจุบันการเพิ่มวิดเจ็ตใหม่จะต้องมีการแก้ไข widgetComponentMap ใน dashboard-management.component.ts ทุกครั้ง
* ข้อเสนอแนะ: สร้าง WidgetRegistryService เพื่อทำหน้าที่ลงทะเบียนวิดเจ็ตโดยเฉพาะ จะช่วยให้ dashboard-management.component สะอาดขึ้น และการเพิ่มวิดเจ็ตใหม่จะไม่ต้องไปยุ่งกับโค้ดส่วนกลาง
โดยรวมแล้วฟังก์ชันการทำงานของแดชบอร์ดสมบูรณ์ดีครับ ข้อเสนอแนะเหล่านี้เป็นแนวทางในการปรับปรุงโครงสร้างเพื่อรองรับการพัฒนาในระยะยาวครับ
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