This commit is contained in:
string 2025-10-27 10:59:35 +05:30
parent cdcf1e07c7
commit 35e3f411a8
10 changed files with 825 additions and 416 deletions

View File

@ -38,19 +38,6 @@ export class CompactFilterComponent implements OnInit, OnChanges {
) { }
ngOnInit(): void {
// Subscribe to filter definitions to get available filters
this.filterService.filters$.subscribe(filters => {
this.availableFilters = filters;
this.updateSelectedFilter();
});
// Subscribe to filter state changes
this.filterService.filterState$.subscribe(state => {
if (this.selectedFilter && state.hasOwnProperty(this.selectedFilter.id)) {
this.filterValue = state[this.selectedFilter.id];
}
});
// Initialize configuration from inputs
this.configFilterKey = this.filterKey;
this.configFilterType = this.filterType;
@ -70,26 +57,53 @@ export class CompactFilterComponent implements OnInit, OnChanges {
// Register this filter with the filter service
this.registerFilter();
// Subscribe to filter definitions to get available filters
this.filterService.filters$.subscribe(filters => {
this.availableFilters = filters;
this.updateSelectedFilter();
});
// Subscribe to filter state changes
this.filterService.filterState$.subscribe(state => {
if (this.selectedFilter && state.hasOwnProperty(this.selectedFilter.id)) {
this.filterValue = state[this.selectedFilter.id];
}
});
}
ngOnChanges(changes: SimpleChanges): void {
// If filterKey or filterType changes, re-register the filter
if (changes.filterKey || changes.filterType) {
// Load available values for the current filter key if it's a dropdown or multiselect
if ((this.filterType === 'dropdown' || this.filterType === 'multiselect') && this.filterKey) {
this.loadAvailableValues(this.filterKey);
}
this.registerFilter();
}
// Handle API URL changes
if (changes.apiUrl && !changes.apiUrl.firstChange) {
if (this.apiUrl) {
this.loadAvailableKeys();
}
}
}
// Register this filter with the filter service
registerFilter(): void {
if (this.filterKey) {
// Get current filter values from the service
const currentFilterValues = this.filterService.getFilterValues();
// Create a filter definition for this compact filter
const filterDef: Filter = {
id: `compact-filter-${this.filterKey}`,
id: `${this.filterKey}`,
field: this.filterKey,
label: this.filterLabel || this.filterKey,
type: this.filterType as any,
options: this.filterOptions,
value: this.filterValue
value: this.filterValue // Use the current filter value
};
// Get current filters
@ -99,9 +113,32 @@ export class CompactFilterComponent implements OnInit, OnChanges {
const existingFilterIndex = currentFilters.findIndex(f => f.id === filterDef.id);
if (existingFilterIndex >= 0) {
// Preserve the existing filter configuration
const existingFilter = currentFilters[existingFilterIndex];
// Preserve the existing filter value if it exists in the service
if (currentFilterValues.hasOwnProperty(existingFilter.id)) {
filterDef.value = currentFilterValues[existingFilter.id];
this.filterValue = filterDef.value; // Update local value
} else if (existingFilter.value !== undefined) {
// Fallback to existing filter's value if no service value
filterDef.value = existingFilter.value;
this.filterValue = filterDef.value;
}
// Preserve other configuration properties
filterDef.label = existingFilter.label;
filterDef.options = existingFilter.options || this.filterOptions;
// Update existing filter
currentFilters[existingFilterIndex] = filterDef;
} else {
// For new filters, check if there's already a value in the service
if (currentFilterValues.hasOwnProperty(filterDef.id)) {
filterDef.value = currentFilterValues[filterDef.id];
this.filterValue = filterDef.value; // Update local value
}
// Add new filter
currentFilters.push(filterDef);
}
@ -118,9 +155,24 @@ export class CompactFilterComponent implements OnInit, OnChanges {
if (this.filterKey && this.availableFilters.length > 0) {
this.selectedFilter = this.availableFilters.find(f => f.field === this.filterKey) || null;
if (this.selectedFilter) {
// Get current value for this filter
// Get current value for this filter from the service
const currentState = this.filterService.getFilterValues();
this.filterValue = currentState[this.selectedFilter.id] || '';
const filterValue = currentState[this.selectedFilter.id];
if (filterValue !== undefined) {
this.filterValue = filterValue;
} else if (this.selectedFilter.value !== undefined) {
// Use the filter's default value if no service value
this.filterValue = this.selectedFilter.value;
} else {
// Use the current filter value as fallback
this.filterValue = this.filterValue || '';
}
// Also update configuration properties from the selected filter
this.configFilterKey = this.selectedFilter.field;
this.configFilterType = this.selectedFilter.type;
this.configFilterLabel = this.selectedFilter.label;
this.configFilterOptions = (this.selectedFilter.options || []).join(',');
}
}
}
@ -130,6 +182,14 @@ export class CompactFilterComponent implements OnInit, OnChanges {
this.filterValue = value;
this.filterService.updateFilterValue(this.selectedFilter.id, value);
this.filterChange.emit({ filterId: this.selectedFilter.id, value: value });
// Update the filter definition in the service to reflect the new value
const currentFilters = this.filterService.getFilters();
const filterIndex = currentFilters.findIndex(f => f.id === this.selectedFilter.id);
if (filterIndex >= 0) {
currentFilters[filterIndex].value = value;
this.filterService.setFilters(currentFilters);
}
}
}
@ -179,11 +239,19 @@ export class CompactFilterComponent implements OnInit, OnChanges {
toggleConfigMode(): void {
this.isConfigMode = !this.isConfigMode;
if (this.isConfigMode) {
// Initialize config values
// Initialize config values from current filter if available
if (this.selectedFilter) {
this.configFilterKey = this.selectedFilter.field;
this.configFilterType = this.selectedFilter.type;
this.configFilterLabel = this.selectedFilter.label;
this.configFilterOptions = (this.selectedFilter.options || []).join(',');
} else {
// Fallback to current properties
this.configFilterKey = this.filterKey;
this.configFilterType = this.filterType;
this.configFilterLabel = this.filterLabel;
this.configFilterOptions = this.filterOptions.join(',');
}
this.configApiUrl = this.apiUrl;
this.configConnectionId = this.connectionId;
}
@ -216,8 +284,8 @@ export class CompactFilterComponent implements OnInit, OnChanges {
}
// Load available values for the selected key if it's a dropdown or multiselect
if ((this.filterType === 'dropdown' || this.filterType === 'multiselect') && this.filterKey) {
this.loadAvailableValues(this.filterKey);
if ((this.configFilterType === 'dropdown' || this.configFilterType === 'multiselect') && this.configFilterKey) {
this.loadAvailableValues(this.configFilterKey);
}
// Register the updated filter with the filter service

View File

@ -782,6 +782,8 @@ export class EditnewdashComponent implements OnInit {
xyz.filterType = this.gadgetsEditdata.filterType || 'text';
xyz.filterLabel = this.gadgetsEditdata.filterLabel || '';
xyz.filterOptions = this.gadgetsEditdata.filterOptions || [];
xyz.table = this.gadgetsEditdata.table || '';
xyz.connection = this.gadgetsEditdata.connection || undefined;
}
console.log(xyz);
@ -818,8 +820,58 @@ export class EditnewdashComponent implements OnInit {
* 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 = {
// For CompactFilterComponent, pass only filter configuration properties
if (item.component && item.component.name === 'CompactFilterComponent') {
const filterInputs = {
filterKey: item['filterKey'] || '',
filterType: item['filterType'] || 'text',
filterLabel: item['filterLabel'] || '',
filterOptions: item['filterOptions'] || [],
apiUrl: item['table'] || '', // Use table as API URL
connectionId: item['connection'] ? parseInt(item['connection'], 10) : undefined
};
// Preserve configuration in the item itself
item['filterKey'] = filterInputs['filterKey'];
item['filterType'] = filterInputs['filterType'];
item['filterLabel'] = filterInputs['filterLabel'];
item['filterOptions'] = filterInputs['filterOptions'];
item['table'] = filterInputs['apiUrl'];
item['connection'] = item['connection'];
// Remove undefined properties to avoid passing unnecessary data
Object.keys(filterInputs).forEach(key => {
if (filterInputs[key] === undefined) {
delete filterInputs[key];
}
});
return filterInputs;
}
// For CommonFilterComponent, pass only filter-related properties
if (item.component && item.component.name === 'CommonFilterComponent') {
const commonFilterInputs = {
baseFilters: item['baseFilters'] || [],
drilldownFilters: item['drilldownFilters'] || [],
drilldownLayers: item['drilldownLayers'] || [],
fieldName: item['name'] || '',
connection: item['connection'] || undefined
};
// Remove undefined properties to avoid passing unnecessary data
Object.keys(commonFilterInputs).forEach(key => {
if (commonFilterInputs[key] === undefined) {
delete commonFilterInputs[key];
}
});
return commonFilterInputs;
}
// For GridViewComponent, pass chart properties with drilldown support
if (item.component && item.component.name === 'GridViewComponent') {
const gridInputs = {
xAxis: item.xAxis,
yAxis: item.yAxis,
table: item.table,
@ -848,24 +900,45 @@ export class EditnewdashComponent implements OnInit {
drilldownLayers: item['drilldownLayers'] || []
};
// For CommonFilterComponent, also pass baseFilters, drilldownFilters, drilldownLayers, fieldName, and connection
if (item.component && item.component.name === 'CommonFilterComponent') {
chartInputs['baseFilters'] = item['baseFilters'] || [];
chartInputs['drilldownFilters'] = item['drilldownFilters'] || [];
chartInputs['drilldownLayers'] = item['drilldownLayers'] || [];
chartInputs['fieldName'] = item['name'] || '';
chartInputs['connection'] = item['connection'] || undefined;
// Remove undefined properties to avoid passing unnecessary data
Object.keys(gridInputs).forEach(key => {
if (gridInputs[key] === undefined) {
delete gridInputs[key];
}
});
return gridInputs;
}
// For CompactFilterComponent, pass filter configuration properties
if (item.component && item.component.name === 'CompactFilterComponent') {
chartInputs['filterKey'] = item['filterKey'] || '';
chartInputs['filterType'] = item['filterType'] || 'text';
chartInputs['filterLabel'] = item['filterLabel'] || '';
chartInputs['filterOptions'] = item['filterOptions'] || [];
chartInputs['apiUrl'] = item['table'] || ''; // Use table as API URL
chartInputs['connectionId'] = item['connection'] ? parseInt(item['connection'], 10) : undefined;
}
// For all other chart components, pass chart-specific properties
const chartInputs = {
xAxis: item.xAxis,
yAxis: item.yAxis,
table: item.table,
datastore: item.datastore,
charttitle: item.charttitle,
chartlegend: item.chartlegend,
showlabel: item.showlabel,
chartcolor: item.chartcolor,
slices: item.slices,
donut: item.donut,
charturl: item.charturl,
chartparameter: item.chartparameter,
datasource: item.datasource,
fieldName: item.name, // Using item.name as fieldName
connection: item['connection'], // Add connection field using bracket notation
// Base drilldown configuration properties
drilldownEnabled: item['drilldownEnabled'],
drilldownApiUrl: item['drilldownApiUrl'],
// Removed drilldownParameterKey since we're using URL templates
drilldownXAxis: item['drilldownXAxis'],
drilldownYAxis: item['drilldownYAxis'],
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
baseFilters: item['baseFilters'] || [], // Add base filters
drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters
// Multi-layer drilldown configurations
drilldownLayers: item['drilldownLayers'] || []
};
// Remove undefined properties to avoid passing unnecessary data
Object.keys(chartInputs).forEach(key => {
@ -929,6 +1002,12 @@ export class EditnewdashComponent implements OnInit {
updatedItem.filterOptions = this.gadgetsEditdata.filterOptions || [];
updatedItem.table = this.gadgetsEditdata.table || ''; // API URL
updatedItem.connection = this.gadgetsEditdata.connection || undefined; // Connection ID
// Also preserve these properties in gadgetsEditdata for consistency
this.gadgetsEditdata.filterKey = updatedItem.filterKey;
this.gadgetsEditdata.filterType = updatedItem.filterType;
this.gadgetsEditdata.filterLabel = updatedItem.filterLabel;
this.gadgetsEditdata.filterOptions = updatedItem.filterOptions;
}
console.log('Updated item:', updatedItem);

View File

@ -1,38 +1,26 @@
<div class="chart-container">
<!-- Compact Filters -->
<div class="compact-filters-container" *ngIf="baseFilters && baseFilters.length > 0">
<app-compact-filter
*ngFor="let filter of baseFilters"
[filterKey]="filter.field"
(filterChange)="onFilterChange($event)">
</app-compact-filter>
</div>
<div style="display: block; height: 100%; width: 100%;">
<!-- No filter controls needed with the new simplified approach -->
<!-- Filters are now configured at the drilldown level -->
<!-- Drilldown mode indicator -->
<div *ngIf="currentDrilldownLevel > 0" class="drilldown-indicator">
<span class="drilldown-text">Drilldown Level: {{currentDrilldownLevel}}</span>
<button class="btn btn-secondary btn-sm" (click)="navigateBack()">
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
Back to Level {{currentDrilldownLevel - 1}}
</button>
<button class="btn btn-danger btn-sm" (click)="resetToOriginalData()">
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
Back to Main View
</button>
</div>
<div class="chart-header">
<h3>{{ charttitle || 'Bar Chart' }}</h3>
</div>
<div class="chart-wrapper">
<div class="chart-content" [class.loading]="barChartData.length === 0 && barChartLabels.length === 0 && !noDataAvailable">
<!-- No data message -->
<div class="no-data-message" *ngIf="noDataAvailable">
<p>No data available</p>
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
No data available
</div>
<!-- Chart display -->
<div *ngIf="!noDataAvailable" style="position: relative; height: calc(100% - 50px);">
<canvas baseChart
*ngIf="!noDataAvailable"
[datasets]="barChartData"
[labels]="barChartLabels"
[type]="barChartType"
@ -40,11 +28,5 @@
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
<!-- Loading overlay -->
<div class="loading-overlay" *ngIf="barChartData.length === 0 && barChartLabels.length === 0 && !noDataAvailable">
<div class="shimmer-bar"></div>
</div>
</div>
</div>
</div>

View File

@ -5,214 +5,27 @@
width: 100%;
}
.chart-container {
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transition: all 0.3s ease;
border: 1px solid #eaeaea;
padding: 20px;
}
.chart-container:hover {
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
.compact-filters-container {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-bottom: 10px;
padding: 5px;
background: #ffffff;
border: 1px solid #e9ecef;
border-radius: 6px;
min-height: 40px;
}
.drilldown-indicator {
background-color: #e0e0e0;
padding: 10px;
margin-bottom: 15px;
border-radius: 8px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.drilldown-text {
font-weight: bold;
color: #333;
font-size: 16px;
}
.btn {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
.btn-secondary {
background-color: #007cba;
color: white;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.chart-header {
margin-bottom: 20px;
h3 {
font-size: 22px;
font-weight: 600;
color: #0a192f;
margin: 0;
text-align: center;
padding-bottom: 10px;
border-bottom: 2px solid #3498db;
}
}
.chart-wrapper {
flex: 1;
.bar-chart-container {
position: relative;
.chart-content {
position: relative;
height: 100%;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
&.loading {
opacity: 0.7;
canvas {
filter: blur(2px);
}
}
canvas {
max-width: 100%;
max-height: 100%;
transition: filter 0.3s ease;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
canvas:hover {
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15));
transform: scale(1.02);
transition: all 0.3s ease;
}
.no-data-message {
text-align: center;
padding: 30px;
color: #666;
font-size: 18px;
font-style: italic;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.no-data-message p {
margin: 0;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.8);
.shimmer-bar {
width: 80%;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
}
}
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
canvas {
display: block;
max-width: 100%;
max-height: 100%;
}
// Responsive design for chart container
@media (max-width: 768px) {
.chart-container {
padding: 15px;
}
.chart-header h3 {
font-size: 18px;
}
.drilldown-indicator {
flex-direction: column;
gap: 5px;
}
.drilldown-text {
font-size: 14px;
}
.compact-filters-container {
flex-wrap: wrap;
.bar-chart-container {
height: 300px;
}
}
@media (max-width: 480px) {
.chart-container {
padding: 10px;
}
.chart-header h3 {
font-size: 16px;
.bar-chart-container {
height: 250px;
}
}

View File

@ -141,12 +141,6 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
}
}
// Handle filter changes from compact filters
onFilterChange(event: { filterId: string, value: any }): void {
console.log('Compact filter changed:', event);
// The filter service will automatically trigger chart updates through the subscription
}
fetchChartData(): void {
// Set flag to prevent recursive calls
this.isFetchingData = true;
@ -179,73 +173,41 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
console.log('Bar chart data URL:', url);
// Convert baseFilters to filter parameters
let filterParams = '';
if (this.baseFilters && this.baseFilters.length > 0) {
const filterObj = {};
// Add base filters
if (this.baseFilters && this.baseFilters.length > 0) {
this.baseFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
}
console.log('Base filters:', this.baseFilters);
console.log('Base filter params:', filterParams);
// Add common filters to filter parameters
// Add common filters
const commonFilters = this.filterService.getFilterValues();
const filterDefinitions = this.filterService.getFilters();
console.log('Common filters from service:', commonFilters);
console.log('Filter definitions:', filterDefinitions);
if (Object.keys(commonFilters).length > 0) {
// Merge common filters with base filters
const mergedFilterObj = {};
// Add base filters first
if (filterParams) {
try {
const baseFilterObj = JSON.parse(filterParams);
Object.assign(mergedFilterObj, baseFilterObj);
} catch (e) {
console.warn('Failed to parse base filter parameters:', e);
}
}
// Add common filters using the field name as the key, not the filter id
Object.keys(commonFilters).forEach(filterId => {
const filterValue = commonFilters[filterId];
console.log(`Processing filter ID: ${filterId}, Value:`, filterValue);
// Find the filter definition to get the field name
const filterDef = this.filterService.getFilters().find(f => f.id === filterId);
console.log(`Filter definition for ${filterId}:`, filterDef);
if (filterDef && filterDef.field) {
const fieldName = filterDef.field;
console.log(`Mapping filter ID ${filterId} to field name: ${fieldName}`);
if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
mergedFilterObj[fieldName] = filterValue;
console.log(`Added to merged filters: ${fieldName} =`, filterValue);
}
} else {
// Fallback to using filterId as field name if no field is defined
console.log(`No field name found for filter ID ${filterId}, using ID as field name`);
if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
mergedFilterObj[filterId] = filterValue;
console.log(`Added to merged filters: ${filterId} =`, filterValue);
filterObj[fieldName] = filterValue;
}
}
});
if (Object.keys(mergedFilterObj).length > 0) {
filterParams = JSON.stringify(mergedFilterObj);
}
// Convert to JSON string for API call
let filterParams = '';
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
console.log('Final merged filter object:', filterParams);
console.log('Final filter object:', filterObj);
// Fetch data from the dashboard service with parameter field and value
// For base level, we pass empty parameter and value, but now also pass filters
const subscription = this.dashboardService.getChartData(this.table, 'bar', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
@ -400,61 +362,47 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
console.log('Drilldown data URL:', url);
// Convert drilldown layer filters to filter parameters (if applicable)
let filterParams = '';
if (drilldownConfig.filters && drilldownConfig.filters.length > 0) {
const filterObj = {};
// Add drilldown layer filters
if (drilldownConfig.filters && drilldownConfig.filters.length > 0) {
drilldownConfig.filters.forEach((filter: any) => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
}
console.log('Drilldown layer filter parameters:', filterParams);
// Convert drilldownFilters to filter parameters for drilldown level
let drilldownFilterParams = '';
// Add drilldownFilters
if (this.drilldownFilters && this.drilldownFilters.length > 0) {
const filterObj = {};
this.drilldownFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
if (Object.keys(filterObj).length > 0) {
drilldownFilterParams = JSON.stringify(filterObj);
}
}
// Add common filters to drilldown filter parameters
const commonFilters = this.filterService.getFilterValues();
if (Object.keys(commonFilters).length > 0) {
// Merge common filters with drilldown filters
const mergedFilterObj = {};
// Add drilldown filters first
if (drilldownFilterParams) {
try {
const drilldownFilterObj = JSON.parse(drilldownFilterParams);
Object.assign(mergedFilterObj, drilldownFilterObj);
} catch (e) {
console.warn('Failed to parse drilldown filter parameters:', e);
}
}
// Add common filters
Object.keys(commonFilters).forEach(key => {
const value = commonFilters[key];
if (value !== undefined && value !== null && value !== '') {
mergedFilterObj[key] = value;
const commonFilters = this.filterService.getFilterValues();
const filterDefinitions = this.filterService.getFilters();
Object.keys(commonFilters).forEach(filterId => {
const filterValue = commonFilters[filterId];
// Find the filter definition to get the field name
const filterDef = this.filterService.getFilters().find(f => f.id === filterId);
if (filterDef && filterDef.field) {
const fieldName = filterDef.field;
if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
filterObj[fieldName] = filterValue;
}
}
});
if (Object.keys(mergedFilterObj).length > 0) {
drilldownFilterParams = JSON.stringify(mergedFilterObj);
}
// Convert to JSON string for API call
let drilldownFilterParams = '';
if (Object.keys(filterObj).length > 0) {
drilldownFilterParams = JSON.stringify(filterObj);
}
console.log('Drilldown filter parameters:', drilldownFilterParams);

View File

@ -4,9 +4,41 @@
<div class="clr-col-8">
<h3>{{charttitle || 'Data Grid'}}</h3>
</div>
<!-- Add drilldown navigation controls -->
<div class="clr-col-4" *ngIf="drilldownEnabled && drilldownStack.length > 0" style="text-align: right;">
<button class="btn btn-sm btn-link" (click)="navigateBack()">
<cds-icon shape="arrow" direction="left"></cds-icon>
Back to {{drilldownStack.length > 1 ? 'Previous Level' : 'Main Data'}}
</button>
</div>
</div>
<!-- Show current drilldown level -->
<div class="clr-row" *ngIf="drilldownEnabled && drilldownStack.length > 0">
<div class="clr-col-12">
<div class="alert alert-info" style="padding: 8px 12px; margin-bottom: 12px;">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="info-circle"></cds-icon>
</div>
<span class="alert-text">
Drilldown Level: {{currentDrilldownLevel}}
<span *ngIf="drilldownStack.length > 0">
(Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedKey}} = {{drilldownStack[drilldownStack.length - 1].clickedValue}})
</span>
</span>
</div>
</div>
</div>
</div>
</div>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-placeholder> <ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
<clr-dg-placeholder>
<ng-template #loadingSpinner>
<clr-spinner>Loading ... </clr-spinner>
</ng-template>
<div *ngIf="error;else loadingSpinner">{{error}}</div>
</clr-dg-placeholder>
@ -19,7 +51,7 @@
<clr-dg-row *clrDgItems="let item of givendata" [clrDgItem]="item">
<!-- Dynamic cells based on response keys -->
<clr-dg-cell *ngFor="let header of dynamicHeaders">
<clr-dg-cell *ngFor="let header of dynamicHeaders" (click)="onRowClick(item, header.key)">
{{item[header.key]}}
</clr-dg-cell>
</clr-dg-row>

View File

@ -1,12 +1,28 @@
@import '../../../../../../../styles1.scss';
input.ng-invalid.ng-touched {
border-color: red;
// Add styles for drilldown navigation
.alert-info {
background-color: #dcedf7;
border-color: #a3d4f5;
color: #21333b;
}
.error_mess {
color: red;
.alert-info .alert-icon {
color: #0072a3;
}
clr-datagrid{
height: 400px; /* Adjust the height as needed */
overflow-y: auto;
.btn-link {
color: #0072a3;
text-decoration: none;
}
.btn-link:hover {
color: #00567a;
text-decoration: underline;
}
.dg-wrapper {
padding: 12px;
}
.clr-row {
margin-bottom: 12px;
}

View File

@ -1,13 +1,17 @@
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, OnInit, Input, OnChanges, OnDestroy, SimpleChanges, ViewChild } from '@angular/core';
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
// Add FilterService import
import { FilterService } from '../../common-filter/filter.service';
// Add Subscription import
import { Subscription } from 'rxjs';
@Component({
selector: 'app-grid-view',
templateUrl: './grid-view.component.html',
styleUrls: ['./grid-view.component.scss']
})
export class GridViewComponent implements OnInit, OnChanges {
export class GridViewComponent implements OnInit, OnChanges, OnDestroy {
@Input() xAxis: string;
@Input() yAxis: string | string[];
@Input() table: string;
@ -23,6 +27,16 @@ export class GridViewComponent implements OnInit, OnChanges {
@Input() datasource: string;
@Input() fieldName: string;
@Input() connection: number; // Add connection input
// Drilldown configuration inputs
@Input() drilldownEnabled: boolean = false;
@Input() drilldownApiUrl: string;
@Input() drilldownXAxis: string;
@Input() drilldownYAxis: string;
@Input() drilldownParameter: string; // Add drilldown parameter input
@Input() baseFilters: any[] = []; // Add base filters input
@Input() drilldownFilters: any[] = []; // Add drilldown filters input
// Multi-layer drilldown configuration inputs
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
loading = false;
givendata: any[] = [];
@ -38,13 +52,37 @@ export class GridViewComponent implements OnInit, OnChanges {
submitted = false;
dynamicHeaders: any[] = [];
constructor(
// Multi-layer drilldown state tracking
drilldownStack: any[] = []; // Stack to track drilldown navigation history
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
originalGridData: any[] = [];
// No data state
noDataAvailable: boolean = false;
// Flag to prevent infinite loops
private isFetchingData: boolean = false;
// Add subscriptions to unsubscribe on destroy
private subscriptions: Subscription[] = [];
constructor(
private mainservice: UsergrpmaintainceService,
private dashboardService: Dashboard3Service,
// Add FilterService to constructor
private filterService: FilterService
) { }
ngOnInit(): void {
// Subscribe to filter changes
this.subscriptions.push(
this.filterService.filterState$.subscribe(filters => {
// When filters change, refresh the grid data
console.log('GridView: Filter state changed:', filters);
this.fetchGridData();
})
);
this.fetchGridData();
}
@ -55,12 +93,21 @@ export class GridViewComponent implements OnInit, OnChanges {
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
const tableChanged = changes.table && !changes.table.firstChange;
const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
const connectionChanged = changes.connection && !changes.connection.firstChange;
const baseFiltersChanged = changes.baseFilters && !changes.baseFilters.firstChange;
// Drilldown configuration changes
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
// Respond to input changes
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) {
console.log('X or Y axis or table or connection changed, fetching new data');
// Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
if (!this.isFetchingData && (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || baseFiltersChanged ||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
drilldownLayersChanged)) {
console.log('X or Y axis or table or connection or base filters or drilldown config changed, fetching new data');
// Only fetch data if xAxis, yAxis, table, connection, baseFilters or drilldown config has changed (and it's not the first change)
this.fetchGridData();
}
}
@ -68,20 +115,87 @@ export class GridViewComponent implements OnInit, OnChanges {
// Dynamic headers for the grid
fetchGridData(): void {
// Set flag to prevent recursive calls
this.isFetchingData = true;
// If we're in drilldown mode, fetch the appropriate drilldown data
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
this.fetchDrilldownData();
// Reset flag after fetching
this.isFetchingData = false;
return;
}
// If we have the necessary data, fetch grid data from the service
if (this.table && this.xAxis) {
console.log('Fetching grid data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
// if (this.table && this.xAxis) {
if (this.table) {
console.log('=== GRID VIEW DEBUG INFO ===');
console.log('Table:', this.table);
console.log('X-Axis:', this.xAxis);
console.log('Y-Axis:', this.yAxis);
console.log('Connection:', this.connection);
// Convert yAxis to string if it's an array
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
// Get the parameter value from the drilldown stack for base level (should be empty)
let parameterValue = '';
// Log the URL that will be called
let url = `chart/getdashjson/grid?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
console.log('Grid data URL:', url);
// Get filter parameters from base filters
const filterObj = {};
// Add base filters
if (this.baseFilters && this.baseFilters.length > 0) {
this.baseFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
}
// Add common filters directly as key-value pairs
const commonFilters = this.filterService.getFilterValues();
const filterDefinitions = this.filterService.getFilters();
// Add common filters using the field name as the key
Object.keys(commonFilters).forEach(filterId => {
const filterValue = commonFilters[filterId];
// Find the filter definition to get the field name
const filterDef = this.filterService.getFilters().find(f => f.id === filterId);
if (filterDef && filterDef.field) {
const fieldName = filterDef.field;
if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
filterObj[fieldName] = filterValue;
}
}
});
// Convert to JSON string for API call
let filterParams = '';
if (Object.keys(filterObj).length > 0) {
filterParams = JSON.stringify(filterObj);
}
console.log('GridView: Final filter object to send to API:', filterObj);
// Fetch data from the dashboard service, similar to other chart components
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe(
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
(data: any) => {
console.log('=== GRID VIEW DATA RESPONSE ===');
console.log('Received grid data:', data);
if (data === null) {
console.warn('Grid API returned null data. Check if the API endpoint is working correctly.');
this.error = "No data Available";
this.noDataAvailable = true;
// Reset flag after fetching
this.isFetchingData = false;
return;
}
@ -90,27 +204,36 @@ export class GridViewComponent implements OnInit, OnChanges {
this.givendata = data.chartData;
this.extractDynamicHeaders(data.chartData);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with data:', this.givendata);
} else if (data && data.data) {
// Handle the original expected format as fallback
this.givendata = data.data;
this.extractDynamicHeaders(data.data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with legacy data format:', this.givendata);
} else if (Array.isArray(data)) {
// Handle case where data is directly an array
this.givendata = data;
this.extractDynamicHeaders(data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with array data:', this.givendata);
} else {
console.warn('Grid received data does not have expected structure', data);
this.error = "No valid data received";
this.givendata = [];
this.noDataAvailable = true;
}
// Reset flag after fetching
this.isFetchingData = false;
}, (error) => {
console.log('Error fetching grid data:', error);
this.error = "Server Error";
this.noDataAvailable = true;
// Reset flag after fetching
this.isFetchingData = false;
});
} else if (this.table) {
console.log('Missing xAxis, falling back to default data fetching');
@ -126,13 +249,342 @@ export class GridViewComponent implements OnInit, OnChanges {
this.givendata = Array.isArray(data) ? data : [];
this.extractDynamicHeaders(data);
this.error = this.givendata && this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata && this.givendata.length === 0;
// Reset flag after fetching
this.isFetchingData = false;
}, (error) => {
console.log(error);
this.error = "Server Error";
this.noDataAvailable = true;
// Reset flag after fetching
this.isFetchingData = false;
});
} else {
console.log('Missing required data for grid:', { table: this.table });
this.error = "Table name is required";
this.noDataAvailable = true;
// Reset flag after fetching
this.isFetchingData = false;
}
}
// Fetch drilldown data based on current drilldown level
fetchDrilldownData(): void {
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
console.log('Drilldown stack:', this.drilldownStack);
// Get the current drilldown configuration based on the current level
let drilldownConfig;
if (this.currentDrilldownLevel === 1) {
// Base drilldown level
drilldownConfig = {
apiUrl: this.drilldownApiUrl,
xAxis: this.drilldownXAxis,
yAxis: this.drilldownYAxis,
parameter: this.drilldownParameter
};
} else {
// Multi-layer drilldown level
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
drilldownConfig = this.drilldownLayers[layerIndex];
} else {
console.warn('Invalid drilldown layer index:', layerIndex);
this.error = "Invalid drilldown configuration";
this.givendata = [];
this.noDataAvailable = true;
return;
}
}
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
// Check if we have valid drilldown configuration
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
this.error = "Missing drilldown configuration";
this.givendata = [];
this.noDataAvailable = true;
return;
}
// Get the parameter value from the drilldown stack
let parameterValue = '';
if (this.drilldownStack.length > 0) {
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
parameterValue = lastEntry.clickedValue || '';
console.log('Parameter value from last click:', parameterValue);
}
// Get the parameter field from drilldown config
const parameterField = drilldownConfig.parameter || '';
console.log('Parameter field:', parameterField);
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
apiUrl: drilldownConfig.apiUrl,
xAxis: drilldownConfig.xAxis,
yAxis: drilldownConfig.yAxis,
parameterField: parameterField,
parameterValue: parameterValue,
connection: this.connection
});
// Build the actual API URL with parameter replacement
let actualApiUrl = drilldownConfig.apiUrl;
console.log('Original API URL:', actualApiUrl);
console.log('Parameter value to use:', parameterValue);
console.log('Parameter field:', parameterField);
// Check if the URL contains angle brackets for parameter replacement
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
if (hasAngleBrackets && parameterValue) {
// Replace angle brackets placeholder with actual value
console.log('Replacing angle brackets with parameter value');
const encodedValue = encodeURIComponent(parameterValue);
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
console.log('URL after angle bracket replacement:', actualApiUrl);
}
// Log the URL that will be called
let url = `chart/getdashjson/grid?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
if (parameterField && parameterValue) {
url += `&parameter=${encodeURIComponent(parameterField)}&parameterValue=${encodeURIComponent(parameterValue)}`;
}
console.log('Drilldown data URL:', url);
// Convert drilldown layer filters to filter parameters (if applicable)
const filterObj = {};
// Add drilldown layer filters
if (drilldownConfig.filters && drilldownConfig.filters.length > 0) {
drilldownConfig.filters.forEach((filter: any) => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
}
// Add drilldownFilters
if (this.drilldownFilters && this.drilldownFilters.length > 0) {
this.drilldownFilters.forEach(filter => {
if (filter.field && filter.value) {
filterObj[filter.field] = filter.value;
}
});
}
// Add common filters
const commonFilters = this.filterService.getFilterValues();
const filterDefinitions = this.filterService.getFilters();
Object.keys(commonFilters).forEach(filterId => {
const filterValue = commonFilters[filterId];
// Find the filter definition to get the field name
const filterDef = this.filterService.getFilters().find(f => f.id === filterId);
if (filterDef && filterDef.field) {
const fieldName = filterDef.field;
if (filterValue !== undefined && filterValue !== null && filterValue !== '') {
filterObj[fieldName] = filterValue;
}
}
});
// Convert to JSON string for API call
let drilldownFilterParams = '';
if (Object.keys(filterObj).length > 0) {
drilldownFilterParams = JSON.stringify(filterObj);
}
console.log('Drilldown filter parameters:', drilldownFilterParams);
// For drilldown level, we pass the parameter value from the drilldown stack and drilldown filters
this.dashboardService.getChartData(
drilldownConfig.apiUrl, 'grid',
drilldownConfig.xAxis, drilldownConfig.yAxis,
this.connection,
parameterField, parameterValue,
drilldownFilterParams
).subscribe(
(data: any) => {
console.log('Received drilldown data:', data);
if (data === null) {
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
this.error = "No data Available";
this.givendata = [];
this.noDataAvailable = true;
return;
}
// Handle the actual data structure returned by the API
if (data && data.chartData) {
this.givendata = data.chartData;
this.extractDynamicHeaders(data.chartData);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with drilldown data:', this.givendata);
} else if (data && data.data) {
// Handle the original expected format as fallback
this.givendata = data.data;
this.extractDynamicHeaders(data.data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with drilldown legacy data format:', this.givendata);
} else if (Array.isArray(data)) {
// Handle case where data is directly an array
this.givendata = data;
this.extractDynamicHeaders(data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
this.noDataAvailable = this.givendata.length === 0;
console.log('Updated grid with drilldown array data:', this.givendata);
} else {
console.warn('Drilldown received data does not have expected structure', data);
this.error = "No valid data received";
this.givendata = [];
this.noDataAvailable = true;
}
},
(error) => {
console.error('Error fetching drilldown data:', error);
this.error = "Server Error";
this.givendata = [];
this.noDataAvailable = true;
}
);
}
// Reset to original data (go back to base level)
resetToOriginalData(): void {
console.log('Resetting to original data');
console.log('Current stack before reset:', this.drilldownStack);
console.log('Current level before reset:', this.currentDrilldownLevel);
this.currentDrilldownLevel = 0;
this.drilldownStack = [];
if (this.originalGridData.length > 0) {
// Create a deep copy to avoid reference issues
this.givendata = JSON.parse(JSON.stringify(this.originalGridData));
this.extractDynamicHeaders(this.givendata);
console.log('Restored original data');
}
console.log('After reset - data:', this.givendata);
// Re-fetch original data
this.fetchGridData();
}
// Navigate back to previous drilldown level
navigateBack(): void {
console.log('Navigating back, current stack:', this.drilldownStack);
console.log('Current level:', this.currentDrilldownLevel);
if (this.drilldownStack.length > 0) {
// Remove the last entry from the stack
const removedEntry = this.drilldownStack.pop();
console.log('Removed entry from stack:', removedEntry);
// Update the current drilldown level
this.currentDrilldownLevel = this.drilldownStack.length;
console.log('New level after pop:', this.currentDrilldownLevel);
console.log('Stack after pop:', this.drilldownStack);
if (this.drilldownStack.length > 0) {
// Fetch data for the previous level
console.log('Fetching data for previous level');
this.fetchDrilldownData();
} else {
// Back to base level
console.log('Back to base level, resetting to original data');
this.resetToOriginalData();
}
} else {
// Already at base level, reset to original data
console.log('Already at base level, resetting to original data');
this.resetToOriginalData();
}
}
// Method to handle grid row clicks for drilldown
onRowClick(item: any, key: string): void {
console.log('Grid row clicked:', { item, key });
// If drilldown is enabled
if (this.drilldownEnabled) {
// Get the value for the clicked key
const clickedValue = item[key];
console.log('Clicked on row value:', { key, value: clickedValue });
// If we're not at the base level, store original data
if (this.currentDrilldownLevel === 0) {
// Store original data before entering drilldown mode
// Create a deep copy to avoid reference issues
this.originalGridData = JSON.parse(JSON.stringify(this.givendata));
console.log('Stored original data for drilldown');
}
// Determine the next drilldown level
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
console.log('Next drilldown level will be:', nextDrilldownLevel);
// Check if there's a drilldown configuration for this level
let hasDrilldownConfig = false;
let drilldownConfig;
if (nextDrilldownLevel === 1) {
// Base drilldown level
drilldownConfig = {
apiUrl: this.drilldownApiUrl,
xAxis: this.drilldownXAxis,
yAxis: this.drilldownYAxis,
parameter: this.drilldownParameter
};
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
} else {
// Multi-layer drilldown level
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
if (layerIndex < this.drilldownLayers.length) {
drilldownConfig = this.drilldownLayers[layerIndex];
hasDrilldownConfig = drilldownConfig.enabled &&
!!drilldownConfig.apiUrl &&
!!drilldownConfig.xAxis &&
!!drilldownConfig.yAxis;
}
}
console.log('Drilldown config for next level:', drilldownConfig);
console.log('Has drilldown config:', hasDrilldownConfig);
// If there's a drilldown configuration for the next level, proceed
if (hasDrilldownConfig) {
// Add this click to the drilldown stack
const stackEntry = {
level: nextDrilldownLevel,
clickedKey: key,
clickedValue: clickedValue
};
this.drilldownStack.push(stackEntry);
console.log('Added to drilldown stack:', stackEntry);
console.log('Current drilldown stack:', this.drilldownStack);
// Update the current drilldown level
this.currentDrilldownLevel = nextDrilldownLevel;
console.log('Entering drilldown level:', this.currentDrilldownLevel);
// Fetch drilldown data for the new level
this.fetchDrilldownData();
} else {
console.log('No drilldown configuration for level:', nextDrilldownLevel);
}
} else {
console.log('Drilldown not enabled');
}
}
@ -175,4 +627,23 @@ export class GridViewComponent implements OnInit, OnChanges {
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
}
ngOnDestroy() {
// Unsubscribe from all subscriptions to prevent memory leaks
console.log('GridViewComponent ngOnDestroy called, unsubscribing from', this.subscriptions.length, 'subscriptions');
this.subscriptions.forEach(subscription => {
if (subscription && !subscription.closed) {
subscription.unsubscribe();
}
});
this.subscriptions = [];
// Clear data to help with garbage collection
this.givendata = [];
this.dynamicHeaders = [];
this.drilldownStack = [];
this.originalGridData = [];
console.log('GridViewComponent destroyed and cleaned up');
}
}

View File

@ -11,6 +11,9 @@
<h3>{{ 'all_dashboard' | translate }}</h3>
</div>
<div class="clr-col-4" style="text-align: right;">
<button class="btn btn-success" [routerLink]="['/cns-portal/shield-dashboard']">
<clr-icon shape="shield"></clr-icon>Shield Dashboard
</button>
<button id="add" class="btn btn-primary" (click)="gotoadd()">
<clr-icon shape="plus"></clr-icon>{{ 'dashboard_builder' | translate }}
</button>
@ -112,6 +115,3 @@
</div>
</div>
</clr-modal>