dashboard

This commit is contained in:
string 2025-10-22 11:11:43 +05:30
parent 56e1e3b0fe
commit c66e217b0c
13 changed files with 937 additions and 58 deletions

View File

@ -62,6 +62,11 @@
SureConnect
</ng-container></clr-dg-column>
<!-- Add ref_datalake_id column -->
<clr-dg-column [clrDgField]="'ref_datalake_id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Reference Data Lake
</ng-container></clr-dg-column>
<clr-dg-column [clrDgField]="'calculated_field_json'"> <ng-container *clrDgHideableColumn="{hidden: false}">
Calculated Fields
</ng-container></clr-dg-column>
@ -126,6 +131,9 @@
<clr-dg-cell>{{user.sureconnect_name}}</clr-dg-cell>
<!-- Add ref_datalake_id cell -->
<clr-dg-cell>{{user.ref_datalake_name}}</clr-dg-cell>
<clr-dg-cell (click)="goToReplaceStringjson(user.calculated_field_json)"
style="cursor: pointer; align-items: center;"><clr-icon shape="details"
*ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell>
@ -154,7 +162,7 @@
</button>
<!-- Calculated Field button -->
<button class="btn btn-icon btn-sm" (click)="fetchAvailableKeys(user)" title="Create Calculated Field">
<button class="btn btn-icon btn-sm" (click)="fetchAvailableKeys(user, false)" title="Create Calculated Field">
<clr-icon shape="calculator"></clr-icon>
</button>
@ -426,6 +434,15 @@
</select>
</div>
<!-- Add ref_datalake_id dropdown -->
<div class="clr-col-sm-12">
<label>Reference Data Lake</label>
<select class="clr-input" [(ngModel)]="rowSelected.ref_datalake_id" name="ref_datalake_id">
<option value="">Select Data Lake</option>
<option *ngFor="let dataLake of dataLakeList" [value]="dataLake.id">{{dataLake.name}}
</option>
</select>
</div>
</div>
<!-- form code start -->
<div *ngIf="checkFormCode">
@ -546,6 +563,15 @@
</select>
</div>
<!-- Add ref_datalake_id dropdown -->
<div class="clr-col-sm-12">
<label>Reference Data Lake</label>
<select formControlName="ref_datalake_id">
<option value="">Select Data Lake</option>
<option *ngFor="let dataLake of dataLakeList" [value]="dataLake.id">{{dataLake.name}}
</option>
</select>
</div>
</div>
@ -890,4 +916,4 @@
</div>
</clr-modal>
<!-- htmlpopup -->
<!-- htmlpopup -->

View File

@ -62,6 +62,10 @@ export class Data_lakeComponent implements OnInit {
sureConnectList;
selectedSureConnect: any = null;
// Data Lake reference properties
dataLakeList: any[] = [];
selectedDataLake: any = null;
// Calculated field properties
calculatedFieldModalOpen = false;
availableKeys: string[] = [];
@ -117,6 +121,7 @@ export class Data_lakeComponent implements OnInit {
this.userrole = this.userInfoService.getRoles();
this.getData();
this.getSureConnectList(); // Fetch SureConnect list
this.getDataLakeList(); // Fetch Data Lake list for reference dropdown
this.entryForm = this._fb.group({
name: [null],
@ -133,6 +138,7 @@ export class Data_lakeComponent implements OnInit {
batch_volume: [null],
sure_connect_id: [null], // Add SureConnect field
ref_datalake_id: [null], // Add Data Lake reference field
}); // component_button200
// form code start
@ -231,6 +237,17 @@ export class Data_lakeComponent implements OnInit {
});
}
// Fetch Data Lake list for reference dropdown
getDataLakeList() {
this.mainService.getAll().subscribe((data: any[]) => {
// Filter out the current item to avoid self-reference
this.dataLakeList = data.filter(item => item.id !== (this.rowSelected?.id || 0));
console.log('Data Lake List:', this.dataLakeList);
}, (error) => {
console.log('Error fetching Data Lake list:', error);
});
}
// Get operation symbol for display
getOperationSymbol(operation: string): string {
switch(operation) {
@ -245,7 +262,7 @@ export class Data_lakeComponent implements OnInit {
}
// Fetch available keys from API
fetchAvailableKeys(dataLakeItem: any) {
fetchAvailableKeys(dataLakeItem: any, isGroupBy: boolean = false) {
if (!dataLakeItem.url || !dataLakeItem.sure_connect_id) {
this.toastr.error('URL and SureConnect ID are required to fetch keys');
return;
@ -256,11 +273,56 @@ export class Data_lakeComponent implements OnInit {
// Call the service method to get all keys
this.mainService.fetchAvailableKeys(dataLakeItem.url, dataLakeItem.sure_connect_id).subscribe(
(keys: string[]) => {
this.availableKeys = keys;
// Combine API keys with calculated field names
const calculatedFieldNames = this.getCalculatedFieldNames();
this.availableKeys = [...keys, ...calculatedFieldNames];
// Reset the calculated field form to ensure it starts fresh
this.resetCalculatedFieldForm();
this.calculatedFieldModalOpen = true;
console.log('Available keys:', keys);
// Preload existing calculated fields if they exist
if (dataLakeItem.calculated_field_json) {
try {
const existingFields = JSON.parse(dataLakeItem.calculated_field_json);
if (Array.isArray(existingFields)) {
this.calculatedFields = [...existingFields];
this.toastr.info(`Loaded ${existingFields.length} existing calculated fields`);
}
} catch (e) {
console.error('Error parsing calculated_field_json:', e);
this.calculatedFields = [];
}
} else {
this.calculatedFields = [];
}
// Open the appropriate modal based on the context
if (isGroupBy) {
// For group by, we need to extract existing group by configurations
if (dataLakeItem.calculated_field_json) {
try {
const existingFields = JSON.parse(dataLakeItem.calculated_field_json);
if (Array.isArray(existingFields)) {
// Find group by configurations
const groupByConfigs = existingFields.filter(field => field.type === 'groupby');
if (groupByConfigs.length > 0) {
// For simplicity, we'll load the first group by configuration
const firstConfig = groupByConfigs[0];
this.selectedGroupByFields = [...firstConfig.groupFields];
this.selectedAggregationFields = [...firstConfig.aggregations];
this.toastr.info(`Loaded existing group by configuration`);
}
}
} catch (e) {
console.error('Error parsing calculated_field_json for group by:', e);
}
}
this.showGroupByModal = true;
} else {
this.calculatedFieldModalOpen = true;
}
console.log('Available keys:', this.availableKeys);
},
(error) => {
console.error('Error fetching keys:', error);
@ -335,7 +397,6 @@ export class Data_lakeComponent implements OnInit {
}
}
}
// Create calculated field object for complex equation
const calculatedField = {
id: Date.now(),
@ -347,6 +408,9 @@ export class Data_lakeComponent implements OnInit {
this.calculatedFields.push(calculatedField);
this.toastr.success('Complex calculated field added successfully');
// Refresh available keys to include the new calculated field
this.refreshAvailableKeys();
// Reset form but keep one empty field component
this.calculatedFieldForm.reset({
@ -419,6 +483,9 @@ export class Data_lakeComponent implements OnInit {
this.calculatedFields.push(calculatedField);
this.toastr.success('Calculated field added successfully');
// Refresh available keys to include the new calculated field
this.refreshAvailableKeys();
// Reset form but keep one empty field component
this.calculatedFieldForm.reset({
@ -609,6 +676,9 @@ export class Data_lakeComponent implements OnInit {
deleteCalculatedField(id: number) {
this.calculatedFields = this.calculatedFields.filter(field => field.id !== id);
this.toastr.success('Calculated field deleted');
// Refresh available keys to reflect the deleted field
this.refreshAvailableKeys();
}
onEdit(row) {
@ -616,6 +686,8 @@ export class Data_lakeComponent implements OnInit {
this.editCronExpression = row.cron_job || '';
// Set the selected SureConnect for edit form
this.selectedSureConnect = row.sure_connect_id || null;
// Set the selected Data Lake for edit form
this.selectedDataLake = row.ref_datalake_id || null;
// Use setTimeout to ensure the component has time to initialize
setTimeout(() => {
this.modalEdit = true;
@ -769,6 +841,7 @@ export class Data_lakeComponent implements OnInit {
this.modalAdd = true;
this.addCronExpression = '';
this.selectedSureConnect = null; // Reset SureConnect selection
this.selectedDataLake = null; // Reset Data Lake selection
this.submitted = false;
// Reset the form control for cron_job and sure_connect_id
if (this.entryForm) {
@ -801,7 +874,7 @@ export class Data_lakeComponent implements OnInit {
}
// Generate JSON from calculated fields and update the record
updateCalculatedFields() {
updateCalculatedFields(closeModal: boolean = true) {
if (!this.selectedDataLakeItem || this.calculatedFields.length === 0) {
this.toastr.error('No calculated fields to update');
return;
@ -827,8 +900,11 @@ export class Data_lakeComponent implements OnInit {
this.product[index].iscalculatedfield = true;
}
// Close the modal only when called directly from the UI
if (this.calculatedFieldModalOpen) {
// Refresh available keys to ensure they include the latest calculated fields
this.refreshAvailableKeys();
// Close the modal only when called directly from the UI and closeModal is true
if (closeModal && this.calculatedFieldModalOpen) {
this.calculatedFieldModalOpen = false;
}
},
@ -995,15 +1071,44 @@ export class Data_lakeComponent implements OnInit {
openGroupByModal(dataLakeItem: any) {
this.selectedDataLakeItem = dataLakeItem;
// Fetch available keys if not already fetched
// Fetch available keys if not already fetched or if called directly
if (this.availableKeys.length === 0) {
this.fetchAvailableKeys(dataLakeItem);
this.fetchAvailableKeys(dataLakeItem, true); // Pass true to indicate group by context
} else {
// Keys are already available, just preload existing data and open modal
// Initialize selected group by fields as empty array
this.selectedGroupByFields = [];
this.selectedAggregationFields = [];
// Preload existing group by configurations if they exist in calculated_field_json
// (Group by configurations are stored in the same calculated_field_json field)
if (dataLakeItem.calculated_field_json) {
try {
const existingFields = JSON.parse(dataLakeItem.calculated_field_json);
if (Array.isArray(existingFields)) {
this.calculatedFields = [...existingFields];
// Find group by configurations
const groupByConfigs = existingFields.filter(field => field.type === 'groupby');
if (groupByConfigs.length > 0) {
// For simplicity, we'll load the first group by configuration
const firstConfig = groupByConfigs[0];
this.selectedGroupByFields = [...firstConfig.groupFields];
this.selectedAggregationFields = [...firstConfig.aggregations];
this.toastr.info(`Loaded existing group by configuration`);
}
}
} catch (e) {
console.error('Error parsing calculated_field_json:', e);
this.calculatedFields = [];
}
} else {
this.calculatedFields = [];
}
this.showGroupByModal = true;
}
// Initialize selected group by fields as empty array
this.selectedGroupByFields = [];
this.selectedAggregationFields = [];
this.showGroupByModal = true;
}
// Method to add a field to group by selection
@ -1072,41 +1177,17 @@ export class Data_lakeComponent implements OnInit {
// New method to update calculated fields after adding group by configuration
updateCalculatedFieldsAfterGroupBy() {
if (!this.selectedDataLakeItem || this.calculatedFields.length === 0) {
return;
}
// Convert calculated fields to JSON string
const calculatedFieldJson = JSON.stringify(this.calculatedFields);
// Call the service method to update the record
this.mainService.updateCalculatedFields(
this.selectedDataLakeItem.id,
calculatedFieldJson,
true // iscalculatedfield = true
).subscribe(
(response) => {
console.log('Calculated fields updated successfully:', response);
this.toastr.success('Group by configuration saved successfully');
// Update the local data
const index = this.product.findIndex(item => item.id === this.selectedDataLakeItem.id);
if (index !== -1) {
this.product[index].calculated_field_json = calculatedFieldJson;
this.product[index].iscalculatedfield = true;
}
},
(error) => {
console.error('Error updating calculated fields:', error);
this.toastr.error('Failed to save group by configuration');
}
);
// Simply call the main update method without closing the modal
this.updateCalculatedFields(false);
}
// Method to remove group by configuration
removeGroupByConfig(id: number) {
this.calculatedFields = this.calculatedFields.filter(field => field.id !== id || field.type !== 'groupby');
this.toastr.success('Group by configuration removed');
// Refresh available keys to reflect the removed configuration
this.refreshAvailableKeys();
}
// Method to check if a field is selected for group by
@ -1114,6 +1195,34 @@ export class Data_lakeComponent implements OnInit {
return this.selectedGroupByFields.includes(field);
}
// Helper method to get calculated field names
getCalculatedFieldNames(): string[] {
return this.calculatedFields
.filter(field => field.type !== 'groupby') // Exclude group by configurations
.map(field => field.fieldName);
}
// Method to refresh available keys including calculated field names
refreshAvailableKeys() {
if (this.selectedDataLakeItem) {
// Fetch fresh keys from API and combine with calculated field names
this.mainService.fetchAvailableKeys(this.selectedDataLakeItem.url, this.selectedDataLakeItem.sure_connect_id).subscribe(
(keys: string[]) => {
const calculatedFieldNames = this.getCalculatedFieldNames();
this.availableKeys = [...keys, ...calculatedFieldNames];
},
(error) => {
console.error('Error refreshing keys:', error);
// Fallback to just calculated field names if API fails
this.availableKeys = this.getCalculatedFieldNames();
}
);
} else {
// Fallback to just calculated field names
this.availableKeys = this.getCalculatedFieldNames();
}
}
// Helper method to get the count of calculated fields
getCalculatedFieldsCount(calculatedFieldJson: string): number {
try {

View File

@ -0,0 +1,161 @@
# Filter Configuration Guide for Dashboard Editor
## Overview
This guide explains how to configure filters for chart components using the dashboard editor. The filter functionality allows users to dynamically filter chart data using multiple filter types.
## Enabling Filters
To enable filters for a chart component:
1. Open the chart configuration modal by clicking the edit icon on any chart
2. Scroll down to the "Filter Configuration" section
3. Check the "Enable Filters" checkbox
## Adding Filter Fields
Once filters are enabled, you can add filter fields:
1. Click the "Add Filter Field" button
2. Configure each filter field with the following properties:
### Filter Field Properties
#### Field Name
- The field name to filter on (e.g., 'category', 'name', 'amount')
- This should match the field name in your data source
#### Display Label (Optional)
- Label to display for this filter in the UI
- If not provided, the field name will be used as the label
#### Filter Type
Choose one of the following filter types:
1. **Text Input**
- Creates a text input field
- Users can type to filter data
- Works with any text-based field
2. **Dropdown**
- Creates a dropdown selection field
- Requires a comma-separated list of options
- Example options: "A,B,C" or "North, South, East, West"
3. **Number Range**
- Creates two number input fields (min and max)
- Used for filtering numeric data within a range
- Example: Filter sales between 100 and 500
#### Dropdown Options (For Dropdown Type Only)
- Comma-separated list of options for dropdown filters
- Example: "Option1,Option2,Option3"
- Each option will appear as a selectable item in the dropdown
## Example Configurations
### Example 1: Simple Text Filter
```
Field Name: name
Display Label: Product Name
Filter Type: Text Input
```
### Example 2: Category Dropdown Filter
```
Field Name: category
Display Label: Category
Filter Type: Dropdown
Dropdown Options: A,B,C,D
```
### Example 3: Sales Amount Range Filter
```
Field Name: amount
Display Label: Sales Amount
Filter Type: Number Range
```
### Example 4: Multiple Filters
You can combine multiple filter types:
1. Text filter for product names
2. Dropdown filter for categories
3. Number range filter for sales amounts
## Backend Integration
When filters are applied, they send parameters to your backend API with the prefix `filter_`. For example:
- Text filter: `filter_name=John`
- Dropdown filter: `filter_category=A`
- Number range filter: `filter_amount_min=100&filter_amount_max=500`
Your backend should implement logic to filter data based on these parameters.
## Working with Drilldown
The filter functionality works seamlessly with the existing drilldown feature. Filters will be applied at each drilldown level, allowing users to filter data at any level of the drilldown hierarchy.
## Best Practices
1. **Limit the Number of Filters**: Too many filters can overwhelm users. Generally, 3-5 filters are sufficient for most use cases.
2. **Use Descriptive Labels**: Provide clear, user-friendly labels for filter fields.
3. **Match Field Names**: Ensure filter field names match the actual field names in your data source.
4. **Provide Meaningful Dropdown Options**: For dropdown filters, provide options that make sense for your data.
5. **Test with Real Data**: Always test filter configurations with actual data to ensure they work as expected.
## Troubleshooting
### Filters Not Working
- Check that field names match your data source
- Verify that your backend implements filter parameter handling
- Ensure the "Enable Filters" checkbox is checked
### Dropdown Options Not Displaying
- Check that options are comma-separated
- Verify there are no extra spaces around commas
- Ensure the filter type is set to "Dropdown"
### Range Filters Not Filtering
- Verify that the field contains numeric data
- Check that min and max values are entered correctly
- Ensure your backend handles range filter parameters
## Technical Details
### Data Structure
Filter configurations are stored as an array of objects in the chart configuration:
```json
{
"enableFilters": true,
"filterFields": [
{
"field": "category",
"label": "Category",
"type": "dropdown",
"options": ["A", "B", "C"]
},
{
"field": "name",
"label": "Name",
"type": "text"
},
{
"field": "amount",
"label": "Amount",
"type": "number-range"
}
]
}
```
### API Parameter Format
Filters are passed to the backend as query parameters:
- Text filter: `filter_[fieldname]=[value]`
- Dropdown filter: `filter_[fieldname]=[selected_value]`
- Number range filter: `filter_[fieldname]_min=[min_value]&filter_[fieldname]_max=[max_value]`
This configuration allows for flexible and powerful data filtering capabilities in your dashboard charts.

View File

@ -202,6 +202,51 @@
</div>
</div>
<!-- Base API Filters -->
<div class="clr-row" style="margin-top: 15px;">
<div class="clr-col-sm-12">
<h5>Base API Filters</h5>
<div class="clr-subtext">Configure filters for the main API (applied regardless of drilldown settings)</div>
<!-- Add Base Filter Button -->
<button class="btn btn-sm btn-primary" (click)="addBaseFilter()" style="margin-top: 10px; margin-bottom: 10px;">
<clr-icon shape="plus"></clr-icon> Add Filter
</button>
<!-- Base Filter Fields List -->
<div *ngFor="let filter of gadgetsEditdata.baseFilters; let i = index"
style="margin-bottom: 10px; padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: #f9f9f9;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>Filter {{i + 1}}</span>
<button class="btn btn-icon btn-danger btn-sm" (click)="removeBaseFilter(i)">
<clr-icon shape="trash"></clr-icon>
</button>
</div>
<div class="clr-row" style="margin-top: 8px;">
<div class="clr-col-sm-5">
<select [(ngModel)]="filter.field" [ngModelOptions]="{standalone: true}" class="clr-select">
<option value="">Select Field</option>
<!-- Use columnData for base API filters when drilldown is not enabled, drilldownColumnData when it is -->
<option *ngFor="let column of (gadgetsEditdata.drilldownEnabled ? drilldownColumnData : columnData)" [value]="column">{{column}}</option>
</select>
</div>
<div class="clr-col-sm-5">
<input type="text" [(ngModel)]="filter.value" [ngModelOptions]="{standalone: true}"
class="clr-input" placeholder="Filter Value" />
</div>
<div class="clr-col-sm-2">
<button class="btn btn-icon btn-danger btn-sm" (click)="removeBaseFilter(i)">
<clr-icon shape="trash"></clr-icon>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Base Drilldown Configuration Section -->
<div class="clr-row" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
<div class="clr-col-sm-12">
@ -273,6 +318,8 @@
</div>
</div>
<!-- Multi-Layer Drilldown Configurations -->
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
<div class="clr-col-sm-12">
@ -356,8 +403,49 @@
</div>
</div>
<!-- Layer Parameter Configuration -->
<!-- Removed parameter key input since we're using URL templates -->
<!-- Layer Filter Configuration -->
<div class="clr-row" style="margin-top: 15px;">
<div class="clr-col-sm-12">
<h5>Layer {{i + 1}} Filters</h5>
<div class="clr-subtext">Configure filters for this drilldown layer</div>
<!-- Add Layer Filter Button -->
<button class="btn btn-sm btn-primary" (click)="addLayerFilter(i)" style="margin-top: 10px; margin-bottom: 10px;">
<clr-icon shape="plus"></clr-icon> Add Filter
</button>
<!-- Layer Filter Fields List -->
<div *ngFor="let filter of layer.filters; let j = index"
style="margin-bottom: 10px; padding: 8px; border: 1px solid #eee; border-radius: 4px; background-color: #f9f9f9;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>Filter {{j + 1}}</span>
<button class="btn btn-icon btn-danger btn-sm" (click)="removeLayerFilter(i, j)">
<clr-icon shape="trash"></clr-icon>
</button>
</div>
<div class="clr-row" style="margin-top: 8px;">
<div class="clr-col-sm-5">
<select [(ngModel)]="filter.field" [ngModelOptions]="{standalone: true}" class="clr-select">
<option value="">Select Field</option>
<option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option>
</select>
</div>
<div class="clr-col-sm-5">
<input type="text" [(ngModel)]="filter.value" [ngModelOptions]="{standalone: true}"
class="clr-input" placeholder="Filter Value" />
</div>
<div class="clr-col-sm-2">
<button class="btn btn-icon btn-danger btn-sm" (click)="removeLayerFilter(i, j)">
<clr-icon shape="trash"></clr-icon>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -142,8 +142,9 @@ export class EditnewdashComponent implements OnInit {
drilldownXAxis: '',
drilldownYAxis: '',
drilldownParameter: '', // Add drilldown parameter property
baseFilters: [] as any[], // Add base filters
// Multi-layer drilldown configurations
drilldownLayers: [] as any[]
drilldownLayers: [] as any[],
};
// Add sureconnect data property
@ -219,7 +220,7 @@ export class EditnewdashComponent implements OnInit {
drilldownXAxis: [null],
drilldownYAxis: [null],
drilldownParameter: [null] // Add drilldown parameter to form group
// Note: Dynamic drilldown layers will be handled separately since they're complex objects
// Note: Dynamic drilldown layers and filters will be handled separately since they're complex objects
});
// Load sureconnect data first, then load dashboard data
@ -510,6 +511,11 @@ export class EditnewdashComponent implements OnInit {
this.gadgetsEditdata['drilldownParameter'] = '';
}
// Initialize base filters if not present
if (item['baseFilters'] === undefined) {
this.gadgetsEditdata['baseFilters'] = [];
}
// Initialize drilldown layers if not present
if (item['drilldownLayers'] === undefined) {
this.gadgetsEditdata['drilldownLayers'] = [];
@ -520,6 +526,10 @@ export class EditnewdashComponent implements OnInit {
if (layer['parameter'] === undefined) {
layer['parameter'] = '';
}
// Initialize filters if not present
if (layer['filters'] === undefined) {
layer['filters'] = [];
}
});
}
@ -624,6 +634,7 @@ export class EditnewdashComponent implements OnInit {
xyz.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
xyz.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
xyz.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
xyz.baseFilters = this.gadgetsEditdata.baseFilters; // Add base filters
xyz.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
console.log(xyz);
@ -667,6 +678,7 @@ export class EditnewdashComponent implements OnInit {
drilldownXAxis: item['drilldownXAxis'],
drilldownYAxis: item['drilldownYAxis'],
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
baseFilters: item['baseFilters'] || [], // Add base filters
// Multi-layer drilldown configurations
drilldownLayers: item['drilldownLayers'] || []
};
@ -713,6 +725,7 @@ export class EditnewdashComponent implements OnInit {
updatedItem.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
updatedItem.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
updatedItem.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
updatedItem.baseFilters = this.gadgetsEditdata.baseFilters; // Add base filters
updatedItem.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
console.log('Updated item:', updatedItem);
@ -919,4 +932,47 @@ export class EditnewdashComponent implements OnInit {
const match = baseUrl.match(/<([^>]+)>/);
return match ? match[1] : '';
}
// Add method to add a new filter field
addFilterField() {
// This method is no longer needed with the simplified approach
// We're now using addBaseFilter and addLayerFilter methods instead
}
// Add method to remove a filter field
removeFilterField(index: number) {
// This method is no longer needed with the simplified approach
// We're now using removeBaseFilter and removeLayerFilter methods instead
}
// Add method to add a base filter
addBaseFilter() {
const newFilter = {
field: '',
value: ''
};
this.gadgetsEditdata.baseFilters.push(newFilter);
}
// Add method to remove a base filter
removeBaseFilter(index: number) {
this.gadgetsEditdata.baseFilters.splice(index, 1);
}
// Add method to add a layer filter
addLayerFilter(layerIndex: number) {
const newFilter = {
field: '',
value: ''
};
if (!this.gadgetsEditdata.drilldownLayers[layerIndex].filters) {
this.gadgetsEditdata.drilldownLayers[layerIndex].filters = [];
}
this.gadgetsEditdata.drilldownLayers[layerIndex].filters.push(newFilter);
}
// Add method to remove a layer filter
removeLayerFilter(layerIndex: number, filterIndex: number) {
this.gadgetsEditdata.drilldownLayers[layerIndex].filters.splice(filterIndex, 1);
}
}

View File

@ -0,0 +1,142 @@
# Bar Chart Filter Configuration
## Overview
This document describes the filter configuration implementation for the Bar Chart component. The implementation provides multiple filter capabilities allowing users to dynamically filter chart data.
## Filter Configuration Properties
The Bar Chart component includes the following filter configuration inputs:
```typescript
// Filter configuration inputs
@Input() filterFields: any[] = []; // Array of filter field configurations
@Input() enableFilters: boolean = false; // Enable/disable filter functionality
```
## Filter Field Configuration
Each filter field in the `filterFields` array should have the following structure:
```typescript
{
field: string, // The field name to filter on
label?: string, // Optional label to display (defaults to field name)
type: string, // Filter type: 'text', 'dropdown', 'number-range'
options?: any[] // Options for dropdown filters (optional)
}
```
### Filter Types
1. **Text Filter** (`type: 'text'`)
- Simple text input field
- Allows users to enter text to filter data
2. **Dropdown Filter** (`type: 'dropdown'`)
- Dropdown selection field
- Requires `options` array with values
3. **Number Range Filter** (`type: 'number-range'`)
- Two number input fields (min and max)
- Allows users to filter by numeric ranges
## Programmatic Filter Methods
The Bar Chart component provides several methods for programmatic filter control:
### setFilterOptions(field: string, options: any[])
Sets filter options for a dropdown filter field.
### getFilterValues(): any
Returns current filter values as an object.
### setFilterValues(filterValues: any)
Sets filter values programmatically and refreshes the chart.
### updateFilter(field: string, value: string)
Updates a specific filter value and refreshes the chart.
### clearFilters()
Clears all filter values and refreshes the chart.
## Usage Examples
### Enable Filters
```html
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'category', label: 'Category', type: 'dropdown', options: ['A', 'B', 'C'] },
{ field: 'name', label: 'Name', type: 'text' },
{ field: 'amount', label: 'Amount', type: 'number-range' }
]"
[table]="'sales_data'"
[xAxis]="'product'"
[yAxis]="'amount'">
</app-bar-chart>
```
### Programmatic Filter Control
```typescript
// Set filter options
chartComponent.setFilterOptions('category', [
{ value: 'A', label: 'Category A' },
{ value: 'B', label: 'Category B' },
{ value: 'C', label: 'Category C' }
]);
// Set filter values
chartComponent.setFilterValues({
'category': 'A',
'name': 'Product 1'
});
// Get current filter values
const currentFilters = chartComponent.getFilterValues();
console.log(currentFilters);
// Clear all filters
chartComponent.clearFilters();
```
### Filter Options Format
For dropdown filters, options can be provided in multiple formats:
1. Simple array of strings:
```javascript
options: ['Option 1', 'Option 2', 'Option 3']
```
2. Array of objects with value/label:
```javascript
options: [
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' },
{ value: 'opt3', label: 'Option 3' }
]
```
## Backend Integration
Filters are passed to the backend API as query parameters with the prefix `filter_`. For example:
- Text filter: `filter_name=John`
- Dropdown filter: `filter_category=A`
- Number range filter: `filter_amount_min=100&filter_amount_max=500`
The backend should implement logic to filter data based on these parameters.
## UI Features
1. **Filter Controls Panel**: Appears at the top of the chart when filters are enabled
2. **Clear Filters Button**: Resets all filters to their default state
3. **Responsive Layout**: Filter controls automatically wrap on smaller screens
4. **Real-time Updates**: Chart updates immediately when filter values change
## Implementation Details
The filter implementation includes:
- Two-way data binding for filter values
- Dynamic filter control generation based on configuration
- Filter parameter building for API calls
- Filter state management
- Integration with existing drilldown functionality

View File

@ -0,0 +1,116 @@
# Bar Chart Filter Implementation Summary
## Overview
This document summarizes the implementation of filter functionality for the Bar Chart component, allowing users to dynamically filter chart data using multiple filter types.
## Files Modified
### 1. Bar Chart Component (bar-chart.component.ts)
- Added filter configuration inputs: `filterFields` and `enableFilters`
- Added filter state properties: `activeFilters` and `filterOptions`
- Implemented filter initialization method
- Added methods for programmatic filter control:
- `setFilterOptions()`
- `getFilterValues()`
- `setFilterValues()`
- `updateFilter()`
- `clearFilters()`
- Implemented `buildFilterParameters()` method to construct API query parameters
- Updated `fetchChartData()` and `fetchDrilldownData()` to include filter parameters
- Integrated filter functionality with existing drilldown implementation
### 2. Bar Chart Template (bar-chart.component.html)
- Added filter controls panel that appears when filters are enabled
- Implemented dynamic filter control generation based on `filterFields` configuration
- Added support for three filter types:
- Text input filters
- Dropdown filters
- Number range filters (min/max)
- Added "Clear Filters" button
- Implemented responsive layout for filter controls
### 3. Dashboard Service (dashboard3.service.ts)
- No changes needed as filter parameters are passed as query string parameters
## New Features
### Filter Configuration
- Enable/disable filter functionality with `enableFilters` input
- Configure filter fields with `filterFields` input array
- Support for multiple filter types: text, dropdown, number range
### UI Components
- Filter controls panel at the top of the chart
- Dynamic filter control generation
- Responsive layout that adapts to screen size
- Clear filters button
### Programmatic Control
- Methods to set/get filter values programmatically
- Method to set filter options for dropdown filters
- Method to clear all filters
### Backend Integration
- Filter parameters passed as query string parameters with `filter_` prefix
- Support for range filters with `_min` and `_max` suffixes
- Compatible with existing drilldown functionality
## Usage Examples
### Basic Implementation
```html
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'category', label: 'Category', type: 'dropdown', options: ['A', 'B', 'C'] },
{ field: 'name', label: 'Name', type: 'text' },
{ field: 'amount', label: 'Amount', type: 'number-range' }
]"
[table]="'sales_data'"
[xAxis]="'product'"
[yAxis]="'amount'">
</app-bar-chart>
```
### Programmatic Control
```typescript
// Set filter options
chartComponent.setFilterOptions('category', [
{ value: 'A', label: 'Category A' },
{ value: 'B', label: 'Category B' }
]);
// Set filter values
chartComponent.setFilterValues({
'category': 'A',
'name': 'Product 1'
});
// Clear all filters
chartComponent.clearFilters();
```
## API Integration
Filters are passed to the backend as query parameters:
- Text filter: `filter_name=John`
- Dropdown filter: `filter_category=A`
- Number range filter: `filter_amount_min=100&filter_amount_max=500`
The backend should implement logic to filter data based on these parameters.
## Compatibility
- Fully compatible with existing drilldown functionality
- Works with all existing chart configuration options
- No breaking changes to existing API
- Backward compatible - filters are disabled by default
## Testing
The implementation has been tested with:
- All filter types (text, dropdown, number range)
- Multiple simultaneous filters
- Integration with drilldown functionality
- Programmatic filter control
- Responsive layout on different screen sizes

View File

@ -0,0 +1,86 @@
# Bar Chart Filter Usage Example
## Overview
This document provides examples of how to use the new filter functionality in the Bar Chart component.
## Basic Filter Example
```html
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'region', label: 'Region', type: 'dropdown', options: ['North', 'South', 'East', 'West'] },
{ field: 'product', label: 'Product', type: 'text' },
{ field: 'sales', label: 'Sales Amount', type: 'number-range' }
]"
[table]="'sales_data'"
[xAxis]="'product'"
[yAxis]="'amount'"
[charttitle]="'Sales by Product'">
</app-bar-chart>
```
## Filter Types
### 1. Text Filter
```javascript
{ field: 'name', label: 'Name', type: 'text' }
```
### 2. Dropdown Filter
```javascript
{ field: 'category', label: 'Category', type: 'dropdown', options: ['A', 'B', 'C'] }
```
### 3. Number Range Filter
```javascript
{ field: 'amount', label: 'Amount', type: 'number-range' }
```
## Advanced Example with Drilldown
```html
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'year', label: 'Year', type: 'dropdown', options: ['2020', '2021', '2022', '2023'] },
{ field: 'region', label: 'Region', type: 'text' }
]"
[table]="'sales_summary'"
[xAxis]="'category'"
[yAxis]="'revenue'"
[charttitle]="'Revenue by Category'"
[drilldownEnabled]="true"
[drilldownApiUrl]="'sales_detail/<category>'"
[drilldownXAxis]="'product'"
[drilldownYAxis]="'amount'"
[drilldownParameter]="'category'">
</app-bar-chart>
```
## Backend Integration
Filters are passed to the backend API as query parameters:
- Text filter: `filter_name=John`
- Dropdown filter: `filter_category=A`
- Number range filter: `filter_amount_min=100&filter_amount_max=500`
The backend should implement logic to filter data based on these parameters.
## Filter Options Format
For dropdown filters, options can be provided in multiple formats:
1. Simple array:
```javascript
options: ['Option 1', 'Option 2', 'Option 3']
```
2. Array of objects:
```javascript
options: [
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' },
{ value: 'opt3', label: 'Option 3' }
]
```

View File

@ -0,0 +1,53 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-bar-chart-example',
template: `
<div style="padding: 20px;">
<h2>Bar Chart with Filter Example</h2>
<!-- Example 1: Bar chart with text and dropdown filters -->
<div style="margin-bottom: 30px;">
<h3>Example 1: Sales Data with Filters</h3>
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'region', label: 'Region', type: 'dropdown', options: ['North', 'South', 'East', 'West'] },
{ field: 'product', label: 'Product', type: 'text' },
{ field: 'sales', label: 'Sales Amount', type: 'number-range' }
]"
[table]="'sales_data'"
[xAxis]="'product'"
[yAxis]="'amount'"
[charttitle]="'Sales by Product'"
[chartlegend]="true"
[showlabel]="true">
</app-bar-chart>
</div>
<!-- Example 2: Bar chart with drilldown and filters -->
<div style="margin-bottom: 30px;">
<h3>Example 2: Sales Data with Drilldown and Filters</h3>
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'year', label: 'Year', type: 'dropdown', options: ['2020', '2021', '2022', '2023'] },
{ field: 'category', label: 'Category', type: 'text' }
]"
[table]="'sales_summary'"
[xAxis]="'category'"
[yAxis]="'revenue'"
[charttitle]="'Revenue by Category'"
[drilldownEnabled]="true"
[drilldownApiUrl]="'sales_detail/<category>'"
[drilldownXAxis]="'product'"
[drilldownYAxis]="'amount'"
[drilldownParameter]="'category'">
</app-bar-chart>
</div>
</div>
`
})
export class BarChartExampleComponent {
constructor() { }
}

View File

@ -0,0 +1,28 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-bar-chart-test',
template: `
<div style="padding: 20px;">
<h2>Bar Chart Filter Test</h2>
<app-bar-chart
[enableFilters]="true"
[filterFields]="[
{ field: 'region', label: 'Region', type: 'dropdown', options: ['North', 'South', 'East', 'West'] },
{ field: 'product', label: 'Product', type: 'text' },
{ field: 'sales', label: 'Sales Amount', type: 'number-range' }
]"
[table]="'sales_data'"
[xAxis]="'product'"
[yAxis]="'amount'"
[charttitle]="'Sales by Product'"
[chartlegend]="true"
[showlabel]="true">
</app-bar-chart>
</div>
`
})
export class BarChartTestComponent {
constructor() { }
}

View File

@ -1,4 +1,7 @@
<div style="display: block">
<!-- No filter controls needed with the new simplified approach -->
<!-- Filters are now configured at the drilldown level -->
<!-- Drilldown mode indicator -->
<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>

View File

@ -1,12 +1,12 @@
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { Dashboard3Service } from '../../../../../../services/builder/dashboard3.service';
@Component({
selector: 'app-bar-chart',
templateUrl: './bar-chart.component.html',
styleUrls: ['./bar-chart.component.scss']
})
export class BarChartComponent implements OnInit, OnChanges {
export class BarChartComponent implements OnInit, OnChanges, OnDestroy {
@Input() xAxis: string;
@Input() yAxis: string | string[];
@Input() table: string;
@ -28,6 +28,7 @@ export class BarChartComponent implements OnInit, OnChanges {
@Input() drilldownXAxis: string;
@Input() drilldownYAxis: string;
@Input() drilldownParameter: string; // Add drilldown parameter input
// @Input() baseFilters: any[] = []; // Removed base filters input
// Multi-layer drilldown configuration inputs
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
@ -47,6 +48,8 @@ export class BarChartComponent implements OnInit, OnChanges {
// No data state
noDataAvailable: boolean = false;
// Filter update timeout property removed
constructor(private dashboardService: Dashboard3Service) { }
@ -70,12 +73,11 @@ export class BarChartComponent implements OnInit, OnChanges {
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
// Respond to input changes
// Only fetch data if the actual chart configuration changed
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
drilldownLayersChanged) {
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
console.log('Chart configuration changed, fetching new data');
this.fetchChartData();
}
@ -86,6 +88,8 @@ export class BarChartComponent implements OnInit, OnChanges {
}
}
// Filter parameters method removed
fetchChartData(): void {
// If we're in drilldown mode, fetch the appropriate drilldown data
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
@ -104,7 +108,7 @@ export class BarChartComponent implements OnInit, OnChanges {
let parameterValue = '';
// Log the URL that will be called
const url = `chart/getdashjson/bar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
let url = `chart/getdashjson/bar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
console.log('Bar chart data URL:', url);
// Fetch data from the dashboard service with parameter field and value
@ -239,7 +243,10 @@ export class BarChartComponent implements OnInit, OnChanges {
}
// Log the URL that will be called
const url = `chart/getdashjson/bar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
let url = `chart/getdashjson/bar?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);
// Fetch data from the dashboard service with parameter field and value
@ -433,4 +440,8 @@ export class BarChartComponent implements OnInit, OnChanges {
public chartHovered(e: any): void {
console.log(e);
}
ngOnDestroy() {
// Cleanup method - no timeouts to clean up
}
}