dashboard
This commit is contained in:
parent
02f37a1bc5
commit
f2b6a4d145
@ -1,76 +1,3 @@
|
||||
<div class="sidebar-filters">
|
||||
<!-- Application Header -->
|
||||
<div class="app-header">
|
||||
<h1 class="app-name">Shield</h1>
|
||||
<h2 class="dashboard-title">Overall Dashboard</h2>
|
||||
</div>
|
||||
|
||||
<!-- KPI Metrics -->
|
||||
<div class="kpi-section">
|
||||
<div class="kpi-card total-leads">
|
||||
<div class="kpi-title">Total Leads</div>
|
||||
<div class="kpi-value">{{ totalLeads }}</div>
|
||||
</div>
|
||||
<div class="kpi-card total-deals">
|
||||
<div class="kpi-title">Total Deals</div>
|
||||
<div class="kpi-value">{{ totalDeals }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="filters-section">
|
||||
<h3 class="filters-title">Filters</h3>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="salesRep">Sales Rep</label>
|
||||
<select
|
||||
id="salesRep"
|
||||
[(ngModel)]="selectedSalesRep"
|
||||
(ngModelChange)="updateFilter('salesRep', $event)"
|
||||
class="filter-select">
|
||||
<option value="">All Sales Reps</option>
|
||||
<option *ngFor="let rep of salesReps" [value]="rep">{{ rep }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="partner">Partner</label>
|
||||
<select
|
||||
id="partner"
|
||||
[(ngModel)]="selectedPartner"
|
||||
(ngModelChange)="updateFilter('partner', $event)"
|
||||
class="filter-select">
|
||||
<option value="">All Partners</option>
|
||||
<option *ngFor="let partner of partners" [value]="partner">{{ partner }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="tractionChannel">Traction Channel</label>
|
||||
<select
|
||||
id="tractionChannel"
|
||||
[(ngModel)]="selectedTractionChannel"
|
||||
(ngModelChange)="updateFilter('tractionChannel', $event)"
|
||||
class="filter-select">
|
||||
<option value="">All Channels</option>
|
||||
<option *ngFor="let channel of tractionChannels" [value]="channel">{{ channel }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label for="subProductLine">Sub Product Line</label>
|
||||
<select
|
||||
id="subProductLine"
|
||||
[(ngModel)]="selectedSubProductLine"
|
||||
(ngModelChange)="updateFilter('subProductLine', $event)"
|
||||
class="filter-select">
|
||||
<option value="">All Lines</option>
|
||||
<option *ngFor="let line of subProductLines" [value]="line">{{ line }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="reset-button" (click)="resetFilters()">
|
||||
Reset Filters
|
||||
</button>
|
||||
</div>
|
||||
<!-- Component Palette Button and List have been moved to the main shield dashboard component -->
|
||||
</div>
|
||||
@ -3,6 +3,35 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.componentbtn {
|
||||
margin: 10px;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
padding: 0;
|
||||
margin: 0 10px;
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 5px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
cursor: move;
|
||||
|
||||
&:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.has-badge {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-header {
|
||||
margin-bottom: 30px;
|
||||
|
||||
@ -55,6 +84,74 @@
|
||||
}
|
||||
}
|
||||
|
||||
.component-palette-section {
|
||||
margin: 20px;
|
||||
|
||||
.component-palette-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 107, 53, 0.5);
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 107, 53, 0.3);
|
||||
border-color: #ff6b35;
|
||||
}
|
||||
}
|
||||
|
||||
.component-palette {
|
||||
margin-top: 15px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
|
||||
.palette-title {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
margin: 0 0 15px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.component-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.component-item {
|
||||
padding: 10px 15px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
border-color: #ff6b35;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters-section {
|
||||
flex: 1;
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { DashboardFilterService, FilterState } from '../../services/dashboard-filter.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { DashboardFilterService } from '../../services/dashboard-filter.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shield-sidebar-filters',
|
||||
@ -8,44 +7,10 @@ import { Observable } from 'rxjs';
|
||||
styleUrls: ['./sidebar-filters.component.scss']
|
||||
})
|
||||
export class SidebarFiltersComponent implements OnInit {
|
||||
// Filter options
|
||||
salesReps = ['All Sales Reps', 'John Smith', 'Jane Doe', 'Mike Johnson', 'Sarah Wilson'];
|
||||
partners = ['All Partners', 'Partner A', 'Partner B', 'Partner C', 'Partner D'];
|
||||
tractionChannels = ['All Channels', 'Direct', 'Indirect', 'Online', 'Referral'];
|
||||
subProductLines = ['All Lines', 'Product Line 1', 'Product Line 2', 'Product Line 3'];
|
||||
|
||||
// Current filter values
|
||||
selectedSalesRep = '';
|
||||
selectedPartner = '';
|
||||
selectedTractionChannel = '';
|
||||
selectedSubProductLine = '';
|
||||
|
||||
// KPI data
|
||||
totalLeads = 1248;
|
||||
totalDeals = 842;
|
||||
|
||||
constructor(private filterService: DashboardFilterService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to KPI data changes
|
||||
this.filterService.kpiData$.subscribe(kpiData => {
|
||||
this.totalLeads = kpiData.totalLeads;
|
||||
this.totalDeals = kpiData.totalDeals;
|
||||
});
|
||||
}
|
||||
|
||||
// Update filter state when any filter changes
|
||||
updateFilter(filterType: keyof FilterState, value: string): void {
|
||||
this.filterService.updateFilter(filterType, value);
|
||||
}
|
||||
|
||||
// Reset all filters to default values
|
||||
resetFilters(): void {
|
||||
this.selectedSalesRep = '';
|
||||
this.selectedPartner = '';
|
||||
this.selectedTractionChannel = '';
|
||||
this.selectedSubProductLine = '';
|
||||
|
||||
this.filterService.resetFilters();
|
||||
// Component initialization
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,30 @@
|
||||
<div class="shield-dashboard">
|
||||
<div class="dashboard-container">
|
||||
<!-- Sidebar Filters -->
|
||||
<app-shield-sidebar-filters class="sidebar"></app-shield-sidebar-filters>
|
||||
<div class="sidebar">
|
||||
<button class="btn componentbtn" (click)="toggleComponentPalette()">
|
||||
<clr-icon shape="plus"></clr-icon> Component
|
||||
</button>
|
||||
|
||||
<ul class="nav-list" style="list-style-type: none;" *ngIf="showComponentPalette">
|
||||
<li *ngFor="let widget of WidgetsMock">
|
||||
<!--
|
||||
Draggable widget from store using vanilla javascript event (dragstart)
|
||||
onDrag() is call, it take $event and a widget identifier as parameters
|
||||
-->
|
||||
<a draggable="true" class="nav-link" (dragstart)="onDrag($event, widget.identifier)">
|
||||
<clr-icon shape="drag-handle" style="margin-right: 10px;"></clr-icon>
|
||||
{{ widget.name }}
|
||||
<clr-icon shape="plugin" class="has-badge"></clr-icon>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Main Dashboard Content -->
|
||||
<div class="main-content">
|
||||
<!-- KPI Metrics -->
|
||||
<div class="kpi-section">
|
||||
<!-- <div class="kpi-section">
|
||||
<div class="kpi-card total-leads">
|
||||
<div class="kpi-title">Total Leads</div>
|
||||
<div class="kpi-value">1,248</div>
|
||||
@ -15,10 +33,29 @@
|
||||
<div class="kpi-title">Total Deals</div>
|
||||
<div class="kpi-value">842</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Deleted Items Section -->
|
||||
<div class="deleted-items-section" *ngIf="deletedItems.length > 0">
|
||||
<h3>Deleted Items</h3>
|
||||
<div class="deleted-items-list">
|
||||
<div *ngFor="let item of deletedItems" class="deleted-item">
|
||||
<span>{{ item.name }}</span>
|
||||
<button class="btn btn-sm btn-primary" (click)="restoreItem(item)">
|
||||
<clr-icon shape="undo"></clr-icon> Restore
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" (click)="clearDeletedItems()">
|
||||
<clr-icon shape="trash"></clr-icon> Clear All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Grid with Drag and Drop -->
|
||||
<gridster [options]="options" style="background-color: transparent;">
|
||||
<!-- <div class="drop-zone-indicator" *ngIf="dashboard.length === 0">
|
||||
<p>Drag components here from the sidebar</p>
|
||||
</div> -->
|
||||
<gridster [options]="options" (drop)="onDrop($event)" style="background-color: transparent; min-height: 500px;">
|
||||
<gridster-item [item]="item" *ngFor="let item of dashboard">
|
||||
<!-- Remove Button -->
|
||||
<button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)">
|
||||
@ -36,10 +73,10 @@
|
||||
<div *ngIf="item.chartType === 'bar-chart'">
|
||||
<app-shield-bar-chart></app-shield-bar-chart>
|
||||
</div>
|
||||
<div *ngIf="item.chartType === 'donut-chart' && item.name === 'End Customer'">
|
||||
<div *ngIf="item.chartType === 'donut-chart' && item.name === 'End Customer Donut'">
|
||||
<app-shield-donut-chart chartType="endCustomer"></app-shield-donut-chart>
|
||||
</div>
|
||||
<div *ngIf="item.chartType === 'donut-chart' && item.name === 'Segment Penetration'">
|
||||
<div *ngIf="item.chartType === 'donut-chart' && item.name === 'Segment Penetration Donut'">
|
||||
<app-shield-donut-chart chartType="segmentPenetration"></app-shield-donut-chart>
|
||||
</div>
|
||||
<div *ngIf="item.chartType === 'map-chart'">
|
||||
|
||||
@ -59,6 +59,42 @@
|
||||
border-top: 4px solid #0f9d58;
|
||||
}
|
||||
|
||||
.deleted-items-section {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.deleted-items-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.deleted-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
@ -81,6 +117,23 @@
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.drop-zone-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
background-color: #f8f9fa;
|
||||
border: 2px dashed #dee2e6;
|
||||
border-radius: 4px;
|
||||
color: #6c757d;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gridster specific styles */
|
||||
gridster {
|
||||
background: transparent !important;
|
||||
|
||||
@ -5,6 +5,12 @@ interface ShieldDashboardItem extends GridsterItem {
|
||||
chartType: string;
|
||||
name: string;
|
||||
id: number;
|
||||
component?: any;
|
||||
}
|
||||
|
||||
interface WidgetModel {
|
||||
name: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -16,13 +22,45 @@ export class ShieldDashboardComponent implements OnInit {
|
||||
options: GridsterConfig;
|
||||
dashboard: Array<ShieldDashboardItem>;
|
||||
|
||||
// Component palette
|
||||
showComponentPalette = false;
|
||||
WidgetsMock: WidgetModel[] = [
|
||||
{
|
||||
name: 'Bar Chart',
|
||||
identifier: 'bar_chart'
|
||||
},
|
||||
{
|
||||
name: 'Doughnut Chart',
|
||||
identifier: 'doughnut_chart'
|
||||
},
|
||||
{
|
||||
name: 'Map Chart',
|
||||
identifier: 'map_chart'
|
||||
},
|
||||
{
|
||||
name: 'Data Table',
|
||||
identifier: 'grid_view'
|
||||
},
|
||||
{
|
||||
name: 'Deal Details',
|
||||
identifier: 'to_do_chart'
|
||||
},
|
||||
{
|
||||
name: 'Quarterwise Flow',
|
||||
identifier: 'line_chart'
|
||||
}
|
||||
];
|
||||
|
||||
// Keep track of deleted items
|
||||
deletedItems: Array<ShieldDashboardItem> = [];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.options = {
|
||||
gridType: 'fit',
|
||||
enableEmptyCellDrop: true,
|
||||
emptyCellDropCallback: this.onDrop.bind(this),
|
||||
emptyCellDropCallback: this.onDrop,
|
||||
pushItems: true,
|
||||
swap: true,
|
||||
pushDirections: { north: true, east: true, south: true, west: true },
|
||||
@ -41,27 +79,165 @@ export class ShieldDashboardComponent implements OnInit {
|
||||
itemResizeCallback: this.itemResize.bind(this)
|
||||
};
|
||||
|
||||
// Initialize the dashboard with default components
|
||||
this.dashboard = [
|
||||
{ cols: 5, rows: 6, y: 0, x: 0, chartType: 'bar-chart', name: 'Bar Chart', id: 1 },
|
||||
{ cols: 5, rows: 6, y: 0, x: 5, chartType: 'donut-chart', name: 'End Customer', id: 2 },
|
||||
{ cols: 5, rows: 6, y: 6, x: 0, chartType: 'donut-chart', name: 'Segment Penetration', id: 3 },
|
||||
{ cols: 5, rows: 6, y: 6, x: 5, chartType: 'map-chart', name: 'Map Chart', id: 4 },
|
||||
{ cols: 10, rows: 6, y: 12, x: 0, chartType: 'data-table', name: 'Data Table', id: 5 },
|
||||
{ cols: 5, rows: 6, y: 18, x: 0, chartType: 'deal-details', name: 'Deal Details', id: 6 },
|
||||
{ cols: 5, rows: 6, y: 18, x: 5, chartType: 'quarterwise-flow', name: 'Quarterwise Flow', id: 7 }
|
||||
];
|
||||
// Initialize the dashboard with empty canvas
|
||||
this.dashboard = [];
|
||||
}
|
||||
|
||||
onDrop(event: any) {
|
||||
// Toggle component palette visibility
|
||||
toggleComponentPalette(): void {
|
||||
this.showComponentPalette = !this.showComponentPalette;
|
||||
}
|
||||
|
||||
// Handle drag start event for components - matching the working implementation
|
||||
onDrag(event: DragEvent, identifier: string): void {
|
||||
console.log("on drag", identifier);
|
||||
console.log("on drag ", event);
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.setData('widgetIdentifier', identifier);
|
||||
}
|
||||
}
|
||||
|
||||
onDrop(ev: any) {
|
||||
// Handle dropping new components onto the dashboard
|
||||
console.log('Item dropped:', event);
|
||||
console.log('Item dropped:', ev);
|
||||
|
||||
// Get the component identifier from the drag event
|
||||
const componentType = ev.dataTransfer ? ev.dataTransfer.getData('widgetIdentifier') : '';
|
||||
console.log('Component type dropped:', componentType);
|
||||
|
||||
if (componentType) {
|
||||
this.addComponentToDashboard(componentType);
|
||||
} else {
|
||||
console.log('No component type found in drag data');
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new component to the dashboard
|
||||
addComponentToDashboard(componentType: string) {
|
||||
// Generate a new ID for the component
|
||||
const newId = this.dashboard.length > 0 ? Math.max(...this.dashboard.map(item => item.id), 0) + 1 : 1;
|
||||
|
||||
let newItem: ShieldDashboardItem;
|
||||
|
||||
switch (componentType) {
|
||||
case "bar_chart":
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'bar-chart',
|
||||
name: 'Bar Chart',
|
||||
id: newId
|
||||
};
|
||||
break;
|
||||
case "doughnut_chart":
|
||||
// For doughnut charts, we'll need to determine which one based on existing items
|
||||
const donutCount = this.dashboard.filter(item => item.chartType === 'donut-chart').length;
|
||||
if (donutCount % 2 === 0) {
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'donut-chart',
|
||||
name: 'End Customer Donut',
|
||||
id: newId
|
||||
};
|
||||
} else {
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'donut-chart',
|
||||
name: 'Segment Penetration Donut',
|
||||
id: newId
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "map_chart":
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'map-chart',
|
||||
name: 'Map Chart',
|
||||
id: newId
|
||||
};
|
||||
break;
|
||||
case "grid_view":
|
||||
newItem = {
|
||||
cols: 10,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'data-table',
|
||||
name: 'Data Table',
|
||||
id: newId
|
||||
};
|
||||
break;
|
||||
case "to_do_chart":
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'deal-details',
|
||||
name: 'Deal Details',
|
||||
id: newId
|
||||
};
|
||||
break;
|
||||
case "line_chart":
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: 'quarterwise-flow',
|
||||
name: 'Quarterwise Flow',
|
||||
id: newId
|
||||
};
|
||||
break;
|
||||
default:
|
||||
newItem = {
|
||||
cols: 5,
|
||||
rows: 6,
|
||||
y: 0,
|
||||
x: 0,
|
||||
chartType: componentType,
|
||||
name: componentType,
|
||||
id: newId
|
||||
};
|
||||
}
|
||||
|
||||
// Add the new item to the dashboard
|
||||
this.dashboard.push(newItem);
|
||||
}
|
||||
|
||||
removeItem(item: ShieldDashboardItem) {
|
||||
// Add the item to deleted items list before removing
|
||||
this.deletedItems.push({...item});
|
||||
|
||||
// Remove the item from the dashboard
|
||||
this.dashboard.splice(this.dashboard.indexOf(item), 1);
|
||||
}
|
||||
|
||||
// Restore a deleted item
|
||||
restoreItem(item: ShieldDashboardItem) {
|
||||
// Remove from deleted items
|
||||
this.deletedItems.splice(this.deletedItems.indexOf(item), 1);
|
||||
|
||||
// Add back to dashboard
|
||||
this.dashboard.push(item);
|
||||
}
|
||||
|
||||
// Clear all deleted items
|
||||
clearDeletedItems() {
|
||||
this.deletedItems = [];
|
||||
}
|
||||
|
||||
itemChange() {
|
||||
console.log('Item changed:', this.dashboard);
|
||||
}
|
||||
@ -71,4 +247,25 @@ export class ShieldDashboardComponent implements OnInit {
|
||||
// Trigger a window resize event to notify charts to resize
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract only the relevant chart configuration properties to pass to chart components
|
||||
* This prevents errors when trying to set properties that don't exist on the components
|
||||
*/
|
||||
getChartInputs(item: any): any {
|
||||
// Only pass properties that are relevant to chart components
|
||||
const chartInputs = {
|
||||
chartType: item.chartType,
|
||||
name: item.name
|
||||
};
|
||||
|
||||
// Remove undefined properties to avoid passing unnecessary data
|
||||
Object.keys(chartInputs).forEach(key => {
|
||||
if (chartInputs[key] === undefined) {
|
||||
delete chartInputs[key];
|
||||
}
|
||||
});
|
||||
|
||||
return chartInputs;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user