diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake 2.zip b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake 2.zip new file mode 100644 index 0000000..d030c4b Binary files /dev/null and b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake 2.zip differ diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake.zip b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake.zip new file mode 100644 index 0000000..e9ed721 Binary files /dev/null and b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake.zip differ diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.html new file mode 100644 index 0000000..b46cb22 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.html @@ -0,0 +1,542 @@ + +
+
+
+

Data lake

+
+
+ + + +
+
+ + + + Loading ... + +
{{error}}
+
+ + Name + + + + Url + + + + schedule + + + + cron job + + + + json + + + + Url Endpoint + + + Batch Volume + + + SureConnect + + + + + Action + + + + + + {{user.name }} + + + {{user.url}} + + + {{user.schedule }} + + + {{user.cron_job }} + + + + + + +
+ + {{user.url | slice:0:30}}{{user.url.length > 30 ? '...' : ''}} + + +
+
+ {{user.batch_volume}} + + {{getSureConnectNameById(user.sure_connect_id)}} + + + + + + +
Who Column
+
Account ID: {{user.accountId}}
+
Created At: {{user.createdAt| date}}
+
Created By: {{user.createdBy}}
+
Updated At: {{user.updatedAt | date}}
+
Updated By: {{user.updatedBy}}
+
+
+ + + +
+ + + + + + + + + +
+ + + Users per page + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} + of {{pagination.totalItems}} users + + +
+ + + +
+
+
+
+
+
+ +
+
+ {{beforeText(item.fieldtext)}} + {{ app[transform(item.fieldtext) ] }} + {{afterText(item.fieldtext)}} +
+ +
+ {{beforeText(item.fieldtext)}} + {{ app[transform(item.fieldtext) ] | date}} + {{afterText(item.fieldtext)}} +
+
+ {{beforeText(item.fieldtext)}} + {{ app[transform(item.fieldtext) ]}} + {{afterText(item.fieldtext)}} +
+ +
+
+
+ + +
+ +
+ +
+ File Preview
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.scss new file mode 100644 index 0000000..12f7fc8 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.scss @@ -0,0 +1,85 @@ +//@import "../../../../assets/scss/var"; +.s-info-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + button { + outline: none; + } +} +.delete,.heading{ + text-align: center; + color: red; +} +.entry-pg { + width: 750px; +} + +.button1::after { + content: none; +} +.button1:hover::after { + content: "ADD ROWS"; +} + +.section { + background-color: #dddddd; + height: 40px; +} + +.section p { + //color: white; + padding: 10px; + font-size: 18px; +} + +.clr-input { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 0.75rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.clr-file { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + //padding: 0.6rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.center { + text-align: center; +} +select{ + width: 100%; + margin-top: 3px; + padding: 5px 5px; + border: 1px solid #ccc; + border-radius: 4px; +} +input[type=text],[type=date],[type=number],textarea { + width: 100%; + padding: 15px 15px; + background-color:rgb(255, 255, 255); + // margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} +.error_mess { + color: red; +} +.universal-section-header { + margin: 24px 0 10px 0; + font-weight: 600; + color: #1a237e; + letter-spacing: 0.5px; + font-size: 1.25rem; +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.ts new file mode 100644 index 0000000..f1a3ddf --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.component.ts @@ -0,0 +1,437 @@ +import { Component, OnInit } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { AlertService } from 'src/app/services/alert.service'; +import { Data_lakeservice } from './Data_lake.service'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators, ValidationErrors } from '@angular/forms'; +import { ExtensionService } from 'src/app/services/fnd/extension.service'; +import { DashboardContentModel2 } from 'src/app/models/builder/dashboard'; +import { Data_lakecardvariable } from './Data_lake_cardvariable'; +import { UserInfoService } from 'src/app/services/user-info.service'; +import { SureconnectService } from '../sureconnect/sureconnect.service'; +declare var JsBarcode: any; +@Component({ + selector: 'app-Data_lake', + templateUrl: './Data_lake.component.html', + styleUrls: ['./Data_lake.component.scss'] +}) +export class Data_lakeComponent implements OnInit { + cardButton = Data_lakecardvariable.cardButton; + cardmodeldata = Data_lakecardvariable.cardmodeldata; + public dashboardArray: DashboardContentModel2[]; + isCardview = Data_lakecardvariable.cardButton; + cardmodal; changeView() { + this.isCardview = !this.isCardview; + } + beforeText(fieldtext: string): string { // Extract the text before the first '<' + const index = fieldtext.indexOf('<'); + return index !== -1 ? fieldtext.substring(0, index) : fieldtext; + } + afterText(fieldtext: string): string { // Extract the text after the last '>' + const index = fieldtext.lastIndexOf('>'); + return index !== -1 ? fieldtext.substring(index + 1) : ''; + } + transform(fieldtext: string): string { + const match = fieldtext.match(/<([^>]*)>/); + return match ? match[1] : ''; // Extract the text between '<' and '>' + } + userrole; + rowSelected: any = {}; + modaldelete = false; + modalEdit = false; + modalAdd = false; + public entryForm: FormGroup; + loading = false; + product; + modalOpenedforNewLine = false; + newLine: any; + additionalFieldsFromBackend: any[] = []; + formcode = 'Data_lake_formCode' + tableName = 'Data_lake'; checkFormCode; selected: any[] = []; + + // New properties for URL handling + urlModalOpen = false; + selectedUrl = ''; + + // Cron job builder properties + addCronExpression = ''; + editCronExpression = ''; + + // SureConnect properties + sureConnectList; + selectedSureConnect: any = null; + + constructor( + private extensionService: ExtensionService, + private userInfoService: UserInfoService, + private mainService: Data_lakeservice, + private alertService: AlertService, + private toastr: ToastrService, + private _fb: FormBuilder, + private sureConnectService: SureconnectService + ) { } + private editInterval: any; + // component button + ngOnInit(): void { + if (this.cardmodeldata !== '') { + this.cardmodal = JSON.parse(this.cardmodeldata); + this.dashboardArray = this.cardmodal.dashboard.slice(); + console.log(this.dashboardArray) + } + this.userrole = this.userInfoService.getRoles(); + this.getData(); + this.getSureConnectList(); // Fetch SureConnect list + this.entryForm = this._fb.group({ + name: [null], + + url: [null], + + schedule: [null], + + cron_job: [null], + + json: [null], + + url_endpoint: [null], + + batch_volume: [null], + + sure_connect_id: [null], // Add SureConnect field + + }); // component_button200 + // form code start + this.extensionService.getJsonObjectsByFormCodeList(this.formcode).subscribe(data => { + console.log(data); + const jsonArray = data.map((str) => JSON.parse(str)); + this.additionalFieldsFromBackend = jsonArray; + this.checkFormCode = this.additionalFieldsFromBackend.some(field => field.formCode === "Data_lake_formCode"); + console.log(this.checkFormCode); + console.log(this.additionalFieldsFromBackend); + if (this.additionalFieldsFromBackend && this.additionalFieldsFromBackend.length > 0) { + this.additionalFieldsFromBackend.forEach(field => { + if (field.formCode === this.formcode) { + if (!this.entryForm.contains(field.extValue)) { + // Add the control only if it doesn't exist in the form + this.entryForm.addControl(field.extValue, this._fb.control(field.fieldValue)); + } + } + }); + } + }); + console.log(this.entryForm.value); + // form code end + + + + + + + + + + + } + ngOnDestroy(): void { + if (this.editInterval) { + clearInterval(this.editInterval); + } + } + + + + + + + + + + + + + error; + getData() { + this.mainService.getAll().subscribe((data) => { + console.log(data); + this.product = data; + this.product = [...this.product].reverse(); if (this.product.length == 0) { + this.error = "No Data Available" + } + }, (error) => { + console.log(error); + if (error) { + this.error = "Server Error"; + } + }); + } + + // Fetch SureConnect list + getSureConnectList() { + this.sureConnectService.getAll().subscribe((data) => { + this.sureConnectList = data; + console.log('SureConnect List:', this.sureConnectList); + }, (error) => { + console.log('Error fetching SureConnect list:', error); + }); + } + + // Get SureConnect name by ID for display in grid + getSureConnectNameById(id: number): string { + if (!id || !this.sureConnectList || this.sureConnectList.length === 0) { + return ''; + } + + const sureConnect = this.sureConnectList.find(item => item.id === id); + return sureConnect ? sureConnect.name : ''; + } + + onEdit(row) { + this.rowSelected = row; + this.editCronExpression = row.cron_job || ''; + // Set the selected SureConnect for edit form + this.selectedSureConnect = row.sure_connect_id || null; + // Use setTimeout to ensure the component has time to initialize + setTimeout(() => { + this.modalEdit = true; + }, 0); + } + onDelete(row) { + this.rowSelected = row; + this.modaldelete = true; + } + delete(id) { + this.modaldelete = false; + console.log("in delete " + id); + this.mainService.delete(id).subscribe( + (data) => { + console.log(data); + this.ngOnInit(); + if (data) { this.toastr.success('Deleted successfully'); } + }); + } + onUpdate(id) { + this.modalEdit = false; + + + + + + + + + + + + + //console.log("in update"); + console.log("id " + id); + console.log(this.rowSelected); + //console.log("out update"); + this.mainService.update(id, this.rowSelected).subscribe( + (data) => { + console.log(data); + if (data || data.status >= 200 && data.status <= 299) { + this.toastr.success("Update Successfully"); + } + setTimeout(() => { + this.ngOnInit(); + }, 500); + + + + + + + + + + + + + }, (error) => { + console.log(error); + if (error.status >= 200 && error.status <= 299) { + // this.toastr.success("update Succesfully"); + } + if (error.status >= 400 && error.status <= 499) { + this.toastr.error("Not Updated"); + } + if (error.status >= 500 && error.status <= 599) { + this.toastr.error("Not Updated"); + } + }); + setTimeout(() => { + this.ngOnInit(); + }, 500); + } + + // New method for updating JSON + updateJson(id: number) { + this.mainService.updateJson(id).subscribe( + (data) => { + console.log(data); + if (data || data.status >= 200 && data.status <= 299) { + this.toastr.success("JSON Updated Successfully"); + } + setTimeout(() => { + this.ngOnInit(); + }, 500); + }, (error) => { + console.log(error); + if (error.status >= 200 && error.status <= 299) { + this.toastr.success("JSON Updated Successfully"); + } + if (error.status >= 400 && error.status <= 499) { + this.toastr.error("JSON Not Updated"); + } + if (error.status >= 500 && error.status <= 599) { + this.toastr.error("JSON Not Updated"); + } + }); + } + + // New method to show full URL in a modal + showFullUrl(url: string) { + this.selectedUrl = url; + this.urlModalOpen = true; + } + + // New method to copy text to clipboard + copyToClipboard(text: string) { + const el = document.createElement('textarea'); + el.value = text; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + this.toastr.success('Copied to clipboard'); + } + + // Handle cron expression change for add form + onAddCronExpressionChange(expression: string) { + this.addCronExpression = expression; + // Update the form control + if (this.entryForm && this.entryForm.get('cron_job')) { + this.entryForm.get('cron_job')?.setValue(expression); + } + } + + // Handle cron expression change for edit form + onEditCronExpressionChange(expression: string) { + this.editCronExpression = expression; + // Update the rowSelected + if (this.rowSelected) { + this.rowSelected.cron_job = expression; + } + } + + // Handle SureConnect selection change in add form + onAddSureConnectChange(event: any) { + const selectedId = event.target.value; + if (this.entryForm && this.entryForm.get('sure_connect_id')) { + this.entryForm.get('sure_connect_id')?.setValue(selectedId); + } + } + + // Handle SureConnect selection change in edit form + onEditSureConnectChange(event: any) { + const selectedId = event.target.value; + if (this.rowSelected) { + this.rowSelected.sure_connect_id = selectedId; + } + } + + onCreate() { + this.modalAdd = false; + + + + + + + + + + + + + this.mainService.create(this.entryForm.value).subscribe( + (data) => { + console.log(data); + if (data || data.status >= 200 && data.status <= 299) { + this.toastr.success("Added Successfully"); + } + setTimeout(() => { + this.ngOnInit(); + }, 500); + + + + + + + + + + + + + }, (error) => { + console.log(error); + if (error.status >= 200 && error.status <= 299) { + // this.toastr.success("Added Succesfully"); + } + if (error.status >= 400 && error.status <= 499) { + this.toastr.error("Not Added"); + } + if (error.status >= 500 && error.status <= 599) { + this.toastr.error("Not Added"); + } + }); + setTimeout(() => { + this.ngOnInit(); + }, 500); + } + goToAdd(row) { + this.modalAdd = true; + this.addCronExpression = ''; + this.selectedSureConnect = null; // Reset SureConnect selection + this.submitted = false; + // Reset the form control for cron_job and sure_connect_id + if (this.entryForm) { + if (this.entryForm.get('cron_job')) { + this.entryForm.get('cron_job')?.setValue(''); + } + if (this.entryForm.get('sure_connect_id')) { + this.entryForm.get('sure_connect_id')?.setValue(null); + } + } + } + submitted = false; + onSubmit() { + console.log(this.entryForm.value); + this.submitted = true; + if (this.entryForm.invalid) { + return; + } this.onCreate(); + + } + + + isValidurl(url: string): boolean { + return /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/.test(url); + } + goTourlUrl(val) { window.open(val) } + + + + + + + rsModaljson = false; + goToReplaceStringjson(row) { + this.rowSelected = row; this.rsModaljson = true; + } + + + + // updateaction +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.service.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.service.ts new file mode 100644 index 0000000..a191ee9 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Observable } from "rxjs"; +import { HttpClient, HttpHeaders, HttpParams, } from "@angular/common/http"; +import { ApiRequestService } from "src/app/services/api/api-request.service"; +import { environment } from 'src/environments/environment'; +@Injectable({ + providedIn: 'root' +}) +export class Data_lakeservice{ + private baseURL = "Data_lake/Data_lake" ; constructor( + private http: HttpClient, + private apiRequest: ApiRequestService, + ) { } + getAll(page?: number, size?: number): Observable { + return this.apiRequest.get(this.baseURL); + } + getById(id: number): Observable { + const _http = this.baseURL + "/" + id; + return this.apiRequest.get(_http); + } + create(data: any): Observable { + return this.apiRequest.post(this.baseURL, data); + } + update(id: number, data: any): Observable { + const _http = this.baseURL + "/" + id; + return this.apiRequest.put(_http, data); + } + delete(id: number): Observable { + const _http = this.baseURL + "/" + id; + return this.apiRequest.delete(_http); + } + + // New method for updating JSON + updateJson(id: number): Observable { + const _http = this.baseURL + "/json/" + id; + return this.apiRequest.put(_http, {}); + } + +// updateaction +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake_cardvariable.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake_cardvariable.ts new file mode 100644 index 0000000..d6f15b0 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/Data_lake_cardvariable.ts @@ -0,0 +1,4 @@ +export const Data_lakecardvariable = { + "cardButton": false, + "cardmodeldata": `` +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.html new file mode 100644 index 0000000..d8df94d --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.html @@ -0,0 +1,66 @@ +
+

Cron Job Builder

+
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ Schedule Description: {{ cronDescription }} +
+
+
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.scss new file mode 100644 index 0000000..66eedb7 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.scss @@ -0,0 +1,39 @@ +.cron-job-builder { + padding: 15px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #f8f8f8; + margin-bottom: 20px; + + h4 { + margin-top: 0; + color: #333; + } + + .form-group { + margin-bottom: 10px; + + label { + display: block; + margin-bottom: 5px; + font-weight: 600; + } + + select, input { + width: 100%; + } + } + + .cron-description { + padding: 10px; + background-color: #e6f7ff; + border: 1px solid #91d5ff; + border-radius: 4px; + margin-top: 10px; + font-size: 14px; + + strong { + color: #1890ff; + } + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.ts new file mode 100644 index 0000000..b00c59c --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component.ts @@ -0,0 +1,172 @@ +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'app-cron-job-builder', + templateUrl: './cron-job-builder.component.html', + styleUrls: ['./cron-job-builder.component.scss'] +}) +export class CronJobBuilderComponent implements OnInit, OnChanges { + @Input() cronExpression: string = ''; + @Input() instanceId: string = ''; // Unique identifier for each instance + @Output() cronExpressionChange = new EventEmitter(); + + // Cron job fields + minute: string = '*'; + hour: string = '*'; + dayOfMonth: string = '*'; + month: string = '*'; + dayOfWeek: string = '*'; + + // Human readable description + cronDescription: string = ''; + + // Options for selectors + minuteOptions: any[] = []; + hourOptions: any[] = []; + dayOfMonthOptions: any[] = []; + monthOptions: any[] = []; + dayOfWeekOptions: any[] = []; + + ngOnInit() { + // Initialize options for each instance + this.minuteOptions = this.generateOptions(0, 59); + this.hourOptions = this.generateOptions(0, 23); + this.dayOfMonthOptions = this.generateOptions(1, 31); + this.monthOptions = [ + { value: '*', label: 'Every month' }, + { value: '1', label: 'January' }, + { value: '2', label: 'February' }, + { value: '3', label: 'March' }, + { value: '4', label: 'April' }, + { value: '5', label: 'May' }, + { value: '6', label: 'June' }, + { value: '7', label: 'July' }, + { value: '8', label: 'August' }, + { value: '9', label: 'September' }, + { value: '10', label: 'October' }, + { value: '11', label: 'November' }, + { value: '12', label: 'December' } + ]; + this.dayOfWeekOptions = [ + { value: '*', label: 'Every day' }, + { value: '0', label: 'Sunday' }, + { value: '1', label: 'Monday' }, + { value: '2', label: 'Tuesday' }, + { value: '3', label: 'Wednesday' }, + { value: '4', label: 'Thursday' }, + { value: '5', label: 'Friday' }, + { value: '6', label: 'Saturday' } + ]; + + if (this.cronExpression) { + this.parseCronExpression(this.cronExpression); + } + // Generate initial description + this.generateDescription(); + } + + ngOnChanges(changes: SimpleChanges) { + // When cronExpression input changes, update the component state + if (changes['cronExpression'] && !changes['cronExpression'].firstChange) { + this.parseCronExpression(changes['cronExpression'].currentValue); + this.generateDescription(); + } + } + + generateOptions(start: number, end: number): any[] { + const options = [{ value: '*', label: `Every (${start === 0 ? '0' : start}-${end})` }]; + for (let i = start; i <= end; i++) { + options.push({ value: i.toString(), label: i.toString() }); + } + return options; + } + + parseCronExpression(expression: string) { + const parts = expression.split(' '); + if (parts.length >= 5) { + this.minute = parts[0]; + this.hour = parts[1]; + this.dayOfMonth = parts[2]; + this.month = parts[3]; + this.dayOfWeek = parts[4]; + } + } + + generateDescription() { + let description = 'Runs '; + + // Minute description + if (this.minute === '*') { + description += 'every minute'; + } else if (this.minute.includes('/')) { + const interval = this.minute.split('/')[1]; + description += `every ${interval} minutes`; + } else { + description += `at minute ${this.minute}`; + } + + // Hour description + if (this.hour === '*') { + if (this.minute === '*') { + description += ''; + } else { + description += ' past every hour'; + } + } else if (this.hour.includes('/')) { + const interval = this.hour.split('/')[1]; + description += ` past every ${interval} hours`; + } else { + description += ` past hour ${this.hour}`; + } + + // Day of month description + if (this.dayOfMonth !== '*' && this.dayOfMonth !== '?') { + description += ` on day ${this.dayOfMonth}`; + } + + // Month description + if (this.month !== '*') { + const monthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December']; + if (this.month.match(/^\d+$/)) { + description += ` in ${monthNames[parseInt(this.month)]}`; + } else { + description += ` in ${this.month}`; + } + } + + // Day of week description + if (this.dayOfWeek !== '*' && this.dayOfWeek !== '?') { + const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + if (this.dayOfWeek.match(/^\d+$/)) { + description += ` on ${dayNames[parseInt(this.dayOfWeek)]}`; + } else { + description += ` on ${this.dayOfWeek}`; + } + } + + // Special case for simple patterns + if (this.minute === '0' && this.hour === '0' && this.dayOfMonth === '1' && this.month === '*' && this.dayOfWeek === '*') { + description = 'Runs at midnight on the first day of every month'; + } else if (this.minute === '0' && this.hour === '0' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') { + description = 'Runs at midnight every day'; + } else if (this.minute === '0' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') { + description = 'Runs at the top of every hour'; + } else if (this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') { + description = 'Runs every minute'; + } + + this.cronDescription = description; + } + + buildCronExpression() { + const expression = `${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`; + this.cronExpressionChange.emit(expression); + this.generateDescription(); // Update description when expression changes + return expression; + } + + onFieldChange() { + this.buildCronExpression(); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.html index 02dce20..c7f4f67 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.html @@ -7,88 +7,102 @@ -->
- -
- {{dashboardName}} -
-
- -
- + +
+ {{dashboardName}}
- -
+
+ +
+ +
+ +
-
- - - - - +
+ + + + + - + - - + -

{{item.name}}

- - -
-
-
+

{{item.name}}

+ + + +
+
-
- - -
- - - +
+
+ + +
+ + + - - -
+ +
- -
 
+ +
 
-
+
- + - - +
-
+
- + - + {{selected}} @@ -169,17 +190,177 @@
- -
+ +
- - +
- + + +
+
+

Base Drilldown Configuration

+
+
+
+ + +
+
+
+
+
+ +
+
+ +
+   + + + +
+
Enter the API URL for base drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<country>
+
+
+ +
+
+ + +
Select the column to use for X-axis in base drilldown view
+
+
+ +
+
+ + +
Select the column to use for Y-axis in base drilldown view
+
+
+ + +
+
+ + +
Select the column to use as parameter for URL template replacement in base drilldown
+
+
+ + +
+
+

Multi-Layer Drilldown Configurations

+ +
Add additional drilldown layers for multi-level navigation
+
+
+ + +
+
+
+
Drilldown Layer {{i + 1}}
+ +
+ +
+
+
+ + +
+
+
+ +
+
+ +
+   + + + +
+
Enter the API URL for layer {{i + 1}} drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<state>
+
+
+ +
+
+ + +
Select the column to use for X-axis in layer {{i + 1}} drilldown view
+
+
+ +
+
+ + +
Select the column to use for Y-axis in layer {{i + 1}} drilldown view
+
+
+ + +
+
+ + +
Select the column to use as parameter for URL template replacement in layer {{i + 1}} drilldown
+
+
+ + + +
+
+ - +
- - - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts index 5104759..9e52f83 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/editnewdash/editnewdash.component.ts @@ -20,7 +20,8 @@ import { GridViewComponent } from '../gadgets/grid-view/grid-view.component'; import { DatastoreService } from 'src/app/services/fnd/datastore.service'; import { AlertsService } from 'src/app/services/fnd/alerts.service'; import { isArray } from 'highcharts'; -// import { ChartItem } from '../chartitem'; +// Add the SureconnectService import +import { SureconnectService } from '../sureconnect/sureconnect.service'; function isNullArray(arr) { return !Array.isArray(arr) || arr.length === 0; @@ -36,487 +37,574 @@ function isNullArray(arr) { export class EditnewdashComponent implements OnInit { - editId:number; - toggle:boolean; - modeledit:boolean = false; + editId: number; + toggle: boolean; + modeledit: boolean = false; public entryForm: FormGroup; WidgetsMock: WidgetModel[] = [ { - name: 'Radar Chart', - identifier: 'radar_chart' + name: 'Radar Chart', + identifier: 'radar_chart' }, { - name: 'Doughnut Chart', - identifier: 'doughnut_chart' + name: 'Doughnut Chart', + identifier: 'doughnut_chart' }, { - name: 'Line Chart', - identifier: 'line_chart' + name: 'Line Chart', + identifier: 'line_chart' + }, + { + name: 'Bar Chart', + identifier: 'bar_chart' + }, + { + name: 'Pie Chart', + identifier: 'pie_chart' + }, + { + name: 'Polar Area Chart', + identifier: 'polar_area_chart' + }, + { + name: 'Bubble Chart', + identifier: 'bubble_chart' + }, + { + name: 'Scatter Chart', + identifier: 'scatter_chart' + }, + // { + // name: 'Dynamic Chart', + // identifier: 'dynamic_chart' + // }, + // { + // name: 'Financial Chart', + // identifier: 'financial_chart' + // }, + { + name: 'To Do', + identifier: 'to_do_chart' }, - { - name: 'Bar Chart', - identifier: 'bar_chart' - }, - { - name: 'Pie Chart', - identifier: 'pie_chart' - }, - { - name: 'Polar Area Chart', - identifier: 'polar_area_chart' - }, - { - name: 'Bubble Chart', - identifier: 'bubble_chart' - }, - { - name: 'Scatter Chart', - identifier: 'scatter_chart' - }, - // { - // name: 'Dynamic Chart', - // identifier: 'dynamic_chart' - // }, - // { - // name: 'Financial Chart', - // identifier: 'financial_chart' - // }, - { - name: 'To Do', - identifier: 'to_do_chart' - }, { name: 'Grid View', identifier: 'grid_view' - } -] + } + ] public options: GridsterConfig; - protected dashboardId: number; - protected dashboardCollection: DashboardModel; - //dashboardCollection:any; - protected dashboardCollection1: DashboardModel[]; - public dashboardArray: DashboardContentModel[]; - public dashArr:[]; + protected dashboardId: number; + protected dashboardCollection: DashboardModel; + //dashboardCollection:any; + protected dashboardCollection1: DashboardModel[]; + public dashboardArray: DashboardContentModel[]; + public dashArr: []; - protected componentCollection = [ - { name: "Line Chart", componentInstance: LineChartComponent }, - { name: "Doughnut Chart", componentInstance: DoughnutChartComponent }, - { name: "Radar Chart", componentInstance: RadarChartComponent }, - { name: "Bar Chart", componentInstance: BarChartComponent }, - { name: "Pie Chart", componentInstance: PieChartComponent }, - { name: "Polar Area Chart", componentInstance: PolarChartComponent }, - { name: "Bubble Chart", componentInstance: BubbleChartComponent }, - { name: "Scatter Chart", componentInstance: ScatterChartComponent }, - { name: "Dynamic Chart", componentInstance: DynamicChartComponent }, - { name: "Financial Chart", componentInstance: FinancialChartComponent }, - { name: "To Do Chart", componentInstance: ToDoChartComponent }, + protected componentCollection = [ + { name: "Line Chart", componentInstance: LineChartComponent }, + { name: "Doughnut Chart", componentInstance: DoughnutChartComponent }, + { name: "Radar Chart", componentInstance: RadarChartComponent }, + { name: "Bar Chart", componentInstance: BarChartComponent }, + { name: "Pie Chart", componentInstance: PieChartComponent }, + { name: "Polar Area Chart", componentInstance: PolarChartComponent }, + { name: "Bubble Chart", componentInstance: BubbleChartComponent }, + { name: "Scatter Chart", componentInstance: ScatterChartComponent }, + { name: "Dynamic Chart", componentInstance: DynamicChartComponent }, + { name: "Financial Chart", componentInstance: FinancialChartComponent }, + { name: "To Do Chart", componentInstance: ToDoChartComponent }, { name: "Grid View", componentInstance: GridViewComponent }, - ]; - model:any; - linesdata:any; - id:any; + ]; + model: any; + linesdata: any; + id: any; gadgetsEditdata = { - donut : '', + donut: '', chartlegend: '', - showlabel : '', + showlabel: '', charturl: '', - chartparameter : '', - datastore : '', - table:'', - datasource : '', - charttitle:'', - id:'', - fieldName:'', - chartcolor:'', - slices:'', - yAxis:'', - xAxis:'' + chartparameter: '', + datastore: '', + table: '', + datasource: '', + charttitle: '', + id: '', + fieldName: '', + chartcolor: '', + slices: '', + yAxis: '', + xAxis: '', + connection: '', // Add connection field + // Drilldown configuration properties (base level) + drilldownEnabled: false, + drilldownApiUrl: '', + // Removed drilldownParameterKey since we're using URL templates + drilldownXAxis: '', + drilldownYAxis: '', + drilldownParameter: '', // Add drilldown parameter property + // Multi-layer drilldown configurations + drilldownLayers: [] as any[] + }; + + // Add sureconnect data property + sureconnectData: any[] = []; + layerColumnData: { [key: number]: any[] } = {}; // Add layer column data property -}; constructor(private route: ActivatedRoute, - private router : Router, - private dashboardService: Dashboard3Service, - private toastr:ToastrService, + private router: Router, + private dashboardService: Dashboard3Service, + private toastr: ToastrService, private _fb: FormBuilder, private datastoreService: DatastoreService, - private alertService:AlertsService,) { } + private alertService: AlertsService, + private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor - ngOnInit(): void { + ngOnInit(): void { - // Grid options - this.options = { - gridType: "fit", - enableEmptyCellDrop: true, - emptyCellDropCallback: this.onDrop, - pushItems: true, - swap: true, - pushDirections: { north: true, east: true, south: true, west: true }, - resizable: { enabled: true }, - itemChangeCallback: this.itemChange.bind(this), - draggable: { - enabled: true, - ignoreContent: true, - dropOverItems: true, - dragHandleClass: "drag-handler", - ignoreContentClass: "no-drag", - }, - displayGrid: "always", - minCols: 10, - minRows: 10 - }; - this.getData(); - - this.editId = this.route.snapshot.params.id; - console.log(this.editId); - this.dashboardService.getById(this.editId).subscribe((data)=>{ - console.log("ngOnInit",data); - this.linesdata = data; - this.id = data.dashbord1_Line[0].id; - console.log("this.id ",this.id); + // Grid options + this.options = { + gridType: "fit", + enableEmptyCellDrop: true, + emptyCellDropCallback: this.onDrop, + pushItems: true, + swap: true, + pushDirections: { north: true, east: true, south: true, west: true }, + resizable: { enabled: true }, + itemChangeCallback: this.itemChange.bind(this), + draggable: { + enabled: true, + ignoreContent: true, + dropOverItems: true, + dragHandleClass: "drag-handler", + ignoreContentClass: "no-drag", }, - (error: any)=>{ + displayGrid: "always", + minCols: 10, + minRows: 10 + }; + + this.editId = this.route.snapshot.params.id; + console.log(this.editId); + this.dashboardService.getById(this.editId).subscribe((data) => { + console.log("ngOnInit", data); + this.linesdata = data; + this.id = data.dashbord1_Line[0].id; + console.log("this.id ", this.id); + }, + (error: any) => { } - ); + ); - this.entryForm = this._fb.group({ - donut : [null], - chartlegend: [null], - showlabel : [null], - charturl: [null], - chartparameter : [null], - datastore:[null], - table:[null], - fieldName: [null], - datasource : [null], - charttitle:[null], - id:[null], - chartcolor:[null], - slices:[null], - yAxis:[null], - xAxis: [null], - }); - } + this.entryForm = this._fb.group({ + donut: [null], + chartlegend: [null], + showlabel: [null], + charturl: [null], + chartparameter: [null], + datastore: [null], + table: [null], + fieldName: [null], + datasource: [null], + charttitle: [null], + id: [null], + chartcolor: [null], + slices: [null], + yAxis: [null], + xAxis: [null], + connection: [null], // Add connection to form group + // Base drilldown configuration form controls + drilldownEnabled: [null], + drilldownApiUrl: [null], + drilldownXAxis: [null], + drilldownYAxis: [null], + drilldownParameter: [null] // Add drilldown parameter to form group + // Note: Dynamic drilldown layers will be handled separately since they're complex objects + }); + + // Load sureconnect data first, then load dashboard data + this.loadSureconnectData(); + } + + // Add method to load sureconnect data + loadSureconnectData() { + this.sureconnectService.getAll().subscribe((data: any[]) => { + this.sureconnectData = data; + console.log('Sureconnect data loaded:', this.sureconnectData); + // Now that sureconnect data is loaded, we can safely load dashboard data + this.getData(); + }, (error) => { + console.log('Error loading sureconnect data:', error); + // Even if there's an error loading sureconnect data, we still need to load dashboard data + this.getData(); + }); + } - toggleMenu() { - this.toggle = !this.toggle; - } + toggleMenu() { + this.toggle = !this.toggle; + } - onDrag(event, identifier) { - console.log("on drag",identifier); - console.log("on drag ",event); - event.dataTransfer.setData('widgetIdentifier', identifier); - } - datagadgets:any; - dashboardLine:any; - dashboardName:any; - getData() { - // We get the id in get current router dashboard/:id - this.route.params.subscribe(params => { - // + is used to cast string to int - this.dashboardId = +params["id"]; - // We make a get request with the dashboard id - this.dashboardService.getById(this.dashboardId).subscribe(dashboard => { - // We fill our dashboardCollection with returned Observable - this.dashboardName = dashboard.dashboard_name; - this.datagadgets = dashboard; - this.dashboardLine = dashboard.dashbord1_Line; - //this.dashboardCollection = dashboard.dashbord1_Line.model; - console.log("this.datagadgets",this.datagadgets); - console.log("this.dashboardLine",this.dashboardLine); - this.dashboardCollection =JSON.parse(this.dashboardLine[0].model) ; - //this.dashboardCollection =this.dashboardLine[0].model ; - console.log("this.dasboard ",this.dashboardCollection ); - console.log(this.dashboardCollection); - // We parse serialized Json to generate components on the fly - this.parseJson(this.dashboardCollection); - // We copy array without reference - this.dashboardArray = this.dashboardCollection.dashboard.slice(); - console.log("this.dashboardArray",this.dashboardArray); - }); + onDrag(event, identifier) { + console.log("on drag", identifier); + console.log("on drag ", event); + event.dataTransfer.setData('widgetIdentifier', identifier); + } + datagadgets: any; + dashboardLine: any; + dashboardName: any; + getData() { + // We get the id in get current router dashboard/:id + this.route.params.subscribe(params => { + // + is used to cast string to int + this.dashboardId = +params["id"]; + // We make a get request with the dashboard id + this.dashboardService.getById(this.dashboardId).subscribe(dashboard => { + // We fill our dashboardCollection with returned Observable + this.dashboardName = dashboard.dashboard_name; + this.datagadgets = dashboard; + this.dashboardLine = dashboard.dashbord1_Line; + //this.dashboardCollection = dashboard.dashbord1_Line.model; + console.log("this.datagadgets", this.datagadgets); + console.log("this.dashboardLine", this.dashboardLine); + this.dashboardCollection = JSON.parse(this.dashboardLine[0].model); + //this.dashboardCollection =this.dashboardLine[0].model ; + console.log("this.dasboard ", this.dashboardCollection); + console.log(this.dashboardCollection); + // We parse serialized Json to generate components on the fly + this.parseJson(this.dashboardCollection); + + // Set default connections for all gadgets if sureconnect data is available + if (this.sureconnectData && this.sureconnectData.length > 0) { + this.dashboardCollection.dashboard.forEach(item => { + if (!item['connection'] || item['connection'] === '') { + item['connection'] = this.sureconnectData[0].id; + } + }); + } + + // We copy array without reference + this.dashboardArray = this.dashboardCollection.dashboard.slice(); + console.log("this.dashboardArray", this.dashboardArray); }); + }); + } + // Super TOKENIZER 2.0 POWERED BY NATCHOIN + parseJson(dashboardCollection: DashboardModel) { + // We loop on our dashboardCollection + dashboardCollection.dashboard.forEach(dashboard => { + // We loop on our componentCollection + this.componentCollection.forEach(component => { + // We check if component key in our dashboardCollection + // is equal to our component name key in our componentCollection + if (dashboard.component === component.name) { + // If it is, we replace our serialized key by our component instance + dashboard.component = component.componentInstance; + } + }); + }); + } - } + serialize(dashboardCollection) { + // We loop on our dashboardCollection + dashboardCollection.forEach(dashboard => { + // We loop on our componentCollection + this.componentCollection.forEach(component => { + // We check if component key in our dashboardCollection + // is equal to our component name key in our componentCollection + if (dashboard.name === component.name) { + dashboard.component = component.name; + } + }); + }); + } - // Super TOKENIZER 2.0 POWERED BY NATCHOIN - parseJson(dashboardCollection: DashboardModel) { - // We loop on our dashboardCollection - dashboardCollection.dashboard.forEach(dashboard => { - // We loop on our componentCollection - this.componentCollection.forEach(component => { - // We check if component key in our dashboardCollection - // is equal to our component name key in our componentCollection - if (dashboard.component === component.name) { - // If it is, we replace our serialized key by our component instance - dashboard.component = component.componentInstance; - } + itemChange() { + this.dashboardCollection.dashboard = this.dashboardArray; + console.log("itemChange this.dashboardCollection.dashboard ", this.dashboardCollection.dashboard); + console.log("itemChange this.dashboardCollection ", this.dashboardCollection); + console.log("itemChange this.dashboardCollection type", typeof this.dashboardCollection); + console.log("itemChange this.dashboardArray ", this.dashboardArray); + let tmp = JSON.stringify(this.dashboardCollection); + console.log("temp data", tmp); + let parsed: DashboardModel = JSON.parse(tmp); + console.log("parsed data", parsed); + console.log("let parsed ", typeof parsed); + this.serialize(parsed.dashboard); + console.log("item chnage function ", typeof this.dashboardArray); + //this._ds.updateDashboard(this.dashboardId, parsed).subscribe(); + } + + onDrop(ev) { + const componentType = ev.dataTransfer.getData("widgetIdentifier"); + let maxChartId = this.dashboardArray?.reduce((maxId, item) => Math.max(maxId, item.chartid), 0); + switch (componentType) { + case "radar_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: RadarChartComponent, + name: "Radar Chart" }); + case "line_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 7, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: LineChartComponent, + name: "Line Chart" + }); + case "doughnut_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: DoughnutChartComponent, + name: "Doughnut Chart" + }); + case "bar_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: BarChartComponent, + name: "Bar Chart" + }); + case "pie_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: PieChartComponent, + name: "Pie Chart" + }); + case "polar_area_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: PolarChartComponent, + name: "Polar Area Chart" + }); + case "bubble_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: BubbleChartComponent, + name: "Bubble Chart" + }); + case "scatter_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: ScatterChartComponent, + name: "Scatter Chart" + }); + case "dynamic_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: DynamicChartComponent, + name: "Dynamic Chart" + }); + case "financial_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 6, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: FinancialChartComponent, + name: "Financial Chart" + }); + case "to_do_chart": + return this.dashboardArray.push({ + cols: 5, + rows: 5, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: ToDoChartComponent, + name: "To Do Chart" + }); + case "grid_view": + return this.dashboardArray.push({ + cols: 5, + rows: 5, + x: 0, + y: 0, + chartid: maxChartId + 1, + component: GridViewComponent, + name: "Grid View" + }); + } + } + removeItem(item) { + this.dashboardArray.splice( + this.dashboardArray.indexOf(item), + 1 + ); + this.itemChange(); + } + + changedOptions() { + this.options.api.optionsChanged(); + } + + modelid: number; + editGadget(item) { + this.modeledit = true; + this.modelid = item.chartid; + console.log(this.modelid); + this.gadgetsEditdata = item; + this.gadgetsEditdata.fieldName = item.name; + if (item.showlabel === undefined) { item.showlabel = true; } + if (item.chartcolor === undefined) { item.chartcolor = true; } + if (item.chartlegend === undefined) { item.chartlegend = true; } + this.getStores(); + + // Set default connection if none is set and we have connections + if ((!item['connection'] || item['connection'] === '') && this.sureconnectData && this.sureconnectData.length > 0) { + this.gadgetsEditdata['connection'] = this.sureconnectData[0].id; + // Also update the form control + this.entryForm.patchValue({ connection: this.sureconnectData[0].id }); + } + + // Initialize base drilldown properties if not present + if (item['drilldownEnabled'] === undefined) { + this.gadgetsEditdata['drilldownEnabled'] = false; + } + if (item['drilldownApiUrl'] === undefined) { + this.gadgetsEditdata['drilldownApiUrl'] = ''; + } + // Removed drilldownParameterKey initialization + if (item['drilldownXAxis'] === undefined) { + this.gadgetsEditdata['drilldownXAxis'] = ''; + } + if (item['drilldownYAxis'] === undefined) { + this.gadgetsEditdata['drilldownYAxis'] = ''; + } + if (item['drilldownParameter'] === undefined) { + this.gadgetsEditdata['drilldownParameter'] = ''; + } + + // Initialize drilldown layers if not present + if (item['drilldownLayers'] === undefined) { + this.gadgetsEditdata['drilldownLayers'] = []; + } else { + // Ensure each layer has proper structure (removed parameterKey, added parameter) + this.gadgetsEditdata['drilldownLayers'].forEach(layer => { + // Initialize parameter if not present + if (layer['parameter'] === undefined) { + layer['parameter'] = ''; + } }); } - - serialize(dashboardCollection) { - // We loop on our dashboardCollection - dashboardCollection.forEach(dashboard => { - // We loop on our componentCollection - this.componentCollection.forEach(component => { - // We check if component key in our dashboardCollection - // is equal to our component name key in our componentCollection - if (dashboard.name === component.name) { - dashboard.component = component.name; - } - }); - }); - } - - itemChange() { - this.dashboardCollection.dashboard = this.dashboardArray; - console.log("itemChange this.dashboardCollection.dashboard ",this.dashboardCollection.dashboard); - console.log("itemChange this.dashboardCollection ",this.dashboardCollection); - console.log("itemChange this.dashboardCollection type",typeof this.dashboardCollection); - console.log("itemChange this.dashboardArray ",this.dashboardArray); - let tmp = JSON.stringify(this.dashboardCollection); - console.log("temp data",tmp); - let parsed: DashboardModel = JSON.parse(tmp); - console.log("parsed data",parsed); - console.log("let parsed ",typeof parsed); - this.serialize(parsed.dashboard); - console.log("item chnage function ", typeof this.dashboardArray); - //this._ds.updateDashboard(this.dashboardId, parsed).subscribe(); - } - - onDrop(ev) { - const componentType = ev.dataTransfer.getData("widgetIdentifier"); - let maxChartId = this.dashboardArray?.reduce((maxId, item) => Math.max(maxId, item.chartid), 0); - switch (componentType) { - case "radar_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: RadarChartComponent, - name: "Radar Chart" - }); - case "line_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 7, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: LineChartComponent, - name: "Line Chart" - }); - case "doughnut_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: DoughnutChartComponent, - name: "Doughnut Chart" - }); - case "bar_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: BarChartComponent, - name: "Bar Chart" - }); - case "pie_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: PieChartComponent, - name: "Pie Chart" - }); - case "polar_area_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: PolarChartComponent, - name: "Polar Area Chart" - }); - case "bubble_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: BubbleChartComponent, - name: "Bubble Chart" - }); - case "scatter_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: ScatterChartComponent, - name: "Scatter Chart" - }); - case "dynamic_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: DynamicChartComponent, - name: "Dynamic Chart" - }); - case "financial_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 6, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: FinancialChartComponent, - name: "Financial Chart" - }); - case "to_do_chart": - return this.dashboardArray.push({ - cols: 5, - rows: 5, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: ToDoChartComponent, - name: "To Do Chart" - }); - case "grid_view": - return this.dashboardArray.push({ - cols: 5, - rows: 5, - x: 0, - y: 0, - chartid:maxChartId + 1, - component: GridViewComponent, - name: "Grid View" - }); - } - } - removeItem(item) { - this.dashboardArray.splice( - this.dashboardArray.indexOf(item), - 1 - ); - this.itemChange(); - } - - changedOptions() { - this.options.api.optionsChanged(); - } - - modelid:number ; - editGadget(item) - { - this.modeledit = true; - this.modelid = item.chartid; - console.log(this.modelid); - this.gadgetsEditdata = item; - this.gadgetsEditdata.fieldName = item.name; - if(item.showlabel === undefined){ item.showlabel = true; } - if(item.chartcolor === undefined ){ item.chartcolor = true;} - if(item.chartlegend === undefined){ item.chartlegend = true; } - this.getStores(); - if(item.datastore !== undefined || '' || null){ + + // Reset drilldown column data + this.drilldownColumnData = []; + + if (item.datastore !== undefined || '' || null) { const datastore = item.datastore; this.getTables(datastore); const table = item.table; - this.getColumns(datastore,table); + this.getColumns(datastore, table); console.log(item.yAxis); - if(isArray(item.yAxis)){ - this.selectedyAxis = item.yAxis; - console.log( this.selectedyAxis); + if (isArray(item.yAxis)) { + this.selectedyAxis = item.yAxis; + console.log(this.selectedyAxis); } - }else{ - this.selectedyAxis = []; - } - console.log(item); + } else { + this.selectedyAxis = []; } + console.log(item); + } - dashbord1_Line = { - //model:JSON.stringify(this.da), - model:'' - } + dashbord1_Line = { + //model:JSON.stringify(this.da), + model: '' + } - UpdateLine() - { + UpdateLine() { console.log('Add button clicked.......'); console.log(this.dashboardArray); console.log(this.dashboardCollection); console.log(typeof this.dashboardCollection); console.log(this.id); - //this.dashbord1_Line.model = JSON.stringify(this.dashboardCollection); + //this.dashbord1_Line.model = JSON.stringify(this.dashboardCollection); - //https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring + //https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring -let cmp=this.dashboardCollection.dashboard.forEach(dashboard=>{ - this.componentCollection.forEach(component=>{ - if (dashboard.name === component.name) { - dashboard.component = component.name; - } }) -}) -console.log(cmp); + let cmp = this.dashboardCollection.dashboard.forEach(dashboard => { + this.componentCollection.forEach(component => { + if (dashboard.name === component.name) { + dashboard.component = component.name; + } + }) + }) + console.log(cmp); let tmp = JSON.stringify(this.dashboardCollection); - // var merged = this.dashboardArray.reduce((current, value, index) => { - // if(index > 0) - // current += ','; + // var merged = this.dashboardArray.reduce((current, value, index) => { + // if(index > 0) + // current += ','; - // return current + value.component; - // }, ''); + // return current + value.component; + // }, ''); - //console.log(merged); - console.log("temp data",typeof tmp); - console.log(tmp); - let parsed= JSON.parse(tmp); - this.serialize(parsed.dashboard); + //console.log(merged); + console.log("temp data", typeof tmp); + console.log(tmp); + let parsed = JSON.parse(tmp); + this.serialize(parsed.dashboard); this.dashbord1_Line.model = tmp; - // let obj = this.dashboardCollection; - // obj[1].component = obj[1].component.toString(); - // let myJSON = JSON.stringify(obj); - // this.dashbord1_Line.model = myJSON; + // let obj = this.dashboardCollection; + // obj[1].component = obj[1].component.toString(); + // let myJSON = JSON.stringify(obj); + // this.dashbord1_Line.model = myJSON; - console.log("line data in addgadget ",this.dashbord1_Line); - console.log("line data in addgadget type ",typeof this.dashbord1_Line); - console.log("line model data ",this.dashbord1_Line.model); - console.log("line model data type",typeof this.dashbord1_Line.model); - this.dashboardService.UpdateLineData(this.id,this.dashbord1_Line).subscribe( - (data: any)=>{ - console.log('Updation Successful...'); - this.ngOnInit(); - console.log(data); - this.router.navigate(["../../all"], { relativeTo: this.route }) - } - ); - // if (data) { - // this.toastr.success('Updated successfully'); - // } - } + console.log("line data in addgadget ", this.dashbord1_Line); + console.log("line data in addgadget type ", typeof this.dashbord1_Line); + console.log("line model data ", this.dashbord1_Line.model); + console.log("line model data type", typeof this.dashbord1_Line.model); + this.dashboardService.UpdateLineData(this.id, this.dashbord1_Line).subscribe( + (data: any) => { + console.log('Updation Successful...'); + this.ngOnInit(); + console.log(data); + this.router.navigate(["../../all"], { relativeTo: this.route }) + } + ); + // if (data) { + // this.toastr.success('Updated successfully'); + // } + } - onSubmit(id) - { + onSubmit(id) { console.log(id); if (!isNullArray(this.selectedyAxis)) { console.log("get y-axis array", this.selectedyAxis); @@ -525,93 +613,310 @@ console.log(cmp); let formdata = this.entryForm.value; let num = id; console.log(this.entryForm.value); - this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => { - if(item.chartid == num) - { + this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => { + if (item.chartid == num) { //item["product_id"] = "thisistest"; - const xyz = {...item,...formdata} + const xyz = { ...item, ...formdata } + + // Explicitly ensure drilldown properties are preserved + xyz.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled; + xyz.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl; + xyz.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis; + xyz.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis; + xyz.drilldownParameter = this.gadgetsEditdata.drilldownParameter; + xyz.drilldownLayers = this.gadgetsEditdata.drilldownLayers; + console.log(xyz); return xyz; - } - return item - }); - console.log(this.dashboardCollection.dashboard); - this.modeledit = false; + } + return item + }); + console.log('dashboard collection ', this.dashboardCollection.dashboard); + this.modeledit = false; - // this.entryForm.reset(); + // this.entryForm.reset(); - } - goBack(){ - this.router.navigate(["../../all"], { relativeTo: this.route }) - } - - onSchedule(){ - this.router.navigate(['../../schedule/'+ this.editId],{relativeTo:this.route}); - } + } + /** + * Extract only the relevant chart configuration properties to pass to chart components + * This prevents errors when trying to set properties that don't exist on the components + */ + getChartInputs(item: any): any { + // Only pass properties that are relevant to chart components + const chartInputs = { + xAxis: item.xAxis, + yAxis: item.yAxis, + table: item.table, + datastore: item.datastore, + charttitle: item.charttitle, + chartlegend: item.chartlegend, + showlabel: item.showlabel, + chartcolor: item.chartcolor, + slices: item.slices, + donut: item.donut, + charturl: item.charturl, + chartparameter: item.chartparameter, + datasource: item.datasource, + fieldName: item.name, // Using item.name as fieldName + connection: item['connection'], // Add connection field using bracket notation + // Base drilldown configuration properties + drilldownEnabled: item['drilldownEnabled'], + drilldownApiUrl: item['drilldownApiUrl'], + // Removed drilldownParameterKey since we're using URL templates + drilldownXAxis: item['drilldownXAxis'], + drilldownYAxis: item['drilldownYAxis'], + drilldownParameter: item['drilldownParameter'], // Add drilldown parameter + // Multi-layer drilldown configurations + drilldownLayers: item['drilldownLayers'] || [] + }; - /////// - storedata; - getStores(){ - this.datastoreService.getAll().subscribe((data) => { - console.log(data); - this.storedata = data; - },(error) => { - console.log(error); - }); - } + // Remove undefined properties to avoid passing unnecessary data + Object.keys(chartInputs).forEach(key => { + if (chartInputs[key] === undefined) { + delete chartInputs[key]; + } + }); + + return chartInputs; + } - selectedStoreId; - storename(val){ - console.log(val); - this.selectedStoreId = val; - this.getTables(this.selectedStoreId); - } - - TableData; - getTables(id){ - this.alertService.getTablefromstore(id).subscribe(gateway =>{ - console.log(gateway); - this.TableData = gateway; - },(error)=>{ - console.log(error); - }); - } - - tablename(val){ - console.log(val); - this.getColumns(this.selectedStoreId,val); + applyChanges(id) { + console.log('Apply changes for chart ID:', id); + + // Check if ID is valid + if (id === null || id === undefined) { + console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid); + id = this.modelid; } - selectedyAxis; - columnData; - getColumns(id,table){ - this.alertService.getColumnfromurl(table).subscribe(data =>{ - console.log(data); - this.columnData = data; - },(error)=>{ - console.log(error); + + // Update the form with selected Y-axis values if it's an array + if (!isNullArray(this.selectedyAxis)) { + console.log("get y-axis array", this.selectedyAxis); + this.entryForm.patchValue({ yAxis: this.selectedyAxis }); + } + + // Get form data + let formdata = this.entryForm.value; + let num = id; + console.log('Form data:', this.entryForm.value); + + // Update the dashboard collection with the new configuration + this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => { + if (item.chartid == num) { + // Merge the existing item with the new form data + const updatedItem = { ...item, ...formdata } + + // Explicitly ensure drilldown properties are preserved + updatedItem.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled; + updatedItem.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl; + updatedItem.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis; + updatedItem.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis; + updatedItem.drilldownParameter = this.gadgetsEditdata.drilldownParameter; + updatedItem.drilldownLayers = this.gadgetsEditdata.drilldownLayers; + + console.log('Updated item:', updatedItem); + return updatedItem; + } + return item + }); + + console.log('Updated dashboard collection:', this.dashboardCollection.dashboard); + + // Update the dashboardArray to reflect changes immediately + // Create a new array with new object references to ensure change detection + this.dashboardArray = this.dashboardCollection.dashboard.map(item => ({ ...item })); + + // Force gridster to refresh + if (this.options && this.options.api) { + this.options.api.optionsChanged(); + } + + // Note: We don't close the modal here, allowing the user to make additional changes + // The user can click "Save" when they're done with all changes + } + + goBack() { + this.router.navigate(["../../all"], { relativeTo: this.route }) + } + + onSchedule() { + this.router.navigate(['../../schedule/' + this.editId], { relativeTo: this.route }); + } + + + /////// + storedata; + getStores() { + this.datastoreService.getAll().subscribe((data) => { + console.log(data); + this.storedata = data; + }, (error) => { + console.log(error); + }); + } + + selectedStoreId; + storename(val) { + console.log(val); + this.selectedStoreId = val; + this.getTables(this.selectedStoreId); + } + + TableData; + getTables(id) { + this.alertService.getTablefromstore(id).subscribe(gateway => { + console.log(gateway); + this.TableData = gateway; + }, (error) => { + console.log(error); + }); + } + + callApi(val) { + console.log(' api value ', val); + this.getColumns(this.selectedStoreId, val); + } + selectedyAxis; + columnData; + drilldownColumnData = []; // Add drilldown column data property + + getColumns(id, table) { + const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined; + this.alertService.getColumnfromurl(table, connectionId).subscribe(data => { + console.log(' api data ', data); + this.columnData = data; + }, (error) => { + console.log(error); + }); + } + + // Add method to refresh drilldown columns + refreshDrilldownColumns() { + if (this.gadgetsEditdata.drilldownApiUrl) { + const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined; + this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => { + console.log('Drilldown column data:', data); + this.drilldownColumnData = data; + }, (error) => { + console.log('Error fetching drilldown columns:', error); + this.drilldownColumnData = []; }); } + } - - // toggleAddToDashboard(item) { - // item.addToDashboard = item.addToDashboard; - // } - - // getChartDataForToggleSwitchTrue() { - // for (let i = 0; i < this.dashArr.length; i++) { - // if (this.dashArr[i].addToDashboard) { - // this.dashboardService.getChartData( - // this.dashArr[i].charturl, // Assuming charturl is the correct property to pass as a string - // true // Pass true to indicate fetching charts with toggle switch set to true - // ).subscribe(tData => { - // console.log(tData); - // // this.dashArr[i].featchData = tData; - // }); - // } - // } - // } + // Add method to reset drilldown configuration + resetDrilldownConfiguration() { + this.gadgetsEditdata.drilldownApiUrl = ''; + // Removed drilldownParameterKey since we're using URL templates + this.gadgetsEditdata.drilldownXAxis = ''; + this.gadgetsEditdata.drilldownYAxis = ''; + this.gadgetsEditdata.drilldownParameter = ''; // Reset drilldown parameter + // Reset drilldown layers but preserve the array structure + this.gadgetsEditdata.drilldownLayers = this.gadgetsEditdata.drilldownLayers.map(layer => ({ + ...layer, + enabled: false, + apiUrl: '', + xAxis: '', + yAxis: '', + parameter: '' // Reset parameter property + })); + this.drilldownColumnData = []; + } + + // Add method to add a new drilldown layer + addDrilldownLayer() { + const newLayer = { + enabled: false, + apiUrl: '', + // Removed parameterKey since we're using URL templates + xAxis: '', + yAxis: '', + parameter: '' // Add parameter property + }; + this.gadgetsEditdata.drilldownLayers.push(newLayer); + } + + // Add method to remove a drilldown layer + removeDrilldownLayer(index: number) { + this.gadgetsEditdata.drilldownLayers.splice(index, 1); + } + + // Add method to refresh drilldown columns for a specific layer + refreshDrilldownLayerColumns(layerIndex: number) { + const layer = this.gadgetsEditdata.drilldownLayers[layerIndex]; + if (layer && layer.apiUrl) { + const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined; + this.alertService.getColumnfromurl(layer.apiUrl, connectionId).subscribe(data => { + console.log(`Drilldown layer ${layerIndex} column data:`, data); + // Store layer column data in a separate property + if (!this.layerColumnData) { + this.layerColumnData = {}; + } + this.layerColumnData[layerIndex] = data; + }, (error) => { + console.log(`Error fetching drilldown layer ${layerIndex} columns:`, error); + if (!this.layerColumnData) { + this.layerColumnData = {}; + } + this.layerColumnData[layerIndex] = []; + }); + } + } + + // Add method to refresh base drilldown columns + refreshBaseDrilldownColumns() { + if (this.gadgetsEditdata.drilldownApiUrl) { + const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined; + this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => { + console.log('Base drilldown column data:', data); + this.drilldownColumnData = data; + }, (error) => { + console.log('Error fetching base drilldown columns:', error); + this.drilldownColumnData = []; + }); + } + } + + // Add method to build drilldown URL with template parameters using angle brackets + buildDrilldownUrl(baseUrl: string, parameterValue: string): string { + // If no base URL, return empty string + if (!baseUrl) { + return ''; + } - - + // If no parameter value, return the base URL as-is + if (!parameterValue) { + return baseUrl; + } + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(baseUrl); + + if (hasAngleBrackets) { + // Replace angle brackets placeholder with actual value + // Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/ + // becomes: http://localhost:9292/State_ListFilter1/State_ListFilter11/india + const encodedValue = encodeURIComponent(parameterValue); + const urlWithReplacedParam = baseUrl.replace(/<[^>]+>/g, encodedValue); + return urlWithReplacedParam; + } else { + // No angle brackets, return the base URL as-is + // This handles normal API endpoints without parameter replacement + return baseUrl; + } + } + + // Add method to get the parameter key from URL template using angle brackets + getParameterKeyFromUrl(baseUrl: string): string { + if (!baseUrl) { + return ''; + } + + // Extract parameter key from angle brackets + // Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/ + // returns: country + const match = baseUrl.match(/<([^>]+)>/); + return match ? match[1] : ''; + } } diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/DRILLDOWN_CONFIGURATION.md b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/DRILLDOWN_CONFIGURATION.md new file mode 100644 index 0000000..7beeabc --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/DRILLDOWN_CONFIGURATION.md @@ -0,0 +1,283 @@ +# Drilldown Configuration Implementation + +## Overview +This document describes the drilldown configuration implementation applied to all chart components in the dashboard system. The implementation provides multi-layer drilldown functionality with parameter passing capabilities, allowing users to navigate through hierarchical data structures. + +## Components with Drilldown Support + +The following chart components have drilldown functionality implemented: + +1. Bar Chart (`bar-chart`) +2. Line Chart (`line-chart`) +3. Pie Chart (`pie-chart`) +4. Bubble Chart (`bubble-chart`) +5. Doughnut Chart (`doughnut-chart`) +6. Polar Chart (`polar-chart`) +7. Radar Chart (`radar-chart`) +8. Scatter Chart (`scatter-chart`) +9. Financial Chart (`financial-chart`) +10. Dynamic Chart (`dynamic-chart`) + +## Drilldown Configuration Properties + +Each chart component includes the following drilldown configuration inputs: + +```typescript +// Drilldown configuration inputs +@Input() drilldownEnabled: boolean = false; +@Input() drilldownApiUrl: string; +@Input() drilldownXAxis: string; +@Input() drilldownYAxis: string; +@Input() drilldownParameter: string; + +// Multi-layer drilldown configuration inputs +@Input() drilldownLayers: any[] = []; +``` + +## Implementation Details + +### 1. State Management + +Each component maintains drilldown state through the following properties: + +```typescript +// Multi-layer drilldown state tracking +drilldownStack: any[] = []; // Stack to track drilldown navigation history +currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + +// Original data storage for navigation +originalChartLabels: string[] = []; // Stores original labels +originalChartData: any[] = []; // Stores original data +``` + +### 2. Core Methods + +#### fetchDrilldownData() +Fetches data for the current drilldown level based on configuration: + +```typescript +fetchDrilldownData(): void { + // Determine drilldown configuration based on current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } + } + + // Get parameter value from drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + } + + // Replace parameter placeholders in API URL + let actualApiUrl = drilldownConfig.apiUrl; + if (parameterValue) { + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + } + + // Fetch data from service + this.dashboardService.getChartData( + actualApiUrl, + chartType, + drilldownConfig.xAxis, + drilldownConfig.yAxis, + this.connection, + drilldownConfig.parameter, + parameterValue + ).subscribe(...); +} +``` + +#### chartClicked() +Handles chart click events to initiate drilldown navigation: + +```typescript +public chartClicked(e: any): void { + // Check if drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get clicked element details + const clickedIndex = e.active[0].index; + const clickedLabel = this.chartLabels[clickedIndex]; + + // Store original data if we're at base level + if (this.currentDrilldownLevel === 0) { + this.originalChartLabels = [...this.chartLabels]; + this.originalChartData = [...this.chartData]; + } + + // Determine next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + // Proceed with drilldown if configuration exists + if (hasDrilldownConfig) { + // Add click to drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel + }; + + this.drilldownStack.push(stackEntry); + this.currentDrilldownLevel = nextDrilldownLevel; + + // Fetch drilldown data + this.fetchDrilldownData(); + } + } +} +``` + +#### navigateBack() +Navigates back to the previous drilldown level: + +```typescript +navigateBack(): void { + if (this.drilldownStack.length > 0) { + // Remove last entry from stack + this.drilldownStack.pop(); + this.currentDrilldownLevel = this.drilldownStack.length; + + if (this.drilldownStack.length > 0) { + // Fetch data for previous level + this.fetchDrilldownData(); + } else { + // Back to base level + this.resetToOriginalData(); + } + } else { + // Already at base level + this.resetToOriginalData(); + } +} +``` + +#### resetToOriginalData() +Resets the chart to its original data: + +```typescript +resetToOriginalData(): void { + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalChartLabels.length > 0) { + this.chartLabels = [...this.originalChartLabels]; + } + if (this.originalChartData.length > 0) { + this.chartData = [...this.originalChartData]; + } + + // Re-fetch original data + this.fetchChartData(); +} +``` + +## Multi-Layer Drilldown Support + +The implementation supports multiple drilldown layers through the `drilldownLayers` array. Each layer can have its own configuration: + +```typescript +drilldownLayers: [ + { + enabled: true, + apiUrl: "second-level-endpoint/", + xAxis: "column1", + yAxis: "column2", + parameter: "selectedColumn" + }, + { + enabled: true, + apiUrl: "third-level-endpoint/", + xAxis: "column3", + yAxis: "column4", + parameter: "selectedColumn" + } +] +``` + +## Parameter Passing + +The drilldown implementation supports parameter passing by replacing placeholders in the API URL: + +1. URL templates use angle brackets for parameter placeholders: `endpoint/` +2. When navigating, the clicked value replaces the placeholder +3. Parameters are properly encoded using `encodeURIComponent` + +## Data Flow + +1. **Initial Load**: Chart loads with base data using `fetchChartData()` +2. **Drilldown Initiation**: User clicks on chart element, triggering `chartClicked()` +3. **Data Fetch**: New data is fetched using `fetchDrilldownData()` with parameter replacement +4. **Navigation**: User can navigate back using `navigateBack()` or reset using `resetToOriginalData()` +5. **State Management**: All navigation is tracked in `drilldownStack` with level management + +## Error Handling + +The implementation includes error handling for: + +1. Missing drilldown configuration +2. API call failures +3. Invalid data structures +4. Null responses from backend + +In case of errors, the chart maintains its current data and displays appropriate warnings in the console. + +## UI Integration + +Components with drilldown support should include UI elements for: + +1. **Back Button**: To navigate to previous drilldown level +2. **Reset Button**: To return to original data +3. **Navigation Indicators**: To show current drilldown level + +Example HTML structure: + +```html +
+ + +
+``` \ 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.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bar-chart/bar-chart.component.html index 0fa4df5..9841e15 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,9 +1,28 @@
- - -
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ + +
+
\ 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 1d2c277..9b8278a 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 @@ -1,33 +1,436 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-bar-chart', templateUrl: './bar-chart.component.html', styleUrls: ['./bar-chart.component.scss'] }) -export class BarChartComponent implements OnInit { +export class BarChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } - - ngOnInit(): void { - } barChartLabels: string[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes']; barChartType: string = 'bar'; - // barChartLegend = true; barChartPlugins = []; barChartData: any[] = [ { data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' } ]; + barChartLegend: boolean = true; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalBarChartLabels: string[] = []; + originalBarChartData: any[] = []; + + // No data state + noDataAvailable: boolean = false; + constructor(private dashboardService: Dashboard3Service) { } + ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); + } + + ngOnChanges(changes: SimpleChanges): void { + console.log('BarChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + + // Update legend visibility if it changed + if (changes.chartlegend !== undefined) { + this.barChartLegend = changes.chartlegend.currentValue; + console.log('Chart legend changed to:', this.barChartLegend); + } + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching bar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Get the parameter value from the drilldown stack for base level (should be empty) + let parameterValue = ''; + + // Log the URL that will be called + const url = `chart/getdashjson/bar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Bar chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'bar', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received bar chart data:', data); + if (data === null) { + console.warn('Bar chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.chartLabels.length === 0; + this.barChartLabels = data.chartLabels; + this.barChartData = data.chartData; + // Trigger change detection + this.barChartData = [...this.barChartData]; + console.log('Updated bar chart with data:', { labels: this.barChartLabels, data: this.barChartData }); + } else if (data && data.labels && data.datasets) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.labels.length === 0; + this.barChartLabels = data.labels; + this.barChartData = data.datasets; + // Trigger change detection + this.barChartData = [...this.barChartData]; + console.log('Updated bar chart with legacy data format:', { labels: this.barChartLabels, data: this.barChartData }); + } else { + console.warn('Bar chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + } + }, + (error) => { + console.error('Error fetching bar chart data:', error); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for bar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/bar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'bar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.chartLabels.length === 0; + this.barChartLabels = data.chartLabels; + this.barChartData = data.chartData; + // Trigger change detection + this.barChartData = [...this.barChartData]; + console.log('Updated bar chart with drilldown data:', { labels: this.barChartLabels, data: this.barChartData }); + } else if (data && data.labels && data.datasets) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.labels.length === 0; + this.barChartLabels = data.labels; + this.barChartData = data.datasets; + // Trigger change detection + this.barChartData = [...this.barChartData]; + console.log('Updated bar chart with drilldown legacy data format:', { labels: this.barChartLabels, data: this.barChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.barChartLabels = []; + this.barChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalBarChartLabels.length > 0) { + this.barChartLabels = [...this.originalBarChartLabels]; + console.log('Restored original labels'); + } + if (this.originalBarChartData.length > 0) { + this.barChartData = [...this.originalBarChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.barChartLabels); + console.log('After reset - data:', this.barChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } // events - public chartClicked(e: any): void { - console.log(e); - } + public chartClicked(e: any): void { + console.log('Bar chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.barChartLabels[clickedIndex]; + + console.log('Clicked on bar:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalBarChartLabels = [...this.barChartLabels]; + this.originalBarChartData = [...this.barChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } - public chartHovered(e: any): void { - console.log(e); - } - -} + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html index 5f6157d..874bbed 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.html @@ -1,9 +1,28 @@
- - -
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ + +
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts index b01a8b0..40d4e6a 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component.ts @@ -1,16 +1,37 @@ -import { Component, OnInit } from '@angular/core'; -import { ChartConfiguration, ChartDataset, ChartOptions } from 'chart.js'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChartConfiguration, ChartDataset, ChartOptions } from 'chart.js'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; + @Component({ selector: 'app-bubble-chart', templateUrl: './bubble-chart.component.html', styleUrls: ['./bubble-chart.component.scss'] }) -export class BubbleChartComponent implements OnInit { +export class BubbleChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } - - ngOnInit(): void { - } public bubbleChartOptions: ChartConfiguration['options'] = { // scales: { // x: { @@ -33,7 +54,6 @@ export class BubbleChartComponent implements OnInit { }; public bubbleChartType: string = 'bubble'; - // public bubbleChartLegend = true; public bubbleChartData: ChartDataset[] = [ { data: [ @@ -61,38 +81,432 @@ export class BubbleChartComponent implements OnInit { hoverBackgroundColor: 'yellow', hoverBorderColor: 'blue', }, - // { - // data: [ - // { x: 10, y: 10, r: 10 }, - // { x: 15, y: 5, r: 15 }, - // { x: 26, y: 12, r: 23 }, - // { x: 7, y: 8, r: 8 }, - // ], - // label: 'Investment Equities', - // backgroundColor: [ - // 'red', - // 'green', - // 'blue', - // 'purple', - // 'yellow', - // 'brown', - // 'magenta', - // 'cyan', - // 'orange', - // 'pink' - // ], - // borderColor: 'blue', - // hoverBackgroundColor: 'purple', - // hoverBorderColor: 'red', - // }, ]; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalBubbleChartData: ChartDataset[] = []; + + // No data state + noDataAvailable: boolean = false; - // events - public chartClicked(e: any): void { - console.log(e); - } + constructor(private dashboardService: Dashboard3Service) { } - public chartHovered(e: any): void { - console.log(e); - } -} + ngOnInit(): void { + this.fetchChartData(); + } + + ngOnChanges(changes: SimpleChanges): void { + console.log('BubbleChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + this.fetchChartData(); + } + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching bubble chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/bubble?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Bubble chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received bubble chart data:', data); + if (data === null) { + console.warn('Bubble chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.bubbleChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For bubble charts, we need to transform the data into bubble format + // Bubble charts expect data in the format: {x: number, y: number, r: number} + this.noDataAvailable = data.chartLabels.length === 0; + this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); + console.log('Updated bubble chart with data:', this.bubbleChartData); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.bubbleChartData = data.datasets; + console.log('Updated bubble chart with legacy data format:', this.bubbleChartData); + } else { + console.warn('Bubble chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.bubbleChartData = []; + } + }, + (error) => { + console.error('Error fetching bubble chart data:', error); + this.noDataAvailable = true; + this.bubbleChartData = []; + } + ); + } else { + console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.bubbleChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.bubbleChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.bubbleChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/bubble?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.bubbleChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For bubble charts, we need to transform the data into bubble format + // Bubble charts expect data in the format: {x: number, y: number, r: number} + this.noDataAvailable = data.chartLabels.length === 0; + this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); + console.log('Updated bubble chart with drilldown data:', this.bubbleChartData); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.bubbleChartData = data.datasets; + console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.bubbleChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.bubbleChartData = []; + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalBubbleChartData.length > 0) { + this.bubbleChartData = [...this.originalBubbleChartData]; + console.log('Restored original data'); + } + + console.log('After reset - data:', this.bubbleChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + private transformToBubbleData(labels: any[], chartData: any[]): ChartDataset[] { + // Transform the API data into bubble chart format + const datasets: ChartDataset[] = []; + + // Create a dataset for each data series + chartData.forEach((series, index) => { + // For bubble charts, we need x, y, and r values + // We'll use the labels as x values and the data as y values + // For radius (r), we'll use a default value or derive it from the data + + const bubbleData = labels.map((label, i) => { + const xValue = isNaN(Number(label)) ? i : Number(label); + const yValue = series.data && series.data[i] !== undefined ? + (isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0; + // Use a default radius or derive from data + const radius = Math.abs(yValue) > 0 ? Math.abs(yValue) / 10 : 5; + + return { + x: xValue, + y: yValue, + r: radius + }; + }); + + datasets.push({ + data: bubbleData, + label: series.label || `Series ${index + 1}`, + backgroundColor: this.getBackgroundColor(index), + borderColor: this.getBorderColor(index), + hoverBackgroundColor: this.getHoverBackgroundColor(index), + hoverBorderColor: this.getHoverBorderColor(index), + }); + }); + + return datasets; + } + + private getBackgroundColor(index: number): string { + const colors = [ + 'rgba(255, 0, 0, 0.6)', // Red + 'rgba(0, 255, 0, 0.6)', // Green + 'rgba(0, 0, 255, 0.6)', // Blue + 'rgba(255, 255, 0, 0.6)', // Yellow + 'rgba(255, 0, 255, 0.6)', // Magenta + 'rgba(0, 255, 255, 0.6)', // Cyan + ]; + return colors[index % colors.length]; + } + + private getBorderColor(index: number): string { + const colors = ['blue', 'green', 'red', 'orange', 'purple', 'cyan']; + return colors[index % colors.length]; + } + + private getHoverBackgroundColor(index: number): string { + const colors = ['purple', 'yellow', 'orange', 'red', 'blue', 'green']; + return colors[index % colors.length]; + } + + private getHoverBorderColor(index: number): string { + const colors = ['red', 'blue', 'green', 'purple', 'yellow', 'orange']; + return colors[index % colors.length]; + } + + // events + public chartClicked(e: any): void { + console.log('Bubble chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the dataset index + const datasetIndex = e.active[0].datasetIndex; + + // Get the data point + const dataPoint = this.bubbleChartData[datasetIndex].data[clickedIndex]; + + console.log('Clicked on bubble:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalBubbleChartData = JSON.parse(JSON.stringify(this.bubbleChartData)); + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // For bubble charts, we'll use the x value as the clicked value + const clickedValue = dataPoint && (dataPoint as any).x !== undefined ? + (dataPoint as any).x.toString() : ''; + + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + datasetIndex: datasetIndex, + clickedIndex: clickedIndex, + clickedValue: clickedValue + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } + + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html index cab5b29..0828c55 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.html @@ -1,8 +1,44 @@ -
- -
+
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ +

{{ charttitle }}

+
+ +
+
+

Loading chart data...

+
+ + +
+

No chart data available

+
+ + + + +
+
+
+ + {{ label }} + {{ doughnutChartData && doughnutChartData[i] !== undefined ? doughnutChartData[i] : 0 }} +
+
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.scss index e69de29..6212182 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.scss +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.scss @@ -0,0 +1,184 @@ +.doughnut-chart-container { + display: flex; + flex-direction: column; + height: 400px; + min-height: 400px; + padding: 20px; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + transition: all 0.3s ease; + border: 1px solid #eaeaea; +} + +.doughnut-chart-container:hover { + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); +} + + + +.chart-title { + font-size: 26px; + font-weight: 700; + color: #2c3e50; + margin-bottom: 20px; + text-align: center; + padding-bottom: 15px; + border-bottom: 2px solid #3498db; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.chart-wrapper { + position: relative; + flex: 1; + min-height: 250px; + margin: 15px 0; + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.chart-wrapper canvas { + max-width: 100%; + max-height: 100%; + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); +} + +.chart-wrapper canvas:hover { + filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15)); + transform: scale(1.02); + transition: all 0.3s ease; +} + +.chart-legend { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 15px; + margin-top: 20px; + padding: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border-radius: 8px; + border: 1px solid #dee2e6; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.legend-item { + display: flex; + align-items: center; + padding: 12px 20px; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 25px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + border: 1px solid #eaeaea; + cursor: pointer; +} + +.legend-item:hover { + transform: translateY(-3px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + border-color: #3498db; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); +} + +.legend-color { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 12px; + display: inline-block; + border: 2px solid white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.legend-label { + font-size: 16px; + font-weight: 600; + color: #2c3e50; + margin-right: 15px; + white-space: nowrap; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} + +.legend-value { + font-size: 16px; + font-weight: 700; + color: #3498db; + background: linear-gradient(135deg, #e9ecef 0%, #dde1e5 100%); + padding: 6px 12px; + border-radius: 12px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + min-width: 40px; + text-align: center; +} + +.loading-indicator, .no-data-message { + text-align: center; + padding: 30px; + color: #666; + font-size: 18px; + font-style: italic; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.loading-indicator p, .no-data-message p { + margin: 10px 0 0 0; +} + +.spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin-bottom: 10px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive design */ +@media (max-width: 768px) { + .doughnut-chart-container { + padding: 15px; + } + + .chart-title { + font-size: 20px; + margin-bottom: 15px; + } + + .chart-wrapper { + min-height: 200px; + } + + .chart-legend { + flex-direction: column; + align-items: center; + } + + .legend-item { + width: 100%; + max-width: 300px; + justify-content: space-between; + } + + .no-data-message { + font-size: 16px; + padding: 20px; + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.ts index 137455b..202a1e9 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component.ts @@ -1,30 +1,620 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewChecked } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-doughnut-chart', templateUrl: './doughnut-chart.component.html', styleUrls: ['./doughnut-chart.component.scss'] }) -export class DoughnutChartComponent implements OnInit { - public doughnutChartLabels: string[] = [ - "Download Sales", - "In-Store Sales", - "Mail-Order Sales" - ]; - public doughnutChartData: number[] = [350, 450, 100]; - public doughnutChartType: string = "doughnut"; +export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewChecked { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - // events - public chartClicked(e: any): void { - console.log(e); - } + public doughnutChartLabels: string[] = ["Category A", "Category B", "Category C"]; + public doughnutChartData: number[] = [30, 50, 20]; + public doughnutChartType: string = "doughnut"; + public doughnutChartOptions: any = { + responsive: true, + maintainAspectRatio: false, + cutout: '60%', // This creates the doughnut effect (Chart.js v3+ syntax) + plugins: { + legend: { + display: false // We'll create our own legend + }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleFont: { + size: 16, + color: '#fff' + }, + bodyFont: { + size: 14, + color: '#fff' + }, + cornerRadius: 4, + displayColors: false + } + }, + animation: { + animateRotate: true, + animateScale: false + }, + elements: { + arc: { + borderWidth: 2, + borderColor: '#fff' + } + } + }; + + // Chart colors for consistent styling + private chartColors: string[] = [ + '#FF6384', + '#36A2EB', + '#FFCE56', + '#4BC0C0', + '#9966FF', + '#FF9F40', + '#FF6384', + '#C9CBCF' + ]; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalDoughnutChartLabels: string[] = []; + originalDoughnutChartData: number[] = []; + + // No data state + noDataAvailable: boolean = false; - public chartHovered(e: any): void { - console.log(e); - } - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Validate initial data + this.validateChartData(); + this.fetchChartData(); + } + + /** + * Validate and sanitize chart data + */ + private validateChartData(): void { + // Ensure we have valid arrays + if (!Array.isArray(this.doughnutChartLabels)) { + this.doughnutChartLabels = []; + } + + if (!Array.isArray(this.doughnutChartData)) { + this.doughnutChartData = []; + } + + // Ensure we have some data to display + if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) { + // Add default data to ensure chart visibility + this.doughnutChartLabels = ["Category A", "Category B", "Category C"]; + this.doughnutChartData = [30, 50, 20]; + } + + // Ensure we have matching arrays + if (this.doughnutChartLabels.length !== this.doughnutChartData.length) { + const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length); + while (this.doughnutChartLabels.length < maxLength) { + this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`); + } + while (this.doughnutChartData.length < maxLength) { + this.doughnutChartData.push(0); + } + } + } + + /** + * Force chart redraw + */ + public redrawChart(): void { + // This method can be called to force a chart redraw if needed + console.log('Redrawing doughnut chart'); } -} + ngOnChanges(changes: SimpleChanges): void { + console.log('DoughnutChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + this.fetchChartData(); + } + } + + ngAfterViewChecked() { + // Debugging: Log component state after view checks + console.log('DoughnutChartComponent state:', { + labels: this.doughnutChartLabels, + data: this.doughnutChartData, + hasData: this.doughnutChartLabels.length > 0 && this.doughnutChartData.length > 0 + }); + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching doughnut chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/doughnut?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Doughnut chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'doughnut', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received doughnut chart data:', data); + if (data === null) { + console.warn('Doughnut chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For doughnut charts, we need to extract the data differently + // The first dataset's data array contains the values for the doughnut chart + this.noDataAvailable = data.chartLabels.length === 0; + this.doughnutChartLabels = data.chartLabels || []; + if (data.chartData && data.chartData.length > 0) { + this.doughnutChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + } else { + this.doughnutChartData = []; + } + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.doughnutChartData = [...this.doughnutChartData]; + console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.doughnutChartLabels = data.labels || []; + this.doughnutChartData = data.data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.doughnutChartData = [...this.doughnutChartData]; + console.log('Updated doughnut chart with legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); + } else { + console.warn('Doughnut chart received data does not have expected structure', data); + // Reset to default data + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + // Validate and sanitize data + this.validateChartData(); + } + + // Log final state for debugging + console.log('Final doughnut chart state:', { + labels: this.doughnutChartLabels, + data: this.doughnutChartData, + labelsLength: this.doughnutChartLabels.length, + dataLength: this.doughnutChartData.length + }); + }, + (error) => { + console.error('Error fetching doughnut chart data:', error); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + } + ); + } else { + console.log('Missing required data for doughnut chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + // Don't set noDataAvailable to true when there's no required data + // This allows static data to be displayed + // Only validate the chart data to ensure we have some data to display + this.validateChartData(); + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/doughnut?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'doughnut', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For doughnut charts, we need to extract the data differently + // The first dataset's data array contains the values for the doughnut chart + this.noDataAvailable = data.chartLabels.length === 0; + this.doughnutChartLabels = data.chartLabels || []; + if (data.chartData && data.chartData.length > 0) { + this.doughnutChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + } else { + this.doughnutChartData = []; + } + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.doughnutChartData = [...this.doughnutChartData]; + console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.doughnutChartLabels = data.labels || []; + this.doughnutChartData = data.data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.doughnutChartData = [...this.doughnutChartData]; + console.log('Updated doughnut chart with drilldown legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + // Validate and sanitize data + this.validateChartData(); + } + + // Log final state for debugging + console.log('Final doughnut chart state:', { + labels: this.doughnutChartLabels, + data: this.doughnutChartData, + labelsLength: this.doughnutChartLabels.length, + dataLength: this.doughnutChartData.length + }); + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.doughnutChartLabels = []; + this.doughnutChartData = []; + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalDoughnutChartLabels.length > 0) { + this.doughnutChartLabels = [...this.originalDoughnutChartLabels]; + console.log('Restored original labels'); + } + if (this.originalDoughnutChartData.length > 0) { + this.doughnutChartData = [...this.originalDoughnutChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.doughnutChartLabels); + console.log('After reset - data:', this.doughnutChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + /** + * Get color for legend item + * @param index Index of the legend item + */ + public getLegendColor(index: number): string { + return this.chartColors[index % this.chartColors.length]; + } + + /** + * Ensure labels and data arrays have the same length + */ + private syncLabelAndDataArrays(): void { + // Handle empty arrays + if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) { + return; + } + + const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length); + + // Pad the shorter array with default values + while (this.doughnutChartLabels.length < maxLength) { + this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`); + } + + while (this.doughnutChartData.length < maxLength) { + this.doughnutChartData.push(0); + } + + // Truncate the longer array if needed + if (this.doughnutChartLabels.length > maxLength) { + this.doughnutChartLabels = this.doughnutChartLabels.slice(0, maxLength); + } + + if (this.doughnutChartData.length > maxLength) { + this.doughnutChartData = this.doughnutChartData.slice(0, maxLength); + } + } + + // events + public chartClicked(e: any): void { + console.log('Doughnut chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.doughnutChartLabels[clickedIndex]; + + console.log('Clicked on doughnut slice:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalDoughnutChartLabels = [...this.doughnutChartLabels]; + this.originalDoughnutChartData = [...this.doughnutChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } + + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.html index e00b379..ea261c3 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.html @@ -1,10 +1,38 @@ -
- - -
- +
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ +

{{ charttitle }}

+
+ +
+
+

Loading chart data...

+
+ + +
+

No chart data available

+
+ + + + +
+ +
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.ts index 2173924..856d9e0 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component.ts @@ -1,18 +1,71 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { ChartConfiguration, ChartData, } from 'chart.js'; +import { Component, OnInit, ViewChild, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ChartConfiguration, ChartData, ChartDataset } from 'chart.js'; import { BaseChartDirective } from 'ng2-charts'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; + @Component({ selector: 'app-dynamic-chart', templateUrl: './dynamic-chart.component.html', styleUrls: ['./dynamic-chart.component.scss'] }) -export class DynamicChartComponent implements OnInit { +export class DynamicChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } + @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined; + + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); + } + + ngOnChanges(changes: SimpleChanges): void { + console.log('DynamicChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } } - @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined; public barChartOptions: ChartConfiguration['options'] = { elements: { @@ -34,37 +87,369 @@ export class DynamicChartComponent implements OnInit { public dynamicChartLabels: string[] = [ '2006', '2007', '2008', '2009', '2010', '2011', '2012' ]; public barChartType: string = 'bar'; - // public barChartData: ChartData<'bar'> = { - // labels: this.barChartLabels, - // datasets: [ - // { data: [ 65, 59, 80, 81, 56, 55, 40 ], label: 'Series A' }, - // { data: [ 28, 48, 40, 19, 86, 27, 90 ], label: 'Series B' } - // ] - // }; - - public dynamicChartData: any = [ { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } ]; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalDynamicChartLabels: string[] = []; + originalDynamicChartData: any = []; + + // No data state + noDataAvailable: boolean = false; + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching dynamic chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/dynamic?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Dynamic chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'dynamic', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received dynamic chart data:', data); + if (data === null) { + console.warn('Dynamic chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.dynamicChartLabels = data.chartLabels; + this.dynamicChartData = data.chartData; + // Trigger change detection + this.dynamicChartData = [...this.dynamicChartData]; + console.log('Updated dynamic chart with data:', { labels: this.dynamicChartLabels, data: this.dynamicChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.dynamicChartLabels = data.labels; + this.dynamicChartData = data.datasets; + // Trigger change detection + this.dynamicChartData = [...this.dynamicChartData]; + console.log('Updated dynamic chart with legacy data format:', { labels: this.dynamicChartLabels, data: this.dynamicChartData }); + } else { + console.warn('Dynamic chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + } + }, + (error) => { + console.error('Error fetching dynamic chart data:', error); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for dynamic chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/dynamic?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'dynamic', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.dynamicChartLabels = data.chartLabels; + this.dynamicChartData = data.chartData; + // Trigger change detection + this.dynamicChartData = [...this.dynamicChartData]; + console.log('Updated dynamic chart with drilldown data:', { labels: this.dynamicChartLabels, data: this.dynamicChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.dynamicChartLabels = data.labels; + this.dynamicChartData = data.datasets; + // Trigger change detection + this.dynamicChartData = [...this.dynamicChartData]; + console.log('Updated dynamic chart with drilldown legacy data format:', { labels: this.dynamicChartLabels, data: this.dynamicChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.dynamicChartLabels = []; + this.dynamicChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalDynamicChartLabels.length > 0) { + this.dynamicChartLabels = [...this.originalDynamicChartLabels]; + console.log('Restored original labels'); + } + if (this.originalDynamicChartData.length > 0) { + this.dynamicChartData = [...this.originalDynamicChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.dynamicChartLabels); + console.log('After reset - data:', this.dynamicChartData); + + // Re-fetch original data + this.fetchChartData(); + } + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + // events public chartClicked(e: any): void { - console.log(e); - } + console.log('Dynamic chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element (if available) + let clickedLabel = ''; + if (this.dynamicChartLabels && this.dynamicChartLabels[clickedIndex]) { + clickedLabel = this.dynamicChartLabels[clickedIndex]; + } + + console.log('Clicked on dynamic chart element:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalDynamicChartLabels = [...this.dynamicChartLabels]; + this.originalDynamicChartData = [...this.dynamicChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } public chartHovered(e: any): void { console.log(e); } - // public chartClicked({ event, active }: { event?: ChartEvent, active?: {}[] }): void { - // console.log(event, active); - // } - - // public chartHovered({ event, active }: { event?: ChartEvent, active?: {}[] }): void { - // console.log(event, active); - // } - + public randomize(): void { this.barChartType = this.barChartType === 'bar' ? 'line' : 'bar'; } -} +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.html index 56547d5..6df8f0e 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.html @@ -1 +1,36 @@ -

financial-chart works!

+
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ +

{{ charttitle }}

+
+ +
+
+

Loading chart data...

+
+ + +
+

No chart data available

+
+ + + + +
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.scss index e69de29..03eca40 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.scss +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.scss @@ -0,0 +1,108 @@ +.financial-chart-container { + display: flex; + flex-direction: column; + height: 400px; + min-height: 400px; + padding: 20px; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + transition: all 0.3s ease; + border: 1px solid #eaeaea; +} + +.financial-chart-container:hover { + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); +} + +.chart-title { + font-size: 26px; + font-weight: 700; + color: #2c3e50; + margin-bottom: 20px; + text-align: center; + padding-bottom: 15px; + border-bottom: 2px solid #3498db; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.chart-wrapper { + position: relative; + flex: 1; + min-height: 250px; + margin: 15px 0; + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.chart-wrapper canvas { + max-width: 100%; + max-height: 100%; + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); +} + +.chart-wrapper canvas:hover { + filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15)); + transform: scale(1.02); + transition: all 0.3s ease; +} + +.loading-indicator, .no-data-message { + text-align: center; + padding: 30px; + color: #666; + font-size: 18px; + font-style: italic; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.loading-indicator p, .no-data-message p { + margin: 10px 0 0 0; +} + +.spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin-bottom: 10px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive design */ +@media (max-width: 768px) { + .financial-chart-container { + padding: 15px; + } + + .chart-title { + font-size: 20px; + margin-bottom: 15px; + } + + .chart-wrapper { + min-height: 200px; + } + + .no-data-message { + font-size: 16px; + padding: 20px; + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.ts index 664e7cd..4e957cd 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/financial-chart/financial-chart.component.ts @@ -1,15 +1,454 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-financial-chart', templateUrl: './financial-chart.component.html', styleUrls: ['./financial-chart.component.scss'] }) -export class FinancialChartComponent implements OnInit { +export class FinancialChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); + } + + ngOnChanges(changes: SimpleChanges): void { + console.log('FinancialChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + } + + // Default financial chart data + public financialChartLabels: string[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; + public financialChartData: any[] = [ + { data: [65, 59, 80, 81, 56, 55, 40], label: 'Revenue' }, + { data: [28, 48, 40, 19, 86, 27, 90], label: 'Expenses' } + ]; + public financialChartType: string = 'line'; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalFinancialChartLabels: string[] = []; + originalFinancialChartData: any[] = []; + + // No data state + noDataAvailable: boolean = false; + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching financial chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/financial?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Financial chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'financial', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received financial chart data:', data); + if (data === null) { + console.warn('Financial chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.financialChartLabels = data.chartLabels; + this.financialChartData = data.chartData.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.financialChartData = [...this.financialChartData]; + console.log('Updated financial chart with data:', { labels: this.financialChartLabels, data: this.financialChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.financialChartLabels = data.labels; + this.financialChartData = data.datasets.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.financialChartData = [...this.financialChartData]; + console.log('Updated financial chart with legacy data format:', { labels: this.financialChartLabels, data: this.financialChartData }); + } else { + console.warn('Financial chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + } + }, + (error) => { + console.error('Error fetching financial chart data:', error); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for financial chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/financial?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'financial', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.financialChartLabels = data.chartLabels; + this.financialChartData = data.chartData.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.financialChartData = [...this.financialChartData]; + console.log('Updated financial chart with drilldown data:', { labels: this.financialChartLabels, data: this.financialChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.financialChartLabels = data.labels; + this.financialChartData = data.datasets.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.financialChartData = [...this.financialChartData]; + console.log('Updated financial chart with drilldown legacy data format:', { labels: this.financialChartLabels, data: this.financialChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.financialChartLabels = []; + this.financialChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalFinancialChartLabels.length > 0) { + this.financialChartLabels = [...this.originalFinancialChartLabels]; + console.log('Restored original labels'); + } + if (this.originalFinancialChartData.length > 0) { + this.financialChartData = [...this.originalFinancialChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.financialChartLabels); + console.log('After reset - data:', this.financialChartData); + + // Re-fetch original data + this.fetchChartData(); } -} + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + // events + public chartClicked(e: any): void { + console.log('Financial chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element (if available) + let clickedLabel = ''; + if (this.financialChartLabels && this.financialChartLabels[clickedIndex]) { + clickedLabel = this.financialChartLabels[clickedIndex]; + } + + console.log('Clicked on financial chart element:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalFinancialChartLabels = [...this.financialChartLabels]; + this.originalFinancialChartData = [...this.financialChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } + + public chartHovered(e: any): void { + console.log(e); + } +} \ 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 94c1edc..2aad01b 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 @@ -1,62 +1,29 @@
-
+
-

User Group Maintenance

+

{{charttitle || 'Data Grid'}}

Loading ... -
{{error}}
- - - User Group No - - - Group Name - - - Description - - - Group Level - - - Status - - - - Updated Date - - - - - {{user.usrGrp}} - {{user.groupName}} - {{user.groupDesc}} - {{user.groupLevel}} - {{user.status}} - - {{user.updateDateFormated}} - - - - +
{{error}}
+ + + + + + {{header.displayName}} + + + + + + + {{item[header.key]}} + - + Record per page @@ -65,5 +32,5 @@
-
-
\ No newline at end of file +
+
\ 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.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.ts index dfb2de1..06f3023 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/grid-view/grid-view.component.ts @@ -1,54 +1,178 @@ -import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ExcelService } from 'src/app/services/excel.service'; -import * as moment from 'moment'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service'; -import { ToastrService } from 'ngx-toastr'; -import { MenuGroupService } from 'src/app/services/admin/menu-group.service'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; + @Component({ selector: 'app-grid-view', templateUrl: './grid-view.component.html', styleUrls: ['./grid-view.component.scss'] }) -export class GridViewComponent implements OnInit { +export class GridViewComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + loading = false; - public entryForm: FormGroup; - givendata; + givendata: any[] = []; orders; - error; - modalAdd= false; - modaledit=false; - modaldelete=false; - rowSelected :any= {}; + error: string; + modalAdd = false; + modaledit = false; + modaldelete = false; + rowSelected: any = {}; mcreate; medit; showdata; - submitted=false; + submitted = false; + dynamicHeaders: any[] = []; constructor( - private excel: ExcelService, - private toastr:ToastrService, - private _fb: FormBuilder, - private router: Router, - private route: ActivatedRoute, - private menuGroupService: MenuGroupService, - private mainservice:UsergrpmaintainceService, + + private mainservice: UsergrpmaintainceService, + private dashboardService: Dashboard3Service, ) { } ngOnInit(): void { - this.mainservice.getAll().subscribe((data) => { - console.log(data); - this.givendata = data; - if(this.givendata.length==0){ - this.error="No data Available"; - console.log(this.error) - } - },(error) => { - console.log(error); - if(error){ - this.error="Server Error"; - } - }); + this.fetchGridData(); } -} + + ngOnChanges(changes: SimpleChanges): void { + console.log('GridViewComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) { + console.log('X or Y axis or table or connection changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change) + this.fetchGridData(); + } + } + + // Dynamic headers for the grid + + fetchGridData(): void { + // If we have the necessary data, fetch grid data from the service + if (this.table && this.xAxis) { + console.log('Fetching grid data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Fetch data from the dashboard service, similar to other chart components + this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe( + (data: any) => { + console.log('Received grid data:', data); + if (data === null) { + console.warn('Grid API returned null data. Check if the API endpoint is working correctly.'); + this.error = "No data Available"; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartData) { + this.givendata = data.chartData; + this.extractDynamicHeaders(data.chartData); + this.error = this.givendata.length === 0 ? "No data Available" : undefined; + console.log('Updated grid with data:', this.givendata); + } else if (data && data.data) { + // Handle the original expected format as fallback + this.givendata = data.data; + this.extractDynamicHeaders(data.data); + this.error = this.givendata.length === 0 ? "No data Available" : undefined; + console.log('Updated grid with legacy data format:', this.givendata); + } else if (Array.isArray(data)) { + // Handle case where data is directly an array + this.givendata = data; + this.extractDynamicHeaders(data); + this.error = this.givendata.length === 0 ? "No data Available" : undefined; + console.log('Updated grid with array data:', this.givendata); + } else { + console.warn('Grid received data does not have expected structure', data); + this.error = "No valid data received"; + this.givendata = []; + } + }, (error) => { + console.log('Error fetching grid data:', error); + this.error = "Server Error"; + }); + } else if (this.table) { + console.log('Missing xAxis, falling back to default data fetching'); + // Fallback to default data fetching when only table is provided + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Fetch data from the dashboard service, similar to other chart components + this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe( + (data: any) => { + // this.mainservice.getAll().subscribe((data: any) => { + console.log('recv data ', data); + this.givendata = Array.isArray(data) ? data : []; + this.extractDynamicHeaders(data); + this.error = this.givendata && this.givendata.length === 0 ? "No data Available" : undefined; + }, (error) => { + console.log(error); + this.error = "Server Error"; + }); + } else { + console.log('Missing required data for grid:', { table: this.table }); + this.error = "Table name is required"; + } + } + + /** + * Extract dynamic headers from the data + * @param data Array of data objects + */ + private extractDynamicHeaders(data: any): void { + // Ensure data is an array + const dataArray = Array.isArray(data) ? data : []; + + if (dataArray && dataArray.length > 0) { + // Get all unique keys from the data objects + const allKeys = new Set(); + dataArray.forEach(item => { + if (item && typeof item === 'object') { + Object.keys(item).forEach(key => allKeys.add(key)); + } + }); + + // Convert to array of header objects with key and display name + this.dynamicHeaders = Array.from(allKeys).map(key => ({ + key: key, + displayName: this.formatHeader(key) + })); + + console.log('Dynamic headers extracted:', this.dynamicHeaders); + } else { + this.dynamicHeaders = []; + } + } + + /** + * Format header name for better display + * @param key The key to format + */ + private formatHeader(key: string): string { + // Convert camelCase to Title Case + return key + .replace(/([A-Z])/g, ' $1') + .replace(/^./, str => str.toUpperCase()); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.html index 18c620d..63edfd9 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.html @@ -1,12 +1,31 @@ -
- +
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ +
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.ts index 3eaad81..13c2e54 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/line-chart/line-chart.component.ts @@ -1,71 +1,474 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-line-chart', templateUrl: './line-chart.component.html', styleUrls: ['./line-chart.component.scss'] }) -export class LineChartComponent implements OnInit { - public lineChartData:Array = [ - {data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'}, - {data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'}, - {data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'} - ]; - public lineChartLabels:Array = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; - public lineChartOptions:any = { - responsive: true - }; - public lineChartColors:Array = [ - { // grey - backgroundColor: 'rgba(148,159,177,0.2)', - borderColor: 'rgba(148,159,177,1)', - pointBackgroundColor: 'rgba(148,159,177,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(148,159,177,0.8)' - }, - { // dark grey - backgroundColor: 'rgba(77,83,96,0.2)', - borderColor: 'rgba(77,83,96,1)', - pointBackgroundColor: 'rgba(77,83,96,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(77,83,96,1)' - }, - { // grey - backgroundColor: 'rgba(148,159,177,0.2)', - borderColor: 'rgba(148,159,177,1)', - pointBackgroundColor: 'rgba(148,159,177,1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(148,159,177,0.8)' - } - ]; - public lineChartLegend:boolean = true; - public lineChartType:string = 'line'; +export class LineChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - public randomize():void { - let _lineChartData:Array = new Array(this.lineChartData.length); - for (let i = 0; i < this.lineChartData.length; i++) { - _lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label}; - for (let j = 0; j < this.lineChartData[i].data.length; j++) { - _lineChartData[i].data[j] = Math.floor((Math.random() * 100) + 1); - } - } - this.lineChartData = _lineChartData; - } + public lineChartData: Array = [ + {data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'}, + {data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'}, + {data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'} + ]; + public lineChartLabels: Array = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; + public lineChartOptions: any = { + responsive: true + }; + public lineChartColors: Array = [ + { // grey + backgroundColor: 'rgba(148,159,177,0.2)', + borderColor: 'rgba(148,159,177,1)', + pointBackgroundColor: 'rgba(148,159,177,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(148,159,177,0.8)' + }, + { // dark grey + backgroundColor: 'rgba(77,83,96,0.2)', + borderColor: 'rgba(77,83,96,1)', + pointBackgroundColor: 'rgba(77,83,96,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(77,83,96,1)' + }, + { // grey + backgroundColor: 'rgba(148,159,177,0.2)', + borderColor: 'rgba(148,159,177,1)', + pointBackgroundColor: 'rgba(148,159,177,1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(148,159,177,0.8)' + } + ]; + public lineChartLegend: boolean = true; + public lineChartType: string = 'line'; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalLineChartLabels: Array = []; + originalLineChartData: Array = []; + + // No data state + noDataAvailable: boolean = false; - // events - public chartClicked(e:any):void { - console.log(e); - } - - public chartHovered(e:any):void { - console.log(e); - } - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); } -} + ngOnChanges(changes: SimpleChanges): void { + console.log('LineChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + + // Update legend visibility if it changed + if (changes.chartlegend !== undefined) { + this.lineChartLegend = changes.chartlegend.currentValue; + console.log('Chart legend changed to:', this.lineChartLegend); + } + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/line?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'line', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received chart data:', data); + if (data === null) { + console.warn('API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.chartLabels.length === 0; + this.lineChartLabels = data.chartLabels; + this.lineChartData = data.chartData; + // Trigger change detection + this.lineChartData = [...this.lineChartData]; + console.log('Updated line chart with data:', { labels: this.lineChartLabels, data: this.lineChartData }); + } else if (data && data.labels && data.datasets) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.labels.length === 0; + this.lineChartLabels = data.labels; + this.lineChartData = data.datasets; + // Trigger change detection + this.lineChartData = [...this.lineChartData]; + console.log('Updated line chart with legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData }); + } else { + console.warn('Received data does not have expected structure', data); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + } + }, + (error) => { + console.error('Error fetching chart data:', error); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/line?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'line', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.chartLabels.length === 0; + this.lineChartLabels = data.chartLabels; + this.lineChartData = data.chartData; + // Trigger change detection + this.lineChartData = [...this.lineChartData]; + console.log('Updated line chart with drilldown data:', { labels: this.lineChartLabels, data: this.lineChartData }); + } else if (data && data.labels && data.datasets) { + // Backend has already filtered the data, just display it + this.noDataAvailable = data.labels.length === 0; + this.lineChartLabels = data.labels; + this.lineChartData = data.datasets; + // Trigger change detection + this.lineChartData = [...this.lineChartData]; + console.log('Updated line chart with drilldown legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.lineChartLabels = []; + this.lineChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalLineChartLabels.length > 0) { + this.lineChartLabels = [...this.originalLineChartLabels]; + console.log('Restored original labels'); + } + if (this.originalLineChartData.length > 0) { + this.lineChartData = [...this.originalLineChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.lineChartLabels); + console.log('After reset - data:', this.lineChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + public randomize(): void { + let _lineChartData: Array = new Array(this.lineChartData.length); + for (let i = 0; i < this.lineChartData.length; i++) { + _lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label}; + for (let j = 0; j < this.lineChartData[i].data.length; j++) { + _lineChartData[i].data[j] = Math.floor((Math.random() * 100) + 1); + } + } + this.lineChartData = _lineChartData; + } + + // events + public chartClicked(e: any): void { + console.log('Line chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.lineChartLabels[clickedIndex]; + + console.log('Clicked on line point:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalLineChartLabels = [...this.lineChartLabels]; + this.originalLineChartData = [...this.lineChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } + + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.html index ef42eed..87ceda0 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.html @@ -1,9 +1,44 @@ -
- - -
+
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ +

{{ charttitle }}

+
+ +
+
+

Loading chart data...

+
+ + +
+

No chart data available

+
+ + + + +
+
+
+ + {{ label }} + {{ pieChartData && pieChartData[i] !== undefined ? pieChartData[i] : 0 }} +
+
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.scss index e69de29..ba07969 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.scss +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.scss @@ -0,0 +1,182 @@ +.pie-chart-container { + display: flex; + flex-direction: column; + height: 400px; + min-height: 400px; + padding: 20px; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + transition: all 0.3s ease; + border: 1px solid #eaeaea; +} + +.pie-chart-container:hover { + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); +} + +.chart-title { + font-size: 26px; + font-weight: 700; + color: #2c3e50; + margin-bottom: 20px; + text-align: center; + padding-bottom: 15px; + border-bottom: 2px solid #3498db; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.chart-wrapper { + position: relative; + flex: 1; + min-height: 250px; + margin: 15px 0; + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.chart-wrapper canvas { + max-width: 100%; + max-height: 100%; + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); +} + +.chart-wrapper canvas:hover { + filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15)); + transform: scale(1.02); + transition: all 0.3s ease; +} + +.chart-legend { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 15px; + margin-top: 20px; + padding: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border-radius: 8px; + border: 1px solid #dee2e6; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.legend-item { + display: flex; + align-items: center; + padding: 12px 20px; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 25px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + border: 1px solid #eaeaea; + cursor: pointer; +} + +.legend-item:hover { + transform: translateY(-3px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + border-color: #3498db; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); +} + +.legend-color { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 12px; + display: inline-block; + border: 2px solid white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.legend-label { + font-size: 16px; + font-weight: 600; + color: #2c3e50; + margin-right: 15px; + white-space: nowrap; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} + +.legend-value { + font-size: 16px; + font-weight: 700; + color: #3498db; + background: linear-gradient(135deg, #e9ecef 0%, #dde1e5 100%); + padding: 6px 12px; + border-radius: 12px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + min-width: 40px; + text-align: center; +} + +.loading-indicator, .no-data-message { + text-align: center; + padding: 30px; + color: #666; + font-size: 18px; + font-style: italic; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +} + +.loading-indicator p, .no-data-message p { + margin: 10px 0 0 0; +} + +.spinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + margin-bottom: 10px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive design */ +@media (max-width: 768px) { + .pie-chart-container { + padding: 15px; + } + + .chart-title { + font-size: 20px; + margin-bottom: 15px; + } + + .chart-wrapper { + min-height: 200px; + } + + .chart-legend { + flex-direction: column; + align-items: center; + } + + .legend-item { + width: 100%; + max-width: 300px; + justify-content: space-between; + } + + .no-data-message { + font-size: 16px; + padding: 20px; + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.ts index a6647a6..1bbe120 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/pie-chart/pie-chart.component.ts @@ -1,27 +1,613 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewChecked } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-pie-chart', templateUrl: './pie-chart.component.html', styleUrls: ['./pie-chart.component.scss'] }) -export class PieChartComponent implements OnInit { +export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } - - ngOnInit(): void { - } - public pieChartLabels: string[] = ['SciFi', 'Drama', 'Comedy']; + public pieChartLabels: string[] = ['Category A', 'Category B', 'Category C']; public pieChartData: number[] = [30, 50, 20]; public pieChartType: string = 'pie'; + public pieChartOptions: any = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false // We'll create our own legend + }, + tooltip: { + enabled: true, + mode: 'index', + intersect: false, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleFont: { + size: 16, + color: '#fff' + }, + bodyFont: { + size: 14, + color: '#fff' + }, + cornerRadius: 4, + displayColors: false + } + }, + animation: { + animateRotate: true, + animateScale: false + }, + elements: { + arc: { + borderWidth: 2, + borderColor: '#fff' + } + } + }; + + // Chart colors for consistent styling + private chartColors: string[] = [ + '#FF6384', + '#36A2EB', + '#FFCE56', + '#4BC0C0', + '#9966FF', + '#FF9F40', + '#FF6384', + '#C9CBCF' + ]; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalPieChartLabels: string[] = []; + originalPieChartData: number[] = []; + + // No data state + noDataAvailable: boolean = false; + constructor(private dashboardService: Dashboard3Service) { } + + /** + * Force chart redraw + */ + public redrawChart(): void { + // This method can be called to force a chart redraw if needed + console.log('Redrawing pie chart'); + this.pieChartData = [...this.pieChartData]; + } + + ngOnInit(): void { + console.log('PieChartComponent initialized with default data:', { labels: this.pieChartLabels, data: this.pieChartData }); + // Validate initial data + this.validateChartData(); + this.fetchChartData(); + } + + ngOnChanges(changes: SimpleChanges): void { + console.log('PieChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching pie chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/pie?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Pie chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'pie', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received pie chart data:', data); + if (data === null) { + console.warn('Pie chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + // Validate and sanitize data to show default data + this.validateChartData(); + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For pie charts, we need to extract the data differently + // The first dataset's data array contains the values for the pie chart + this.noDataAvailable = data.chartLabels.length === 0; + this.pieChartLabels = data.chartLabels || []; + if (data.chartData && data.chartData.length > 0) { + this.pieChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + } else { + this.pieChartData = []; + } + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.pieChartData = [...this.pieChartData]; + console.log('Updated pie chart with data:', { labels: this.pieChartLabels, data: this.pieChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.pieChartLabels = data.labels || []; + this.pieChartData = data.data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.pieChartData = [...this.pieChartData]; + console.log('Updated pie chart with legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData }); + } else { + console.warn('Pie chart received data does not have expected structure', data); + // Reset to default data + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + // Validate and sanitize data to show default data + this.validateChartData(); + } + }, + (error) => { + console.error('Error fetching pie chart data:', error); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + // Validate and sanitize data to show default data + this.validateChartData(); + } + ); + } else { + console.log('Missing required data for pie chart, showing default data:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + // Don't set noDataAvailable to true when there's no required data + // This allows static data to be displayed + this.noDataAvailable = false; + // Validate the chart data to ensure we have some data to display + this.validateChartData(); + // Force a redraw to ensure the chart displays + this.pieChartData = [...this.pieChartData]; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/pie?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'pie', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For pie charts, we need to extract the data differently + // The first dataset's data array contains the values for the pie chart + this.noDataAvailable = data.chartLabels.length === 0; + this.pieChartLabels = data.chartLabels || []; + if (data.chartData && data.chartData.length > 0) { + this.pieChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + } else { + this.pieChartData = []; + } + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.pieChartData = [...this.pieChartData]; + console.log('Updated pie chart with drilldown data:', { labels: this.pieChartLabels, data: this.pieChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.pieChartLabels = data.labels || []; + this.pieChartData = data.data.map(value => { + // Convert to number if it's not already + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + // Validate and sanitize data + this.validateChartData(); + // Trigger change detection + this.pieChartData = [...this.pieChartData]; + console.log('Updated pie chart with drilldown legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + // Validate and sanitize data + this.validateChartData(); + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.pieChartLabels = []; + this.pieChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalPieChartLabels.length > 0) { + this.pieChartLabels = [...this.originalPieChartLabels]; + console.log('Restored original labels'); + } + if (this.originalPieChartData.length > 0) { + this.pieChartData = [...this.originalPieChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.pieChartLabels); + console.log('After reset - data:', this.pieChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + /** + * Get color for legend item + * @param index Index of the legend item + */ + public getLegendColor(index: number): string { + return this.chartColors[index % this.chartColors.length]; + } + + /** + * Ensure labels and data arrays have the same length + */ + private syncLabelAndDataArrays(): void { + // Ensure we have matching arrays + if (this.pieChartLabels.length !== this.pieChartData.length) { + const maxLength = Math.max(this.pieChartLabels.length, this.pieChartData.length); + while (this.pieChartLabels.length < maxLength) { + this.pieChartLabels.push(`Label ${this.pieChartLabels.length + 1}`); + } + while (this.pieChartData.length < maxLength) { + this.pieChartData.push(0); + } + } + } + + /** + * Validate and sanitize chart data + */ + private validateChartData(): void { + console.log('Validating chart data:', { labels: this.pieChartLabels, data: this.pieChartData }); + + // Ensure we have valid arrays + if (!Array.isArray(this.pieChartLabels)) { + this.pieChartLabels = []; + } + + if (!Array.isArray(this.pieChartData)) { + this.pieChartData = []; + } + + // Ensure we have some data to display + if (this.pieChartLabels.length === 0 && this.pieChartData.length === 0) { + // Add default data to ensure chart visibility + this.pieChartLabels = ['Category A', 'Category B', 'Category C']; + this.pieChartData = [30, 50, 20]; + console.log('Added default data for chart display'); + } + + // Ensure labels and data arrays have the same length + this.syncLabelAndDataArrays(); + + // Ensure all data values are numbers + this.pieChartData = this.pieChartData.map(value => { + const numValue = Number(value); + return isNaN(numValue) ? 0 : numValue; + }); + + console.log('After validation:', { labels: this.pieChartLabels, data: this.pieChartData }); + } + + ngAfterViewChecked() { + // Debugging: Log component state after view checks + console.log('PieChartComponent state:', { + labels: this.pieChartLabels, + data: this.pieChartData, + hasData: this.pieChartLabels.length > 0 && this.pieChartData.length > 0 + }); + } + + /** + * Check if chart data is valid and ready to display + */ + public isChartDataValid(): boolean { + return this.pieChartLabels && this.pieChartData && + Array.isArray(this.pieChartLabels) && Array.isArray(this.pieChartData) && + this.pieChartLabels.length > 0 && this.pieChartData.length > 0 && + this.pieChartLabels.length === this.pieChartData.length; + } + // events - public chartClicked(e: any): void { - console.log(e); - } + public chartClicked(e: any): void { + console.log('Pie chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.pieChartLabels[clickedIndex]; + + console.log('Clicked on pie slice:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalPieChartLabels = [...this.pieChartLabels]; + this.originalPieChartData = [...this.pieChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } - public chartHovered(e: any): void { - console.log(e); - } - -} + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.html index 421e078..caac3dc 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.html @@ -1,10 +1,28 @@ -
- - -
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ + +
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.ts index 56652fe..bc7d9dc 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/polar-chart/polar-chart.component.ts @@ -1,37 +1,449 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-polar-chart', templateUrl: './polar-chart.component.html', styleUrls: ['./polar-chart.component.scss'] }) -export class PolarChartComponent implements OnInit { +export class PolarChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); } + + ngOnChanges(changes: SimpleChanges): void { + console.log('PolarChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + } + public polarAreaChartLabels: string[] = [ 'Download Sales', 'In-Store Sales', 'Mail Sales', 'Telesales', 'Corporate Sales' ]; public polarAreaChartData: any = [ { data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'} ]; public polarAreaChartType: string = 'polarArea'; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalPolarAreaChartLabels: string[] = []; + originalPolarAreaChartData: any = []; + + // No data state + noDataAvailable: boolean = false; + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching polar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/polar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Polar chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'polar', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received polar chart data:', data); + if (data === null) { + console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For polar charts, we need to extract the data differently + // The first dataset's data array contains the values for the polar chart + this.noDataAvailable = data.chartLabels.length === 0; + this.polarAreaChartLabels = data.chartLabels; + if (data.chartData && data.chartData.length > 0) { + this.polarAreaChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }); + } else { + this.polarAreaChartData = []; + } + // Trigger change detection + this.polarAreaChartData = [...this.polarAreaChartData]; + console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.polarAreaChartLabels = data.labels; + this.polarAreaChartData = data.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }); + // Trigger change detection + this.polarAreaChartData = [...this.polarAreaChartData]; + console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); + } else { + console.warn('Polar chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + } + }, + (error) => { + console.error('Error fetching polar chart data:', error); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/polar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'polar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For polar charts, we need to extract the data differently + // The first dataset's data array contains the values for the polar chart + this.noDataAvailable = data.chartLabels.length === 0; + this.polarAreaChartLabels = data.chartLabels; + if (data.chartData && data.chartData.length > 0) { + this.polarAreaChartData = data.chartData[0].data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }); + } else { + this.polarAreaChartData = []; + } + // Trigger change detection + this.polarAreaChartData = [...this.polarAreaChartData]; + console.log('Updated polar chart with drilldown data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); + } else if (data && data.labels && data.data) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.polarAreaChartLabels = data.labels; + this.polarAreaChartData = data.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }); + // Trigger change detection + this.polarAreaChartData = [...this.polarAreaChartData]; + console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.polarAreaChartLabels = []; + this.polarAreaChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalPolarAreaChartLabels.length > 0) { + this.polarAreaChartLabels = [...this.originalPolarAreaChartLabels]; + console.log('Restored original labels'); + } + if (this.originalPolarAreaChartData.length > 0) { + this.polarAreaChartData = [...this.originalPolarAreaChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.polarAreaChartLabels); + console.log('After reset - data:', this.polarAreaChartData); + + // Re-fetch original data + this.fetchChartData(); + } - - - // public radarChartData: any = [ - // { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, - // { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } - // ]; + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } // events public chartClicked(e: any): void { - console.log(e); + console.log('Polar chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.polarAreaChartLabels[clickedIndex]; + + console.log('Clicked on polar slice:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalPolarAreaChartLabels = [...this.polarAreaChartLabels]; + this.originalPolarAreaChartData = [...this.polarAreaChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } } public chartHovered(e: any): void { console.log(e); } -} +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.html index 3b26b0a..50f80cd 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.html @@ -1,8 +1,28 @@
- -
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ + +
+
\ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.ts index 3c23092..a5906e5 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/radar-chart/radar-chart.component.ts @@ -1,39 +1,460 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-radar-chart', templateUrl: './radar-chart.component.html', styleUrls: ['./radar-chart.component.scss'] }) -export class RadarChartComponent implements OnInit { -// Radar -public radarChartLabels: string[] = [ - "Eating", - "Drinking", - "Sleeping", - "Designing", - "Coding", - "Cycling", - "Running" -]; +export class RadarChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations -public radarChartData: any = [ - { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, - { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } -]; -public radarChartType: string = "radar"; + // Radar + public radarChartLabels: string[] = [ + "Eating", + "Drinking", + "Sleeping", + "Designing", + "Coding", + "Cycling", + "Running" + ]; -// events -public chartClicked(e: any): void { - console.log(e); -} + public radarChartData: any = [ + { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, + { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } + ]; + public radarChartType: string = "radar"; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalRadarChartLabels: string[] = []; + originalRadarChartData: any = []; + + // No data state + noDataAvailable: boolean = false; -public chartHovered(e: any): void { - console.log(e); -} - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + this.fetchChartData(); } -} + ngOnChanges(changes: SimpleChanges): void { + console.log('RadarChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + this.fetchChartData(); + } + } + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching radar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/radar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Radar chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'radar', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received radar chart data:', data); + if (data === null) { + console.warn('Radar chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.radarChartLabels = data.chartLabels; + // For radar charts, we need to ensure the data is properly formatted + // Each dataset should have a data array with numeric values + this.radarChartData = data.chartData.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.radarChartData = [...this.radarChartData]; + console.log('Updated radar chart with data:', { labels: this.radarChartLabels, data: this.radarChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.radarChartLabels = data.labels; + this.radarChartData = data.datasets.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.radarChartData = [...this.radarChartData]; + console.log('Updated radar chart with legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData }); + } else { + console.warn('Radar chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + } + }, + (error) => { + console.error('Error fetching radar chart data:', error); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + } + ); + } else { + console.log('Missing required data for radar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/radar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'radar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // Map the API response to the format expected by the chart + this.noDataAvailable = data.chartLabels.length === 0; + this.radarChartLabels = data.chartLabels; + // For radar charts, we need to ensure the data is properly formatted + // Each dataset should have a data array with numeric values + this.radarChartData = data.chartData.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.radarChartData = [...this.radarChartData]; + console.log('Updated radar chart with drilldown data:', { labels: this.radarChartLabels, data: this.radarChartData }); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.radarChartLabels = data.labels; + this.radarChartData = data.datasets.map(dataset => ({ + ...dataset, + data: dataset.data ? dataset.data.map(value => { + // Convert to number if it's not already + return isNaN(Number(value)) ? 0 : Number(value); + }) : [] + })); + // Trigger change detection + this.radarChartData = [...this.radarChartData]; + console.log('Updated radar chart with drilldown legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData }); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.radarChartLabels = []; + this.radarChartData = []; + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalRadarChartLabels.length > 0) { + this.radarChartLabels = [...this.originalRadarChartLabels]; + console.log('Restored original labels'); + } + if (this.originalRadarChartData.length > 0) { + this.radarChartData = [...this.originalRadarChartData]; + console.log('Restored original data'); + } + + console.log('After reset - labels:', this.radarChartLabels); + console.log('After reset - data:', this.radarChartData); + + // Re-fetch original data + this.fetchChartData(); + } + + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + // events + public chartClicked(e: any): void { + console.log('Radar chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the label of the clicked element + const clickedLabel = this.radarChartLabels[clickedIndex]; + + console.log('Clicked on radar point:', { index: clickedIndex, label: clickedLabel }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalRadarChartLabels = [...this.radarChartLabels]; + this.originalRadarChartData = [...this.radarChartData]; + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + clickedIndex: clickedIndex, + clickedLabel: clickedLabel, + clickedValue: clickedLabel // Using label as value for now + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } + } + + public chartHovered(e: any): void { + console.log(e); + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.html index e0b67e3..03d532b 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.html +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.html @@ -1,8 +1,27 @@
- - -
+ +
+ Drilldown Level: {{currentDrilldownLevel}} + + +
+ + +
+ No data available +
+ + +
+ + +
+ \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.ts index 1af87bc..63d6db4 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component.ts @@ -1,44 +1,72 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChartData,ChartDataset } from 'chart.js'; +import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; @Component({ selector: 'app-scatter-chart', templateUrl: './scatter-chart.component.html', styleUrls: ['./scatter-chart.component.scss'] }) -export class ScatterChartComponent implements OnInit { +export class ScatterChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input + // Drilldown configuration inputs + @Input() drilldownEnabled: boolean = false; + @Input() drilldownApiUrl: string; + @Input() drilldownXAxis: string; + @Input() drilldownYAxis: string; + @Input() drilldownParameter: string; // Add drilldown parameter input + // Multi-layer drilldown configuration inputs + @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations - constructor() { } + constructor(private dashboardService: Dashboard3Service) { } ngOnInit(): void { + // Initialize with default data + this.fetchChartData(); } + + ngOnChanges(changes: SimpleChanges): void { + console.log('ScatterChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; + // Drilldown configuration changes + const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; + const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; + const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; + const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; + const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged || + drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || + drilldownLayersChanged) { + console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change) + this.fetchChartData(); + } + } + public scatterChartLabels: string[] = [ 'Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running' ]; public scatterChartData: ChartDataset[] = [ - // { - // data: [ - // { x: 1, y: 1 }, - // { x: 2, y: 3 }, - // { x: 3, y: -2 }, - // { x: 4, y: 4 }, - // { x: 5, y: -3, r: 20 }, - // ], - // label: 'Series A', - // pointRadius: 10, - // backgroundColor: 'red', - // }, - // { - // data: [ - // { x: 2, y: 2 }, - // { x: 3, y: 1 }, - // { x: 4, y: 3 }, - // { x: 5, y: 2 }, - // { x: 6, y: 4, r: 15 }, - // ], - // label: 'Series B', - // pointRadius: 8, - // backgroundColor: 'green', - // }, { data: [ { x: 1, y: 1 }, @@ -64,14 +92,378 @@ export class ScatterChartComponent implements OnInit { }, ]; public scatterChartType: string = 'scatter'; + + // Multi-layer drilldown state tracking + drilldownStack: any[] = []; // Stack to track drilldown navigation history + currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level) + originalScatterChartData: ChartDataset[] = []; + + // No data state + noDataAvailable: boolean = false; + + fetchChartData(): void { + // If we're in drilldown mode, fetch the appropriate drilldown data + if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { + this.fetchDrilldownData(); + return; + } + + // If we have the necessary data, fetch chart data from the service + if (this.table && this.xAxis && this.yAxis) { + console.log('Fetching scatter chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + + // Convert yAxis to string if it's an array + const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; + + // Log the URL that will be called + const url = `chart/getdashjson/scatter?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Scatter chart data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // For base level, we pass empty parameter and value + this.dashboardService.getChartData(this.table, 'scatter', this.xAxis, yAxisString, this.connection, '', '').subscribe( + (data: any) => { + console.log('Received scatter chart data:', data); + if (data === null) { + console.warn('Scatter chart API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.scatterChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For scatter charts, we need to transform the data into scatter format + // Scatter charts expect data in the format: {x: number, y: number} + this.noDataAvailable = data.chartLabels.length === 0; + this.scatterChartData = this.transformToScatterData(data.chartLabels, data.chartData); + console.log('Updated scatter chart with data:', this.scatterChartData); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.scatterChartData = data.datasets; + console.log('Updated scatter chart with legacy data format:', this.scatterChartData); + } else { + console.warn('Scatter chart received data does not have expected structure', data); + this.noDataAvailable = true; + this.scatterChartData = []; + } + }, + (error) => { + console.error('Error fetching scatter chart data:', error); + this.noDataAvailable = true; + this.scatterChartData = []; + // Keep default data in case of error + } + ); + } else { + console.log('Missing required data for scatter chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); + this.noDataAvailable = true; + this.scatterChartData = []; + } + } + + // Fetch drilldown data based on current drilldown level + fetchDrilldownData(): void { + console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); + console.log('Drilldown stack:', this.drilldownStack); + + // Get the current drilldown configuration based on the current level + let drilldownConfig; + if (this.currentDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + } else { + // Multi-layer drilldown level + const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + } else { + console.warn('Invalid drilldown layer index:', layerIndex); + this.noDataAvailable = true; + this.scatterChartData = []; + return; + } + } + + console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); + + // Check if we have valid drilldown configuration + if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { + console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); + this.noDataAvailable = true; + this.scatterChartData = []; + return; + } + + // Get the parameter value from the drilldown stack + let parameterValue = ''; + if (this.drilldownStack.length > 0) { + const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; + parameterValue = lastEntry.clickedValue || ''; + console.log('Parameter value from last click:', parameterValue); + } + + // Get the parameter field from drilldown config + const parameterField = drilldownConfig.parameter || ''; + console.log('Parameter field:', parameterField); + + console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { + apiUrl: drilldownConfig.apiUrl, + xAxis: drilldownConfig.xAxis, + yAxis: drilldownConfig.yAxis, + parameterField: parameterField, + parameterValue: parameterValue, + connection: this.connection + }); + + // Build the actual API URL with parameter replacement + let actualApiUrl = drilldownConfig.apiUrl; + console.log('Original API URL:', actualApiUrl); + console.log('Parameter value to use:', parameterValue); + console.log('Parameter field:', parameterField); + + // Check if the URL contains angle brackets for parameter replacement + const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); + + if (hasAngleBrackets && parameterValue) { + // Replace angle brackets placeholder with actual value + console.log('Replacing angle brackets with parameter value'); + const encodedValue = encodeURIComponent(parameterValue); + actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); + console.log('URL after angle bracket replacement:', actualApiUrl); + } + + // Log the URL that will be called + const url = `chart/getdashjson/scatter?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; + console.log('Drilldown data URL:', url); + + // Fetch data from the dashboard service with parameter field and value + // Backend handles filtering, we just pass the parameter field and value + this.dashboardService.getChartData(actualApiUrl, 'scatter', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( + (data: any) => { + console.log('Received drilldown data:', data); + if (data === null) { + console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); + this.noDataAvailable = true; + this.scatterChartData = []; + return; + } + + // Handle the actual data structure returned by the API + if (data && data.chartLabels && data.chartData) { + // For scatter charts, we need to transform the data into scatter format + // Scatter charts expect data in the format: {x: number, y: number} + this.noDataAvailable = data.chartLabels.length === 0; + this.scatterChartData = this.transformToScatterData(data.chartLabels, data.chartData); + console.log('Updated scatter chart with drilldown data:', this.scatterChartData); + } else if (data && data.labels && data.datasets) { + // Handle the original expected format as fallback + this.noDataAvailable = data.labels.length === 0; + this.scatterChartData = data.datasets; + console.log('Updated scatter chart with drilldown legacy data format:', this.scatterChartData); + } else { + console.warn('Drilldown received data does not have expected structure', data); + this.noDataAvailable = true; + this.scatterChartData = []; + } + }, + (error) => { + console.error('Error fetching drilldown data:', error); + this.noDataAvailable = true; + this.scatterChartData = []; + // Keep current data in case of error + } + ); + } + + // Reset to original data (go back to base level) + resetToOriginalData(): void { + console.log('Resetting to original data'); + console.log('Current stack before reset:', this.drilldownStack); + console.log('Current level before reset:', this.currentDrilldownLevel); + + this.currentDrilldownLevel = 0; + this.drilldownStack = []; + + if (this.originalScatterChartData.length > 0) { + this.scatterChartData = [...this.originalScatterChartData]; + console.log('Restored original data'); + } + + console.log('After reset - data:', this.scatterChartData); + + // Re-fetch original data + this.fetchChartData(); + } + // Navigate back to previous drilldown level + navigateBack(): void { + console.log('Navigating back, current stack:', this.drilldownStack); + console.log('Current level:', this.currentDrilldownLevel); + + if (this.drilldownStack.length > 0) { + // Remove the last entry from the stack + const removedEntry = this.drilldownStack.pop(); + console.log('Removed entry from stack:', removedEntry); + + // Update the current drilldown level + this.currentDrilldownLevel = this.drilldownStack.length; + console.log('New level after pop:', this.currentDrilldownLevel); + console.log('Stack after pop:', this.drilldownStack); + + if (this.drilldownStack.length > 0) { + // Fetch data for the previous level + console.log('Fetching data for previous level'); + this.fetchDrilldownData(); + } else { + // Back to base level + console.log('Back to base level, resetting to original data'); + this.resetToOriginalData(); + } + } else { + // Already at base level, reset to original data + console.log('Already at base level, resetting to original data'); + this.resetToOriginalData(); + } + } + + private transformToScatterData(labels: any[], chartData: any[]): ChartDataset[] { + // Transform the API data into scatter chart format + const datasets: ChartDataset[] = []; + + // Create a dataset for each data series + chartData.forEach((series, index) => { + // For scatter charts, we need x and y values + // We'll use the labels as x values and the data as y values + + const scatterData = labels.map((label, i) => { + const xValue = isNaN(Number(label)) ? i : Number(label); + const yValue = series.data && series.data[i] !== undefined ? + (isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0; + + return { + x: xValue, + y: yValue + }; + }); + + datasets.push({ + data: scatterData, + label: series.label || `Series ${index + 1}`, + pointRadius: 10, + backgroundColor: this.getBackgroundColor(index), + }); + }); + + return datasets; + } + + private getBackgroundColor(index: number): string { + const colors = [ + 'red', 'green', 'blue', 'purple', 'yellow', + 'brown', 'magenta', 'cyan', 'orange', 'pink' + ]; + return colors[index % colors.length]; + } // events public chartClicked(e: any): void { - console.log(e); + console.log('Scatter chart clicked:', e); + + // If drilldown is enabled and we have a valid click event + if (this.drilldownEnabled && e.active && e.active.length > 0) { + // Get the index of the clicked element + const clickedIndex = e.active[0].index; + + // Get the dataset index + const datasetIndex = e.active[0].datasetIndex; + + // Get the data point + const dataPoint = this.scatterChartData[datasetIndex].data[clickedIndex]; + + console.log('Clicked on scatter point:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint }); + + // If we're not at the base level, store original data + if (this.currentDrilldownLevel === 0) { + // Store original data before entering drilldown mode + this.originalScatterChartData = JSON.parse(JSON.stringify(this.scatterChartData)); + console.log('Stored original data for drilldown'); + } + + // Determine the next drilldown level + const nextDrilldownLevel = this.currentDrilldownLevel + 1; + + console.log('Next drilldown level will be:', nextDrilldownLevel); + + // Check if there's a drilldown configuration for this level + let hasDrilldownConfig = false; + let drilldownConfig; + + if (nextDrilldownLevel === 1) { + // Base drilldown level + drilldownConfig = { + apiUrl: this.drilldownApiUrl, + xAxis: this.drilldownXAxis, + yAxis: this.drilldownYAxis, + parameter: this.drilldownParameter + }; + hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; + } else { + // Multi-layer drilldown level + const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown + if (layerIndex < this.drilldownLayers.length) { + drilldownConfig = this.drilldownLayers[layerIndex]; + hasDrilldownConfig = drilldownConfig.enabled && + !!drilldownConfig.apiUrl && + !!drilldownConfig.xAxis && + !!drilldownConfig.yAxis; + } + } + + console.log('Drilldown config for next level:', drilldownConfig); + console.log('Has drilldown config:', hasDrilldownConfig); + + // If there's a drilldown configuration for the next level, proceed + if (hasDrilldownConfig) { + // For scatter charts, we'll use the x value as the clicked value + const clickedValue = dataPoint && (dataPoint as any).x !== undefined ? + (dataPoint as any).x.toString() : ''; + + // Add this click to the drilldown stack + const stackEntry = { + level: nextDrilldownLevel, + datasetIndex: datasetIndex, + clickedIndex: clickedIndex, + clickedValue: clickedValue + }; + + this.drilldownStack.push(stackEntry); + + console.log('Added to drilldown stack:', stackEntry); + console.log('Current drilldown stack:', this.drilldownStack); + + // Update the current drilldown level + this.currentDrilldownLevel = nextDrilldownLevel; + + console.log('Entering drilldown level:', this.currentDrilldownLevel); + + // Fetch drilldown data for the new level + this.fetchDrilldownData(); + } else { + console.log('No drilldown configuration for level:', nextDrilldownLevel); + } + } else { + console.log('Drilldown not enabled or invalid click event'); + } } public chartHovered(e: any): void { console.log(e); } -} +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component.ts index 481fcca..ae84296 100644 --- a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component.ts +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component.ts @@ -1,27 +1,76 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'app-to-do-chart', templateUrl: './to-do-chart.component.html', styleUrls: ['./to-do-chart.component.scss'] }) -export class ToDoChartComponent implements OnInit { +export class ToDoChartComponent implements OnInit, OnChanges { + @Input() xAxis: string; + @Input() yAxis: string | string[]; + @Input() table: string; + @Input() datastore: string; + @Input() charttitle: string; + @Input() chartlegend: boolean = true; + @Input() showlabel: boolean = true; + @Input() chartcolor: boolean; + @Input() slices: boolean; + @Input() donut: boolean; + @Input() charturl: string; + @Input() chartparameter: string; + @Input() datasource: string; + @Input() fieldName: string; + @Input() connection: number; // Add connection input constructor() { } ngOnInit(): void { } + + ngOnChanges(changes: SimpleChanges): void { + console.log('ToDoChartComponent input changes:', changes); + + // Check if any of the key properties have changed + const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; + const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; + const tableChanged = changes.table && !changes.table.firstChange; + const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection + + // Respond to input changes + if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) { + console.log('X or Y axis or table or connection changed, fetching new data'); + // Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change) + this.fetchToDoData(); + } + } + data: any; todo: string; todoList = ['todo 1']; + + fetchToDoData(): void { + // If we have the necessary data, fetch to-do data from the service + if (this.table) { + console.log('Fetching to-do data for:', { table: this.table }); + + // For to-do chart, we might want to fetch data differently + // This is a placeholder implementation - you may need to adjust based on your API + console.log('To-do chart would fetch data from table:', this.table); + + // In a real implementation, you would connect to your service here + // For now, we'll just keep the default to-do list + } else { + console.log('Missing required data for to-do chart:', { table: this.table }); + } + } public addTodo(todo: string) { this.todoList.push(todo); -} + } -public removeTodo(todoIx: number) { + public removeTodo(todoIx: number) { if (this.todoList.length) { this.todoList.splice(todoIx, 1); } -} -} + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css new file mode 100644 index 0000000..b2de1cb --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css @@ -0,0 +1,75 @@ +.s-info-bar { + display: flex; + flex-direction: row; + justify-content: space-between; +} +.s-info-bar button { + outline: none; +} + +.entry-pg { + width: 750px; +} + +.button1::after { + content: none; +} + +.button1:hover::after { + content: "ADD ROWS"; +} + +.section { + background-color: #dddddd; + height: 40px; +} + +.section p { + font-weight: bold; + padding: 10px; + font-size: 18px; +} + +.clr-input { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 0.75rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.clr-file { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.center { + text-align: center; +} + +input[type=text], [type=date], textarea { + width: 100%; + padding: 15px 15px; + background-color: rgb(255, 255, 255); + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.required-field { + color: red; +} + +select { + width: 100%; + padding: 5px 5px; + border: 1px solid #ccc; + border-radius: 4px; +}/*# sourceMappingURL=editsureconnect.component.css.map */ \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css.map b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css.map new file mode 100644 index 0000000..998fe7d --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["editsureconnect.component.scss","editsureconnect.component.css"],"names":[],"mappings":"AACA;EACE,aAAA;EACA,mBAAA;EACA,8BAAA;ACAF;ADCE;EACE,aAAA;ACCJ;;ADGA;EACE,YAAA;ACAF;;ADGA;EACE,aAAA;ACAF;;ADEA;EACE,mBAAA;ACCF;;ADIA;EACE,yBAHS;EAIT,YAAA;ACDF;;ADIA;EAEE,iBAAA;EACA,aAAA;EACA,eAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EACA,wBAAA;EACA,eAAA;EACA,WAAA;EACA,mBAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EAEA,eAAA;EACA,WAAA;EACA,mBAAA;ACHF;;ADMA;EACE,kBAAA;ACHF;;ADKA;EACE,WAAA;EACA,kBAAA;EAED,oCAAA;EACC,qBAAA;EACA,sBAAA;EACA,kBAAA;EACA,sBAAA;ACHF;;ADMA;EACE,UAAA;ACHF;;ADMA;EACE,WAAA;EACA,gBAAA;EACA,sBAAA;EACA,kBAAA;ACHF","file":"editsureconnect.component.css"} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.html new file mode 100644 index 0000000..1d9f166 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.html @@ -0,0 +1,164 @@ +

Connection

+Edit Mode
+Define A connection to use in a job, that can calls APIs from another App. + +
+ + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + +
+
+ + + + + + +
+

Default Settings

+
+These configurations default. +
+ +
+
+ + +
+ +
+ + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+ +
+ + + +
+
+ + + + +
+

Generate Token

+
+
+
+
+ + +
+
+ + +
+
+
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + +
+ + +
+
+ + + + + + + +
+ + +
+

Response

+
+
+ code + 200
+
+ + +
+ +
+
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.scss new file mode 100644 index 0000000..6161c34 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.scss @@ -0,0 +1,79 @@ +//@import "../../../../assets/scss/var"; +.s-info-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + button { + outline: none; + } +} + +.entry-pg { + width: 750px; +} + +.button1::after { + content: none; +} +.button1:hover::after { + content: "ADD ROWS"; +} + +$bg-color: #dddddd; + +.section { + background-color: $bg-color; + height: 40px; +} + +.section p { + //color: white; + font-weight: bold; + padding: 10px; + font-size: 18px; +} + +.clr-input { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 0.75rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.clr-file { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + //padding: 0.6rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.center { + text-align: center; +} +input[type=text],[type=date],textarea { + width: 100%; + padding: 15px 15px; + // margin: 8px 0; + background-color:rgb(255, 255, 255); + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.required-field{ + color: red; + +} +select{ + width: 100%; + padding: 5px 5px; + border: 1px solid #ccc; + border-radius: 4px; +} diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.spec.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.spec.ts new file mode 100644 index 0000000..aeec0cb --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditsureconnectComponent } from './editsureconnect.component'; + +describe('EditsureconnectComponent', () => { + let component: EditsureconnectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ EditsureconnectComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EditsureconnectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.ts new file mode 100644 index 0000000..add3f7c --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SureconnectService } from '../sureconnect.service'; +@Component({ + selector: 'app-editsureconnect', + templateUrl: './editsureconnect.component.html', + styleUrls: ['./editsureconnect.component.scss'] +}) +export class EditsureconnectComponent implements OnInit { + id: number; + editdata: any = {} + constructor(private route: ActivatedRoute, + private router: Router, + private sureconnectservice: SureconnectService,) { } + + ngOnInit(): void { + this.id = this.route.snapshot.params["id"]; + console.log("update with id = ", this.id); + this.getById(this.id); + } + getById(id: any) { + this.sureconnectservice.getOne(id).subscribe((data) => { + this.editdata = data; + console.log(this.editdata); + }) + } + onupdate() { + this.sureconnectservice.update(this.editdata, this.id).subscribe((data) => { + console.log('after edit ', data); + // Redirect back to sureconnect page after successful update + this.router.navigate(['../../'], { relativeTo: this.route }); + }) + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css new file mode 100644 index 0000000..99e694d --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css @@ -0,0 +1,75 @@ +.s-info-bar { + display: flex; + flex-direction: row; + justify-content: space-between; +} +.s-info-bar button { + outline: none; +} + +.entry-pg { + width: 750px; +} + +.button1::after { + content: none; +} + +.button1:hover::after { + content: "ADD ROWS"; +} + +.section { + background-color: #dddddd; + height: 40px; +} + +.section p { + font-weight: bold; + padding: 10px; + font-size: 18px; +} + +.clr-input { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 0.75rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.clr-file { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.center { + text-align: center; +} + +input[type=text], [type=date], textarea { + width: 100%; + padding: 15px 15px; + background-color: rgb(255, 255, 255); + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.required-field { + color: red; +} + +select { + width: 100%; + padding: 5px 5px; + border: 1px solid #ccc; + border-radius: 4px; +}/*# sourceMappingURL=oauth.component.css.map */ \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css.map b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css.map new file mode 100644 index 0000000..2a0296e --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["oauth.component.scss","oauth.component.css"],"names":[],"mappings":"AACA;EACE,aAAA;EACA,mBAAA;EACA,8BAAA;ACAF;ADCE;EACE,aAAA;ACCJ;;ADGA;EACE,YAAA;ACAF;;ADGA;EACE,aAAA;ACAF;;ADEA;EACE,mBAAA;ACCF;;ADIA;EACE,yBAHS;EAIT,YAAA;ACDF;;ADIA;EAEE,iBAAA;EACA,aAAA;EACA,eAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EACA,wBAAA;EACA,eAAA;EACA,WAAA;EACA,mBAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EAEA,eAAA;EACA,WAAA;EACA,mBAAA;ACHF;;ADMA;EACE,kBAAA;ACHF;;ADKA;EACE,WAAA;EACA,kBAAA;EAED,oCAAA;EACC,qBAAA;EACA,sBAAA;EACA,kBAAA;EACA,sBAAA;ACHF;;ADMA;EACE,UAAA;ACHF;;ADMA;EACE,WAAA;EACA,gBAAA;EACA,sBAAA;EACA,kBAAA;ACHF","file":"oauth.component.css"} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.html new file mode 100644 index 0000000..492b3e0 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.html @@ -0,0 +1,185 @@ +

New Connection

+Add Mode
+Define A connection to use in a job, that can calls APIs from another App. + +
+ + + + + + + + +
+ + + + + +
+
+
+ + +
+
+ + + + +
+
+ + + + + + +
+

Default Settings

+
+These configurations default. +
+ +
+
+ + +
+ +
+ + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+ +
+ + + +
+
+ + + +
+
+

Generate Token

+ +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + + + +
+ + +
+ +
+ + +
+ + +
+

Test Connection

+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + + + +
+ + +
+
+ +
+ +
+

Response

+
+
+ code + 200
+
+ + +
+ + +
diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.scss new file mode 100644 index 0000000..6161c34 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.scss @@ -0,0 +1,79 @@ +//@import "../../../../assets/scss/var"; +.s-info-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + button { + outline: none; + } +} + +.entry-pg { + width: 750px; +} + +.button1::after { + content: none; +} +.button1:hover::after { + content: "ADD ROWS"; +} + +$bg-color: #dddddd; + +.section { + background-color: $bg-color; + height: 40px; +} + +.section p { + //color: white; + font-weight: bold; + padding: 10px; + font-size: 18px; +} + +.clr-input { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 0.75rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.clr-file { + color: #212529; + border: 1px solid #ced4da; + border-radius: 0.25rem; + //padding: 0.6rem 0.75rem; + margin-top: 3px; + width: 100%; + margin-bottom: 10px; +} + +.center { + text-align: center; +} +input[type=text],[type=date],textarea { + width: 100%; + padding: 15px 15px; + // margin: 8px 0; + background-color:rgb(255, 255, 255); + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.required-field{ + color: red; + +} +select{ + width: 100%; + padding: 5px 5px; + border: 1px solid #ccc; + border-radius: 4px; +} diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.spec.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.spec.ts new file mode 100644 index 0000000..ed653b4 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OauthComponent } from './oauth.component'; + +describe('OauthComponent', () => { + let component: OauthComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ OauthComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(OauthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.ts new file mode 100644 index 0000000..ddc9133 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/oauth/oauth.component.ts @@ -0,0 +1,70 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoginService } from 'src/app/services/api/login.service'; +import { SureconnectService } from '../sureconnect.service'; +@Component({ + selector: 'app-oauth', + templateUrl: './oauth.component.html', + styleUrls: ['./oauth.component.scss'] +}) +export class OauthComponent implements OnInit { + public entryForm: FormGroup; + model: any = {}; + errMsg: string = ''; + constructor(private sureconnectservice: SureconnectService, + private _fb: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private loginService: LoginService,) { } + + ngOnInit(): void { + this.entryForm = this._fb.group({ + connection_name: [null], + description: [null], + type: [null], + access_token: [null], + client_id: [null], + username: [null], + password: [null], + + }); + } + onSubmit() { + this.sureconnectservice.create(this.entryForm.value).subscribe((data) => { + console.log(' data after add ', data); + // Redirect back to sureconnect page after successful creation + this.router.navigate(['../'], { relativeTo: this.route }); + }) + } + onLogin() { + // tslint:disable-next-line:max-line-length + this.loginService.getToken(this.model.email, this.model.password) + .subscribe(resp => { + if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") { + this.errMsg = 'Checking Email or password'; + return; + } + this.router.navigate([resp.landingPage]);// add , {skipLocationChange: true} + }, + (errResponse: HttpErrorResponse) => { + + switch (errResponse.status) { + case 401: + this.errMsg = 'Email or password is incorrect!'; + break; + case 404: + this.errMsg = 'Service not found'; + case 408: + this.errMsg = 'Request Timedout'; + case 500: + this.errMsg = 'Internal Server Error'; + default: + this.errMsg = 'Server Error'; + } + } + ); + + } +} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css new file mode 100644 index 0000000..63cdb72 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css @@ -0,0 +1,4 @@ +.delete, .heading { + text-align: center; + color: red; +}/*# sourceMappingURL=sureconnect.component.css.map */ \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css.map b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css.map new file mode 100644 index 0000000..64b95e1 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["sureconnect.component.scss","sureconnect.component.css"],"names":[],"mappings":"AAAA;EACE,kBAAA;EACA,UAAA;ACCF","file":"sureconnect.component.css"} \ No newline at end of file diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.html b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.html new file mode 100644 index 0000000..404cbf9 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.html @@ -0,0 +1,109 @@ + + +
+ + +
+
+
+

All SureConnect

+
+ +
+ + + +
+
+ + + + + + + + + Loading ... +
{{error}}
+ + + + + Name + + + Description + + + Action + + + + + + + {{user.connection_name}} + {{user.description}} + + + + + +
Who Column
+
Account ID: {{user.accountId}}
+
Created At: {{user.createdAt| date}}
+
Created By: {{user.createdBy}}
+
Updated At: {{user.updatedAt | date}}
+
Updated By: {{user.updatedBy}}
+
+
+
+ + + + + + + + + + + + + + +
Name: {{user.Name}}
Description:{{user.description}}
+
+
+ + + + data per page + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} + of {{pagination.totalItems}} data + + +
+
+ + + + + + + diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.scss b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.scss new file mode 100644 index 0000000..76ab29b --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.scss @@ -0,0 +1,4 @@ +.delete,.heading{ + text-align: center; + color: red; +} diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.spec.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.spec.ts new file mode 100644 index 0000000..921b801 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SureconnectComponent } from './sureconnect.component'; + +describe('SureconnectComponent', () => { + let component: SureconnectComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SureconnectComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SureconnectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.ts new file mode 100644 index 0000000..8748261 --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.component.ts @@ -0,0 +1,85 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ToastrService } from 'ngx-toastr'; +import { MenuGroupService } from 'src/app/services/admin/menu-group.service'; +import { SureconnectService } from './sureconnect.service'; +@Component({ + selector: 'app-sureconnect', + templateUrl: './sureconnect.component.html', + styleUrls: ['./sureconnect.component.scss'] +}) +export class SureconnectComponent implements OnInit { + loading = false; + + selected: any[] = []; + rowSelected: any = {}; + modaldelete = false; + modaladd = false; + data; + alldata; + mcreate; + medit; + mdelete; + showdata; + error; + constructor(private router: Router, + private route: ActivatedRoute, + private toastr: ToastrService, + private menuGroupService: MenuGroupService, + private sureservice: SureconnectService) { } + + ngOnInit(): void { + this.showdata = this.menuGroupService.getdata(); + console.log(this.showdata); + this.mcreate = this.showdata.mcreate; + console.log(this.mcreate); + this.mdelete = this.showdata.mdelete + console.log(this.mdelete); + this.medit = this.showdata.medit + console.log(this.medit); + this.getall(); + } + getall() { + this.sureservice.getAll().subscribe((data) => { + this.alldata = data; + console.log(this.alldata); + if (this.alldata.length == 0) { + this.error = "No data Available"; + console.log(this.error) + } + }, (error) => { + console.log(error); + if (error) { + this.error = "Server Error"; + } + }) + } + goToAdd() { + this.router.navigate(["../oauth"], { relativeTo: this.route }); + } + goToEdit(id) { + this.router.navigate(["../editconnect/" + id], { relativeTo: this.route }); + } + onDelete(row) { + this.rowSelected = row; + this.modaldelete = true; + } + delete(id) { + this.modaldelete = false; + console.log("in delete " + id); + this.sureservice.delete(id).subscribe( + (data) => { + console.log(data); + this.ngOnInit(); + if (data) { + this.toastr.success('Deleted successfully'); + } + }, + (error) => { + console.log('Error in adding data...', +error); + if (error) { + this.toastr.error('Not deleted Data Getting Some Error'); + } + }); + } +} diff --git a/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.service.ts b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.service.ts new file mode 100644 index 0000000..40b1b4f --- /dev/null +++ b/frontend/angular-clarity-master/src/app/modules/main/builder/dashboardnew/sureconnect/sureconnect.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import baseUrl from 'src/app/services/api/helper'; +import { ApiRequestService } from 'src/app/services/api/api-request.service'; +@Injectable({ + providedIn: 'root' +}) +export class SureconnectService { + + constructor(private _http: HttpClient, + private apiRequest: ApiRequestService,) { } + public create(data: any){ + return this._http.post(`${baseUrl}/Sure_Connect`, data); + } + + // create card + public update(data: any,id:any){ + return this._http.put(`${baseUrl}/Sure_Connect/${id}`, data); + } + + // get all cards + public getAll(){ + return this._http.get(`${baseUrl}/Sure_Connect`); + } + + // get one card + public getOne(id: any){ + return this._http.get(`${baseUrl}/Sure_Connect/${id}`); + } + + // delete card + public delete(id: any){ + return this._http.delete(`${baseUrl}/Sure_Connect/${id}`); + } +}