dashboard
This commit is contained in:
		
							parent
							
								
									56e1e3b0fe
								
							
						
					
					
						commit
						c66e217b0c
					
				
										
											Binary file not shown.
										
									
								
							| @ -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 -->    | ||||
| @ -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 { | ||||
|  | ||||
| @ -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. | ||||
| @ -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> | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| @ -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 | ||||
| @ -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' } | ||||
| ] | ||||
| ``` | ||||
| @ -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() { } | ||||
| } | ||||
| @ -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() { } | ||||
| } | ||||
| @ -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> | ||||
|  | ||||
| @ -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 += `¶meter=${encodeURIComponent(parameterField)}¶meterValue=${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
 | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user