dash
This commit is contained in:
		
							parent
							
								
									b11fda4858
								
							
						
					
					
						commit
						4fe5afae7a
					
				
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -0,0 +1,542 @@ | ||||
| <ol class="breadcrumb breadcrumb-arrow font-trirong"> | ||||
|   <li><a href="javascript://"> Data lake</a></li> | ||||
| </ol> | ||||
| <div class="dg-wrapper"> | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-8"> | ||||
|       <h3>Data lake </h3> | ||||
|     </div> | ||||
|     <div class="clr-col-4" style="text-align: right;"> | ||||
|       <button *ngIf="cardButton" id="add" class="btn btn-primary btn-icon" (click)="changeView()"> | ||||
|         <clr-icon *ngIf="!isCardview" shape="grid-view"></clr-icon> <clr-icon *ngIf="isCardview" | ||||
|           shape="bars"></clr-icon> | ||||
|       </button> | ||||
|       <!-- button --> | ||||
|       <button id="add" class="btn btn-primary" (click)="goToAdd(product)"> | ||||
|         <clr-icon shape="plus"></clr-icon>ADD | ||||
|       </button> | ||||
|     </div> | ||||
|   </div> | ||||
|   <ng-container *ngIf="!isCardview"> <!-- GET ALL --> <clr-datagrid [clrDgLoading]="loading" | ||||
|       [(clrDgSelected)]="selected"> | ||||
|       <clr-dg-placeholder> | ||||
|         <ng-template #loadingSpinner> | ||||
|           <clr-spinner>Loading ... </clr-spinner> | ||||
|         </ng-template> | ||||
|         <div *ngIf="error;else loadingSpinner">{{error}}</div> | ||||
|       </clr-dg-placeholder> | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'schedule'"> <ng-container *clrDgHideableColumn="{hidden: false}"> schedule | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'url_endpoint'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url Endpoint | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume | ||||
|         </ng-container></clr-dg-column> | ||||
|          | ||||
|       <clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}"> SureConnect | ||||
|         </ng-container></clr-dg-column> | ||||
| 
 | ||||
|       <!-- who column --> | ||||
|       <clr-dg-column> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|           <clr-icon shape="bars"></clr-icon> Action | ||||
|         </ng-container></clr-dg-column> | ||||
|       <!-- end --> | ||||
| 
 | ||||
|       <clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user"> | ||||
| 
 | ||||
|         <clr-dg-cell>{{user.name }}</clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|         <clr-dg-cell (click)="goTourlUrl(user.url)" | ||||
|           style="cursor: pointer; color: rgb(108, 108, 194);">{{user.url}}</clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|         <clr-dg-cell>{{user.schedule }}</clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|         <clr-dg-cell>{{user.cron_job }}</clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|         <clr-dg-cell (click)="goToReplaceStringjson(user.json)" style="cursor: pointer; align-items: center;"><clr-icon | ||||
|             shape="details"></clr-icon></clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|         <clr-dg-cell> | ||||
|           <div style="display: flex; align-items: center; max-width: 200px;"> | ||||
|             <span style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;"  | ||||
|                   title="{{user.url}}" (click)="showFullUrl(user.url)"> | ||||
|               {{user.url | slice:0:30}}{{user.url.length > 30 ? '...' : ''}} | ||||
|             </span> | ||||
|             <button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url)" title="Copy URL"> | ||||
|               <clr-icon shape="copy-to-clipboard"></clr-icon> | ||||
|             </button> | ||||
|           </div> | ||||
|         </clr-dg-cell> | ||||
|         <clr-dg-cell>{{user.batch_volume}}</clr-dg-cell> | ||||
|          | ||||
|         <clr-dg-cell>{{getSureConnectNameById(user.sure_connect_id)}}</clr-dg-cell> | ||||
| 
 | ||||
|         <!-- who column --> | ||||
|         <clr-dg-cell> | ||||
|           <clr-signpost> | ||||
|             <span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" | ||||
|                 style="color: rgb(0, 130, 236);"></clr-icon></span> | ||||
|             <clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen> | ||||
|               <h5 style="margin-top: 0">Who Column</h5> | ||||
|               <div>Account ID: <code class="clr-code">{{user.accountId}}</code></div> | ||||
|               <div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div> | ||||
|               <div>Created By: <code class="clr-code">{{user.createdBy}}</code></div> | ||||
|               <div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div> | ||||
|               <div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div> | ||||
|             </clr-signpost-content> | ||||
|           </clr-signpost> | ||||
| 
 | ||||
|           <!-- New JSON Update button --> | ||||
|           <button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON"> | ||||
|             <clr-icon shape="refresh"></clr-icon> | ||||
|           </button> | ||||
|         </clr-dg-cell> | ||||
| 
 | ||||
|         <!-- who colmn --> | ||||
| 
 | ||||
|         <!-- <clr-dg-cell> | ||||
|   <button class="btn btn-icon" (click)="onEdit(user)"> | ||||
|     <clr-icon shape="pencil"></clr-icon> | ||||
|   </button> | ||||
|   <button class="btn btn-icon" (click)="onDelete(user)"> | ||||
|     <clr-icon shape="trash"></clr-icon> | ||||
|   </button> | ||||
|    | ||||
| </clr-dg-cell> --> | ||||
| 
 | ||||
|         <clr-dg-action-overflow> | ||||
|           <button class="action-item" (click)="onEdit(user)">Edit</button> | ||||
|           <button class="action-item" (click)="onDelete(user)">Delete</button> | ||||
|         </clr-dg-action-overflow> | ||||
|       </clr-dg-row> | ||||
|       <clr-dg-footer> | ||||
|         <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||
|           <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size> | ||||
|           {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} | ||||
|           of {{pagination.totalItems}} users | ||||
|         </clr-dg-pagination> | ||||
|       </clr-dg-footer> | ||||
|     </clr-datagrid> </ng-container> | ||||
|   <ng-template #showInfo> | ||||
|     <div class="alert alert-info" role="alert"> | ||||
|       <div class="alert-items"> | ||||
|         <div class="alert-item static"> | ||||
|           <span class="alert-text"> | ||||
|             <clr-icon class="alert-icon" shape="info-circle"></clr-icon> | ||||
|             Data could be found. Loading.. | ||||
|             <clr-spinner [clrMedium]="true">Loading ...</clr-spinner> | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </ng-template><ng-container *ngIf="isCardview"> | ||||
|     <div *ngIf="product; else showInfo" class="clr-row clr-align-items-start clr-justify-content-start"> | ||||
|       <div *ngFor="let app of product| filter:search; let index = i" class="clr-col-auto"> | ||||
|         <div class="clr-row"> | ||||
|           <div class="clr-col-lg-12 clr-col-md-4 clr-col-sm-4 clr-col-12" style="width: 410px;"> | ||||
|             <div class="card" style="padding: 10px; " | ||||
|               [style.background-color]="cardmodal.cardColor !== '' ? cardmodal.cardColor : 'white'"> | ||||
|               <div class="card-body" | ||||
|                 style="display: grid; grid-template-columns: repeat(13, 1fr); grid-template-rows: repeat(7, 1fr); gap: 5px;"> | ||||
|                 <ng-container *ngFor="let item of dashboardArray"> | ||||
|                   <div [style.gridColumn]="item.x + 1" [style.gridRow]="item.y + 1" | ||||
|                     [style.gridColumnEnd]="item.x + item.cols + 1" [style.gridRowEnd]="item.y + item.rows + 1"> | ||||
|                     <div *ngIf="item.name === 'textField'" class="title-card card-title" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'" | ||||
|                       [style.background-color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor" | ||||
|                       [style.color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor"> | ||||
|                       {{beforeText(item.fieldtext)}} | ||||
|                       {{ app[transform(item.fieldtext) ] }} | ||||
|                       {{afterText(item.fieldtext)}} | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div *ngIf="item.name === 'dateField'" class="title-card card-title" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'" | ||||
|                       [style.background-color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor" | ||||
|                       [style.color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor"> | ||||
|                       {{beforeText(item.fieldtext)}} | ||||
|                       {{ app[transform(item.fieldtext) ] | date}} | ||||
|                       {{afterText(item.fieldtext)}} | ||||
|                     </div> | ||||
|                     <div *ngIf="item.name === 'numberField'" class="title-card card-title" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'" | ||||
|                       [style.background-color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor" | ||||
|                       [style.color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor"> | ||||
|                       {{beforeText(item.fieldtext)}} | ||||
|                       {{ app[transform(item.fieldtext) ]}} | ||||
|                       {{afterText(item.fieldtext)}} | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div *ngIf="item.name === 'Line'" class="title-card card-title" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'"> | ||||
|                       <hr> | ||||
|                     </div> | ||||
| 
 | ||||
| 
 | ||||
|                     <div *ngIf="item.name === 'Icon'" class="icon-card" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'"> | ||||
|                       <clr-icon [attr.shape]="item.iconName"></clr-icon> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div *ngIf="item.name == 'Image'" | ||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||
|                       [style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'" | ||||
|                       [style.font-style]="item.italic == true ? 'Italic' : 'normal'" | ||||
|                       [style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :  | ||||
|           (item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))" | ||||
|                       [style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'" | ||||
|                       [style.color]="item.textcolor !== '' ? item.textcolor : 'black'" | ||||
|                       [style.background-color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor" | ||||
|                       [style.color]="item.conditionValue ==  app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor"> | ||||
|                       <img id="filePreview" [src]="item.imageURL" alt="File Preview" | ||||
|                         [style.width]="item.imagewidth !== '' ? item.imagewidth + 'px' : '100px'" | ||||
|                         [style.height]="item.imagewidth !== '' ? item.imagewidth + 'px' : '100px'"></div> | ||||
|                   </div> | ||||
|                 </ng-container> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </ng-container> | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <clr-modal [(clrModalOpen)]="rsModaljson" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true"> | ||||
|   <div class="modal-body"> | ||||
|     <textarea class="form-control" style="width:100%; height: 400px;" readonly>{{rowSelected}}</textarea> | ||||
|   </div> | ||||
| </clr-modal> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <!-- //		EDIT DATA......... --> | ||||
| <clr-modal [(clrModalOpen)]="modalEdit" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> | ||||
|   <h3 class="modal-title">Update Data lake | ||||
|     <!--update button --> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   </h3> | ||||
|   <div class="modal-body" *ngIf="rowSelected.id"> | ||||
|     <h2 class="heading">{{rowSelected.id}}</h2> | ||||
|     <!-- button --> | ||||
|     <form> | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>Name</label> | ||||
|           <input class="clr-input" type="text" [(ngModel)]="rowSelected.name" name="name" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> Url</label> | ||||
|           <input type="url" name="url" [(ngModel)]="rowSelected.url" placeholder="Enter URL" class="clr-input" | ||||
|             pattern="https?://.+"> | ||||
|           <div *ngIf="!isValidurl(rowSelected.url )" class="error_mess"> * Please enter a valid URL. </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>schedule</label> | ||||
|           <input class="clr-input" type="text" [(ngModel)]="rowSelected.schedule" name="schedule" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>cron job</label> | ||||
|           <input class="clr-input" type="text" [(ngModel)]="rowSelected.cron_job" name="cron_job" /> | ||||
|           <app-cron-job-builder [cronExpression]="editCronExpression" (cronExpressionChange)="onEditCronExpressionChange($event)" instanceId="edit-form-{{rowSelected.id}}"></app-cron-job-builder> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> json</label> | ||||
|           <input id="name" type="Text" class="form-control" style="border: none; outline: none; height:33px !important;" | ||||
|             [(ngModel)]="rowSelected.json" name=" json " /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>Url</label> | ||||
|           <input class="clr-input" type="text" [(ngModel)]="rowSelected.url_endpoint" name="url_endpoint" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>Batch Volume</label> | ||||
|           <input class="clr-input" type="number" [(ngModel)]="rowSelected.batch_volume" name="batch_volume" /> | ||||
|         </div> | ||||
|          | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>SureConnect</label> | ||||
|           <select class="clr-input" [(ngModel)]="rowSelected.sure_connect_id" name="sure_connect_id" (change)="onEditSureConnectChange($event)"> | ||||
|             <option value="">Select SureConnect</option> | ||||
|             <option *ngFor="let sureConnect of sureConnectList" [value]="sureConnect.id">{{sureConnect.name}}</option> | ||||
|           </select> | ||||
|         </div> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       <!-- form code start --> | ||||
|       <div *ngIf="checkFormCode"> | ||||
|         <h4 style="font-weight: 300;display: inline;">Extension</h4> | ||||
|         <br> | ||||
|         <hr> | ||||
|         <div class="clr-row"> | ||||
|           <div class="clr-col-4" *ngFor="let field of additionalFieldsFromBackend"> | ||||
|             <ng-container *ngIf="field.formCode === formcode" [ngSwitch]="field.fieldType"> | ||||
|               <!-- Text Input --> <label *ngSwitchCase="'text'">{{ field.fieldName }}</label> | ||||
|               <input *ngSwitchCase="'text'" [type]="field.fieldType" name="{{ field.extValue }}" | ||||
|                 [(ngModel)]="rowSelected[field.extValue]" class="clr-input" /> | ||||
| 
 | ||||
|               <!-- Date Input --> <label *ngSwitchCase="'date'">{{ field.fieldName }}</label> | ||||
|               <input *ngSwitchCase="'date'" [type]="field.fieldType" name="{{ field.extValue }}" | ||||
|                 [(ngModel)]="rowSelected[field.extValue]" class="clr-input" /> | ||||
| 
 | ||||
|               <!-- Textarea --> <label *ngSwitchCase="'textarea'">{{ field.fieldName }}</label> | ||||
|               <textarea *ngSwitchCase="'textarea'" name="{{ field.extValue }}" [(ngModel)]="rowSelected[field.extValue]" | ||||
|                 col="10" row="2"></textarea> | ||||
| 
 | ||||
|               <!-- Checkbox --> <label *ngSwitchCase="'checkbox'">{{ field.fieldName }}</label><br> | ||||
|               <input *ngSwitchCase="'checkbox'" [type]="field.fieldType" name="{{ field.extValue }}" | ||||
|                 [(ngModel)]="rowSelected[field.extValue]" class="clr-checkbox" /> | ||||
|             </ng-container> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- form code end --> | ||||
|       <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-outline" (click)="modalEdit = false">Cancel</button> | ||||
|         <button type="submit" class="btn btn-primary" (click)="onUpdate(rowSelected.id)">Update</button> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </clr-modal> | ||||
| <clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> | ||||
|   <div class="modal-body" *ngIf="rowSelected.id"> | ||||
|     <h1 class="delete">Are You Sure Want to delete?</h1> | ||||
|     <h2 class="heading">{{rowSelected.id}}</h2> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button> | ||||
|       <button type="button" (click)="delete(rowSelected.id)" class="btn btn-primary">Delete</button> | ||||
|     </div> | ||||
|   </div> | ||||
| </clr-modal> | ||||
| <!-- ADD FORM ..... --> | ||||
| <clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> | ||||
|   <h3 class="modal-title">Add Data lake | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     <!-- aeroplane icon --> | ||||
|             | ||||
|     <a *ngIf="userrole?.includes('ADMIN')" style="float: right;" href="javascript:void(0)" role="tooltip" | ||||
|       aria-haspopup="true" class="tooltip tooltip-sm tooltip-bottom-left"> | ||||
|       <a id="build_extension" [routerLink]="['../extension/all']" [queryParams]="{ formCode: 'Data_lake_formCode' }"> | ||||
|         <clr-icon shape="airplane" size="32"></clr-icon> | ||||
|       </a> | ||||
|       <span class="tooltip-content">Form Extension</span> | ||||
|     </a> | ||||
|   </h3> | ||||
|   <div class="modal-body"> | ||||
|     <form [formGroup]="entryForm"> | ||||
|       <div class="clr-row" style="height: fit-content;"> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> Name</label> | ||||
|           <input class="clr-input" type="text" formControlName="name" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> Url </label> | ||||
|           <input type="url" formControlName="url" class="clr-input" pattern="https?://.+"> | ||||
|           <div *ngIf="entryForm.controls['url'].errors" class="error_mess"> | ||||
|             <div *ngIf="entryForm.controls['url'].hasError('pattern')" class="error_mess"> * Please enter a valid URL. | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> schedule</label> | ||||
|           <input class="clr-input" type="text" formControlName="schedule" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> cron job</label> | ||||
|           <input class="clr-input" type="text" formControlName="cron_job" /> | ||||
|           <app-cron-job-builder [cronExpression]="addCronExpression" (cronExpressionChange)="onAddCronExpressionChange($event)" instanceId="add-form"></app-cron-job-builder> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>json</label> | ||||
|           <input class="form-control" type="Text" formControlName="json" | ||||
|             style="border: none; outline: none; height:33px !important;" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> Url Endpoint</label> | ||||
|           <input class="clr-input" type="text" formControlName="url_endpoint" /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label> Batch Volume</label> | ||||
|           <input class="clr-input" type="number" formControlName="batch_volume" /> | ||||
|         </div> | ||||
|          | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label>SureConnect</label> | ||||
|           <select class="clr-input" formControlName="sure_connect_id" (change)="onAddSureConnectChange($event)"> | ||||
|             <option value="">Select SureConnect</option> | ||||
|             <option *ngFor="let sureConnect of sureConnectList" [value]="sureConnect.id">{{sureConnect.name}}</option> | ||||
|           </select> | ||||
|         </div> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
|       <!-- form code start --> | ||||
|       <div *ngIf="checkFormCode"> | ||||
|         <h4 style="font-weight: 300;display: inline;">Extension</h4> | ||||
|         <br> | ||||
|         <hr> | ||||
|         <div class="clr-row"> | ||||
|           <div class="clr-col-4" *ngFor="let field of additionalFieldsFromBackend"> | ||||
|             <ng-container *ngIf="field.formCode === formcode" [ngSwitch]="field.fieldType"> | ||||
|               <!-- Text Input --> <label *ngSwitchCase="'text'">{{ field.fieldName }}</label> | ||||
|               <input *ngSwitchCase="'text'" [type]="field.fieldType" [formControlName]="field.extValue" | ||||
|                 class="clr-input" /> | ||||
|               <!-- Date Input --> <label *ngSwitchCase="'date'">{{ field.fieldName }}</label> | ||||
|               <input *ngSwitchCase="'date'" [type]="field.fieldType" [formControlName]="field.extValue" | ||||
|                 class="clr-input" /> | ||||
|               <!-- Textarea --> <label *ngSwitchCase="'textarea'">{{ field.fieldName }}</label> | ||||
|               <textarea *ngSwitchCase="'textarea'" [formControlName]="field.extValue" col="10" row="2"></textarea> | ||||
|               <!-- Checkbox --> <label *ngSwitchCase="'checkbox'">{{ field.fieldName }}</label><br> | ||||
|               <input *ngSwitchCase="'checkbox'" [type]="field.fieldType" [formControlName]="field.extValue" | ||||
|                 class="clr-checkbox" /> | ||||
|             </ng-container> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <!-- form code end --> | ||||
|       <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button> | ||||
|         <button type="submit" class="btn btn-primary" (click)="onSubmit()">ADD</button> | ||||
|       </div> | ||||
|     </form> | ||||
|   </div> | ||||
| </clr-modal> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <!-- htmlpopup --> | ||||
| @ -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; | ||||
| } | ||||
| @ -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
 | ||||
| } | ||||
| @ -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<any> { | ||||
|     return this.apiRequest.get(this.baseURL); | ||||
|   } | ||||
|   getById(id: number): Observable<any> { | ||||
|     const _http = this.baseURL + "/" + id; | ||||
|     return this.apiRequest.get(_http); | ||||
|   } | ||||
|   create(data: any): Observable<any> { | ||||
|     return this.apiRequest.post(this.baseURL, data); | ||||
|   } | ||||
|   update(id: number, data: any): Observable<any> { | ||||
|     const _http = this.baseURL + "/" + id; | ||||
|     return this.apiRequest.put(_http, data); | ||||
|   } | ||||
|   delete(id: number): Observable<any> { | ||||
|     const _http = this.baseURL + "/" + id; | ||||
|     return this.apiRequest.delete(_http); | ||||
|   } | ||||
|    | ||||
|   // New method for updating JSON
 | ||||
|   updateJson(id: number): Observable<any> { | ||||
|     const _http = this.baseURL + "/json/" + id; | ||||
|     return this.apiRequest.put(_http, {}); | ||||
|   } | ||||
| 
 | ||||
| // updateaction
 | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| export const Data_lakecardvariable = { | ||||
|     "cardButton": false, | ||||
|     "cardmodeldata": `` | ||||
| } | ||||
| @ -0,0 +1,66 @@ | ||||
| <div class="cron-job-builder"> | ||||
|   <h4>Cron Job Builder</h4> | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Minute</label> | ||||
|         <select class="form-control" [(ngModel)]="minute" (change)="onFieldChange()"> | ||||
|           <option *ngFor="let option of minuteOptions" [value]="option.value">{{ option.label }}</option> | ||||
|         </select> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Hour</label> | ||||
|         <select class="form-control" [(ngModel)]="hour" (change)="onFieldChange()"> | ||||
|           <option *ngFor="let option of hourOptions" [value]="option.value">{{ option.label }}</option> | ||||
|         </select> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Day of Month</label> | ||||
|         <select class="form-control" [(ngModel)]="dayOfMonth" (change)="onFieldChange()"> | ||||
|           <option *ngFor="let option of dayOfMonthOptions" [value]="option.value">{{ option.label }}</option> | ||||
|         </select> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-6 clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Month</label> | ||||
|         <select class="form-control" [(ngModel)]="month" (change)="onFieldChange()"> | ||||
|           <option *ngFor="let option of monthOptions" [value]="option.value">{{ option.label }}</option> | ||||
|         </select> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-12 clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Day of Week</label> | ||||
|         <select class="form-control" [(ngModel)]="dayOfWeek" (change)="onFieldChange()"> | ||||
|           <option *ngFor="let option of dayOfWeekOptions" [value]="option.value">{{ option.label }}</option> | ||||
|         </select> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-12"> | ||||
|       <div class="form-group"> | ||||
|         <label>Generated Cron Expression:</label> | ||||
|         <input type="text" class="form-control" [value]="buildCronExpression()" readonly> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|   <div class="clr-row" *ngIf="cronDescription"> | ||||
|     <div class="clr-col-12"> | ||||
|       <div class="cron-description"> | ||||
|         <strong>Schedule Description:</strong> {{ cronDescription }} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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<string>(); | ||||
|    | ||||
|   // 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(); | ||||
|   } | ||||
| } | ||||
| @ -47,7 +47,8 @@ | ||||
|         </button> | ||||
| 
 | ||||
|         <button class="btn btn-icon" style="margin-top: 10px; float: right;"> | ||||
|               <input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch" (change)="toggleAddToDashboard(item)" /> | ||||
|           <input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch" | ||||
|             (change)="toggleAddToDashboard(item)" /> | ||||
|         </button> | ||||
| 
 | ||||
|         <!-- <label for="workflow_name">Add to Dasboard</label> | ||||
| @ -58,8 +59,10 @@ | ||||
|         </button> | ||||
| 
 | ||||
|         <h4 style="margin-top: 0px; margin-left: 10px;">{{item.name}}</h4> | ||||
|             <ndc-dynamic class="no-drag" [ndcDynamicComponent]="item.component" (moduleInfo)="display($event)"></ndc-dynamic> | ||||
|         <ndc-dynamic class="no-drag" [ndcDynamicComponent]="item.component" [ndcDynamicInputs]="getChartInputs(item)" | ||||
|           (moduleInfo)="display($event)"></ndc-dynamic> | ||||
|         <!-- </ng-container> --> | ||||
| 
 | ||||
|       </gridster-item> | ||||
|     </gridster> | ||||
|   </div> | ||||
| @ -79,16 +82,27 @@ | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="charttitle">Chart Title</label> | ||||
|           <input id="chartparameter" type="text" formControlName="charttitle" class="clr-input" [(ngModel)]="gadgetsEditdata.charttitle" > | ||||
|           <input id="chartparameter" type="text" formControlName="charttitle" class="clr-input" | ||||
|             [(ngModel)]="gadgetsEditdata.charttitle"> | ||||
|         </div> | ||||
|       </div> | ||||
|       <!-- <div class="clr-row"> | ||||
|        | ||||
|       <!-- Add Connection Selection Field --> | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="id">ID</label> | ||||
|           <input id="datasource" type="text" formControlName="id" class="clr-input" [(ngModel)]="gadgetsEditdata.id"> | ||||
|           <label for="connection">Connection</label> | ||||
|           <select id="connection" formControlName="connection" [(ngModel)]="gadgetsEditdata.connection" class="clr-select"> | ||||
|             <option value="">Select Connection</option> | ||||
|             <option *ngFor="let conn of sureconnectData" [value]="conn.id"> | ||||
|               {{conn.connection_name || conn.id}} | ||||
|             </option> | ||||
|           </select> | ||||
|           <div class="clr-subtext">Select a SureConnect connection to use for this chart</div> | ||||
|         </div> | ||||
|       </div> --> | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Grid View' && gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|       </div> | ||||
|        | ||||
|       <div class="clr-row" | ||||
|         *ngIf="gadgetsEditdata?.fieldName !== 'Grid View' && gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <div class="clr-form-control" style="margin-top: 5px;margin-bottom: 10px;"> | ||||
|             <div class="clr-control-container"> | ||||
| @ -105,11 +119,13 @@ | ||||
|                 <label for="donut" class="clr-control-label">Show donut</label> | ||||
|               </div> --> | ||||
|               <div class="clr-checkbox-wrapper"> | ||||
|                 <input type="checkbox" id="chartlegend" formControlName="chartlegend" [(ngModel)]="gadgetsEditdata.chartlegend" class="clr-checkbox" /> | ||||
|                 <input type="checkbox" id="chartlegend" formControlName="chartlegend" | ||||
|                   [(ngModel)]="gadgetsEditdata.chartlegend" class="clr-checkbox" /> | ||||
|                 <label for="chartlegend" class="clr-control-label">Show Chart Legend</label> | ||||
|               </div> | ||||
|               <div class="clr-checkbox-wrapper"> | ||||
|                 <input type="checkbox" id="showlabel" formControlName="showlabel" [(ngModel)]="gadgetsEditdata.showlabel" class="clr-checkbox" /> | ||||
|                 <input type="checkbox" id="showlabel" formControlName="showlabel" | ||||
|                   [(ngModel)]="gadgetsEditdata.showlabel" class="clr-checkbox" /> | ||||
|                 <label for="showlabel" class="clr-control-label">Show Chart Label</label> | ||||
|               </div> | ||||
|             </div> | ||||
| @ -128,8 +144,10 @@ | ||||
| 
 | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="table">Table Name</label> | ||||
|           <div><input type="urk" id="table" formControlName="table" class="clr-input" [(ngModel)]="gadgetsEditdata.table" style="width:90%"> <span><button  class="btn btn-icon btn-primary" style="margin: 0px;" (click)="tablename(gadgetsEditdata.table)"> | ||||
|           <label for="table">Api Url</label> | ||||
|           <div><input type="urk" id="table" formControlName="table" class="clr-input" | ||||
|               [(ngModel)]="gadgetsEditdata.table" style="width:90%"> <span><button class="btn btn-icon btn-primary" | ||||
|                 style="margin: 0px;" (click)="callApi(gadgetsEditdata.table)"> | ||||
|                 <clr-icon shape="redo"></clr-icon> </button></span></div> | ||||
|           <!-- <select  id="table" formControlName="table" [(ngModel)]="gadgetsEditdata.table" (change)="tablename($event.target.value)"> | ||||
|             <option value="null">choose Table</option> | ||||
| @ -137,9 +155,11 @@ | ||||
|            </select> --> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Grid View'&& gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|       <div class="clr-row" | ||||
|         *ngIf="gadgetsEditdata?.fieldName !== 'Grid View'&& gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="xAxis">X-Axis <span *ngIf="gadgetsEditdata?.fieldName === 'Bubble Chart' || gadgetsEditdata?.fieldName === 'Scatter Chart' ">(Numeric)</span></label> | ||||
|           <label for="xAxis">X-Axis <span | ||||
|               *ngIf="gadgetsEditdata?.fieldName === 'Bubble Chart' || gadgetsEditdata?.fieldName === 'Scatter Chart' ">(Numeric)</span></label> | ||||
|           <!-- <input id="xAxis" type="text" formControlName="xAxis" class="clr-input" [(ngModel)]="gadgetsEditdata.xAxis"> --> | ||||
|           <select id="xAxis" formControlName="xAxis" [(ngModel)]="gadgetsEditdata.xAxis"> | ||||
|             <option value="null">choose Column</option> | ||||
| @ -147,7 +167,8 @@ | ||||
|           </select> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' && gadgetsEditdata?.fieldName !== 'Polar Area Chart' && gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|       <div class="clr-row" | ||||
|         *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' && gadgetsEditdata?.fieldName !== 'Polar Area Chart' && gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="yAxis" *ngIf="gadgetsEditdata?.fieldName === 'Grid View'; else yaxislable">Columns</label> | ||||
|           <ng-template #yaxislable> | ||||
| @ -170,7 +191,8 @@ | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata?.fieldName == 'Pie Chart' || gadgetsEditdata?.fieldName == 'Polar Area Chart' || gadgetsEditdata?.fieldName == 'To Do Chart'"> | ||||
|       <div class="clr-row" | ||||
|         *ngIf="gadgetsEditdata?.fieldName == 'Pie Chart' || gadgetsEditdata?.fieldName == 'Polar Area Chart' || gadgetsEditdata?.fieldName == 'To Do Chart'"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="yAxis">Y-Axis (Numeric)</label> | ||||
|           <select id="yAxis" formControlName="yAxis" [(ngModel)]="gadgetsEditdata.yAxis"> | ||||
| @ -180,6 +202,165 @@ | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Base Drilldown Configuration Section --> | ||||
|       <div class="clr-row" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <h4>Base Drilldown Configuration</h4> | ||||
|           <div class="clr-form-control"> | ||||
|             <div class="clr-control-container"> | ||||
|               <div class="clr-checkbox-wrapper"> | ||||
|                 <input type="checkbox" id="drilldownEnabled" formControlName="drilldownEnabled" | ||||
|                   [(ngModel)]="gadgetsEditdata.drilldownEnabled" class="clr-checkbox"  | ||||
|                   (change)="gadgetsEditdata.drilldownEnabled ? null : resetDrilldownConfiguration()" /> | ||||
|                 <label for="drilldownEnabled" class="clr-control-label">Enable Base Drilldown</label> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="drilldownApiUrl">Base Drilldown API URL</label> | ||||
|           <div> | ||||
|             <input type="text" id="drilldownApiUrl" formControlName="drilldownApiUrl" class="clr-input" | ||||
|               [(ngModel)]="gadgetsEditdata.drilldownApiUrl" style="width:90%" [ngModelOptions]="{standalone: true}">  | ||||
|             <span> | ||||
|               <button class="btn btn-icon btn-primary" style="margin: 0px;"  | ||||
|                 (click)="refreshBaseDrilldownColumns()" [disabled]="!gadgetsEditdata.drilldownApiUrl"> | ||||
|                 <clr-icon shape="redo"></clr-icon> | ||||
|               </button> | ||||
|             </span> | ||||
|           </div> | ||||
|           <div class="clr-subtext">Enter the API URL for base drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<country></div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="drilldownXAxis">Base Drilldown X-Axis</label> | ||||
|           <select id="drilldownXAxis" formControlName="drilldownXAxis" [(ngModel)]="gadgetsEditdata.drilldownXAxis" [ngModelOptions]="{standalone: true}"> | ||||
|             <option value="">Select X-Axis Column</option> | ||||
|             <option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option> | ||||
|           </select> | ||||
|           <div class="clr-subtext">Select the column to use for X-axis in base drilldown view</div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled &&  | ||||
|         gadgetsEditdata?.fieldName !== 'Pie Chart' &&  | ||||
|         gadgetsEditdata?.fieldName !== 'Polar Area Chart' &&  | ||||
|         gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="drilldownYAxis">Base Drilldown Y-Axis</label> | ||||
|           <select id="drilldownYAxis" formControlName="drilldownYAxis" [(ngModel)]="gadgetsEditdata.drilldownYAxis" [ngModelOptions]="{standalone: true}"> | ||||
|             <option value="">Select Y-Axis Column</option> | ||||
|             <option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option> | ||||
|           </select> | ||||
|           <div class="clr-subtext">Select the column to use for Y-axis in base drilldown view</div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Base Drilldown Parameter Configuration --> | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="drilldownParameter">Base Drilldown Parameter</label> | ||||
|           <select id="drilldownParameter" [(ngModel)]="gadgetsEditdata.drilldownParameter" [ngModelOptions]="{standalone: true}"> | ||||
|             <option value="">Select Parameter Column</option> | ||||
|             <option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option> | ||||
|           </select> | ||||
|           <div class="clr-subtext">Select the column to use as parameter for URL template replacement in base drilldown</div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Multi-Layer Drilldown Configurations --> | ||||
|       <div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <h4>Multi-Layer Drilldown Configurations</h4> | ||||
|           <button class="btn btn-sm btn-primary" (click)="addDrilldownLayer()"> | ||||
|             <clr-icon shape="plus"></clr-icon> Add Drilldown Layer | ||||
|           </button> | ||||
|           <div class="clr-subtext">Add additional drilldown layers for multi-level navigation</div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Dynamic Drilldown Layers --> | ||||
|       <div class="clr-row" *ngFor="let layer of gadgetsEditdata.drilldownLayers; let i = index"> | ||||
|         <div class="clr-col-sm-12" style="margin-top: 15px; padding: 10px; border: 1px solid #eee; border-radius: 4px;"> | ||||
|           <div style="display: flex; justify-content: space-between; align-items: center;"> | ||||
|             <h5>Drilldown Layer {{i + 1}}</h5> | ||||
|             <button class="btn btn-icon btn-danger btn-sm" (click)="removeDrilldownLayer(i)"> | ||||
|               <clr-icon shape="trash"></clr-icon> | ||||
|             </button> | ||||
|           </div> | ||||
|            | ||||
|           <div class="clr-form-control"> | ||||
|             <div class="clr-control-container"> | ||||
|               <div class="clr-checkbox-wrapper"> | ||||
|                 <input type="checkbox" [id]="'layerEnabled' + i" [(ngModel)]="layer.enabled" class="clr-checkbox" [ngModelOptions]="{standalone: true}" /> | ||||
|                 <label [for]="'layerEnabled' + i" class="clr-control-label">Enable Layer {{i + 1}} Drilldown</label> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <div class="clr-row"> | ||||
|             <div class="clr-col-sm-12"> | ||||
|               <label [for]="'layerApiUrl' + i">Layer {{i + 1}} API URL</label> | ||||
|               <div> | ||||
|                 <input type="text" [id]="'layerApiUrl' + i" class="clr-input" | ||||
|                   [(ngModel)]="layer.apiUrl" style="width:90%" [ngModelOptions]="{standalone: true}">  | ||||
|                 <span> | ||||
|                   <button class="btn btn-icon btn-primary" style="margin: 0px;"  | ||||
|                     (click)="refreshDrilldownLayerColumns(i)" [disabled]="!layer.apiUrl"> | ||||
|                     <clr-icon shape="redo"></clr-icon> | ||||
|                   </button> | ||||
|                 </span> | ||||
|               </div> | ||||
|               <div class="clr-subtext">Enter the API URL for layer {{i + 1}} drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<state></div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <div class="clr-row"> | ||||
|             <div class="clr-col-sm-12"> | ||||
|               <label [for]="'layerXAxis' + i">Layer {{i + 1}} X-Axis</label> | ||||
|               <select [id]="'layerXAxis' + i" [(ngModel)]="layer.xAxis" [ngModelOptions]="{standalone: true}"> | ||||
|                 <option value="">Select X-Axis Column</option> | ||||
|                 <option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option> | ||||
|               </select> | ||||
|               <div class="clr-subtext">Select the column to use for X-axis in layer {{i + 1}} drilldown view</div> | ||||
|             </div> | ||||
|           </div> | ||||
|            | ||||
|           <div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' &&  | ||||
|             gadgetsEditdata?.fieldName !== 'Polar Area Chart' &&  | ||||
|             gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||
|             <div class="clr-col-sm-12"> | ||||
|               <label [for]="'layerYAxis' + i">Layer {{i + 1}} Y-Axis</label> | ||||
|               <select [id]="'layerYAxis' + i" [(ngModel)]="layer.yAxis" [ngModelOptions]="{standalone: true}"> | ||||
|                 <option value="">Select Y-Axis Column</option> | ||||
|                 <option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option> | ||||
|               </select> | ||||
|               <div class="clr-subtext">Select the column to use for Y-axis in layer {{i + 1}} drilldown view</div> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <!-- Parameter Selection for Drilldown Layer --> | ||||
|           <div class="clr-row"> | ||||
|             <div class="clr-col-sm-12"> | ||||
|               <label [for]="'layerParameter' + i">Layer {{i + 1}} Parameter</label> | ||||
|               <select [id]="'layerParameter' + i" [(ngModel)]="layer.parameter" [ngModelOptions]="{standalone: true}"> | ||||
|                 <option value="">Select Parameter Column</option> | ||||
|                 <option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option> | ||||
|               </select> | ||||
|               <div class="clr-subtext">Select the column to use as parameter for URL template replacement in layer {{i + 1}} drilldown</div> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <!-- Layer Parameter Configuration --> | ||||
|           <!-- Removed parameter key input since we're using URL templates --> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- <div class="clr-row"> | ||||
|         <div class="clr-col-sm-12"> | ||||
|           <label for="chartparameter">API parameter</label> | ||||
| @ -188,11 +369,10 @@ | ||||
|       </div> --> | ||||
|       <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-outline" (click)="modeledit = false">Cancel</button> | ||||
|       <button type="submit" class="btn btn-primary"  (click)="onSubmit(modelid)" >save</button> | ||||
|         <button type="button" class="btn btn-primary" (click)="applyChanges(modelid)">Apply</button> | ||||
|         <button type="submit" class="btn btn-primary" (click)="onSubmit(modelid)">Save</button> | ||||
|       </div> | ||||
| 
 | ||||
|     </form> | ||||
|   </div> | ||||
| </clr-modal> | ||||
|    | ||||
|    | ||||
| @ -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; | ||||
| @ -132,16 +133,31 @@ export class EditnewdashComponent implements OnInit { | ||||
|     chartcolor: '', | ||||
|     slices: '', | ||||
|     yAxis: '', | ||||
|     xAxis:'' | ||||
| 
 | ||||
|     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 _fb: FormBuilder, | ||||
|     private datastoreService: DatastoreService, | ||||
|     private alertService:AlertsService,) { } | ||||
|     private alertService: AlertsService, | ||||
|     private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor
 | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
| 
 | ||||
| @ -166,7 +182,6 @@ export class EditnewdashComponent implements OnInit { | ||||
|       minCols: 10, | ||||
|       minRows: 10 | ||||
|     }; | ||||
|       this.getData(); | ||||
| 
 | ||||
|     this.editId = this.route.snapshot.params.id; | ||||
|     console.log(this.editId); | ||||
| @ -197,6 +212,31 @@ export class EditnewdashComponent implements OnInit { | ||||
|       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(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -232,13 +272,21 @@ export class EditnewdashComponent implements OnInit { | ||||
|         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
 | ||||
| @ -426,8 +474,7 @@ export class EditnewdashComponent implements OnInit { | ||||
|   } | ||||
| 
 | ||||
|   modelid: number; | ||||
|     editGadget(item) | ||||
|     { | ||||
|   editGadget(item) { | ||||
|     this.modeledit = true; | ||||
|     this.modelid = item.chartid; | ||||
|     console.log(this.modelid); | ||||
| @ -437,6 +484,48 @@ export class EditnewdashComponent implements OnInit { | ||||
|     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'] = ''; | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|      | ||||
|     // Reset drilldown column data
 | ||||
|     this.drilldownColumnData = []; | ||||
|      | ||||
|     if (item.datastore !== undefined || '' || null) { | ||||
|       const datastore = item.datastore; | ||||
|       this.getTables(datastore); | ||||
| @ -459,8 +548,7 @@ export class EditnewdashComponent implements OnInit { | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|     UpdateLine() | ||||
|     { | ||||
|   UpdateLine() { | ||||
|     console.log('Add button clicked.......'); | ||||
|     console.log(this.dashboardArray); | ||||
|     console.log(this.dashboardCollection); | ||||
| @ -474,7 +562,8 @@ let cmp=this.dashboardCollection.dashboard.forEach(dashboard=>{ | ||||
|       this.componentCollection.forEach(component => { | ||||
|         if (dashboard.name === component.name) { | ||||
|           dashboard.component = component.name; | ||||
|     }  }) | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
|     console.log(cmp); | ||||
| 
 | ||||
| @ -515,8 +604,7 @@ console.log(cmp); | ||||
|     //       }
 | ||||
|   } | ||||
| 
 | ||||
|     onSubmit(id) | ||||
|     { | ||||
|   onSubmit(id) { | ||||
|     console.log(id); | ||||
|     if (!isNullArray(this.selectedyAxis)) { | ||||
|       console.log("get y-axis array", this.selectedyAxis); | ||||
| @ -526,21 +614,128 @@ console.log(cmp); | ||||
|     let num = id; | ||||
|     console.log(this.entryForm.value); | ||||
|     this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => { | ||||
|       if(item.chartid == num) | ||||
|         { | ||||
|       if (item.chartid == num) { | ||||
|         //item["product_id"] = "thisistest";
 | ||||
|         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); | ||||
|     console.log('dashboard collection ', this.dashboardCollection.dashboard); | ||||
|     this.modeledit = false; | ||||
| 
 | ||||
|     // this.entryForm.reset();
 | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 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'] || [] | ||||
|     }; | ||||
|      | ||||
|     // Remove undefined properties to avoid passing unnecessary data
 | ||||
|     Object.keys(chartInputs).forEach(key => { | ||||
|       if (chartInputs[key] === undefined) { | ||||
|         delete chartInputs[key]; | ||||
|       } | ||||
|     }); | ||||
|      | ||||
|     return chartInputs; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|     } | ||||
|      | ||||
|     // 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 }) | ||||
|   } | ||||
| @ -578,40 +773,150 @@ console.log(cmp); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|      tablename(val){ | ||||
|       console.log(val); | ||||
|   callApi(val) { | ||||
|     console.log(' api value ', val); | ||||
|     this.getColumns(this.selectedStoreId, val); | ||||
|   } | ||||
|   selectedyAxis; | ||||
|   columnData; | ||||
|   drilldownColumnData = []; // Add drilldown column data property
 | ||||
| 
 | ||||
|   getColumns(id, table) { | ||||
|       this.alertService.getColumnfromurl(table).subscribe(data =>{ | ||||
|         console.log(data); | ||||
|     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); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|     // 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 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 = []; | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 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/<country>
 | ||||
|       // 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/<country>
 | ||||
|     // returns: country
 | ||||
|     const match = baseUrl.match(/<([^>]+)>/); | ||||
|     return match ? match[1] : ''; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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/<parameter>", | ||||
|     xAxis: "column1", | ||||
|     yAxis: "column2", | ||||
|     parameter: "selectedColumn" | ||||
|   }, | ||||
|   { | ||||
|     enabled: true, | ||||
|     apiUrl: "third-level-endpoint/<parameter>", | ||||
|     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/<parameter>` | ||||
| 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 | ||||
| <div *ngIf="drilldownEnabled && currentDrilldownLevel > 0" class="drilldown-controls"> | ||||
|   <button (click)="navigateBack()" class="btn btn-secondary"> | ||||
|     ← Back to Level {{ currentDrilldownLevel - 1 }} | ||||
|   </button> | ||||
|   <button (click)="resetToOriginalData()" class="btn btn-outline"> | ||||
|     ↺ Reset to Original | ||||
|   </button> | ||||
| </div> | ||||
| ``` | ||||
| @ -1,4 +1,22 @@ | ||||
| <div style="display: block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|     [datasets]="barChartData" | ||||
|     [labels]="barChartLabels" | ||||
| @ -7,3 +25,4 @@ | ||||
|     (chartClick)="chartClicked($event)"> | ||||
|   </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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); | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,22 @@ | ||||
| <div style="display:block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|     [datasets]="bubbleChartData" | ||||
|     [type]="bubbleChartType" | ||||
| @ -7,3 +25,4 @@ | ||||
|     (chartClick)="chartClicked($event)"> | ||||
|   </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -1,16 +1,37 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| 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,35 +81,429 @@ 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; | ||||
| 
 | ||||
|   constructor(private dashboardService: Dashboard3Service) { } | ||||
| 
 | ||||
|   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(e); | ||||
|     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 { | ||||
|  | ||||
| @ -1,8 +1,44 @@ | ||||
| <div style="display: block"> | ||||
| <div class="doughnut-chart-container"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3> | ||||
|   <div class="chart-wrapper"> | ||||
|     <!-- Show loading indicator --> | ||||
|     <div class="loading-indicator" *ngIf="doughnutChartLabels.length === 0 && doughnutChartData.length === 0 && !noDataAvailable"> | ||||
|       <div class="spinner"></div> | ||||
|       <p>Loading chart data...</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show no data message --> | ||||
|     <div class="no-data-message" *ngIf="noDataAvailable"> | ||||
|       <p>No chart data available</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show chart when data is available --> | ||||
|     <canvas baseChart  | ||||
|       *ngIf="!noDataAvailable && doughnutChartLabels.length > 0 && doughnutChartData.length > 0" | ||||
|       [data]="doughnutChartData"  | ||||
|       [labels]="doughnutChartLabels"  | ||||
|       [type]="doughnutChartType" | ||||
|       [options]="doughnutChartOptions" | ||||
|       (chartHover)="chartHovered($event)" | ||||
| 	(chartClick)="chartClicked($event)"></canvas> | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
|   <div class="chart-legend" *ngIf="!noDataAvailable && showlabel && doughnutChartLabels && doughnutChartLabels.length > 0"> | ||||
|     <div class="legend-item" *ngFor="let label of doughnutChartLabels; let i = index"> | ||||
|       <span class="legend-color" [style.background-color]="getLegendColor(i)"></span> | ||||
|       <span class="legend-label">{{ label }}</span> | ||||
|       <span class="legend-value">{{ doughnutChartData && doughnutChartData[i] !== undefined ? doughnutChartData[i] : 0 }}</span> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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]; | ||||
| 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
 | ||||
| 
 | ||||
|   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; | ||||
| 
 | ||||
|   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(e); | ||||
|     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); | ||||
|   } | ||||
|   constructor() { } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,5 +1,32 @@ | ||||
| <div style="display: block"> | ||||
|   <canvas baseChart [datasets]="dynamicChartData" | ||||
| <div class="dynamic-chart-container"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3> | ||||
|   <div class="chart-wrapper"> | ||||
|     <!-- Show loading indicator --> | ||||
|     <div class="loading-indicator" *ngIf="dynamicChartLabels.length === 0 && dynamicChartData.length === 0 && !noDataAvailable"> | ||||
|       <div class="spinner"></div> | ||||
|       <p>Loading chart data...</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show no data message --> | ||||
|     <div class="no-data-message" *ngIf="noDataAvailable"> | ||||
|       <p>No chart data available</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show chart when data is available --> | ||||
|     <canvas baseChart  | ||||
|       *ngIf="!noDataAvailable && dynamicChartLabels.length > 0 && dynamicChartData.length > 0" | ||||
|       [datasets]="dynamicChartData" | ||||
|       [options]="barChartOptions" | ||||
|       [type]="barChartType" | ||||
|       [labels]="dynamicChartLabels" | ||||
| @ -8,3 +35,4 @@ | ||||
|     </canvas> | ||||
|   </div> | ||||
|   <button class="btn btn-primary" (click)="randomize()">Update</button> | ||||
| </div> | ||||
| @ -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,35 +87,367 @@ 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'; | ||||
|  | ||||
| @ -1 +1,36 @@ | ||||
| <p>financial-chart works!</p> | ||||
| <div class="financial-chart-container"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3> | ||||
|   <div class="chart-wrapper"> | ||||
|     <!-- Show loading indicator --> | ||||
|     <div class="loading-indicator" *ngIf="financialChartLabels.length === 0 && financialChartData.length === 0 && !noDataAvailable"> | ||||
|       <div class="spinner"></div> | ||||
|       <p>Loading chart data...</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show no data message --> | ||||
|     <div class="no-data-message" *ngIf="noDataAvailable"> | ||||
|       <p>No chart data available</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show chart when data is available --> | ||||
|     <canvas baseChart  | ||||
|       *ngIf="!noDataAvailable && financialChartLabels.length > 0 && financialChartData.length > 0" | ||||
|       [datasets]="financialChartData" | ||||
|       [labels]="financialChartLabels" | ||||
|       [type]="financialChartType" | ||||
|       (chartHover)="chartHovered($event)" | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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); | ||||
|   } | ||||
| } | ||||
| @ -2,59 +2,26 @@ | ||||
|   <div class="dg-wrapper"> | ||||
|     <div class="clr-row"> | ||||
|       <div class="clr-col-8"> | ||||
|         <h3>User Group Maintenance</h3> | ||||
|         <h3>{{charttitle || 'Data Grid'}}</h3> | ||||
|       </div> | ||||
|     </div> | ||||
|     <clr-datagrid [clrDgLoading]="loading"> | ||||
|       <clr-dg-placeholder> <ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template> | ||||
|         <div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder> | ||||
|         <div *ngIf="error;else loadingSpinner">{{error}}</div> | ||||
|       </clr-dg-placeholder> | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="'usrGrp'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         User Group No | ||||
|       </ng-container></clr-dg-column> | ||||
|       <clr-dg-column [clrDgField]="'groupName'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         Group Name | ||||
|       </ng-container></clr-dg-column> | ||||
|       <clr-dg-column [clrDgField]="'groupDesc'"><ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         Description | ||||
|         </ng-container></clr-dg-column > | ||||
|       <clr-dg-column [clrDgField]="'groupLevel'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         Group Level | ||||
|       </ng-container></clr-dg-column> | ||||
|       <clr-dg-column [clrDgField]="'status'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         Status | ||||
|       </ng-container></clr-dg-column> | ||||
|       <!-- <clr-dg-column [clrDgField]="'usrGrp'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|         User Group | ||||
|       </ng-container></clr-dg-column> --> | ||||
|       <clr-dg-column [clrDgField]="'updateDateFormated'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|       Updated Date | ||||
|       </ng-container></clr-dg-column> | ||||
|       <!-- Dynamic columns based on response keys --> | ||||
|       <clr-dg-column *ngFor="let header of dynamicHeaders" [clrDgField]="header.key">  | ||||
|         <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|           {{header.displayName}} | ||||
|         </ng-container> | ||||
|       </clr-dg-column> | ||||
| 
 | ||||
|    | ||||
|       <clr-dg-row *clrDgItems="let user of givendata?.slice()?.reverse();let i = index" [clrDgItem]="user"> | ||||
|         <clr-dg-cell>{{user.usrGrp}}</clr-dg-cell> | ||||
|         <clr-dg-cell>{{user.groupName}}</clr-dg-cell> | ||||
|         <clr-dg-cell >{{user.groupDesc}}</clr-dg-cell> | ||||
|         <clr-dg-cell>{{user.groupLevel}}</clr-dg-cell> | ||||
|         <clr-dg-cell>{{user.status}}</clr-dg-cell> | ||||
|         <!-- <clr-dg-cell>{{user.usrGrp}}</clr-dg-cell> --> | ||||
|         <clr-dg-cell>{{user.updateDateFormated}}</clr-dg-cell> | ||||
|    | ||||
|         <!-- <clr-dg-action-overflow> | ||||
|    | ||||
|         </clr-dg-action-overflow> --> | ||||
|    | ||||
|         <!-- <clr-dg-row-detail *clrIfExpanded> | ||||
|           <table class="table"> | ||||
|             <tr> | ||||
|               <td class="td-title">username</td> | ||||
|               <td class="td-content">{{user.groupName}}</td> | ||||
|             </tr> | ||||
|    | ||||
|    | ||||
|           </table> | ||||
|         </clr-dg-row-detail> --> | ||||
|       <clr-dg-row *clrDgItems="let item of givendata" [clrDgItem]="item"> | ||||
|         <!-- Dynamic cells based on response keys --> | ||||
|         <clr-dg-cell *ngFor="let header of dynamicHeaders"> | ||||
|           {{item[header.key]}} | ||||
|         </clr-dg-cell> | ||||
|       </clr-dg-row> | ||||
| 
 | ||||
|       <clr-dg-footer> | ||||
|  | ||||
| @ -1,22 +1,33 @@ | ||||
| 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; | ||||
|   error: string; | ||||
|   modalAdd = false; | ||||
|   modaledit = false; | ||||
|   modaldelete = false; | ||||
| @ -25,30 +36,143 @@ export class GridViewComponent implements OnInit { | ||||
|   medit; | ||||
|   showdata; | ||||
|   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 dashboardService: Dashboard3Service, | ||||
|   ) { } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.mainservice.getAll().subscribe((data) => { | ||||
|       console.log(data); | ||||
|       this.givendata = data; | ||||
|       if(this.givendata.length==0){ | ||||
|     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"; | ||||
|         console.log(this.error) | ||||
|             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); | ||||
|       if(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<string>(); | ||||
|       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()); | ||||
|   } | ||||
| } | ||||
| @ -1,4 +1,22 @@ | ||||
| <div style="display: block;"> | ||||
| <div style="display: block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|                 [datasets]="lineChartData" | ||||
|                 [labels]="lineChartLabels" | ||||
| @ -9,4 +27,5 @@ | ||||
|                 (chartHover)="chartHovered($event)" | ||||
|                 (chartClick)="chartClicked($event)"></canvas> | ||||
|   </div> | ||||
| </div> | ||||
| <!--     [color]="lineChartColors"--> | ||||
| @ -1,11 +1,36 @@ | ||||
| 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 { | ||||
| 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 lineChartData: Array<any> = [ | ||||
|     {data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'}, | ||||
|     {data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'}, | ||||
| @ -44,6 +69,309 @@ export class LineChartComponent implements OnInit { | ||||
|   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<any> = []; | ||||
|   originalLineChartData: Array<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('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<any> = new Array(this.lineChartData.length); | ||||
|     for (let i = 0; i < this.lineChartData.length; i++) { | ||||
| @ -57,15 +385,90 @@ export class LineChartComponent implements OnInit { | ||||
| 
 | ||||
|   // events
 | ||||
|   public chartClicked(e: any): void { | ||||
| 		console.log(e); | ||||
|     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); | ||||
|   } | ||||
|   constructor() { } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,9 +1,44 @@ | ||||
| <div style="display: block;"> | ||||
| <div class="pie-chart-container"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3> | ||||
|   <div class="chart-wrapper"> | ||||
|     <!-- Show loading indicator --> | ||||
|     <div class="loading-indicator" *ngIf="pieChartLabels.length === 0 && pieChartData.length === 0 && !noDataAvailable"> | ||||
|       <div class="spinner"></div> | ||||
|       <p>Loading chart data...</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show no data message --> | ||||
|     <div class="no-data-message" *ngIf="noDataAvailable && pieChartLabels.length === 0"> | ||||
|       <p>No chart data available</p> | ||||
|     </div> | ||||
|      | ||||
|     <!-- Show chart when data is available --> | ||||
|     <canvas baseChart  | ||||
|       *ngIf="pieChartLabels.length > 0 && pieChartData.length > 0" | ||||
|       [data]="pieChartData"  | ||||
|       [labels]="pieChartLabels"  | ||||
|       [type]="pieChartType" | ||||
|       [options]="pieChartOptions" | ||||
|       (chartHover)="chartHovered($event)" | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
|   <div class="chart-legend" *ngIf="showlabel && pieChartLabels && pieChartLabels.length > 0"> | ||||
|     <div class="legend-item" *ngFor="let label of pieChartLabels; let i = index"> | ||||
|       <span class="legend-color" [style.background-color]="getLegendColor(i)"></span> | ||||
|       <span class="legend-label">{{ label }}</span> | ||||
|       <span class="legend-value">{{ pieChartData && pieChartData[i] !== undefined ? pieChartData[i] : 0 }}</span> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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); | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,10 +1,28 @@ | ||||
| 
 | ||||
| <div style="display: block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|     [datasets]="polarAreaChartData" | ||||
|       [data]="polarAreaChartData" | ||||
|       [labels]="polarAreaChartLabels" | ||||
|       [type]="polarAreaChartType" | ||||
|       (chartHover)="chartHovered($event)" | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -1,16 +1,68 @@ | ||||
| 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'} | ||||
| @ -18,16 +70,376 @@ export class PolarChartComponent implements OnInit { | ||||
| 
 | ||||
|   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; | ||||
|    | ||||
| 	// 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" }
 | ||||
| 	// ];
 | ||||
|   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(); | ||||
|   } | ||||
| 
 | ||||
|   // 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 { | ||||
|  | ||||
| @ -1,8 +1,28 @@ | ||||
| <div style="display: block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|       [datasets]="radarChartData" | ||||
|       [labels]="radarChartLabels" | ||||
|       [type]="radarChartType" | ||||
|       (chartHover)="chartHovered($event)" | ||||
| 	(chartClick)="chartClicked($event)"></canvas> | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -1,11 +1,36 @@ | ||||
| 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 { | ||||
| 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
 | ||||
| 
 | ||||
|   // Radar
 | ||||
|   public radarChartLabels: string[] = [ | ||||
|     "Eating", | ||||
| @ -23,17 +48,413 @@ public radarChartData: any = [ | ||||
|   ]; | ||||
|   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; | ||||
| 
 | ||||
|   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(e); | ||||
|     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); | ||||
|   } | ||||
|   constructor() { } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,22 @@ | ||||
| <div style="display: block"> | ||||
|   <!-- Drilldown mode indicator --> | ||||
|   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||
|     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||
|     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Level {{currentDrilldownLevel - 1}} | ||||
|     </button> | ||||
|     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||
|       Back to Main View | ||||
|     </button> | ||||
|   </div> | ||||
|    | ||||
|   <!-- No data message --> | ||||
|   <div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;"> | ||||
|     No data available | ||||
|   </div> | ||||
|    | ||||
|   <!-- Chart display --> | ||||
|   <div *ngIf="!noDataAvailable"> | ||||
|     <canvas baseChart | ||||
|       [datasets]="scatterChartData" | ||||
|       [type]="scatterChartType" | ||||
| @ -6,3 +24,4 @@ | ||||
|       (chartClick)="chartClicked($event)"> | ||||
|     </canvas> | ||||
|   </div> | ||||
| </div> | ||||
| @ -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 }, | ||||
| @ -65,10 +93,374 @@ 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 { | ||||
|  | ||||
| @ -1,20 +1,69 @@ | ||||
| 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); | ||||
|   } | ||||
|  | ||||
| @ -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 */ | ||||
| @ -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"} | ||||
| @ -0,0 +1,164 @@ | ||||
| <h4 style="font-weight: 300;display: inline;"><b> Connection</b></h4> | ||||
| <span class="label label-light-blue" style="display: inline;margin-left: 10px;">Edit Mode</span><br> | ||||
| Define A connection to use in a job, that can calls APIs from another App. | ||||
| <!-- <span style="display: inline; float: right;"> <button id="add" class="btn btn-primary" ><clr-icon shape="plus" ></clr-icon>Import</button></span> --> | ||||
| <hr> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <div class="container"> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   <!-- entry form--> | ||||
|   <form  > | ||||
|     <div class="clr-row"> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="projectName">Connection Name</label> | ||||
|         <input id="projectName" type="text"  placeholder="Enter Connection Name" name="connection_name" [(ngModel)]="editdata.connection_name" | ||||
|           class="clr-input"> | ||||
|       </div> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="description">Description (optional)</label> | ||||
| 
 | ||||
|           <textarea  id="" cols="10" rows="2"  name="description"  placeholder="Enter Description" [(ngModel)]="editdata.description"> | ||||
|           </textarea> | ||||
| 
 | ||||
|       </div> | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       <!-- add field here --> | ||||
|       <div style="margin-top: 40px;"> | ||||
|       <h4 style="font-weight: 300;display: inline;"> Default Settings</h4> | ||||
| </div> | ||||
| These configurations default. | ||||
| <hr> | ||||
| 
 | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|           <label for="technologyStack">Type</label> | ||||
|           <select  selected="null" class="clr-dropdown" name="type" [(ngModel)]="editdata.type"> | ||||
|             <option value="null">Choose Type</option> | ||||
|             <option>No AUTH</option> | ||||
|             <option>API KEY</option> | ||||
|             <option>Bearer Token</option> | ||||
|             <option>Basic Oauth</option> | ||||
|             <option>Digest Auth</option> | ||||
|           </select> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|           <label for="description">Access Token</label> | ||||
| 
 | ||||
|             <textarea  id="" cols="10" rows="2"  name="access_token" [(ngModel)]="editdata.access_token" placeholder="Enter token"> | ||||
|             </textarea> | ||||
| 
 | ||||
|         </div></div> | ||||
| <div class="clr-row"> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="projectPrefix">Client ID</label> | ||||
|     <input type="text"  placeholder="Enter Clientid" class="clr-input" name="client_id" [(ngModel)]="editdata.client_id"> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="clr-row"> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="projectPrefix"> Username</label> | ||||
|         <input type="text"  placeholder="Enter Database Username" class="clr-input" name="username" [(ngModel)]="editdata.username"> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="dbPassword"> Password</label> | ||||
|         <input type="text" placeholder="Enter Database Password" class="clr-input" name="password" [(ngModel)]="editdata.password"> | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
|     <span style="float: right;"> | ||||
|       <button type="submit" class="btn btn-primary" (click)="onupdate()">Update</button> | ||||
|       </span> | ||||
| 
 | ||||
|       <div style="margin-top: 40px;"> | ||||
|         <h4 style="font-weight: 300;display: inline;">Generate Token</h4> | ||||
|   </div> | ||||
|   <hr> | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|       <label for="projectName">URL</label> | ||||
|       <input id="projectName" type="text"  placeholder="Enter Connection Name" | ||||
|         class="clr-input"> | ||||
|     </div> | ||||
|     <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|       <label for="description">Method</label> | ||||
|       <select  selected="null" class="clr-dropdown"> | ||||
|         <option selected>GET</option> | ||||
|         <option>POST</option> | ||||
|         <option>PUT</option> | ||||
|         <option>PATCH</option> | ||||
|         <option>DELETE</option> | ||||
|         <option>COPY</option> | ||||
|       </select> | ||||
|     </div> | ||||
|     </div> | ||||
| <div class=clr-row> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="description"> Token</label> | ||||
| 
 | ||||
|       <textarea  id="" cols="10" rows="2"  name=" remarks"  placeholder="Enter token"> | ||||
|       </textarea> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|   <label for="projectPrefix">URL</label> | ||||
|   <input type="text"  placeholder="Enter url" class="clr-input"> | ||||
| 
 | ||||
| </div> | ||||
| </div> | ||||
| <div class=clr-row> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="description">Body</label> | ||||
| 
 | ||||
|       <textarea  id="" cols="10" rows="2"  name=" remarks"  placeholder="Enter body"> | ||||
|       </textarea> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| <div> | ||||
|   <span class="center" style="text-align: center;"> | ||||
|     <button class="btn btn-primary" type="submit">Send</button> | ||||
|      <button type="submit" class="btn btn-primary">Use</button> | ||||
|         </span> | ||||
| <span style="float: right;"> | ||||
| <button type="submit" class="btn btn-primary">TEST</button> | ||||
| </span> | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
|           <div style="margin-top: 40px;"> | ||||
|             <h4 style="font-weight: 300;display: inline;">Response</h4> | ||||
|       </div> | ||||
|       <hr> | ||||
|       code | ||||
|       <span class="label success" style="background-color: green;">200</span><br> | ||||
|       <div class="clr-row" style="padding-top: 10px;"> | ||||
|         <textarea  id="" cols="10" rows="10"  name=" remarks"  placeholder="Enter Description"> | ||||
|         </textarea> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
|   </form> | ||||
| </div> | ||||
| @ -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; | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| 
 | ||||
| import { EditsureconnectComponent } from './editsureconnect.component'; | ||||
| 
 | ||||
| describe('EditsureconnectComponent', () => { | ||||
|   let component: EditsureconnectComponent; | ||||
|   let fixture: ComponentFixture<EditsureconnectComponent>; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ EditsureconnectComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(EditsureconnectComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @ -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 }); | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| @ -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 */ | ||||
| @ -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"} | ||||
| @ -0,0 +1,185 @@ | ||||
| <h4 style="font-weight: 300;display: inline;"><b>New Connection</b></h4> | ||||
| <span class="label label-light-blue" style="display: inline;margin-left: 10px;">Add Mode</span><br> | ||||
| Define A connection to use in a job, that can calls APIs from another App. | ||||
| <!-- <span style="display: inline; float: right;"> <button id="add" class="btn btn-primary" ><clr-icon shape="plus" ></clr-icon>Import</button></span> --> | ||||
| <hr> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| <div class="container"> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   <!-- entry form--> | ||||
|   <form  [formGroup]="entryForm"> | ||||
|     <div class="clr-row"> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="projectName">Connection Name</label> | ||||
|         <input id="projectName" type="text"  placeholder="Enter Connection Name" | ||||
|           class="clr-input" formControlName="connection_name"> | ||||
|       </div> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="description">Description (optional)</label> | ||||
| 
 | ||||
|           <textarea  id="" cols="10" rows="2"  name="remarks"  placeholder="Enter Description" formControlName="description"> | ||||
|           </textarea> | ||||
| 
 | ||||
|       </div> | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       <!-- add field here --> | ||||
|       <div style="margin-top: 40px;"> | ||||
|       <h4 style="font-weight: 300;display: inline;"> Default Settings</h4> | ||||
| </div> | ||||
| These configurations default. | ||||
| <hr> | ||||
| 
 | ||||
|       <div class="clr-row"> | ||||
|         <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|           <label for="technologyStack">Type</label> | ||||
|           <select  selected="null" class="clr-dropdown" formControlName="type"> | ||||
|             <option value="null">Choose Type</option> | ||||
|             <option>No AUTH</option> | ||||
|             <option>API KEY</option> | ||||
|             <option>Bearer Token</option> | ||||
|             <option>Basic Oauth</option> | ||||
|             <option>Digest Auth</option> | ||||
|           </select> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|           <label for="description">Access Token</label> | ||||
| 
 | ||||
|             <textarea  id="" cols="10" rows="2"  name=" remarks"  placeholder="Enter token" formControlName="access_token"> | ||||
|             </textarea> | ||||
| 
 | ||||
|         </div></div> | ||||
| <div class="clr-row"> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="projectPrefix">Client ID</label> | ||||
|     <input type="text"  placeholder="Enter Clientid" class="clr-input" formControlName="client_id"> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="clr-row"> | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="projectPrefix"> Username</label> | ||||
|         <input type="text"  placeholder="Enter Database Username" class="clr-input" formControlName="username"> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|         <label for="dbPassword"> Password</label> | ||||
|         <input type="text" placeholder="Enter Database Password" class="clr-input" formControlName="password"> | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
|     <span style="float: right;"> | ||||
|       <button type="submit" class="btn btn-primary" (click)="onSubmit()">SUBMIT</button> | ||||
|       </span> | ||||
|     </form> | ||||
|       <div style="margin-top: 40px;"> | ||||
|         <h4 style="font-weight: 300;display: inline;">Generate Token</h4> | ||||
| 
 | ||||
|   </div> | ||||
|   <hr> | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|       <label for="description">Request Method</label> | ||||
|       <select  selected="null" class="clr-dropdown"> | ||||
|         <option selected>GET</option> | ||||
|         <option>POST</option> | ||||
|         <option>PUT</option> | ||||
|         <option>PATCH</option> | ||||
|         <option>DELETE</option> | ||||
|         <option>COPY</option> | ||||
|       </select> | ||||
|     </div> | ||||
|     <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|       <label for="projectName">URL</label> | ||||
|       <input id="projectName" type="text"  placeholder="https://example.com" | ||||
|         class="clr-input"> | ||||
|     </div> | ||||
|     </div> | ||||
| 
 | ||||
| <div class="clr-row"> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="description">Body</label> | ||||
| 
 | ||||
|       <textarea  id="" cols="10" rows="2"  name=" remarks"  placeholder="Enter body" > | ||||
|       </textarea> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
|   <div class="center" style="text-align: center;"> | ||||
|     <button class="btn btn-primary" type="submit" (click)="onLogin()">Send</button> | ||||
|      <button type="submit" class="btn btn-primary">Use</button> | ||||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
| <div style="margin-top: 40px;"> | ||||
|   <h4 style="font-weight: 300;display: inline;">Test Connection</h4> | ||||
| 
 | ||||
| </div> | ||||
| <hr> | ||||
| <div class="clr-row"> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="projectName">URL</label> | ||||
|     <input id="projectName" type="text"  placeholder="Enter Connection Name" | ||||
|       class="clr-input"> | ||||
|   </div> | ||||
|   <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|     <label for="description">Method</label> | ||||
|     <select  selected="null" class="clr-dropdown"> | ||||
|       <option selected>GET</option> | ||||
|       <option>POST</option> | ||||
|       <option>PUT</option> | ||||
|       <option>PATCH</option> | ||||
|       <option>DELETE</option> | ||||
|       <option>COPY</option> | ||||
|     </select> | ||||
|   </div> | ||||
|   </div> | ||||
|   <div class=clr-row> | ||||
|     <div class="clr-col-md-4 clr-col-sm-12"> | ||||
|       <label for="description">Body</label> | ||||
| 
 | ||||
|         <textarea  id="" cols="10" rows="2"  name=" remarks"  placeholder="Enter body"> | ||||
|         </textarea> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </div> | ||||
|   <div style="float: right;"> | ||||
|     <button type="submit" class="btn btn-primary">TEST</button> | ||||
|     </div> | ||||
| 
 | ||||
|           <div style="margin-top: 40px;"> | ||||
|             <h4 style="font-weight: 300;display: inline;">Response</h4> | ||||
|       </div> | ||||
|       <hr> | ||||
|       code | ||||
|       <span class="label success" style="background-color: green;color:white;">200</span><br> | ||||
|       <div class="clr-row" style="padding-top: 10px;"> | ||||
|         <textarea  id="" cols="10" rows="10"  name=" remarks"> | ||||
|         </textarea> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| @ -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; | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| 
 | ||||
| import { OauthComponent } from './oauth.component'; | ||||
| 
 | ||||
| describe('OauthComponent', () => { | ||||
|   let component: OauthComponent; | ||||
|   let fixture: ComponentFixture<OauthComponent>; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ OauthComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(OauthComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @ -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'; | ||||
|           } | ||||
|         } | ||||
|       ); | ||||
| 
 | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| .delete, .heading { | ||||
|   text-align: center; | ||||
|   color: red; | ||||
| }/*# sourceMappingURL=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"} | ||||
| @ -0,0 +1,109 @@ | ||||
| 
 | ||||
| <ol class="breadcrumb breadcrumb-arrow font-trirong"> | ||||
|   <li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"> <clr-icon shape="home"></clr-icon></a></li> | ||||
|   <li><a href="javascript://"> <clr-icon shape="flag"></clr-icon> SureConnect</a></li> | ||||
| </ol> | ||||
| <br> | ||||
| 
 | ||||
| 
 | ||||
| <div class="dg-wrapper"> | ||||
|   <div class="clr-row"> | ||||
|     <div class="clr-col-8"> | ||||
|       <h3>All SureConnect </h3> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="clr-col-4" style="text-align: right;"> | ||||
| 
 | ||||
|       <button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'"  (click)="goToAdd()"> | ||||
|         <clr-icon shape="plus"></clr-icon>ADD | ||||
|       </button> | ||||
| 
 | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   <clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected" > | ||||
|     <clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template> | ||||
|       <div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder> | ||||
| 
 | ||||
|       <clr-dg-column [clrDgField]="''" style="max-width: 40px;"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|       </ng-container></clr-dg-column>   | ||||
|     <clr-dg-column [clrDgField]="'Name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|        Name | ||||
|     </ng-container></clr-dg-column> | ||||
|     <clr-dg-column [clrDgField]="'description'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||
|       Description | ||||
|     </ng-container></clr-dg-column> | ||||
|     <clr-dg-column> <ng-container *clrDgHideableColumn="{hidden: false}"> <clr-icon shape="bars"></clr-icon>  | ||||
|       Action | ||||
|     </ng-container></clr-dg-column> | ||||
| 
 | ||||
|     <clr-dg-row *clrDgItems="let user of alldata" [clrDgItem]="user"> | ||||
|       <clr-dg-cell style="max-width: 40px;"> | ||||
|         <span style="cursor: pointer;"><clr-icon shape="edit" (click)="goToEdit(user.id)" class="red is-error" style="color:red;"></clr-icon></span> | ||||
|       </clr-dg-cell> | ||||
|       <clr-dg-cell id="word">{{user.connection_name}}</clr-dg-cell> | ||||
|       <clr-dg-cell id="word">{{user.description}}</clr-dg-cell> | ||||
|       <clr-dg-cell> | ||||
|         <span style="cursor: pointer;padding: 10px; "><clr-icon shape="trash"  (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span> | ||||
|         <clr-signpost> | ||||
|           <span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span> | ||||
|           <clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen> | ||||
|             <h5 style="margin-top: 0">Who Column</h5> | ||||
|                   <div>Account ID: <code class="clr-code">{{user.accountId}}</code></div> | ||||
|                   <div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div> | ||||
|                   <div>Created By: <code class="clr-code">{{user.createdBy}}</code></div> | ||||
|                   <div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div> | ||||
|                   <div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div> | ||||
|           </clr-signpost-content> | ||||
|         </clr-signpost> | ||||
|       </clr-dg-cell> | ||||
| 
 | ||||
| 
 | ||||
|       <!-- <clr-dg-action-overflow> | ||||
|         <button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user.id)">Edit <clr-icon shape="edit" class="is-error"></clr-icon></button> | ||||
|         <button class="action-item" *ngIf="mdelete == 'true'" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> | ||||
|       </clr-dg-action-overflow> --> | ||||
| 
 | ||||
|       <clr-dg-row-detail *clrIfExpanded > | ||||
|         <table class="table"> | ||||
|           <tr> | ||||
|             <td class="td-title">Name: </td> | ||||
|             <td class="td-content">{{user.Name}}</td> | ||||
|           </tr> | ||||
|           <tr> | ||||
|             <td class="td-title"> Description:</td> | ||||
|             <td class="td-content">{{user.description}}</td> | ||||
|           </tr> | ||||
|         </table> | ||||
|       </clr-dg-row-detail> | ||||
|     </clr-dg-row> | ||||
| 
 | ||||
|     <clr-dg-footer> | ||||
|       <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||
|         <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">data per page</clr-dg-page-size> | ||||
|         {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} | ||||
|         of {{pagination.totalItems}} data | ||||
|       </clr-dg-pagination> | ||||
|     </clr-dg-footer> | ||||
|   </clr-datagrid> | ||||
| </div> | ||||
| 
 | ||||
| <clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> | ||||
| 
 | ||||
|   <div class="modal-body" *ngIf="rowSelected.id"> | ||||
|     <h1 class="delete">Are You Sure Want to delete?</h1> | ||||
|     <h2 class="heading">{{rowSelected.id}}</h2> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button> | ||||
|     <button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary" >Delete</button> | ||||
|     </div> | ||||
|   </div> | ||||
| </clr-modal> | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,4 @@ | ||||
| .delete,.heading{ | ||||
|   text-align: center; | ||||
|   color: red; | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| 
 | ||||
| import { SureconnectComponent } from './sureconnect.component'; | ||||
| 
 | ||||
| describe('SureconnectComponent', () => { | ||||
|   let component: SureconnectComponent; | ||||
|   let fixture: ComponentFixture<SureconnectComponent>; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SureconnectComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SureconnectComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @ -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'); | ||||
|         } | ||||
|       }); | ||||
|   } | ||||
| } | ||||
| @ -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}`); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user