diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html index 83d155a..ff8a2cb 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html @@ -1,16 +1,282 @@
- - + +
+ +
+

Base Filters

+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+ + +
+ +
+ + +
+ +
+ + +
+
+ {{ filter.field }} + + ({{ getSelectedOptionsCount(filter) }} selected) + + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + to + +
+
+ + +
+ + +
+
+
+
+ + +
+

Drilldown Filters

+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+ + +
+ +
+ + +
+ +
+ + +
+
+ {{ filter.field }} + + ({{ getSelectedOptionsCount(filter) }} selected) + + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + to + +
+
+ + +
+ + +
+
+
+
+ + +
+

Layer Filters

+
+
+
{{ filter.field }} ({{ filter.type || 'text' }})
+ + +
+ +
+ + +
+ +
+ + +
+
+ {{ filter.field }} + + ({{ getSelectedOptionsCount(filter) }} selected) + + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + to + +
+
+ + +
+ + +
+
+
+
+ + +
+ +
+
- -
- Drilldown Level: {{currentDrilldownLevel}} - - + +
+
+

{{charttitle || 'Bar Chart'}}

+
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+ + Drilldown Level: {{currentDrilldownLevel}} + + (Clicked on: {{drilldownStack[drilldownStack.length - 1].clickedLabel}}) + + +
+
+
+
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss index 2f11ebd..a9282e4 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.scss @@ -1,31 +1,192 @@ -// Bar Chart Component Styles -:host { - display: block; - height: 100%; - width: 100%; +.filter-section { + margin-bottom: 20px; + padding: 15px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #f9f9f9; } -.bar-chart-container { - position: relative; - height: 100%; - width: 100%; -} - -canvas { - display: block; - max-width: 100%; - max-height: 100%; -} - -// Responsive design for chart container -@media (max-width: 768px) { - .bar-chart-container { - height: 300px; +.filter-group { + margin-bottom: 15px; + + h4 { + margin-top: 0; + margin-bottom: 10px; + color: #333; + font-weight: 600; } } -@media (max-width: 480px) { - .bar-chart-container { - height: 250px; +.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; + } } } \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts index 68105a0..f8aed36 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.ts @@ -90,6 +90,11 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { // Subscriptions to unsubscribe on destroy private subscriptions: Subscription[] = []; + // Add properties for filter functionality + private openMultiselects: Map = new Map(); // Map of filterId -> context + private documentClickHandler: ((event: MouseEvent) => void) | null = null; + private filtersInitialized: boolean = false; + constructor( private dashboardService: Dashboard3Service, private filterService: FilterService @@ -111,6 +116,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { ngOnChanges(changes: SimpleChanges): void { 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 const xAxisChanged = changes.xAxis && !changes.xAxis.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 { // Set flag to prevent recursive calls this.isFetchingData = true; @@ -661,6 +982,12 @@ export class BarChartComponent implements OnInit, OnChanges, OnDestroy { this.originalBarChartLabels = []; this.originalBarChartData = []; + // Clear multiselect tracking + this.openMultiselects.clear(); + + // Remove document click handler + this.removeDocumentClickHandler(); + console.log('BarChartComponent destroyed and cleaned up'); } } \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.html index b4bf8f7..67590d9 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.html @@ -246,9 +246,9 @@
-
+