diff --git a/apis/core-apis/package.json b/apis/core-apis/package.json index 95a4f9b7..de4a71fa 100644 --- a/apis/core-apis/package.json +++ b/apis/core-apis/package.json @@ -10,9 +10,9 @@ "author": "", "license": "ISC", "dependencies": { + "2fa-util": "^1.1.1", "@azure/storage-blob": "^12.12.0", "@types/speakeasy": "^2.0.7", - "2fa-util": "^1.1.1", "aws-sdk": "^2.633.0", "axios": "^0.19.2", "azure-storage": "^2.10.5", @@ -21,7 +21,7 @@ "cors": "^2.8.5", "csvtojson": "^2.0.10", "dotenv": "^8.2.0", - "express": "^4.17.1", + "express": "^4.18.2", "group-array": "^1.0.0", "json2csv": "^5.0.6", "jsonexport": "^3.0.1", diff --git a/apis/wrapper-api/index.js b/apis/wrapper-api/index.js index 7d8596ae..1977d1d7 100644 --- a/apis/wrapper-api/index.js +++ b/apis/wrapper-api/index.js @@ -10,8 +10,8 @@ app.use(cors()); const client = new Client({ user: "postgres", // Replace with your PostgreSQL username host: "localhost", // Replace with the hostname of your PostgreSQL server - database: "POC", // Replace with the name of your database - password: "Root@123", // Replace with your password + database: "cqube", // Replace with the name of your database + password: "postgres", // Replace with your password port: 5432, // Replace with the port of your PostgreSQL server (usually 5432) }); diff --git a/reference-visualization-app/dashboard/package.json b/reference-visualization-app/dashboard/package.json index 80f98795..44c4af3b 100644 --- a/reference-visualization-app/dashboard/package.json +++ b/reference-visualization-app/dashboard/package.json @@ -18,7 +18,7 @@ "@angular/compiler": "^14.0.0", "@angular/core": "^14.0.0", "@angular/forms": "^14.0.0", - "@angular/material": "^14.0.4", + "@angular/material": "^14.2.7", "@angular/platform-browser": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0", "@angular/router": "^14.0.0", @@ -32,6 +32,7 @@ "@ngx-translate/http-loader": "^7.0.0", "@project-sunbird/sb-dashlet-v14": "^1.0.6", "@project-sunbird/sb-themes": "^0.0.78", + "angular-material-datepicker": "^1.0.2", "apexcharts": "^3.35.5", "bootstrap": "^4.3.1", "chart.js": "^2.9.4", @@ -51,6 +52,7 @@ "ng-apexcharts": "^1.7.1", "ng-circle-progress": "^1.6.0", "ngx-bootstrap": "^9.0.0", + "ngx-daterangepicker-material": "^6.0.4", "ngx-spinner": "^14.0.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", diff --git a/reference-visualization-app/dashboard/src/app/core/services/wrapper.service.ts b/reference-visualization-app/dashboard/src/app/core/services/wrapper.service.ts index ceb65863..2e882c10 100644 --- a/reference-visualization-app/dashboard/src/app/core/services/wrapper.service.ts +++ b/reference-visualization-app/dashboard/src/app/core/services/wrapper.service.ts @@ -8,11 +8,14 @@ export class WrapperService { constructor(private readonly _commonService: CommonService) { } - constructFilters(filters: any, filtersConfig: any): any { - filtersConfig.forEach((filter, index) => { + async constructFilters(filters: any, filtersConfig: any): Promise { + // filtersConfig.forEach((filter, index) => { + for (let index = 0; index < filtersConfig.length; index++) { + let filter = filtersConfig[index] let query = this.parseQuery(filtersConfig, filters, index); if (query) { - this._commonService.getReportDataNew(query).subscribe((res: any) => { + let res = await this.runQuery(query); + if (res) { let { rows } = res; let findFilter = filters.find(fil => fil.valueProp === filter.valueProp); if (findFilter) { @@ -33,9 +36,19 @@ export class WrapperService { })) }); } - }); + } + } - }); + }; + return filters; + } + + runQuery(query: string): any { + return new Promise((resolve, reject) => { + this._commonService.getReportDataNew(query).subscribe((res: any) => { + resolve(res); + }); + }) } formatToolTip(tooltipTemplate: string, record: any): string { @@ -51,23 +64,23 @@ export class WrapperService { const strArr = tooltipTemplate.split(''); let counter = 0; for (let i = 0, len = strArr.length; i < len; i++) { - if (strArr[i] === "{") { - counter++; - } else if (strArr[i] === "}") { - counter--; - }; - if (counter < 0) { - return false; - }; + if (strArr[i] === "{") { + counter++; + } else if (strArr[i] === "}") { + counter--; + }; + if (counter < 0) { + return false; + }; }; if (counter === 0) { - return true; + return true; }; return false; }; substituteValues(tooltipTemplate: string): any { - + }; parseQuery(filtersConfig, filters, index): string { @@ -75,14 +88,14 @@ export class WrapperService { let { query } = filter; let startIndex = query.indexOf('{'); let endIndex = query.indexOf('}'); - + if (query && startIndex > -1) { while (startIndex > -1) { let propertyName = query.substring(startIndex + 1, endIndex); let parentFilter = filters.find(filter => filter.valueProp === propertyName); if (parentFilter && parentFilter.value) { let re = new RegExp(`{${propertyName}}`); - query = query.replace(re, parentFilter.value); + query = query.replace(re, '\'' + parentFilter.value + '\''); } else { query = null; break; @@ -95,4 +108,19 @@ export class WrapperService { return query; } + + constructTooltip(tooltipMetrics: any, row: any, selectedMetricValue: any): string { + let tooltip = ''; + tooltipMetrics.forEach((metric: any) => { + if (row[metric.value] !== undefined && row[metric.value] !== null) { + if (metric.value === selectedMetricValue) { + tooltip += '' + metric.valuePrefix.replace(/\n/g, '
') + (isNaN(row[metric.value]) ? row[metric.value] : Number(row[metric.value])) + metric.valueSuffix.replace(/\n/g, '
') + '
' + } + else { + tooltip += metric.valuePrefix.replace(/\n/g, '
') + '' + (isNaN(row[metric.value]) ? row[metric.value] : Number(row[metric.value])) + '' + metric.valueSuffix.replace(/\n/g, '
') + } + } + }); + return tooltip; + } } diff --git a/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.html b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.html new file mode 100644 index 00000000..1dd39c20 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.html @@ -0,0 +1,25 @@ +
+
+ {{bigNumberReportData?.reportName}} +
+
+

{{averagePercentage + this.valueSuffix}}

+
+
+
+

{{differenceInPercentage + this.valueSuffix}}

+
+
+
+ +
+
+ +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.scss b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.scss new file mode 100644 index 00000000..a91e30ea --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.scss @@ -0,0 +1,60 @@ +.fa-arrow-down{ + color: rgb(246, 49, 49); +} + +.fa-arrow-up{ + color: rgb(53, 185, 53); +} + +.fa-minus{ + color: black; +} + +.report-wrapper{ + border-radius: 14px; + width: 100px; + padding: 5px; + margin-bottom: 20px; + width: auto; + background-color: #4c30f5; + display: flex; + flex-direction: column; +} + +.average-wrapper{ + // margin: 30px; + margin-right: auto; + margin-left: auto; + margin-top: 10px; + margin-bottom: 10px; + display: flex; + padding: 5px; + align-items: center; + justify-content: center; +} + +.difference-wrapper{ + padding: 5px; + justify-content: end; + display: flex; + flex-direction: row; +} + +.difference-percentage{ + margin-right: 3px; + font-size: medium; + font-weight: bolder; + color: white; +} + +.average-percentage{ + font-size: 40px; + font-weight: bolder; + color: white; +} + +.report-name{ + margin: 5px 0px; + font-size: medium; + color: white; +} diff --git a/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.spec.ts b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.spec.ts new file mode 100644 index 00000000..344fc916 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BigNumberComponent } from './big-number.component'; + +describe('BigNumberComponent', () => { + let component: BigNumberComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BigNumberComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BigNumberComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.ts b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.ts new file mode 100644 index 00000000..2f90cdfc --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/big-number/big-number.component.ts @@ -0,0 +1,43 @@ +import { Component, Input, OnChanges, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-big-number', + templateUrl: './big-number.component.html', + styleUrls: ['./big-number.component.scss'] +}) +export class BigNumberComponent implements OnInit, OnChanges { + + @Input() bigNumberReportData: any; + averagePercentage: any; + differenceInPercentage: any; + differenceIndicator: boolean = undefined; + valueSuffix: any; + constructor() { } + + ngOnInit(): void { + + } + + ngOnChanges(): void { + this.updateValues(); + } + + updateValues(): void { + this.averagePercentage = this.bigNumberReportData?.averagePercentage; + this.valueSuffix = this.bigNumberReportData.valueSuffix ? this.bigNumberReportData.valueSuffix : ''; + if(this.bigNumberReportData && this.bigNumberReportData.differencePercentage && this.bigNumberReportData.averagePercentage) { + this.differenceInPercentage = (this.bigNumberReportData.averagePercentage - this.bigNumberReportData.differencePercentage).toFixed(2); + } + if(this.differenceInPercentage > 0){ + this.differenceIndicator = true; + } + else if(this.differenceInPercentage < 0) { + this.differenceIndicator = false; + this.differenceInPercentage = Math.abs(this.differenceInPercentage) + } + else { + this.differenceIndicator = undefined; + } + } + +} diff --git a/reference-visualization-app/dashboard/src/app/shared/components/buttons/material-button-group/material-button-group.component.html b/reference-visualization-app/dashboard/src/app/shared/components/buttons/material-button-group/material-button-group.component.html index c45b2e62..b50dd7ce 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/buttons/material-button-group/material-button-group.component.html +++ b/reference-visualization-app/dashboard/src/app/shared/components/buttons/material-button-group/material-button-group.component.html @@ -1,7 +1,7 @@
- +
\ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/shared/components/filter-panel/filter-panel.component.ts b/reference-visualization-app/dashboard/src/app/shared/components/filter-panel/filter-panel.component.ts index e4d52df5..f9c749e5 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/filter-panel/filter-panel.component.ts +++ b/reference-visualization-app/dashboard/src/app/shared/components/filter-panel/filter-panel.component.ts @@ -9,7 +9,7 @@ export class FilterPanelComponent implements OnInit, OnChanges { @Input() filters: any = []; @Input() colSize: any = "md:col-span-3 xs:col-span-12 xmd:col-span-4 4k:col-span-2"; - @Input() resetOthers = true; + @Input() resetOthers = false; @Output() filtersUpdated = new EventEmitter(); @@ -22,16 +22,17 @@ export class FilterPanelComponent implements OnInit, OnChanges { } onSelectOption(event: any, ind: number): void { - // if (this.resetOthers) { - // this.filters = this.filters.map((filter: any, filterInd: number) => { - // if (filterInd > ind) { - // filter.options = []; - // filter.value = null; - // } - - // return filter; - // }); - // } + if (this.resetOthers) { + this.filters = this.filters.filter((filter: any, filterInd: number) => { + if (filterInd > ind) { + // filter.options = []; + // filter.value = null; + return false; + } + + return true; + }); + } this.filtersUpdated.emit(this.filters); } diff --git a/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.html b/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.html index b66952e9..71d9639b 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.html +++ b/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.html @@ -1,8 +1,8 @@ -
-
+
+
-
+
{{ option.label }} diff --git a/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.scss b/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.scss index e69de29b..3e71d58d 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.scss +++ b/reference-visualization-app/dashboard/src/app/shared/components/level-n-metric-filter-panel/level-n-metric-filter-panel.component.scss @@ -0,0 +1,3 @@ +.filter-wrapper{ + padding: 0px 5px; +} \ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.scss b/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.scss index fe875dd5..3885055a 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.scss +++ b/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.scss @@ -9,4 +9,22 @@ // // word-break: break-word; // min-width: fit-content !important; // } -// } \ No newline at end of file +// } +::ng-deep{ + .leaflet-top { + pointer-events: auto !important; + } + .clickable-range{ + cursor: pointer; + } + .legend-range { + margin-top: 10px; + padding: 5px; + height: 4.5vh; + font-size: 0.75rem; + text-align: center; + border-radius: 10px; + font-weight: bold; + width: 100%; + } +} \ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.ts b/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.ts index 458f8a8f..5a366293 100644 --- a/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.ts +++ b/reference-visualization-app/dashboard/src/app/shared/components/maps/leaflet-map/leaflet-map.component.ts @@ -1,11 +1,12 @@ import { state } from '@angular/animations'; import { ThisReceiver } from '@angular/compiler'; -import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'; import * as L from "leaflet"; import * as R from "leaflet-responsive-popup"; import { StateCodes } from 'src/app/core/config/StateCodes'; import { environment } from 'src/environments/environment'; import * as config from '../../../../../assets/data/config.json'; +import invert from 'invert-color'; @Component({ selector: 'app-leaflet-map', @@ -24,6 +25,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { @Input() mapData!: any; @Input() level = 'state'; @Input() perCapitaReport: any = false; + @Input() hierarchyLevel: any = environment.config === 'national' ? 1 : 2; + + @Output() drillDownFilter: EventEmitter = new EventEmitter(); @ViewChild('map') mapContainer!: ElementRef; @@ -40,13 +44,15 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { ngOnChanges(): void { this.markers.clearLayers(); - if (this.level === 'district') { - // this.updateMap(); - this.initMap(); - } - else { - this.initMap(); - } + this.legend?.remove(); + // if (this.level === 'district') { + // // this.updateMap(); + // this.initMap(); + // } + // else { + // this.initMap(); + // } + this.initMap(); } async initMap(): Promise { @@ -63,7 +69,7 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { this.map = L.map(this.mapContainer.nativeElement, { zoomSnap: 0.05, minZoom: 4, zoomControl: true, scrollWheelZoom: false, touchZoom: false }).setView([this.mapCenterLatlng.lat, this.mapCenterLatlng.lng], this.mapCenterLatlng.zoomLevel); try { await this.applyCountryBorder(this.mapData); - const tiles = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { + const tiles = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { subdomains: 'abcd' }); @@ -72,12 +78,14 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { // var imageUrl ='https://i.stack.imgur.com/khgzZ.png', // imageBounds = [[80.0, -350.0], [-40.0, 400.0]]; // L.imageOverlay(imageUrl, imageBounds, {opacity: 0.3}).addTo(this.map); - if (environment.config === 'national' && this.level === 'district') { + if ((environment.config === 'national' && this.level === 'district') || environment.config === 'state') { this.createMarkers(this.mapData); } - this.map.on('resize', () => { - this.fitBoundsToCountryBorder(); - }); + if (this.hierarchyLevel < 3) { + this.map.on('resize', () => { + this.fitBoundsToCountryBorder(); + }); + } } catch (e) { console.error(e); this.error = true; @@ -96,7 +104,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { this.markers.clearLayers(); this.legend?.remove(); - this.fitBoundsToCountryBorder(); + if (this.hierarchyLevel < 3) { + this.fitBoundsToCountryBorder(); + } this.createMarkers(this.mapData); } @@ -111,23 +121,22 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { } if (reportTypeBoolean) { if (e.trim() == "Yes") { - return "#1D4586"; + return "#00FF00"; } else { - return "#FFFFFF"; + return "#FF0000"; } } else { { - return e > 75 ? "#1D4586" : - e > 50 ? "#1156CC" : - e > 25 ? "#6D9FEB" : - e >= 0 ? "#C9DAF7" : "#fff"; + return e > 75 ? "#00FF00" : + e > 50 ? "#FFFF00" : + e >= 0 ? "#FF0000" : "#fff"; } } } } - async applyCountryBorder(mapData: any): Promise { + async applyCountryBorder(mapData: any, singleColor?: any): Promise { let reportTypeIndicator = this.mapData.options && this.mapData.options.tooltip && this.mapData.options.tooltip.reportTypeIndicator ? this.mapData.options.tooltip.reportTypeIndicator : (typeof this.mapData.data[0].indicator === 'string') ? 'boolean' : 'value' let parent = this; return new Promise(async (resolve, reject) => { @@ -164,18 +173,18 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { let partSize = (range / 4 % 1 === 0) ? range / 4 : Number((range / 4).toFixed(2)); if (range && range <= 4) { for (let i = 1; i <= 5; i++) { - if(i === 5){ + if (i === 5) { if (min === 0) { values.push(0.1); } - else{ + else { values.push(Number(min)) } } - else if(i === 1) { + else if (i === 1) { values.push(Number(max)) } - else { + else if (i !== 4){ let value = Number((max - partSize * (i - 1))) values.push(value >= 1 ? value : 1) } @@ -197,6 +206,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { values.push(this.perCapitaReport ? max : Math.ceil(max)); continue; } + if (i === 4) { + continue; + } if (this.perCapitaReport) { let value = Number((max - partSize * (i - 1)).toFixed(2)) values.push(value) @@ -207,7 +219,7 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { } } } - else{ + else { values.push(min); } @@ -228,9 +240,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { color = parent.getLayerColor(state.indicator ? (max - min ? (state.indicator - min) / (max - min) * 100 : state.indicator) : -1); } }); - if(parent.level === 'state' || environment.config === 'state'){ + if (parent.level === 'state' || environment.config === 'state') { return { - fillColor: color, + fillColor: singleColor ? (color === '#fff' ? color : singleColor) : color, weight: 1, opacity: 1, color: 'grey', @@ -239,9 +251,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { }; } else { - return + return } - + } function getPopUp(feature: any) { @@ -272,8 +284,14 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { fillOpacity: 0, fontWeight: "bold" }).addTo(this.map); - this.fitBoundsToCountryBorder(); - if (this.level === 'state' || (environment.config === 'state' && this.level === 'district')) { + if (this.hierarchyLevel < 3) { + this.fitBoundsToCountryBorder(); + } + // this.countryGeoJSON.eachLayer((layer: any) => { + // layer._path.id = StateCodes[Number(layer.feature.properties.state_code)]; + // }); + + if (this.hierarchyLevel < 2 && !singleColor) { this.createLegend(reportTypeIndicator, this.mapData.options, values); } resolve('India map borders plotted successfully'); @@ -289,7 +307,7 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { }); } - createMarkers(mapData: any): void { + createMarkers(mapData: any, singleColor?: any): void { let reportTypeIndicator = this.mapData.options && this.mapData.options.tooltip && this.mapData.options.tooltip.reportTypeIndicator ? this.mapData.options.tooltip.reportTypeIndicator : (typeof this.mapData.data[0].indicator === 'string') ? 'boolean' : 'value' if (mapData && this.level !== 'state') { let min!: number, max!: number, values: any[] = []; @@ -309,18 +327,18 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { let partSize = (range / 4 % 1 === 0) ? range / 4 : Number((range / 4).toFixed(2)); if (range && range <= 4) { for (let i = 1; i <= 5; i++) { - if(i === 5){ + if (i === 5) { if (min === 0) { values.push(0.1); } - else{ + else { values.push(Number(min)) } } - else if(i === 1) { + else if (i === 1) { values.push(Number(max)) } - else { + else if (i !== 4) { let value = Number((max - partSize * (i - 1))) values.push(value >= 1 ? value : 1) } @@ -342,6 +360,9 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { values.push(this.perCapitaReport ? max : Math.ceil(max)); continue; } + if (i === 4) { + continue; + } if (this.perCapitaReport) { let value = Number((max - partSize * (i - 1)).toFixed(2)) values.push(value) @@ -352,15 +373,34 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { } } } - else{ + else { values.push(min); } } - mapData.data.forEach((data: any) => { + let re = new RegExp("_id$"); + let filterIds = {}; + + Object.keys(data).forEach((prop: any) => { + // if(re.test(prop)){ + // idProp = prop; + // return false; + // } + // return true; + if (prop.match(re)) { + filterIds = { + ...filterIds, + [prop.match(re).input]: data[prop.match(re)?.input] + } + } + }) + let markerIcon = L.circleMarker([data.Latitude, data.Longitude], { + filterIds: filterIds, + hierarchyLevel: data.hierarchyLevel, color: "gray", - fillColor: this.getZoneColor(reportTypeIndicator, data.indicator >= 1 ? (max - min ? (data.indicator - min) / (max - min) * 100 : data.indicator) : -1), + // fillColor: this.getZoneColor(reportTypeIndicator, data.indicator >= 1 ? (max - min ? (data.indicator - min) / (max - min) * 100 : data.indicator) : -1), + fillColor: singleColor ? singleColor : this.getZoneColor(reportTypeIndicator, data.indicator >= 1 ? (max - min ? (data.indicator - min) / (max - min) * 100 : data.indicator) : -1), fillOpacity: 1, strokeWeight: 0.01, weight: 1 @@ -386,13 +426,22 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { e.target.closePopup(); }); + markerIcon.on("click", (e: any) => { + this.drillDownMarker(e.target.options.filterIds) + }) + markerIcon.addTo(this.map).bindPopup(popup, { closeButton: false }); this.markers.addLayer(markerIcon); }); this.map.addLayer(this.markers); - if (this.level === 'district') { + if (this.hierarchyLevel > 2) { + this.map.fitBounds(this.markers.getBounds(), { + padding: [250, 250] + }); + } + if (!singleColor) { this.createLegend(reportTypeIndicator, this.mapData.options, values); } } @@ -405,10 +454,11 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { legend.onAdd = function (map: any) { let div = L.DomUtil.create('div', 'info legend'); + let clickable = false; if (mapOptions.legend && mapOptions.legend.title) { labels.push(`${mapOptions.selectedMetric ? mapOptions.selectedMetric : mapOptions.legend.title}:`) } - if(values.length <= 1 && reportTypeIndicator !== 'boolean'){ + if (values.length <= 1 && reportTypeIndicator !== 'boolean') { labels.push(` ${values[0]}`); } else if (reportTypeIndicator === 'boolean') { @@ -416,35 +466,75 @@ export class LeafletMapComponent implements OnInit, AfterViewInit, OnChanges { for (let i = 0; i < values.length; i++) { labels.push(` ${values[i]}`); } + // } else { + // values = values && values.length > 0 ? values : [100, 75, 50, 25, 0]; + // for (let i = values.length; i > 1; i--) { + // labels.push( + // ` + // ${values[values.length - i + 1] ? values[values.length - i + 1] : 0} ‐ ${values[values.length - i]}${reportTypeIndicator === 'percent' ? '%' : ''}` + // ); + // } + // } } else { - values = values && values.length > 0 ? values : [100, 75, 50, 25, 0]; + values = values && values.length > 0 ? values : [100, 75, 50, 0]; + console.log(values) + div.innerHTML = labels[0] + '
'; for (let i = values.length; i > 1; i--) { - labels.push( - ` - ${values[values.length - i + 1] ? values[values.length - i + 1] : 0} ‐ ${values[values.length - i]}${reportTypeIndicator === 'percent' ? '%' : ''}` - ); + let span = L.DomUtil.create('span', 'clickable-range'); + span.innerHTML = `
` + L.DomEvent.addListener(span, 'click', () => { + ref.applyRange(Number(values[values.length - i + 1] ? values[values.length - i + 1] : 0), Number(values[values.length - i]), Number(values[values.length - 1]), ref.getLayerColor(25 * (i), true)) + }) + div.appendChild(span) + clickable = true; } } - div.innerHTML = labels.join('
'); + // div.innerHTML = labels.join('
'); + + if (!clickable) { + div.innerHTML = labels.join('
'); + } return div; }; legend.addTo(this.map); + this.legend?.remove(); this.legend = legend; } getZoneColor(reportTypeIndicator: string, value: string | number) { if (reportTypeIndicator === 'boolean') { if (value == "Yes") { - return "#1D4586"; + return "#00FF00"; } else { - return "#FFFFFF"; + return "#FF0000"; } } else { - return value > 75 ? "#1D4586" : - value > 50 ? "#1156CC" : - value > 25 ? "#6D9FEB" : - value >= 0 ? "#C9DAF7" : "#fff"; + return value > 75 ? "#00FF00" : + value > 50 ? "#FFFF00" : + value >= 0 ? "#FF0000" : "#fff"; + } + } + + applyRange(min: any, max: any, baseValue: any, rangeColour: any): void { + let temp = this.mapData.data.filter((obj: any) => { + return obj.indicator <= max && (min === baseValue ? obj.indicator >= min : obj.indicator > min) + }) + let filteredData = { + ...this.mapData, + data: temp } + if ((environment.config === 'national' && this.level === 'district') || environment.config === 'state') { + this.markers.clearLayers(); + this.createMarkers(filteredData, rangeColour); + } + else { + console.log(filteredData) + this.applyCountryBorder(filteredData, rangeColour); + } + } + + drillDownMarker(value: any) { + this.drillDownFilter.emit(value); } } diff --git a/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.html b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.html new file mode 100644 index 00000000..1271c77e --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.scss b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.spec.ts b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.spec.ts new file mode 100644 index 00000000..3aa2cee3 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TimeSeriesFilterPanelComponent } from './time-series-filter-panel.component'; + +describe('TimeSeriesFilterPanelComponent', () => { + let component: TimeSeriesFilterPanelComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TimeSeriesFilterPanelComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TimeSeriesFilterPanelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.ts b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.ts new file mode 100644 index 00000000..4a8a48bd --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/shared/components/time-series-filter-panel/time-series-filter-panel.component.ts @@ -0,0 +1,24 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-time-series-filter-panel', + templateUrl: './time-series-filter-panel.component.html', + styleUrls: ['./time-series-filter-panel.component.scss'] +}) +export class TimeSeriesFilterPanelComponent implements OnInit { + + @Output() timeSeriesUpdated = new EventEmitter(); + @Input() minDate: any; + @Input() maxDate: any; + + constructor() { } + + selected: any; + + ngOnInit(): void { + } + + changeDate(event: any) { + this.timeSeriesUpdated.emit(this.selected) + } +} diff --git a/reference-visualization-app/dashboard/src/app/shared/directives/table-heat-map/table-heat-map.directive.ts b/reference-visualization-app/dashboard/src/app/shared/directives/table-heat-map/table-heat-map.directive.ts index 072b320d..50c55bb4 100644 --- a/reference-visualization-app/dashboard/src/app/shared/directives/table-heat-map/table-heat-map.directive.ts +++ b/reference-visualization-app/dashboard/src/app/shared/directives/table-heat-map/table-heat-map.directive.ts @@ -108,12 +108,12 @@ export class TableHeatMapDirective implements AfterViewInit { } private getColor(id: string, value: string | number) { - const color = this.config[id].color; - let [r, g, b, a] = parseToRgba(Array.isArray(color) ? color[color.length - 1] : color); + const color = this.config[id] ? this.config[id].color : '#fff'; + let [r, g, b, a] = parseToRgba(Array.isArray(color) ? color[color.length - 1] : (typeof color === 'object' && color !== null) ? color.values[color.values.length -1].color : color); if (!isNaN(Number(value))) { value = Number(value); - let color = this.config[id].color; + let color = this.config[id] ? this.config[id].color : '#fff'; let textColor = null; let bgColor = null; if (color != null) { @@ -127,14 +127,30 @@ export class TableHeatMapDirective implements AfterViewInit { } textColor = readableColor(bgColor);*/ if (Array.isArray(color)) { - if (value / this.highestValues[id] > 0.7) { + if (value / this.highestValues[id] > 0.75) { color = color[0]; - } else if (value / this.highestValues[id] > 0.45) { + } else if (value / this.highestValues[id] > 0.5) { color = color[1]; } else { color = color[2]; } } + else if(typeof color === 'object' && color !== null) { + let singleColor; + if(color.type === 'percentage'){ + color.values?.every((item) => { + if(value > item.breakPoint){ + singleColor = item.color + return false + } + return true; + }); + } + if(singleColor === undefined){ + color = '#fff' + } + color = singleColor; + } let [r, g, b, a] = parseToRgba(color); bgColor = rgba(r, g, b, +(Math.min(1, value / this.highestValues[id] + 0.06)).toFixed(3)); diff --git a/reference-visualization-app/dashboard/src/app/shared/shared.module.ts b/reference-visualization-app/dashboard/src/app/shared/shared.module.ts index 5eca52df..7bbaae73 100644 --- a/reference-visualization-app/dashboard/src/app/shared/shared.module.ts +++ b/reference-visualization-app/dashboard/src/app/shared/shared.module.ts @@ -39,6 +39,9 @@ import { NgApexchartsModule } from "ng-apexcharts"; import { ManagementSelectorComponent } from './components/core-components/management-selector/management-selector.component'; import { DownloadButtonComponent } from './components/buttons/download-button/download-button.component'; +import { TimeSeriesFilterPanelComponent } from './components/time-series-filter-panel/time-series-filter-panel.component'; +import { NgxDaterangepickerMd } from 'ngx-daterangepicker-material'; +import { BigNumberComponent } from './components/big-number/big-number.component'; const IMPORTS: any[] = [ MatTableModule, @@ -53,7 +56,8 @@ const IMPORTS: any[] = [ TooltipModule.forRoot(), NgCircleProgressModule.forRoot(), NgApexchartsModule, - TooltipModule.forRoot() + TooltipModule.forRoot(), + NgxDaterangepickerMd.forRoot() ]; const DECLARATIONS = [ @@ -81,12 +85,14 @@ const DECLARATIONS = [ BubblesComponent, ProgressCircleComponent, ManagementSelectorComponent, - DownloadButtonComponent + DownloadButtonComponent, + TimeSeriesFilterPanelComponent, + BigNumberComponent ]; @NgModule({ declarations: [ - DECLARATIONS, + DECLARATIONS ], imports: [ CommonModule, diff --git a/reference-visualization-app/dashboard/src/app/utilities/QueryBuilder.ts b/reference-visualization-app/dashboard/src/app/utilities/QueryBuilder.ts index 0e463a7c..e8ec9538 100644 --- a/reference-visualization-app/dashboard/src/app/utilities/QueryBuilder.ts +++ b/reference-visualization-app/dashboard/src/app/utilities/QueryBuilder.ts @@ -1,5 +1,7 @@ -export const buildQuery = (query: string, levels: any, filters: any = []) => { - let level = "district"; + + +export const buildQuery = (query: string, defaultLevel: any = undefined, levels: any, filters: any = [], startDate: any, endDate: any, key: any, compareDateRange: any = undefined) => { + let level = "state"; let newQuery = ""; if (filters && filters.length > 0) { @@ -10,19 +12,31 @@ export const buildQuery = (query: string, levels: any, filters: any = []) => { return filter.value }); - newQuery = getCubeNameFromSelFilter(filters); + newQuery = getCubeNameFromSelFilter(filters, levels, startDate, endDate, compareDateRange, key); + } + let selectedLevel; + if (levels && levels.length > 0) { + levels.forEach((level: any) => { + selectedLevel = level.selected ? level.value : selectedLevel + }) + } + if (selectedLevel !== undefined) { + query = parseLevelQuery(query, selectedLevel, levels) + } + else { + query = parseLevelQuery(query, defaultLevel, levels) } return newQuery !== "" ? newQuery : query; } -const getCubeNameFromSelFilter = (filters) => { +const getCubeNameFromSelFilter = (filters, levels, startDate, endDate, compareDateRange, key) => { let newQuery = ""; if (filters.length > 0) { filters.forEach(({ actions: { level, query } }, index) => { if (level && level !== '') { - newQuery = parseQuery(filters, index); + newQuery = parseQuery(filters, levels, index, startDate, endDate, compareDateRange, key); } }); } @@ -30,27 +44,114 @@ const getCubeNameFromSelFilter = (filters) => { return newQuery; } -function parseQuery(filters, index): string { +function parseQuery(filters, levels, index, startDate, endDate, compareDateRange, key): string { const filter = filters[index]; - let { query } = filter.actions; - let startIndex = query.indexOf('{'); - let endIndex = query.indexOf('}'); - + let query; + if (compareDateRange && key.toLowerCase().includes('comparison')) { + let endDate = new Date(); + let days = endDate.getDate() - compareDateRange; + let startDate = new Date(); + startDate.setDate(days) + query = parseTimeSeriesQuery(filter?.timeSeriesQueries[key], startDate.toISOString().split('T')[0], endDate.toISOString().split('T')[0]) + } + else if (startDate !== undefined && endDate !== undefined && Object.keys(filter?.timeSeriesQueries).length > 0) { + query = parseTimeSeriesQuery(filter?.timeSeriesQueries[key], startDate, endDate) + } + else { + let { queries } = filter.actions; + query = queries[key] + } + let selectedLevel; + let { level } = filter.actions; + if (levels && levels.length > 0) { + levels.forEach((level: any) => { + selectedLevel = level.selected ? level.value : selectedLevel + }) + } + if (selectedLevel !== undefined) { + query = parseLevelQuery(query, selectedLevel, levels) + } + else { + query = parseLevelQuery(query, level, levels) + } + + let startIndex = query?.indexOf('{'); + let endIndex = query?.indexOf('}'); + if (query && startIndex > -1) { - while (startIndex > -1) { - let propertyName = query.substring(startIndex + 1, endIndex); - if (filter.value) { - let re = new RegExp(`{${propertyName}}`, "g"); - query = query.replace(re, filter.value); - } else { - query = null; - break; - } + while (startIndex > -1) { + let propertyName = query.substring(startIndex + 1, endIndex); + if (filter.value) { + let re = new RegExp(`{${propertyName}}`, "g"); + query = query.replace(re, '\'' + filter.value + '\''); + } else { + query = null; + break; + } - startIndex = query.indexOf('{'); - endIndex = query.indexOf('}'); - } + startIndex = query?.indexOf('{'); + endIndex = query?.indexOf('}'); + } } return query; } + +export function parseTimeSeriesQuery(query, startDate, endDate): string { + let startIndex = query?.indexOf('{'); + if (query && startIndex > -1) { + if (startDate && endDate) { + let minDateRE = new RegExp(`{startDate}`, "g"); + let maxDateRE = new RegExp(`{endDate}`, "g"); + query = query.replace(minDateRE, '\'' + startDate + '\''); + query = query.replace(maxDateRE, '\'' + endDate + '\''); + } + else { + query = null; + } + } + return query; +} + +function parseLevelQuery(query, selectedLevel, levels): string { + let newQuery = query; + let startIndex = query?.indexOf('{'); + + if (newQuery && startIndex > -1 && levels && selectedLevel) { + if (selectedLevel) { + newQuery = addMasterProps(newQuery, selectedLevel, levels) + let level = new RegExp(`{level}`, "g"); + newQuery = newQuery.replace(level, selectedLevel); + } + else { + newQuery = null; + } + } + + return newQuery +} + +function addMasterProps(query: string, selectedLevel: any, levelConfig: any): string { + let newQuery = query; + let levelInfo = levelConfig.find((level: any) => { + return level.value == selectedLevel + }) + let { actions } = levelInfo ?? {}; + let { drilldown } = actions ?? {}; + if (drilldown && drilldown.length > 0) { + let masterPropsString = ''; + let queryArray = newQuery.split(''); + let temp: any = newQuery.match(/ingestion.dimensions as [a-zA-z0-9]+ /) ? newQuery.match(/ingestion.dimensions as [a-zA-z0-9]+ /)[0] : undefined; + let dimensionAlias = temp?.replace('ingestion.dimensions as ', '').trim(); + + drilldown.forEach((prop: any) => { + masterPropsString += (dimensionAlias + '.' + prop + ', ') + }); + queryArray.splice(7, 0, masterPropsString) + masterPropsString = masterPropsString.slice(0, -2) + queryArray.splice(newQuery.length + 1, 0, ', ' + masterPropsString ) + newQuery = queryArray.join(''); + } + return newQuery +} + diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/attendance.component.html b/reference-visualization-app/dashboard/src/app/views/attendance/attendance.component.html index 99d89db9..576ce39f 100644 --- a/reference-visualization-app/dashboard/src/app/views/attendance/attendance.component.html +++ b/reference-visualization-app/dashboard/src/app/views/attendance/attendance.component.html @@ -15,8 +15,8 @@ - - + + @@ -24,5 +24,11 @@ + + + + + +
diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/attendance.module.ts b/reference-visualization-app/dashboard/src/app/views/attendance/attendance.module.ts index bb6015de..ce0effe9 100644 --- a/reference-visualization-app/dashboard/src/app/views/attendance/attendance.module.ts +++ b/reference-visualization-app/dashboard/src/app/views/attendance/attendance.module.ts @@ -13,6 +13,10 @@ import { TeacherAttendanceComponent } from './pages/teacher-attendance/teacher-a import { SatTrendsReportComponent } from './pages/sat-trends-report/sat-trends-report.component'; import { SharedModule } from 'src/app/shared/shared.module'; import { StudentAttendanceNewComponent } from './pages/student-attendance-new/student-attendance-new.component'; +import { StudentAttendanceMapComponent } from './pages/student-attendance-map/student-attendance-map.component'; +import { StudentAttendanceBarComponent } from './pages/student-attendance-bar/student-attendance-bar.component'; +import { DashletModule} from '@project-sunbird/sb-dashlet-v14'; +import { DataService } from 'src/app/core/services/data.service'; @NgModule({ @@ -21,9 +25,14 @@ import { StudentAttendanceNewComponent } from './pages/student-attendance-new/st StudentAttendanceComponent, TeacherAttendanceComponent, SatTrendsReportComponent, - StudentAttendanceNewComponent + StudentAttendanceNewComponent, + StudentAttendanceMapComponent, + StudentAttendanceBarComponent ], imports: [ + DashletModule.forRoot({ + dataService: DataService + }), SharedModule, MatTabsModule, FormsModule, diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/config/attendance_config.ts b/reference-visualization-app/dashboard/src/app/views/attendance/config/attendance_config.ts new file mode 100644 index 00000000..e7e510ad --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/config/attendance_config.ts @@ -0,0 +1,400 @@ +export const config = { + student_attendance_complaince: { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id group by t1.state_id, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where date between {startDate} and {endDate}" + }, + "timeSeriesQueries": { + "table": "select state_name, avg(percentage) as percentage from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id where date between {startDate} and {endDate} group by t1.state_id, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where date between {startDate} and {endDate}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where date between {startDate} and {endDate}" + }, + "defaultLevel": "state", + "filters": [ + { + "name": "State", + "labelProp": "state_name", + "valueProp": "state_id", + "query": "select t1.state_id, state_name from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id group by t1.state_id,state_name", + "timeSeriesQueries": { + "table": "select district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where (date between {startDate} and {endDate}) and state_id={state_id} group by t1.district_id,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where state_id = {state_id} and date between {startDate} and {endDate}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where state_id = {state_id} and date between {startDate} and {endDate}" + }, + "actions": { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where state_id={state_id} group by t1.district_id,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where state_id = {state_id}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_state where state_id = {state_id} and date between {startDate} and {endDate}" + }, + "level": "district" + } + }, + { + "name": "District", + "labelProp": "district_name", + "valueProp": "district_id", + "timeSeriesQueries": { + "table": "select block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where (date between {startDate} and {endDate}) and district_id={district_id} group by t1.block_id,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_district where district_id = {district_id} and date between {startDate} and {endDate}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_district where district_id = {district_id} and date between {startDate} and {endDate}" + }, + "query": "select t1.district_id, district_name from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where state_id={state_id} group by t1.district_id,district_name", + "actions": { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where district_id={district_id} group by t1.block_id,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_district where district_id = {district_id}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_district where district_id = {district_id} and date between {startDate} and {endDate}" + }, + "level": "block" + } + }, + { + "name": "Block", + "labelProp": "block_name", + "valueProp": "block_id", + "timeSeriesQueries": { + "table": "select cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_cluster as t1 left join ingestion.student_attendance as t2 on t1.cluster_id = t2.cluster_id where (date between {startDate} and {endDate}) and block_id={block_id} group by t1.cluster_id,cluster_name,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_block where block_id = {block_id} and date between {startDate} and {endDate}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_block where block_id = {block_id} and date between {startDate} and {endDate}" + }, + "query": "select t1.block_id, block_name from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where district_id={district_id} group by t1.block_id,block_name", + "actions": { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_cluster as t1 left join ingestion.student_attendance as t2 on t1.cluster_id = t2.cluster_id where block_id={block_id} group by t1.cluster_id,cluster_name,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_block where block_id = {block_id}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_block where block_id = {block_id} and date between {startDate} and {endDate}" + }, + "level": "cluster" + } + }, + { + "name": "Cluster", + "labelProp": "cluster_name", + "valueProp": "cluster_id", + "timeSeriesQueries": { + "table": "select school_name, cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_school as t1 left join ingestion.student_attendance as t2 on t1.school_id = t2.school_id where (date between {startDate} and {endDate}) and cluster_id={cluster_id} group by t1.school_id,school_name,cluster_name,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_cluster where cluster_id = {cluster_id} and date between {startDate} and {endDate}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_cluster where cluster_id = {cluster_id} and date between {startDate} and {endDate}", + }, + "query": "select t1.cluster_id, cluster_name from ingestion.student_attendance_by_cluster as t1 left join ingestion.student_attendance as t2 on t1.cluster_id = t2.cluster_id where block_id={block_id} group by t1.cluster_id,cluster_name", + "actions": { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, school_name, cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_school as t1 left join ingestion.student_attendance as t2 on t1.school_id = t2.school_id where cluster_id={cluster_id} group by t1.school_id,school_name,cluster_name,block_name,district_name, state_name", + "bigNumber": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_cluster where cluster_id = {cluster_id}", + "bigNumberComparison": "select round(avg((sum/count)* 100.00),2) as percentage from ingestion.student_attendance_marked_above_50_percent_by_cluster where cluster_id = {cluster_id} and date between {startDate} and {endDate}", + }, + "level": "school" + } + }, + { + "name": "School", + "labelProp": "school_name", + "valueProp": "school_id", + "timeSeriesQueries": { + "table": "select grade, school_name, cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_class as t1 left join ingestion.student_attendance as t2 on t1.school_id = t2.school_id where (date between {startDate} and {endDate}) and t1.school_id={school_id} group by grade,school_name,cluster_name,block_name,district_name, state_name", + }, + "query": "select t1.school_id, school_name from ingestion.student_attendance_by_school as t1 left join ingestion.student_attendance as t2 on t1.school_id = t2.school_id where cluster_id={cluster_id} group by t1.school_id,school_name", + "actions": { + "queries": { + "table": "select min(date) as min_date, max(date) as max_date, grade, school_name, cluster_name, block_name, district_name, state_name, avg(percentage) as percentage from ingestion.student_attendance_by_class as t1 left join ingestion.student_attendance as t2 on t1.school_id = t2.school_id where t1.school_id={school_id} group by grade,school_name,cluster_name,block_name,district_name, state_name" + }, + "level": "grade" + } + } + ], + "options": { + "table": { + "columns": [ + { + name: "State", + property: "state_name", + sticky: true, + class: "text-center" + }, + { + name: "District", + property: "district_name", + sticky: true, + class: "text-center" + }, + { + name: "Block", + property: "block_name", + sticky: true, + class: "text-center" + }, + { + name: "Cluster", + property: "cluster_name", + sticky: true, + class: "text-center" + }, + { + name: "School", + property: "school_name", + sticky: true, + class: "text-center" + }, + { + name: "Grade", + property: "grade", + sticky: true, + class: "text-center" + }, + { + name: "Student Attendance Complaince", + property: "percentage", + sticky: true, + class: "text-center", + isHeatMapRequired: true, + color: { + type: "percentage", + values: [ + { + color: "#00FF00", + breakPoint: 56 + }, + { + color: "#FFFF00", + breakPoint: 50 + }, + { + color: "#FF0000", + breakPoint: 0 + } + ] + }, + } + ], + "sortByProperty": "state_name", + "sortDirection": "desc" + }, + "bigNumber": { + "valueSuffix": '%' + } + } + }, + student_attendance_map: { + "queries": { + "map": "select min(date) as min_date, max(date) as max_date, {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_{level} as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id group by t1.{level}_id, {level}_name, {level}_lat, {level}_long" + }, + "timeSeriesQueries": { + "map": "select {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_{level} as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id where date between {startDate} and {endDate} group by t1.{level}_id, {level}_name, {level}_lat, {level}_long", + }, + "defaultLevel": "district", + "filters": [ + { + "name": "District", + "hierarchyLevel": "2", + "labelProp": "district_name", + "valueProp": "district_id", + "query": "select district_name, district_id from ingestion.dimensions group by district_id, district_name", + "timeSeriesQueries": { + "map": "select {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_{level} as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id where district_id = {district_id} and date between {startDate} and {endDate} group by t1.{level}_id, {level}_name, {level}_lat, {level}_long", + }, + "actions": { + "queries": { + "map": "select min(date) as min_date, max(date) as max_date, {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_{level} as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id where district_id = {district_id} group by t1.{level}_id, {level}_name, {level}_lat, {level}_long" + }, + "level": "block" + } + }, + { + "name": "Block", + "hierarchyLevel": "3", + "labelProp": "block_name", + "valueProp": "block_id", + "query": "select block_name, block_id from ingestion.dimensions where district_id = {district_id} group by block_id, block_name", + "timeSeriesQueries": { + "map": "select {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_cluster as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id where block_id = {block_id} and date between {startDate} and {endDate} group by t1.{level}_id, {level}_name, {level}_lat, {level}_long", + }, + "actions": { + "queries": { + "map": "select min(date) as min_date, max(date) as max_date, {level}_name, t1.{level}_id, avg(percentage) as percentage, avg(count) as count, avg(sum) as sum, {level}_lat as latitude, {level}_long as longitude from ingestion.student_attendance_by_{level} as t1 left join ingestion.dimensions as t2 on t1.{level}_id = t2.{level}_id where block_id = {block_id} group by t1.{level}_id, {level}_name, {level}_lat, {level}_long" + }, + "level": "cluster" + } + }, + { + "name": "Cluster", + "hierarchyLevel": "4", + "labelProp": "cluster_name", + "valueProp": "cluster_id", + "query": "select cluster_name, cluster_id from ingestion.dimensions where block_id = {block_id} group by cluster_id, cluster_name", + "timeSeriesQueries": { + }, + "actions": { + "queries": { + }, + "level": "school" + } + } + ], + "levels": [ + { + "hierarchyLevel": "2", + "name": "District", + "value": "district", + "actions": { + "drilldown": [ + ] + } + }, + { + "hierarchyLevel": "3", + "name": "Blocks", + "value": "block", + "actions": { + "drilldown": [ + "district_name", "district_id" + ] + } + }, + { + "hierarchyLevel": "4", + "name": "Clusters", + "value": "cluster", + "actions": { + "drilldown": [ + "district_name", "district_id", "block_name", "block_id" + ] + } + } + ], + "options": { + "chart": { + "type": "map", + "title": "Student Attendance" + }, + "map": { + "metricFilterNeeded": true, + "metrics": [ + { + "label": "Average Percentage", + "value": "percentage" + }, + { + "label": "Sum Of Students", + "value": "sum" + }, + { + "label": "Count of Schools", + "value": "count" + } + ], + "indicator": "percentage", + "indicatorType": "percent", + "legend": { + "title": "Student Attendance" + }, + "tooltipMetrics": [ + { + "valuePrefix": "Average Percentage is ", + "value": "percentage", + "valueSuffix": "\n" + }, + { + "valuePrefix": "Total Schools are ", + "value": "count", + "valueSuffix": "\n" + }, + { + "valuePrefix": "District: ", + "value": "district_name", + "valueSuffix": "\n" + }, + { + "valuePrefix": "Block: ", + "value": "block_name", + "valueSuffix": "\n" + }, + { + "valuePrefix": "Cluster: ", + "value": "cluster_name", + "valueSuffix": "\n" + } + ] + } + } + }, + student_attendance_bar: { + "queries": { + "barChart": "select min(date) as min_date, max(date) as max_date, state_name as location, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id group by t1.state_id, state_name", + }, + "timeSeriesQueries": { + "barChart": "select state_name as location, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id where date between {startDate} and {endDate} group by t1.state_id, state_name", + }, + "defaultLevel": "state", + "filters": [ + { + "name": "State", + "labelProp": "state_name", + "valueProp": "state_id", + "query": "select t1.state_id, state_name from ingestion.student_attendance_by_state as t1 left join ingestion.student_attendance as t2 on t1.state_id = t2.state_id group by t1.state_id,state_name", + "timeSeriesQueries": { + "barChart": "select district_name as location, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where (date between {startDate} and {endDate}) and state_id={state_id} group by t1.district_id,district_name, state_name", + }, + "actions": { + "queries": { + "barChart": "select min(date) as min_date, max(date) as max_date, district_name as location, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where state_id={state_id} group by t1.district_id,district_name, state_name", + }, + "level": "district" + } + }, + { + "name": "District", + "labelProp": "district_name", + "valueProp": "district_id", + "timeSeriesQueries": { + "barChart": "select block_name as location, district_name, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where (date between {startDate} and {endDate}) and district_id={district_id} group by t1.block_id,block_name,district_name, state_name", + }, + "query": "select t1.district_id, district_name from ingestion.student_attendance_by_district as t1 left join ingestion.student_attendance as t2 on t1.district_id = t2.district_id where state_id={state_id} group by t1.district_id,district_name", + "actions": { + "queries": { + "barChart": "select min(date) as min_date, max(date) as max_date, block_name as location, district_name, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where district_id={district_id} group by t1.block_id,block_name,district_name, state_name", + }, + "level": "block" + } + }, + { + "name": "Block", + "labelProp": "block_name", + "valueProp": "block_id", + "timeSeriesQueries": { + "barChart": "select cluster_name as location, block_name, district_name, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_cluster as t1 left join ingestion.student_attendance as t2 on t1.cluster_id = t2.cluster_id where (date between {startDate} and {endDate}) and block_id={block_id} group by t1.cluster_id,cluster_name,block_name,district_name, state_name", + }, + "query": "select t1.block_id, block_name from ingestion.student_attendance_by_block as t1 left join ingestion.student_attendance as t2 on t1.block_id = t2.block_id where district_id={district_id} group by t1.block_id,block_name", + "actions": { + "queries": { + "barChart": "select min(date) as min_date, max(date) as max_date, cluster_name as location, block_name, district_name, state_name, avg(percentage) as percentage, min(count) as count from ingestion.student_attendance_by_cluster as t1 left join ingestion.student_attendance as t2 on t1.cluster_id = t2.cluster_id where block_id={block_id} group by t1.cluster_id,cluster_name,block_name,district_name, state_name", + }, + "level": "cluster" + } + } + ], + "options": { + "barChart": { + "yAxis": { + "title": "level", + "label": "location", + "value": "location" + }, + "xAxis": { + "title": "Number", + "metrics": [ + { + "label": "Average Percentage", + "value": "percentage" + }, + { + "label": "Count of Students", + "value": "count" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.html b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.html new file mode 100644 index 00000000..6352f965 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.html @@ -0,0 +1,19 @@ +
+
+
+ +
+
+
+ + +
+
+ +
+
+ + + +
+
\ No newline at end of file diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.scss b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.spec.ts b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.spec.ts new file mode 100644 index 00000000..096094cc --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StudentAttendanceBarComponent } from './student-attendance-bar.component'; + +describe('StudentAttendanceBarComponent', () => { + let component: StudentAttendanceBarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StudentAttendanceBarComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StudentAttendanceBarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.ts b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.ts new file mode 100644 index 00000000..a6126a64 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-bar/student-attendance-bar.component.ts @@ -0,0 +1,140 @@ +import { Component, OnInit } from '@angular/core'; +import { Axis } from 'highcharts'; +import { getBarDatasetConfig, getChartJSConfig } from 'src/app/core/config/ChartjsConfig'; +import { CommonService } from 'src/app/core/services/common/common.service'; +import { WrapperService } from 'src/app/core/services/wrapper.service'; +import { formatNumberForReport } from 'src/app/utilities/NumberFomatter'; +import { buildQuery, parseTimeSeriesQuery } from 'src/app/utilities/QueryBuilder'; +import { environment } from 'src/environments/environment'; +import { config } from '../../config/attendance_config'; + +@Component({ + selector: 'app-student-attendance-bar', + templateUrl: './student-attendance-bar.component.html', + styleUrls: ['./student-attendance-bar.component.scss'] +}) +export class StudentAttendanceBarComponent implements OnInit { + + title: any; + chartHeight: any; + marginTop: any; + config; + data; + fileName: string = "Student_Attendance_Bar"; + reportName: string = 'student_attendance_bar'; + filters: any = []; + levels: any; + tableReportData: any; + startDate: any; + endDate: any; + minDate: any; + maxDate: any; + level = environment.config === 'national' ? 'state' : 'district'; + filterIndex: any; + + constructor(private readonly _commonService: CommonService, private readonly _wrapperService: WrapperService) { } + + ngOnInit(): void { + this.getReportData(); + } + + async getReportData(): Promise { + let reportConfig = config + + let { timeSeriesQueries, queries, levels, defaultLevel, filters, options } = reportConfig[this.reportName]; + let onLoadQuery; + + this._wrapperService.constructFilters(this.filters, filters); + + Object.keys(queries).forEach((key: any) => { + if (this.startDate !== undefined && this.endDate !== undefined && Object.keys(timeSeriesQueries).length > 0) { + onLoadQuery = parseTimeSeriesQuery(timeSeriesQueries[key], this.startDate, this.endDate) + } + else { + onLoadQuery = queries[key] + } + let query = buildQuery(onLoadQuery, defaultLevel, this.levels, this.filters, this.startDate, this.endDate, key); + + if (query && key === 'barChart') { + this.getBarChartReportData(query, options); + } + + + }) + } + + getBarChartReportData(query, options): void { + this._commonService.getReportDataNew(query).subscribe((res: any) => { + let { rows } = res; + let { barChart: { yAxis, xAxis } } = options; + rows.forEach(row => { + if (this.minDate !== undefined && this.maxDate !== undefined) { + if (row['min_date'] < this.minDate) { + this.minDate = row['min_date'] + } + if (row['max_date'] > this.maxDate) { + this.maxDate = row['max_date'] + } + } + else { + this.minDate = row['min_date'] + this.maxDate = row['max_date'] + } + }); + this.tableReportData = { + values: rows + } + this.config = getChartJSConfig({ + labelExpr: yAxis.value, + datasets: getBarDatasetConfig(xAxis?.metrics?.map((metric: any) => { + return { + dataExpr: metric.value, label: metric.label + } + })), + options: { + height: (rows.length * 15 + 150).toString(), + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + let multistringText = []; + if (tooltipItem.datasetIndex === 0) { + xAxis.metrics.forEach((metric: any) => { + multistringText.push(`${metric.label}: ${formatNumberForReport(rows[tooltipItem.index][metric.value])}`); + }); + } + + return multistringText; + } + } + }, + scales: { + yAxes: [{ + scaleLabel: { + display: true, + labelString: yAxis.title + } + }], + xAxes: [{ + scaleLabel: { + display: true, + labelString: xAxis.title + } + }] + } + } + }); + }); + } + + filtersUpdated(filters: any): void { + this.filters = filters; + this.getReportData(); + } + + timeSeriesUpdated(event: any): void { + this.startDate = event?.startDate?.toDate().toISOString().split('T')[0] + this.endDate = event?.endDate?.toDate().toISOString().split('T')[0] + this.getReportData(); + } + +} diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.html b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.html new file mode 100644 index 00000000..2eadce9b --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.html @@ -0,0 +1,21 @@ +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.scss b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.spec.ts b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.spec.ts new file mode 100644 index 00000000..e8cbb761 --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StudentAttendanceMapComponent } from './student-attendance-map.component'; + +describe('StudentAttendanceMapComponent', () => { + let component: StudentAttendanceMapComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StudentAttendanceMapComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StudentAttendanceMapComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.ts b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.ts new file mode 100644 index 00000000..7c70c32a --- /dev/null +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-map/student-attendance-map.component.ts @@ -0,0 +1,195 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonService } from 'src/app/core/services/common/common.service'; +import { WrapperService } from 'src/app/core/services/wrapper.service'; +import { buildQuery, parseTimeSeriesQuery } from 'src/app/utilities/QueryBuilder'; +import { environment } from 'src/environments/environment'; +import { config } from '../../config/attendance_config'; + +@Component({ + selector: 'app-student-attendance-map', + templateUrl: './student-attendance-map.component.html', + styleUrls: ['./student-attendance-map.component.scss'] +}) +export class StudentAttendanceMapComponent implements OnInit { + + reportName: string = 'student_attendance_map'; + filters: any = []; + metricFilter: any = {}; + levels: any; + reportData: any; + startDate: any; + endDate: any; + minDate: any; + maxDate: any; + level = environment.config === 'national' ? 'state' : 'district'; + currentHierarchyLevel: any = this.level === 'state' ? 1 : 2; + + constructor(private readonly _commonService: CommonService, private readonly _wrapperService: WrapperService) { } + + ngOnInit(): void { + this.getReportData(); + } + + async getReportData(): Promise { + let reportConfig = config + + let { timeSeriesQueries, queries, levels, filters, options, defaultLevel } = reportConfig[this.reportName]; + if (this.levels === undefined && levels?.length > 0) { + levels[0].selected = true; + this.levels = levels; + this.updateLevels(true); + } + let onLoadQuery; + + this.filters = await this._wrapperService.constructFilters(this.filters, filters); + + Object.keys(queries).forEach((key: any) => { + if (this.startDate !== undefined && this.endDate !== undefined && Object.keys(timeSeriesQueries).length > 0) { + onLoadQuery = parseTimeSeriesQuery(timeSeriesQueries[key], this.startDate, this.endDate) + } + else { + onLoadQuery = queries[key] + } + let query = buildQuery(onLoadQuery, defaultLevel, this.levels, this.filters, this.startDate, this.endDate, key); + + if (key === 'map') { + this.getMapReportData(query, options); + } + }) + } + + getMapReportData(query: any, options: any): void { + this._commonService.getReportDataNew(query).subscribe((res: any) => { + let { rows } = res; + let { map: { indicator, indicatorType, legend, metrics, metricFilterNeeded, tooltipMetrics } } = options ?? {}; + let selectedMetric = { + name: undefined, + value: undefined + } + if (metricFilterNeeded && metrics && Object.keys(this.metricFilter).length === 0) { + this.metricFilter = { + name: "Metrics to be shown", + options: metrics, + value: null + }; + + + } + + if (this.metricFilter.value !== null) { + let results = metrics.filter((metric: any) => { + return this.metricFilter.value == metric.value + }) + selectedMetric = results.length > 0 ? results[0] : selectedMetric + } + + if (metrics && indicator && Object.keys(this.metricFilter).length > 0 && this.metricFilter.value === null) { + let results = metrics.filter((metric: any) => { + return indicator == metric.value + }) + selectedMetric = results.length > 0 ? results[0] : selectedMetric + this.metricFilter.value = indicator; + } + + this.reportData = { + data: rows.map(row => { + if (this.minDate !== undefined && this.maxDate !== undefined) { + if (row['min_date'] < this.minDate) { + this.minDate = row['min_date'] + } + if (row['max_date'] > this.maxDate) { + this.maxDate = row['max_date'] + } + } + else { + this.minDate = row['min_date'] + this.maxDate = row['max_date'] + } + row = { + ...row, + Latitude: row['latitude'], + Longitude: row['longitude'], + indicator: Number(row[selectedMetric.value]), + tooltip: this._wrapperService.constructTooltip(tooltipMetrics, row, selectedMetric.value) + }; + + return row; + }), + options: { + reportIndicatorType: indicatorType, + legend, + selectedMetric: selectedMetric.name + } + } + }); + } + + async filtersUpdated(filters: any): Promise { + await new Promise(r => setTimeout(r, 100)); + this.filters = filters + setTimeout(() => { + let tempLevel = this.level === 'state' ? 1 : 2; + this.filters.forEach((filter: any) => { + tempLevel = filter.hierarchyLevel > tempLevel ? filter.hierarchyLevel : tempLevel; + }) + this.currentHierarchyLevel = tempLevel; + this.updateLevels(false); + }, 100); + this.getReportData(); + } + + onSelectMetricFilter(metricFilter: any): void { + this.metricFilter = metricFilter + this.getReportData(); + } + + onSelectLevel(levels: any): void { + this.levels = levels.items; + this.getReportData(); + } + + timeSeriesUpdated(event: any): void { + this.startDate = event?.startDate?.toDate().toISOString().split('T')[0] + this.endDate = event?.endDate?.toDate().toISOString().split('T')[0] + this.getReportData(); + } + + updateLevels(init: boolean): void { + let flag = 1; + this.levels = this.levels.map((level: any) => { + if (level.hierarchyLevel >= this.currentHierarchyLevel) { + level.hidden = false; + if (flag) { + level.selected = true; + flag = 0; + } + else { + level.selected = false; + } + } + else { + level.selected = false; + level.hidden = true; + } + return level; + }); + if (!init) this.getReportData(); + } + + + async drillDownFilterUpdate(value: any) { + for (let i = 0; i < Object.keys(value).length; i++) { + let id = Object.keys(value)[i] + await new Promise(r => setTimeout(r, 500)); + let filters = [...this.filters]; + filters.map(async (filter: any) => { + if (filter.valueProp === id) { + filter.value = value[id] + } + return filter + }); + this.filtersUpdated(filters) + } + } + +} diff --git a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-new/student-attendance-new.component.html b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-new/student-attendance-new.component.html index c415db43..8a2aff86 100644 --- a/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-new/student-attendance-new.component.html +++ b/reference-visualization-app/dashboard/src/app/views/attendance/pages/student-attendance-new/student-attendance-new.component.html @@ -5,15 +5,22 @@
-->
- + +
- +
+
+ +
-
+ +
+