barchart
This commit is contained in:
		
							parent
							
								
									ced99e0940
								
							
						
					
					
						commit
						96b90e5dbd
					
				| @ -1,16 +1,282 @@ | |||||||
| <div style="display: block; height: 100%; width: 100%;"> | <div style="display: block; height: 100%; width: 100%;"> | ||||||
|   <!-- No filter controls needed with the new simplified approach --> |   <!-- Filter Controls Section --> | ||||||
|   <!-- Filters are now configured at the drilldown level --> |   <div class="filter-section" *ngIf="hasActiveFilters()"> | ||||||
|  |     <!-- Base Filters --> | ||||||
|  |     <div class="filter-group" *ngIf="baseFilters && baseFilters.length > 0"> | ||||||
|  |       <h4>Base Filters</h4> | ||||||
|  |       <div class="filter-controls"> | ||||||
|  |         <div *ngFor="let filter of baseFilters" class="filter-item"> | ||||||
|  |           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||||
|  |            | ||||||
|  |           <!-- Text Filter --> | ||||||
|  |           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||||
|  |             <input type="text"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onBaseFilterChange(filter)" | ||||||
|  |                    [placeholder]="filter.field" | ||||||
|  |                    class="clr-input filter-text-input"> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Dropdown Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||||
|  |             <select [(ngModel)]="filter.value"  | ||||||
|  |                     (ngModelChange)="onBaseFilterChange(filter)" | ||||||
|  |                     class="clr-select filter-select"> | ||||||
|  |               <option value="">Select {{ filter.field }}</option> | ||||||
|  |               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||||
|  |             </select> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||||
|  |           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||||
|  |             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'base')"> | ||||||
|  |               <span class="multiselect-label">{{ filter.field }}</span> | ||||||
|  |               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||||
|  |                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||||
|  |               </span> | ||||||
|  |               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||||
|  |             </div> | ||||||
|  |             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'base')"> | ||||||
|  |               <div class="checkbox-group"> | ||||||
|  |                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||||
|  |                   <input type="checkbox"  | ||||||
|  |                          [checked]="isOptionSelected(filter, option)"  | ||||||
|  |                          (change)="onMultiSelectChange(filter, option, $event)" | ||||||
|  |                          [id]="'base-' + filter.field + '-' + i"  | ||||||
|  |                          class="clr-checkbox"> | ||||||
|  |                   <label [for]="'base-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Date Range Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||||
|  |             <div class="date-input-group"> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.start"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||||
|  |                      placeholder="Start Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |               <span class="date-separator">to</span> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.end"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||||
|  |                      placeholder="End Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Toggle Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||||
|  |             <input type="checkbox"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onToggleChange(filter, $event)" | ||||||
|  |                    clrToggle  | ||||||
|  |                    class="clr-toggle"> | ||||||
|  |             <label class="toggle-label">{{ filter.field }}</label> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Drilldown Filters --> | ||||||
|  |     <div class="filter-group" *ngIf="drilldownFilters && drilldownFilters.length > 0 && currentDrilldownLevel > 0"> | ||||||
|  |       <h4>Drilldown Filters</h4> | ||||||
|  |       <div class="filter-controls"> | ||||||
|  |         <div *ngFor="let filter of drilldownFilters" class="filter-item"> | ||||||
|  |           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||||
|  |            | ||||||
|  |           <!-- Text Filter --> | ||||||
|  |           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||||
|  |             <input type="text"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onDrilldownFilterChange(filter)" | ||||||
|  |                    [placeholder]="filter.field" | ||||||
|  |                    class="clr-input filter-text-input"> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Dropdown Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||||
|  |             <select [(ngModel)]="filter.value"  | ||||||
|  |                     (ngModelChange)="onDrilldownFilterChange(filter)" | ||||||
|  |                     class="clr-select filter-select"> | ||||||
|  |               <option value="">Select {{ filter.field }}</option> | ||||||
|  |               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||||
|  |             </select> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||||
|  |           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||||
|  |             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'drilldown')"> | ||||||
|  |               <span class="multiselect-label">{{ filter.field }}</span> | ||||||
|  |               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||||
|  |                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||||
|  |               </span> | ||||||
|  |               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||||
|  |             </div> | ||||||
|  |             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'drilldown')"> | ||||||
|  |               <div class="checkbox-group"> | ||||||
|  |                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||||
|  |                   <input type="checkbox"  | ||||||
|  |                          [checked]="isOptionSelected(filter, option)"  | ||||||
|  |                          (change)="onMultiSelectChange(filter, option, $event)" | ||||||
|  |                          [id]="'drilldown-' + filter.field + '-' + i"  | ||||||
|  |                          class="clr-checkbox"> | ||||||
|  |                   <label [for]="'drilldown-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Date Range Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||||
|  |             <div class="date-input-group"> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.start"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||||
|  |                      placeholder="Start Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |               <span class="date-separator">to</span> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.end"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||||
|  |                      placeholder="End Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Toggle Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||||
|  |             <input type="checkbox"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onToggleChange(filter, $event)" | ||||||
|  |                    clrToggle  | ||||||
|  |                    class="clr-toggle"> | ||||||
|  |             <label class="toggle-label">{{ filter.field }}</label> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Layer Filters --> | ||||||
|  |     <div class="filter-group" *ngIf="hasActiveLayerFilters()"> | ||||||
|  |       <h4>Layer Filters</h4> | ||||||
|  |       <div class="filter-controls"> | ||||||
|  |         <div *ngFor="let filter of getActiveLayerFilters()" class="filter-item"> | ||||||
|  |           <div class="filter-label">{{ filter.field }} ({{ filter.type || 'text' }})</div> | ||||||
|  |            | ||||||
|  |           <!-- Text Filter --> | ||||||
|  |           <div *ngIf="!filter.type || filter.type === 'text'" class="filter-input"> | ||||||
|  |             <input type="text"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onLayerFilterChange(filter)" | ||||||
|  |                    [placeholder]="filter.field" | ||||||
|  |                    class="clr-input filter-text-input"> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Dropdown Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'dropdown'" class="filter-input"> | ||||||
|  |             <select [(ngModel)]="filter.value"  | ||||||
|  |                     (ngModelChange)="onLayerFilterChange(filter)" | ||||||
|  |                     class="clr-select filter-select"> | ||||||
|  |               <option value="">Select {{ filter.field }}</option> | ||||||
|  |               <option *ngFor="let option of getFilterOptions(filter)" [value]="option">{{ option }}</option> | ||||||
|  |             </select> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Multi-Select Filter - Updated to show key first, then dropdown on click --> | ||||||
|  |           <div *ngIf="filter.type === 'multiselect'" class="filter-input multiselect-container"> | ||||||
|  |             <div class="multiselect-display" (click)="toggleMultiselect(filter, 'layer')"> | ||||||
|  |               <span class="multiselect-label">{{ filter.field }}</span> | ||||||
|  |               <span class="multiselect-value" *ngIf="getSelectedOptionsCount(filter) > 0"> | ||||||
|  |                 ({{ getSelectedOptionsCount(filter) }} selected) | ||||||
|  |               </span> | ||||||
|  |               <clr-icon shape="caret down" class="dropdown-icon"></clr-icon> | ||||||
|  |             </div> | ||||||
|  |             <div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'layer')"> | ||||||
|  |               <div class="checkbox-group"> | ||||||
|  |                 <div *ngFor="let option of getFilterOptions(filter); let i = index" class="checkbox-item"> | ||||||
|  |                   <input type="checkbox"  | ||||||
|  |                          [checked]="isOptionSelected(filter, option)"  | ||||||
|  |                          (change)="onMultiSelectChange(filter, option, $event)" | ||||||
|  |                          [id]="'layer-' + filter.field + '-' + i"  | ||||||
|  |                          class="clr-checkbox"> | ||||||
|  |                   <label [for]="'layer-' + filter.field + '-' + i" class="checkbox-label">{{ option }}</label> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Date Range Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'date-range'" class="filter-input date-range"> | ||||||
|  |             <div class="date-input-group"> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.start"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: $event, end: filter.value.end })" | ||||||
|  |                      placeholder="Start Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |               <span class="date-separator">to</span> | ||||||
|  |               <input type="date"  | ||||||
|  |                      [(ngModel)]="filter.value.end"  | ||||||
|  |                      (ngModelChange)="onDateRangeChange(filter, { start: filter.value.start, end: $event })" | ||||||
|  |                      placeholder="End Date" | ||||||
|  |                      class="clr-input filter-date"> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |            | ||||||
|  |           <!-- Toggle Filter --> | ||||||
|  |           <div *ngIf="filter.type === 'toggle'" class="filter-input toggle"> | ||||||
|  |             <input type="checkbox"  | ||||||
|  |                    [(ngModel)]="filter.value"  | ||||||
|  |                    (ngModelChange)="onToggleChange(filter, $event)" | ||||||
|  |                    clrToggle  | ||||||
|  |                    class="clr-toggle"> | ||||||
|  |             <label class="toggle-label">{{ filter.field }}</label> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <!-- Clear Filters Button --> | ||||||
|  |     <div class="filter-actions"> | ||||||
|  |       <button class="btn btn-sm btn-outline" (click)="clearAllFilters()">Clear All Filters</button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|    |    | ||||||
|   <!-- Drilldown mode indicator --> |   <!-- Header row with chart title and drilldown navigation --> | ||||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> |   <div class="clr-row header-row"> | ||||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> |     <div class="clr-col-6"> | ||||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> |       <h3 class="chart-title">{{charttitle || 'Bar Chart'}}</h3> | ||||||
|       Back to Level {{currentDrilldownLevel - 1}} |     </div> | ||||||
|     </button> |     <div class="clr-col-6" style="text-align: right;"> | ||||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> |       <!-- Add drilldown navigation controls --> | ||||||
|       Back to Main View |       <button class="btn btn-sm btn-link" *ngIf="drilldownEnabled && drilldownStack.length > 0" (click)="navigateBack()"> | ||||||
|     </button> |         <cds-icon shape="arrow" direction="left"></cds-icon> | ||||||
|  |         Back to {{drilldownStack.length > 1 ? 'Previous Level' : 'Main Data'}} | ||||||
|  |       </button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |    | ||||||
|  |   <!-- Show current drilldown level --> | ||||||
|  |   <div class="clr-row" *ngIf="drilldownEnabled && drilldownStack.length > 0"> | ||||||
|  |     <div class="clr-col-12"> | ||||||
|  |       <div class="alert alert-info" style="padding: 8px 12px; margin-bottom: 12px;"> | ||||||
|  |         <div class="alert-items"> | ||||||
|  |           <div class="alert-item static"> | ||||||
|  |             <div class="alert-icon-wrapper"> | ||||||
|  |               <cds-icon class="alert-icon" shape="info-circle"></cds-icon> | ||||||
|  |             </div> | ||||||
|  |             <span class="alert-text"> | ||||||
|  |               Drilldown Level: {{currentDrilldownLevel}}  | ||||||
|  |               <span *ngIf="drilldownStack.length > 0"> | ||||||
|  |                 (Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedLabel}}) | ||||||
|  |               </span> | ||||||
|  |             </span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
|    |    | ||||||
|   <!-- No data message --> |   <!-- No data message --> | ||||||
|  | |||||||
| @ -1,31 +1,192 @@ | |||||||
| // Bar Chart Component Styles | .filter-section { | ||||||
| :host { |   margin-bottom: 20px; | ||||||
|   display: block; |   padding: 15px; | ||||||
|   height: 100%; |   border: 1px solid #ddd; | ||||||
|   width: 100%; |   border-radius: 4px; | ||||||
|  |   background-color: #f9f9f9; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .bar-chart-container { | .filter-group { | ||||||
|   position: relative; |   margin-bottom: 15px; | ||||||
|   height: 100%; |    | ||||||
|   width: 100%; |   h4 { | ||||||
| } |     margin-top: 0; | ||||||
| 
 |     margin-bottom: 10px; | ||||||
| canvas { |     color: #333; | ||||||
|   display: block; |     font-weight: 600; | ||||||
|   max-width: 100%; |  | ||||||
|   max-height: 100%; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Responsive design for chart container |  | ||||||
| @media (max-width: 768px) { |  | ||||||
|   .bar-chart-container { |  | ||||||
|     height: 300px; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @media (max-width: 480px) { | .filter-controls { | ||||||
|   .bar-chart-container { |   display: flex; | ||||||
|     height: 250px; |   flex-wrap: wrap; | ||||||
|  |   gap: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .filter-item { | ||||||
|  |   flex: 1 1 300px; | ||||||
|  |   min-width: 250px; | ||||||
|  |   padding: 10px; | ||||||
|  |   background: white; | ||||||
|  |   border: 1px solid #e0e0e0; | ||||||
|  |   border-radius: 4px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .filter-label { | ||||||
|  |   font-weight: 500; | ||||||
|  |   margin-bottom: 8px; | ||||||
|  |   color: #555; | ||||||
|  |   font-size: 14px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .filter-input { | ||||||
|  |   width: 100%; | ||||||
|  |    | ||||||
|  |   .filter-text-input, | ||||||
|  |   .filter-select, | ||||||
|  |   .filter-date { | ||||||
|  |     width: 100%; | ||||||
|  |     padding: 6px 12px; | ||||||
|  |     border: 1px solid #ccc; | ||||||
|  |     border-radius: 4px; | ||||||
|  |     font-size: 14px; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .filter-select { | ||||||
|  |     height: 34px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .multiselect-container { | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .multiselect-display { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   background: white; | ||||||
|  |   cursor: pointer; | ||||||
|  |   min-height: 34px; | ||||||
|  |    | ||||||
|  |   .multiselect-label { | ||||||
|  |     flex: 1; | ||||||
|  |     font-size: 14px; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .multiselect-value { | ||||||
|  |     color: #666; | ||||||
|  |     font-size: 12px; | ||||||
|  |     margin-right: 8px; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .dropdown-icon { | ||||||
|  |     flex-shrink: 0; | ||||||
|  |     transition: transform 0.2s ease; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   &:hover { | ||||||
|  |     border-color: #999; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .multiselect-dropdown { | ||||||
|  |   position: absolute; | ||||||
|  |   top: 100%; | ||||||
|  |   left: 0; | ||||||
|  |   right: 0; | ||||||
|  |   z-index: 1000; | ||||||
|  |   background: white; | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   border-top: none; | ||||||
|  |   border-radius: 0 0 4px 4px; | ||||||
|  |   box-shadow: 0 2px 4px rgba(0,0,0,0.1); | ||||||
|  |   max-height: 200px; | ||||||
|  |   overflow-y: auto; | ||||||
|  |    | ||||||
|  |   .checkbox-group { | ||||||
|  |     padding: 8px; | ||||||
|  |      | ||||||
|  |     .checkbox-item { | ||||||
|  |       display: flex; | ||||||
|  |       align-items: center; | ||||||
|  |       gap: 8px; | ||||||
|  |       padding: 4px 0; | ||||||
|  |        | ||||||
|  |       .checkbox-label { | ||||||
|  |         margin: 0; | ||||||
|  |         font-size: 14px; | ||||||
|  |         cursor: pointer; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .date-range { | ||||||
|  |   .date-input-group { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     gap: 8px; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .date-separator { | ||||||
|  |     margin: 0 5px; | ||||||
|  |     color: #777; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .toggle { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   gap: 8px; | ||||||
|  |    | ||||||
|  |   .toggle-label { | ||||||
|  |     margin: 0; | ||||||
|  |     font-size: 14px; | ||||||
|  |     cursor: pointer; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .filter-actions { | ||||||
|  |   margin-top: 15px; | ||||||
|  |   padding-top: 15px; | ||||||
|  |   border-top: 1px solid #eee; | ||||||
|  |    | ||||||
|  |   .btn { | ||||||
|  |     font-size: 13px; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New header row styling | ||||||
|  | .header-row { | ||||||
|  |   margin-bottom: 15px; | ||||||
|  |   padding-bottom: 10px; | ||||||
|  |   border-bottom: 1px solid #eee; | ||||||
|  |    | ||||||
|  |   .chart-title { | ||||||
|  |     margin: 0; | ||||||
|  |     font-size: 18px; | ||||||
|  |     font-weight: 600; | ||||||
|  |     color: #333; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Responsive design | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |   .filter-controls { | ||||||
|  |     flex-direction: column; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .filter-item { | ||||||
|  |     min-width: 100%; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .header-row { | ||||||
|  |     .chart-title { | ||||||
|  |       font-size: 16px; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -90,6 +90,11 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|   // Subscriptions to unsubscribe on destroy
 |   // Subscriptions to unsubscribe on destroy
 | ||||||
|   private subscriptions: Subscription[] = []; |   private subscriptions: Subscription[] = []; | ||||||
| 
 | 
 | ||||||
|  |   // Add properties for filter functionality
 | ||||||
|  |   private openMultiselects: Map<string, string> = new Map(); // Map of filterId -> context
 | ||||||
|  |   private documentClickHandler: ((event: MouseEvent) => void) | null = null; | ||||||
|  |   private filtersInitialized: boolean = false; | ||||||
|  | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private dashboardService: Dashboard3Service, |     private dashboardService: Dashboard3Service, | ||||||
|     private filterService: FilterService |     private filterService: FilterService | ||||||
| @ -111,6 +116,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|   ngOnChanges(changes: SimpleChanges): void { |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|     console.log('BarChartComponent input changes:', changes); |     console.log('BarChartComponent input changes:', changes); | ||||||
|      |      | ||||||
|  |     // Initialize filter values if they haven't been initialized yet
 | ||||||
|  |     if (!this.filtersInitialized && (changes.baseFilters || changes.drilldownFilters || changes.drilldownLayers)) { | ||||||
|  |       this.initializeFilterValues(); | ||||||
|  |       this.filtersInitialized = true; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // Check if any of the key properties have changed
 |     // Check if any of the key properties have changed
 | ||||||
|     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; |     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; | ||||||
|     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; |     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; | ||||||
| @ -141,6 +152,316 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Initialize filter values with proper default values based on type
 | ||||||
|  |   private initializeFilterValues(): void { | ||||||
|  |     console.log('Initializing filter values'); | ||||||
|  |      | ||||||
|  |     // Initialize base filters
 | ||||||
|  |     if (this.baseFilters) { | ||||||
|  |       this.baseFilters.forEach(filter => { | ||||||
|  |         if (filter.value === undefined || filter.value === null) { | ||||||
|  |           switch (filter.type) { | ||||||
|  |             case 'multiselect': | ||||||
|  |               filter.value = []; | ||||||
|  |               break; | ||||||
|  |             case 'date-range': | ||||||
|  |               filter.value = { start: null, end: null }; | ||||||
|  |               break; | ||||||
|  |             case 'toggle': | ||||||
|  |               filter.value = false; | ||||||
|  |               break; | ||||||
|  |             default: | ||||||
|  |               filter.value = ''; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Initialize drilldown filters
 | ||||||
|  |     if (this.drilldownFilters) { | ||||||
|  |       this.drilldownFilters.forEach(filter => { | ||||||
|  |         if (filter.value === undefined || filter.value === null) { | ||||||
|  |           switch (filter.type) { | ||||||
|  |             case 'multiselect': | ||||||
|  |               filter.value = []; | ||||||
|  |               break; | ||||||
|  |             case 'date-range': | ||||||
|  |               filter.value = { start: null, end: null }; | ||||||
|  |               break; | ||||||
|  |             case 'toggle': | ||||||
|  |               filter.value = false; | ||||||
|  |               break; | ||||||
|  |             default: | ||||||
|  |               filter.value = ''; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Initialize layer filters
 | ||||||
|  |     if (this.drilldownLayers) { | ||||||
|  |       this.drilldownLayers.forEach(layer => { | ||||||
|  |         if (layer.filters) { | ||||||
|  |           layer.filters.forEach((filter: any) => { | ||||||
|  |             if (filter.value === undefined || filter.value === null) { | ||||||
|  |               switch (filter.type) { | ||||||
|  |                 case 'multiselect': | ||||||
|  |                   filter.value = []; | ||||||
|  |                   break; | ||||||
|  |                 case 'date-range': | ||||||
|  |                   filter.value = { start: null, end: null }; | ||||||
|  |                   break; | ||||||
|  |                 case 'toggle': | ||||||
|  |                   filter.value = false; | ||||||
|  |                   break; | ||||||
|  |                 default: | ||||||
|  |                   filter.value = ''; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('Filter values initialized:', { | ||||||
|  |       baseFilters: this.baseFilters, | ||||||
|  |       drilldownFilters: this.drilldownFilters, | ||||||
|  |       drilldownLayers: this.drilldownLayers | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Check if there are active filters
 | ||||||
|  |   hasActiveFilters(): boolean { | ||||||
|  |     return (this.baseFilters && this.baseFilters.length > 0) ||  | ||||||
|  |            (this.drilldownFilters && this.drilldownFilters.length > 0) ||  | ||||||
|  |            this.hasActiveLayerFilters(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Check if there are active layer filters for current drilldown level
 | ||||||
|  |   hasActiveLayerFilters(): boolean { | ||||||
|  |     if (this.currentDrilldownLevel > 1 && this.drilldownLayers && this.drilldownLayers.length > 0) { | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       return layerIndex < this.drilldownLayers.length &&  | ||||||
|  |              this.drilldownLayers[layerIndex].filters &&  | ||||||
|  |              this.drilldownLayers[layerIndex].filters.length > 0; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Get active layer filters for current drilldown level
 | ||||||
|  |   getActiveLayerFilters(): any[] { | ||||||
|  |     if (this.currentDrilldownLevel > 1 && this.drilldownLayers && this.drilldownLayers.length > 0) { | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       if (layerIndex < this.drilldownLayers.length &&  | ||||||
|  |           this.drilldownLayers[layerIndex].filters) { | ||||||
|  |         return this.drilldownLayers[layerIndex].filters; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Get filter options for dropdown/multiselect filters
 | ||||||
|  |   getFilterOptions(filter: any): string[] { | ||||||
|  |     if (filter.options) { | ||||||
|  |       if (Array.isArray(filter.options)) { | ||||||
|  |         return filter.options; | ||||||
|  |       } else if (typeof filter.options === 'string') { | ||||||
|  |         return filter.options.split(',').map(opt => opt.trim()).filter(opt => opt); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Check if an option is selected for multiselect filters
 | ||||||
|  |   isOptionSelected(filter: any, option: string): boolean { | ||||||
|  |     if (!filter.value) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (Array.isArray(filter.value)) { | ||||||
|  |       return filter.value.includes(option); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return filter.value === option; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle base filter changes
 | ||||||
|  |   onBaseFilterChange(filter: any): void { | ||||||
|  |     console.log('Base filter changed:', filter); | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle drilldown filter changes
 | ||||||
|  |   onDrilldownFilterChange(filter: any): void { | ||||||
|  |     console.log('Drilldown filter changed:', filter); | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle layer filter changes
 | ||||||
|  |   onLayerFilterChange(filter: any): void { | ||||||
|  |     console.log('Layer filter changed:', filter); | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle multiselect changes
 | ||||||
|  |   onMultiSelectChange(filter: any, option: string, event: any): void { | ||||||
|  |     const checked = event.target.checked; | ||||||
|  |      | ||||||
|  |     // Initialize filter.value as array if it's not already
 | ||||||
|  |     if (!Array.isArray(filter.value)) { | ||||||
|  |       filter.value = []; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (checked) { | ||||||
|  |       // Add option to array if not already present
 | ||||||
|  |       if (!filter.value.includes(option)) { | ||||||
|  |         filter.value.push(option); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // Remove option from array
 | ||||||
|  |       filter.value = filter.value.filter((item: string) => item !== option); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle date range changes
 | ||||||
|  |   onDateRangeChange(filter: any, dateRange: { start: string | null, end: string | null }): void { | ||||||
|  |     filter.value = dateRange; | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Handle toggle changes
 | ||||||
|  |   onToggleChange(filter: any, checked: boolean): void { | ||||||
|  |     filter.value = checked; | ||||||
|  |     // Refresh data when filter changes
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Toggle multiselect dropdown visibility
 | ||||||
|  |   toggleMultiselect(filter: any, context: string): void { | ||||||
|  |     const filterId = `${context}-${filter.field}`; | ||||||
|  |     if (this.isMultiselectOpen(filter, context)) { | ||||||
|  |       this.openMultiselects.delete(filterId); | ||||||
|  |     } else { | ||||||
|  |       // Close all other multiselects first
 | ||||||
|  |       this.openMultiselects.clear(); | ||||||
|  |       this.openMultiselects.set(filterId, context); | ||||||
|  |        | ||||||
|  |       // Add document click handler to close dropdown when clicking outside
 | ||||||
|  |       this.addDocumentClickHandler(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Add document click handler to close dropdowns when clicking outside
 | ||||||
|  |   private addDocumentClickHandler(): void { | ||||||
|  |     if (!this.documentClickHandler) { | ||||||
|  |       this.documentClickHandler = (event: MouseEvent) => { | ||||||
|  |         const target = event.target as HTMLElement; | ||||||
|  |         // Check if click is outside any multiselect dropdown
 | ||||||
|  |         if (!target.closest('.multiselect-container')) { | ||||||
|  |           this.openMultiselects.clear(); | ||||||
|  |           this.removeDocumentClickHandler(); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |        | ||||||
|  |       // Use setTimeout to ensure the click event that opened the dropdown doesn't immediately close it
 | ||||||
|  |       setTimeout(() => { | ||||||
|  |         document.addEventListener('click', this.documentClickHandler!); | ||||||
|  |       }, 0); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Remove document click handler
 | ||||||
|  |   private removeDocumentClickHandler(): void { | ||||||
|  |     if (this.documentClickHandler) { | ||||||
|  |       document.removeEventListener('click', this.documentClickHandler); | ||||||
|  |       this.documentClickHandler = null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Check if multiselect dropdown is open
 | ||||||
|  |   isMultiselectOpen(filter: any, context: string): boolean { | ||||||
|  |     const filterId = `${context}-${filter.field}`; | ||||||
|  |     return this.openMultiselects.has(filterId); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Get count of selected options for a multiselect filter
 | ||||||
|  |   getSelectedOptionsCount(filter: any): number { | ||||||
|  |     if (!filter.value) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (Array.isArray(filter.value)) { | ||||||
|  |       return filter.value.length; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Clear all filters
 | ||||||
|  |   clearAllFilters(): void { | ||||||
|  |     // Clear base filters
 | ||||||
|  |     if (this.baseFilters) { | ||||||
|  |       this.baseFilters.forEach(filter => { | ||||||
|  |         if (filter.type === 'multiselect') { | ||||||
|  |           filter.value = []; | ||||||
|  |         } else if (filter.type === 'date-range') { | ||||||
|  |           filter.value = { start: null, end: null }; | ||||||
|  |         } else if (filter.type === 'toggle') { | ||||||
|  |           filter.value = false; | ||||||
|  |         } else { | ||||||
|  |           filter.value = ''; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Clear drilldown filters
 | ||||||
|  |     if (this.drilldownFilters) { | ||||||
|  |       this.drilldownFilters.forEach(filter => { | ||||||
|  |         if (filter.type === 'multiselect') { | ||||||
|  |           filter.value = []; | ||||||
|  |         } else if (filter.type === 'date-range') { | ||||||
|  |           filter.value = { start: null, end: null }; | ||||||
|  |         } else if (filter.type === 'toggle') { | ||||||
|  |           filter.value = false; | ||||||
|  |         } else { | ||||||
|  |           filter.value = ''; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Clear layer filters
 | ||||||
|  |     if (this.drilldownLayers) { | ||||||
|  |       this.drilldownLayers.forEach(layer => { | ||||||
|  |         if (layer.filters) { | ||||||
|  |           layer.filters.forEach((filter: any) => { | ||||||
|  |             if (filter.type === 'multiselect') { | ||||||
|  |               filter.value = []; | ||||||
|  |             } else if (filter.type === 'date-range') { | ||||||
|  |               filter.value = { start: null, end: null }; | ||||||
|  |             } else if (filter.type === 'toggle') { | ||||||
|  |               filter.value = false; | ||||||
|  |             } else { | ||||||
|  |               filter.value = ''; | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Close all multiselect dropdowns
 | ||||||
|  |     this.openMultiselects.clear(); | ||||||
|  |      | ||||||
|  |     // Refresh data
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   fetchChartData(): void { |   fetchChartData(): void { | ||||||
|     // Set flag to prevent recursive calls
 |     // Set flag to prevent recursive calls
 | ||||||
|     this.isFetchingData = true; |     this.isFetchingData = true; | ||||||
| @ -661,6 +982,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     this.originalBarChartLabels = []; |     this.originalBarChartLabels = []; | ||||||
|     this.originalBarChartData = []; |     this.originalBarChartData = []; | ||||||
|      |      | ||||||
|  |     // Clear multiselect tracking
 | ||||||
|  |     this.openMultiselects.clear(); | ||||||
|  |      | ||||||
|  |     // Remove document click handler
 | ||||||
|  |     this.removeDocumentClickHandler(); | ||||||
|  |      | ||||||
|     console.log('BarChartComponent destroyed and cleaned up'); |     console.log('BarChartComponent destroyed and cleaned up'); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -246,9 +246,9 @@ | |||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|     <div class="clr-row"> |     <div class="clr-row"> | ||||||
|       <div class="clr-col-8"> |       <!-- <div class="clr-col-8"> | ||||||
|         <h3>{{charttitle || 'Data Grid'}}</h3> |         <h3>{{charttitle || 'Data Grid'}}</h3> | ||||||
|       </div> |       </div> --> | ||||||
|       <!-- Add drilldown navigation controls --> |       <!-- Add drilldown navigation controls --> | ||||||
|       <div class="clr-col-4" *ngIf="drilldownEnabled && drilldownStack.length > 0" style="text-align: right;"> |       <div class="clr-col-4" *ngIf="drilldownEnabled && drilldownStack.length > 0" style="text-align: right;"> | ||||||
|         <button class="btn btn-sm btn-link" (click)="navigateBack()"> |         <button class="btn btn-sm btn-link" (click)="navigateBack()"> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user