line
This commit is contained in:
@@ -1,13 +1,282 @@
|
||||
<div style="display: block">
|
||||
<!-- 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>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
<div style="display: block; height: 100%; width: 100%;">
|
||||
<!-- Filter Controls Section -->
|
||||
<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>
|
||||
|
||||
<!-- Header row with chart title and drilldown navigation -->
|
||||
<div class="clr-row header-row">
|
||||
<div class="clr-col-6">
|
||||
<h3 class="chart-title">{{charttitle || 'Line Chart'}}</h3>
|
||||
</div>
|
||||
<div class="clr-col-6" style="text-align: right;">
|
||||
<!-- Add drilldown navigation controls -->
|
||||
<button class="btn btn-sm btn-link" *ngIf="drilldownEnabled && drilldownStack.length > 0" (click)="navigateBack()">
|
||||
<cds-icon shape="arrow" direction="left"></cds-icon>
|
||||
Back to {{drilldownStack.length > 1 ? 'Previous Level' : 'Main Data'}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show current drilldown level -->
|
||||
<div class="clr-row" *ngIf="drilldownEnabled && drilldownStack.length > 0">
|
||||
<div class="clr-col-12">
|
||||
<div class="alert alert-info" style="padding: 8px 12px; margin-bottom: 12px;">
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<div class="alert-icon-wrapper">
|
||||
<cds-icon class="alert-icon" shape="info-circle"></cds-icon>
|
||||
</div>
|
||||
<span class="alert-text">
|
||||
Drilldown Level: {{currentDrilldownLevel}}
|
||||
<span *ngIf="drilldownStack.length > 0">
|
||||
(Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedLabel}})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
@@ -16,12 +285,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<div *ngIf="!noDataAvailable" style="position: relative; height: calc(100% - 50px);">
|
||||
<canvas baseChart
|
||||
[datasets]="lineChartData"
|
||||
[labels]="lineChartLabels"
|
||||
[options]="lineChartOptions"
|
||||
|
||||
[legend]="lineChartLegend"
|
||||
[type]="lineChartType"
|
||||
(chartHover)="chartHovered($event)"
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
margin-bottom: 15px;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Subscription } from 'rxjs';
|
||||
templateUrl: './line-chart.component.html',
|
||||
styleUrls: ['./line-chart.component.scss']
|
||||
})
|
||||
export class LineChartComponent implements OnInit, OnChanges {
|
||||
export class LineChartComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@@ -88,6 +88,11 @@ export class LineChartComponent implements OnInit, OnChanges {
|
||||
// Subscriptions to unsubscribe on destroy
|
||||
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(
|
||||
private dashboardService: Dashboard3Service,
|
||||
private filterService: FilterService
|
||||
@@ -109,6 +114,12 @@ export class LineChartComponent implements OnInit, OnChanges {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('LineChartComponent 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
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
@@ -140,6 +151,318 @@ export class LineChartComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(sub => sub.unsubscribe());
|
||||
// Clean up document click handler
|
||||
this.removeDocumentClickHandler();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Public method to refresh data when filters change
|
||||
|
||||
Reference in New Issue
Block a user