dashboard

This commit is contained in:
string 2025-10-25 19:28:03 +05:30
parent 02f37a1bc5
commit f2b6a4d145
6 changed files with 405 additions and 129 deletions

View File

@ -1,76 +1,3 @@
<div class="sidebar-filters"> <div class="sidebar-filters">
<!-- Application Header --> <!-- Component Palette Button and List have been moved to the main shield dashboard component -->
<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>
</div> </div>

View File

@ -3,6 +3,35 @@
display: flex; display: flex;
flex-direction: column; 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 { .app-header {
margin-bottom: 30px; 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 { .filters-section {
flex: 1; flex: 1;

View File

@ -1,6 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { DashboardFilterService, FilterState } from '../../services/dashboard-filter.service'; import { DashboardFilterService } from '../../services/dashboard-filter.service';
import { Observable } from 'rxjs';
@Component({ @Component({
selector: 'app-shield-sidebar-filters', selector: 'app-shield-sidebar-filters',
@ -8,44 +7,10 @@ import { Observable } from 'rxjs';
styleUrls: ['./sidebar-filters.component.scss'] styleUrls: ['./sidebar-filters.component.scss']
}) })
export class SidebarFiltersComponent implements OnInit { 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) { } constructor(private filterService: DashboardFilterService) { }
ngOnInit(): void { ngOnInit(): void {
// Subscribe to KPI data changes // Component initialization
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();
} }
} }

View File

@ -1,12 +1,30 @@
<div class="shield-dashboard"> <div class="shield-dashboard">
<div class="dashboard-container"> <div class="dashboard-container">
<!-- Sidebar Filters --> <!-- 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 --> <!-- Main Dashboard Content -->
<div class="main-content"> <div class="main-content">
<!-- KPI Metrics --> <!-- KPI Metrics -->
<div class="kpi-section"> <!-- <div class="kpi-section">
<div class="kpi-card total-leads"> <div class="kpi-card total-leads">
<div class="kpi-title">Total Leads</div> <div class="kpi-title">Total Leads</div>
<div class="kpi-value">1,248</div> <div class="kpi-value">1,248</div>
@ -15,10 +33,29 @@
<div class="kpi-title">Total Deals</div> <div class="kpi-title">Total Deals</div>
<div class="kpi-value">842</div> <div class="kpi-value">842</div>
</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> </div>
<!-- Dashboard Grid with Drag and Drop --> <!-- 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"> <gridster-item [item]="item" *ngFor="let item of dashboard">
<!-- Remove Button --> <!-- Remove Button -->
<button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)"> <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'"> <div *ngIf="item.chartType === 'bar-chart'">
<app-shield-bar-chart></app-shield-bar-chart> <app-shield-bar-chart></app-shield-bar-chart>
</div> </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> <app-shield-donut-chart chartType="endCustomer"></app-shield-donut-chart>
</div> </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> <app-shield-donut-chart chartType="segmentPenetration"></app-shield-donut-chart>
</div> </div>
<div *ngIf="item.chartType === 'map-chart'"> <div *ngIf="item.chartType === 'map-chart'">

View File

@ -59,6 +59,42 @@
border-top: 4px solid #0f9d58; 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 { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
@ -81,6 +117,23 @@
cursor: move; 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 specific styles */
gridster { gridster {
background: transparent !important; background: transparent !important;

View File

@ -5,6 +5,12 @@ interface ShieldDashboardItem extends GridsterItem {
chartType: string; chartType: string;
name: string; name: string;
id: number; id: number;
component?: any;
}
interface WidgetModel {
name: string;
identifier: string;
} }
@Component({ @Component({
@ -15,6 +21,38 @@ interface ShieldDashboardItem extends GridsterItem {
export class ShieldDashboardComponent implements OnInit { export class ShieldDashboardComponent implements OnInit {
options: GridsterConfig; options: GridsterConfig;
dashboard: Array<ShieldDashboardItem>; 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() { } constructor() { }
@ -22,7 +60,7 @@ export class ShieldDashboardComponent implements OnInit {
this.options = { this.options = {
gridType: 'fit', gridType: 'fit',
enableEmptyCellDrop: true, enableEmptyCellDrop: true,
emptyCellDropCallback: this.onDrop.bind(this), emptyCellDropCallback: this.onDrop,
pushItems: true, pushItems: true,
swap: true, swap: true,
pushDirections: { north: true, east: true, south: true, west: true }, pushDirections: { north: true, east: true, south: true, west: true },
@ -41,26 +79,164 @@ export class ShieldDashboardComponent implements OnInit {
itemResizeCallback: this.itemResize.bind(this) itemResizeCallback: this.itemResize.bind(this)
}; };
// Initialize the dashboard with default components // Initialize the dashboard with empty canvas
this.dashboard = [ 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 }, // Toggle component palette visibility
{ cols: 5, rows: 6, y: 6, x: 5, chartType: 'map-chart', name: 'Map Chart', id: 4 }, toggleComponentPalette(): void {
{ cols: 10, rows: 6, y: 12, x: 0, chartType: 'data-table', name: 'Data Table', id: 5 }, this.showComponentPalette = !this.showComponentPalette;
{ 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 }
]; // 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(event: any) { onDrop(ev: any) {
// Handle dropping new components onto the dashboard // 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) { 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); 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() { itemChange() {
console.log('Item changed:', this.dashboard); 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 // Trigger a window resize event to notify charts to resize
window.dispatchEvent(new Event('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;
}
} }