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(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -7,88 +7,102 @@ | |||||||
| </ol> --> | </ol> --> | ||||||
| 
 | 
 | ||||||
| <div style="display: inline;"> | <div style="display: inline;"> | ||||||
|     <button class="btn componentbtn" (click)="toggleMenu()"><clr-icon shape="plus"></clr-icon>component</button> |   <button class="btn componentbtn" (click)="toggleMenu()"><clr-icon shape="plus"></clr-icon>component</button> | ||||||
|     <div style="display: inline;"> |   <div style="display: inline;"> | ||||||
|         {{dashboardName}} |     {{dashboardName}} | ||||||
|     </div> |  | ||||||
|     <div style="display: inline; float: right;"> |  | ||||||
|       <!-- <button class="btn btn-primary">Build</button> |  | ||||||
|       <button class="btn btn-primary" (click)="onSchedule()">Schedule</button> --> |  | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|   </div> |   </div> | ||||||
|    |   <div style="display: inline; float: right;"> | ||||||
|   <div class="content-container"> |     <!-- <button class="btn btn-primary">Build</button> | ||||||
|  |       <button class="btn btn-primary" (click)="onSchedule()">Schedule</button> --> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="content-container"> | ||||||
|   <nav class="sidenav" *ngIf="toggle" style="width: 16%;"> |   <nav class="sidenav" *ngIf="toggle" style="width: 16%;"> | ||||||
|     <ul class="nav-list" style="list-style-type: none;"> |     <ul class="nav-list" style="list-style-type: none;"> | ||||||
|       <li *ngFor="let widget of WidgetsMock"> |       <li *ngFor="let widget of WidgetsMock"> | ||||||
|    | 
 | ||||||
|         <!-- |         <!-- | ||||||
|           Draggable widget from store using vanilla javascript event (dragstart) |           Draggable widget from store using vanilla javascript event (dragstart) | ||||||
|           onDrag() is call, it take $event and a widget identifier as parameters |           onDrag() is call, it take $event and a widget identifier as parameters | ||||||
|         --> |         --> | ||||||
|         <a draggable="true" class="nav-link" (dragstart)="onDrag($event, widget.identifier)"> |         <a draggable="true" class="nav-link" (dragstart)="onDrag($event, widget.identifier)"> | ||||||
|           <clr-icon shape="drag-handle" style="margin-right: 10px;"></clr-icon> |           <clr-icon shape="drag-handle" style="margin-right: 10px;"></clr-icon> | ||||||
|             {{ widget.name }} |           {{ widget.name }} | ||||||
|           <clr-icon shape="plugin" class="has-badge"></clr-icon> |           <clr-icon shape="plugin" class="has-badge"></clr-icon> | ||||||
|         </a> |         </a> | ||||||
|       </li> |       </li> | ||||||
|     </ul> |     </ul> | ||||||
|   </nav> |   </nav> | ||||||
|       <div style="width: 100%;"> |   <div style="width: 100%;"> | ||||||
|           <gridster [options]="options" (drop)="onDrop($event)" style="background-color: transparent;"> |     <gridster [options]="options" (drop)="onDrop($event)" style="background-color: transparent;"> | ||||||
|             <gridster-item [item]="item" *ngFor="let item of dashboardArray"> |       <gridster-item [item]="item" *ngFor="let item of dashboardArray"> | ||||||
|               <!-- <ng-container *ngIf="addToDashboard && item.addToDashboard"> --> |         <!-- <ng-container *ngIf="addToDashboard && item.addToDashboard"> --> | ||||||
|               <button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)"> |         <button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)"> | ||||||
|                 <clr-icon shape="trash"></clr-icon> |           <clr-icon shape="trash"></clr-icon> | ||||||
|               </button> |         </button> | ||||||
|             <button class="btn btn-icon drag-handler" style="margin-left: 10px; margin-top: 10px;"> |         <button class="btn btn-icon drag-handler" style="margin-left: 10px; margin-top: 10px;"> | ||||||
|               <clr-icon shape="drag-handle"></clr-icon> |           <clr-icon shape="drag-handle"></clr-icon> | ||||||
|             </button> |         </button> | ||||||
| 
 | 
 | ||||||
|             <button class="btn btn-icon" style="margin-top: 10px; float: right;"> |         <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" | ||||||
|             </button> |             (change)="toggleAddToDashboard(item)" /> | ||||||
|  |         </button> | ||||||
| 
 | 
 | ||||||
|               <!-- <label for="workflow_name">Add to Dasboard</label> |         <!-- <label for="workflow_name">Add to Dasboard</label> | ||||||
|               <input class="btn btn-icon" style="margin-top: 10px;float: right;"  type="checkbox" clrToggle value="billable" name="billable" /> |               <input class="btn btn-icon" style="margin-top: 10px;float: right;"  type="checkbox" clrToggle value="billable" name="billable" /> | ||||||
|             --> |             --> | ||||||
|             <button class="btn btn-icon" style="margin-top: 10px;float: right;" (click)="editGadget(item)"> |         <button class="btn btn-icon" style="margin-top: 10px;float: right;" (click)="editGadget(item)"> | ||||||
|               <clr-icon shape="pencil"></clr-icon> |           <clr-icon shape="pencil"></clr-icon> | ||||||
|             </button> |         </button> | ||||||
| 
 | 
 | ||||||
|             <h4 style="margin-top: 0px; margin-left: 10px;">{{item.name}}</h4> |         <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)" | ||||||
|           <!-- </ng-container> --> |           (moduleInfo)="display($event)"></ndc-dynamic> | ||||||
|           </gridster-item> |         <!-- </ng-container> --> | ||||||
|         </gridster> | 
 | ||||||
|       </div> |       </gridster-item> | ||||||
|  |     </gridster> | ||||||
|   </div> |   </div> | ||||||
|   <div style="text-align: center;"> | </div> | ||||||
|     <button class="btn btn-outline" (click)="goBack()">Back</button> | <div style="text-align: center;"> | ||||||
|     <button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()" > |   <button class="btn btn-outline" (click)="goBack()">Back</button> | ||||||
|         <b>Update</b> |   <button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()"> | ||||||
|     </button> |     <b>Update</b> | ||||||
|   </div> |   </button> | ||||||
|    | </div> | ||||||
|    | 
 | ||||||
|   <clr-modal [(clrModalOpen)]="modeledit"  [clrModalStaticBackdrop]="true"> | 
 | ||||||
|  | <clr-modal [(clrModalOpen)]="modeledit" [clrModalStaticBackdrop]="true"> | ||||||
|   <h3 class="modal-title">Configure Chart</h3> |   <h3 class="modal-title">Configure Chart</h3> | ||||||
|   <div class="modal-body" > |   <div class="modal-body"> | ||||||
|     <form [formGroup]="entryForm" class="clr-form-horizontal" > |     <form [formGroup]="entryForm" class="clr-form-horizontal"> | ||||||
|       <div class="clr-row"> |       <div class="clr-row"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="charttitle">Chart Title</label> |           <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> |       </div> | ||||||
|       <!-- <div class="clr-row"> |        | ||||||
|  |       <!-- Add Connection Selection Field --> | ||||||
|  |       <div class="clr-row"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="id">ID</label> |           <label for="connection">Connection</label> | ||||||
|           <input id="datasource" type="text" formControlName="id" class="clr-input" [(ngModel)]="gadgetsEditdata.id"> |           <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> --> |       </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"> |         <div class="clr-col-sm-12"> | ||||||
|           <div class="clr-form-control" style="margin-top: 5px;margin-bottom: 10px;"> |           <div class="clr-form-control" style="margin-top: 5px;margin-bottom: 10px;"> | ||||||
|             <div class="clr-control-container"> |             <div class="clr-control-container"> | ||||||
| @ -105,15 +119,17 @@ | |||||||
|                 <label for="donut" class="clr-control-label">Show donut</label> |                 <label for="donut" class="clr-control-label">Show donut</label> | ||||||
|               </div> --> |               </div> --> | ||||||
|               <div class="clr-checkbox-wrapper"> |               <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> |                 <label for="chartlegend" class="clr-control-label">Show Chart Legend</label> | ||||||
|               </div> |               </div> | ||||||
|               <div class="clr-checkbox-wrapper"> |               <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> |                 <label for="showlabel" class="clr-control-label">Show Chart Label</label> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|             </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <!-- <div class="clr-row"> |       <!-- <div class="clr-row"> | ||||||
| @ -125,38 +141,43 @@ | |||||||
|             </select> |             </select> | ||||||
|         </div> |         </div> | ||||||
|       </div> --> |       </div> --> | ||||||
|    | 
 | ||||||
|       <div class="clr-row" > |       <div class="clr-row"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="table">Table Name</label> |           <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)="tablename(gadgetsEditdata.table)"> |           <div><input type="urk" id="table" formControlName="table" class="clr-input" | ||||||
|          <clr-icon shape="redo"></clr-icon> </button></span></div> |               [(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)"> |           <!-- <select  id="table" formControlName="table" [(ngModel)]="gadgetsEditdata.table" (change)="tablename($event.target.value)"> | ||||||
|             <option value="null">choose Table</option> |             <option value="null">choose Table</option> | ||||||
|             <option *ngFor="let data of TableData" [value]="data">{{data}}</option> |             <option *ngFor="let data of TableData" [value]="data">{{data}}</option> | ||||||
|            </select> --> |            </select> --> | ||||||
|         </div> |         </div> | ||||||
|       </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"> |         <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"> --> |           <!-- <input id="xAxis" type="text" formControlName="xAxis" class="clr-input" [(ngModel)]="gadgetsEditdata.xAxis"> --> | ||||||
|           <select id="xAxis"  formControlName="xAxis" [(ngModel)]="gadgetsEditdata.xAxis"> |           <select id="xAxis" formControlName="xAxis" [(ngModel)]="gadgetsEditdata.xAxis"> | ||||||
|             <option value="null">choose Column</option> |             <option value="null">choose Column</option> | ||||||
|             <option *ngFor="let data of columnData" [value]="data">{{data}}</option> |             <option *ngFor="let data of columnData" [value]="data">{{data}}</option> | ||||||
|            </select> |           </select> | ||||||
|         </div> |         </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-row" | ||||||
|  |         *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' && gadgetsEditdata?.fieldName !== 'Polar Area Chart' && gadgetsEditdata?.fieldName !== 'To Do Chart'"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="yAxis" *ngIf="gadgetsEditdata?.fieldName === 'Grid View'; else yaxislable">Columns</label> |           <label for="yAxis" *ngIf="gadgetsEditdata?.fieldName === 'Grid View'; else yaxislable">Columns</label> | ||||||
|           <ng-template #yaxislable> |           <ng-template #yaxislable> | ||||||
|             <label for="yAxis">Y-Axis (Numeric)</label> |             <label for="yAxis">Y-Axis (Numeric)</label> | ||||||
|           </ng-template> |           </ng-template> | ||||||
|            | 
 | ||||||
|           <!-- <input id="yAxis" type="text" formControlName="yAxis" class="clr-input" [(ngModel)]="gadgetsEditdata.yAxis"> --> |           <!-- <input id="yAxis" type="text" formControlName="yAxis" class="clr-input" [(ngModel)]="gadgetsEditdata.yAxis"> --> | ||||||
|           <clr-combobox-container style="margin-top: 10px !important;"> |           <clr-combobox-container style="margin-top: 10px !important;"> | ||||||
|             <clr-combobox  id="yAxis" [(ngModel)]="selectedyAxis" formControlName="yAxis" clrMulti="true" required> |             <clr-combobox id="yAxis" [(ngModel)]="selectedyAxis" formControlName="yAxis" clrMulti="true" required> | ||||||
|               <ng-container *clrOptionSelected="let selected;let i = alias"> |               <ng-container *clrOptionSelected="let selected;let i = alias"> | ||||||
|                 {{selected}} |                 {{selected}} | ||||||
|               </ng-container> |               </ng-container> | ||||||
| @ -169,17 +190,177 @@ | |||||||
|           </clr-combobox-container> |           </clr-combobox-container> | ||||||
|         </div> |         </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-row" | ||||||
|  |         *ngIf="gadgetsEditdata?.fieldName == 'Pie Chart' || gadgetsEditdata?.fieldName == 'Polar Area Chart' || gadgetsEditdata?.fieldName == 'To Do Chart'"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="yAxis">Y-Axis (Numeric)</label> |           <label for="yAxis">Y-Axis (Numeric)</label> | ||||||
|           <select id="yAxis"  formControlName="yAxis" [(ngModel)]="gadgetsEditdata.yAxis"> |           <select id="yAxis" formControlName="yAxis" [(ngModel)]="gadgetsEditdata.yAxis"> | ||||||
|             <option value="null">choose Column</option> |             <option value="null">choose Column</option> | ||||||
|             <option *ngFor="let data of columnData" [value]="data">{{data}}</option> |             <option *ngFor="let data of columnData" [value]="data">{{data}}</option> | ||||||
|            </select> |           </select> | ||||||
|         </div> |         </div> | ||||||
|       </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-row"> | ||||||
|         <div class="clr-col-sm-12"> |         <div class="clr-col-sm-12"> | ||||||
|           <label for="chartparameter">API parameter</label> |           <label for="chartparameter">API parameter</label> | ||||||
| @ -187,12 +368,11 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> --> |       </div> --> | ||||||
|       <div class="modal-footer"> |       <div class="modal-footer"> | ||||||
|       <button type="button" class="btn btn-outline" (click)="modeledit = false">Cancel</button> |         <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> |       </div> | ||||||
|    | 
 | ||||||
|     </form> |     </form> | ||||||
|   </div> |   </div> | ||||||
|   </clr-modal> | </clr-modal> | ||||||
|    |  | ||||||
|    |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -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,9 +1,28 @@ | |||||||
| <div style="display: block"> | <div style="display: block"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|   [datasets]="barChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|   [labels]="barChartLabels" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|   [type]="barChartType" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|   (chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|   (chartClick)="chartClicked($event)"> |     </button> | ||||||
| </canvas> |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| </div> |       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" | ||||||
|  |     [type]="barChartType" | ||||||
|  |     (chartHover)="chartHovered($event)" | ||||||
|  |     (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({ | @Component({ | ||||||
|   selector: 'app-bar-chart', |   selector: 'app-bar-chart', | ||||||
|   templateUrl: './bar-chart.component.html', |   templateUrl: './bar-chart.component.html', | ||||||
|   styleUrls: ['./bar-chart.component.scss'] |   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']; |   barChartLabels: string[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes']; | ||||||
|   barChartType: string = 'bar'; |   barChartType: string = 'bar'; | ||||||
|  // barChartLegend = true;
 |  | ||||||
|   barChartPlugins = []; |   barChartPlugins = []; | ||||||
|   barChartData: any[] = [ |   barChartData: any[] = [ | ||||||
|     { data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' } |     { 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
 |   // events
 | ||||||
| 	public chartClicked(e: any): void { |   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 { |   public chartHovered(e: any): void { | ||||||
| 		console.log(e); |     console.log(e); | ||||||
| 	} |   } | ||||||
| 
 | } | ||||||
| } |  | ||||||
| @ -1,9 +1,28 @@ | |||||||
| <div style="display:block"> | <div style="display:block"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|   [datasets]="bubbleChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|   [type]="bubbleChartType" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|   [options]="bubbleChartOptions" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|   (chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|   (chartClick)="chartClicked($event)"> |     </button> | ||||||
| </canvas> |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| </div> |       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" | ||||||
|  |     [options]="bubbleChartOptions" | ||||||
|  |     (chartHover)="chartHovered($event)" | ||||||
|  |     (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 { ChartConfiguration, ChartDataset, ChartOptions } from 'chart.js'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
|  | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-bubble-chart', |   selector: 'app-bubble-chart', | ||||||
|   templateUrl: './bubble-chart.component.html', |   templateUrl: './bubble-chart.component.html', | ||||||
|   styleUrls: ['./bubble-chart.component.scss'] |   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'] = { |   public bubbleChartOptions: ChartConfiguration['options'] = { | ||||||
|     // scales: {
 |     // scales: {
 | ||||||
|     //   x: {
 |     //   x: {
 | ||||||
| @ -33,7 +54,6 @@ export class BubbleChartComponent implements OnInit { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   public bubbleChartType: string = 'bubble'; |   public bubbleChartType: string = 'bubble'; | ||||||
|  // public bubbleChartLegend = true;
 |  | ||||||
|   public bubbleChartData: ChartDataset[] = [ |   public bubbleChartData: ChartDataset[] = [ | ||||||
|     { |     { | ||||||
|       data: [ |       data: [ | ||||||
| @ -61,38 +81,432 @@ export class BubbleChartComponent implements OnInit { | |||||||
|       hoverBackgroundColor: 'yellow', |       hoverBackgroundColor: 'yellow', | ||||||
|       hoverBorderColor: 'blue', |       hoverBorderColor: 'blue', | ||||||
|     }, |     }, | ||||||
|     // {
 |  | ||||||
|     //   data: [
 |  | ||||||
|     //     { x: 10, y: 10, r: 10 },
 |  | ||||||
|     //     { x: 15, y: 5, r: 15 },
 |  | ||||||
|     //     { x: 26, y: 12, r: 23 },
 |  | ||||||
|     //     { x: 7, y: 8, r: 8 },
 |  | ||||||
|     //   ],
 |  | ||||||
|     //   label: 'Investment Equities',
 |  | ||||||
|     //   backgroundColor: [
 |  | ||||||
|     //     'red',
 |  | ||||||
|     //     'green',
 |  | ||||||
|     //     'blue',
 |  | ||||||
|     //     'purple',
 |  | ||||||
|     //     'yellow',
 |  | ||||||
|     //     'brown',
 |  | ||||||
|     //     'magenta',
 |  | ||||||
|     //     'cyan',
 |  | ||||||
|     //     'orange',
 |  | ||||||
|     //     'pink'
 |  | ||||||
|     //   ],
 |  | ||||||
|     //   borderColor: 'blue',
 |  | ||||||
|     //   hoverBackgroundColor: 'purple',
 |  | ||||||
|     //   hoverBorderColor: 'red',
 |  | ||||||
|     // },
 |  | ||||||
|   ]; |   ]; | ||||||
|  |    | ||||||
|  |   // Multi-layer drilldown state tracking
 | ||||||
|  |   drilldownStack: any[] = []; // Stack to track drilldown navigation history
 | ||||||
|  |   currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
 | ||||||
|  |   originalBubbleChartData: ChartDataset[] = []; | ||||||
|  |    | ||||||
|  |   // No data state
 | ||||||
|  |   noDataAvailable: boolean = false; | ||||||
| 
 | 
 | ||||||
|    // events
 |   constructor(private dashboardService: Dashboard3Service) { } | ||||||
| 	public chartClicked(e: any): void { |  | ||||||
| 		console.log(e); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	public chartHovered(e: any): void { |   ngOnInit(): void { | ||||||
| 		console.log(e); |     this.fetchChartData(); | ||||||
| 	} |   } | ||||||
| } | 
 | ||||||
|  |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|  |     console.log('BubbleChartComponent input changes:', changes); | ||||||
|  |      | ||||||
|  |     // Check if any of the key properties have changed
 | ||||||
|  |     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; | ||||||
|  |     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; | ||||||
|  |     const tableChanged = changes.table && !changes.table.firstChange; | ||||||
|  |     const connectionChanged = changes.connection && !changes.connection.firstChange; | ||||||
|  |     // Drilldown configuration changes
 | ||||||
|  |     const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; | ||||||
|  |     const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; | ||||||
|  |     const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; | ||||||
|  |     const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; | ||||||
|  |     const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; | ||||||
|  |      | ||||||
|  |     // Respond to input changes
 | ||||||
|  |     if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||  | ||||||
|  |         drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || | ||||||
|  |         drilldownLayersChanged) { | ||||||
|  |       console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); | ||||||
|  |       this.fetchChartData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetchChartData(): void { | ||||||
|  |     // If we're in drilldown mode, fetch the appropriate drilldown data
 | ||||||
|  |     if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { | ||||||
|  |       this.fetchDrilldownData(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // If we have the necessary data, fetch chart data from the service
 | ||||||
|  |     if (this.table && this.xAxis && this.yAxis) { | ||||||
|  |       console.log('Fetching bubble chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |        | ||||||
|  |       // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  |        | ||||||
|  |       // Log the URL that will be called
 | ||||||
|  |       const url = `chart/getdashjson/bubble?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |       console.log('Bubble chart data URL:', url); | ||||||
|  |        | ||||||
|  |       // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |       // For base level, we pass empty parameter and value
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '').subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |           console.log('Received bubble chart data:', data); | ||||||
|  |           if (data === null) { | ||||||
|  |             console.warn('Bubble chart API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.bubbleChartData = []; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Handle the actual data structure returned by the API
 | ||||||
|  |           if (data && data.chartLabels && data.chartData) { | ||||||
|  |             // For bubble charts, we need to transform the data into bubble format
 | ||||||
|  |             // Bubble charts expect data in the format: {x: number, y: number, r: number}
 | ||||||
|  |             this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |             this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); | ||||||
|  |             console.log('Updated bubble chart with data:', this.bubbleChartData); | ||||||
|  |           } else if (data && data.labels && data.datasets) { | ||||||
|  |             // Handle the original expected format as fallback
 | ||||||
|  |             this.noDataAvailable = data.labels.length === 0; | ||||||
|  |             this.bubbleChartData = data.datasets; | ||||||
|  |             console.log('Updated bubble chart with legacy data format:', this.bubbleChartData); | ||||||
|  |           } else { | ||||||
|  |             console.warn('Bubble chart received data does not have expected structure', data); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.bubbleChartData = []; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         (error) => { | ||||||
|  |           console.error('Error fetching bubble chart data:', error); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.bubbleChartData = []; | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.bubbleChartData = []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Fetch drilldown data based on current drilldown level
 | ||||||
|  |   fetchDrilldownData(): void { | ||||||
|  |     console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); | ||||||
|  |     console.log('Drilldown stack:', this.drilldownStack); | ||||||
|  |      | ||||||
|  |     // Get the current drilldown configuration based on the current level
 | ||||||
|  |     let drilldownConfig; | ||||||
|  |     if (this.currentDrilldownLevel === 1) { | ||||||
|  |       // Base drilldown level
 | ||||||
|  |       drilldownConfig = { | ||||||
|  |         apiUrl: this.drilldownApiUrl, | ||||||
|  |         xAxis: this.drilldownXAxis, | ||||||
|  |         yAxis: this.drilldownYAxis, | ||||||
|  |         parameter: this.drilldownParameter | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       // Multi-layer drilldown level
 | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { | ||||||
|  |         drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |       } else { | ||||||
|  |         console.warn('Invalid drilldown layer index:', layerIndex); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.bubbleChartData = []; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); | ||||||
|  |      | ||||||
|  |     // Check if we have valid drilldown configuration
 | ||||||
|  |     if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { | ||||||
|  |       console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.bubbleChartData = []; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter value from the drilldown stack
 | ||||||
|  |     let parameterValue = ''; | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; | ||||||
|  |       parameterValue = lastEntry.clickedValue || ''; | ||||||
|  |       console.log('Parameter value from last click:', parameterValue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter field from drilldown config
 | ||||||
|  |     const parameterField = drilldownConfig.parameter || ''; | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { | ||||||
|  |       apiUrl: drilldownConfig.apiUrl, | ||||||
|  |       xAxis: drilldownConfig.xAxis, | ||||||
|  |       yAxis: drilldownConfig.yAxis, | ||||||
|  |       parameterField: parameterField, | ||||||
|  |       parameterValue: parameterValue, | ||||||
|  |       connection: this.connection | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  |     // Build the actual API URL with parameter replacement
 | ||||||
|  |     let actualApiUrl = drilldownConfig.apiUrl; | ||||||
|  |     console.log('Original API URL:', actualApiUrl); | ||||||
|  |     console.log('Parameter value to use:', parameterValue); | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     // Check if the URL contains angle brackets for parameter replacement
 | ||||||
|  |     const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); | ||||||
|  |      | ||||||
|  |     if (hasAngleBrackets && parameterValue) { | ||||||
|  |       // Replace angle brackets placeholder with actual value
 | ||||||
|  |       console.log('Replacing angle brackets with parameter value'); | ||||||
|  |       const encodedValue = encodeURIComponent(parameterValue); | ||||||
|  |       actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); | ||||||
|  |       console.log('URL after angle bracket replacement:', actualApiUrl); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Log the URL that will be called
 | ||||||
|  |     const url = `chart/getdashjson/bubble?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |     console.log('Drilldown data URL:', url); | ||||||
|  |      | ||||||
|  |     // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |     // Backend handles filtering, we just pass the parameter field and value
 | ||||||
|  |     this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( | ||||||
|  |       (data: any) => { | ||||||
|  |         console.log('Received drilldown data:', data); | ||||||
|  |         if (data === null) { | ||||||
|  |           console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.bubbleChartData = []; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Handle the actual data structure returned by the API
 | ||||||
|  |         if (data && data.chartLabels && data.chartData) { | ||||||
|  |           // For bubble charts, we need to transform the data into bubble format
 | ||||||
|  |           // Bubble charts expect data in the format: {x: number, y: number, r: number}
 | ||||||
|  |           this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |           this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData); | ||||||
|  |           console.log('Updated bubble chart with drilldown data:', this.bubbleChartData); | ||||||
|  |         } else if (data && data.labels && data.datasets) { | ||||||
|  |           // Handle the original expected format as fallback
 | ||||||
|  |           this.noDataAvailable = data.labels.length === 0; | ||||||
|  |           this.bubbleChartData = data.datasets; | ||||||
|  |           console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData); | ||||||
|  |         } else { | ||||||
|  |           console.warn('Drilldown received data does not have expected structure', data); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.bubbleChartData = []; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       (error) => { | ||||||
|  |         console.error('Error fetching drilldown data:', error); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.bubbleChartData = []; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Reset to original data (go back to base level)
 | ||||||
|  |   resetToOriginalData(): void { | ||||||
|  |     console.log('Resetting to original data'); | ||||||
|  |     console.log('Current stack before reset:', this.drilldownStack); | ||||||
|  |     console.log('Current level before reset:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     this.currentDrilldownLevel = 0; | ||||||
|  |     this.drilldownStack = []; | ||||||
|  |      | ||||||
|  |     if (this.originalBubbleChartData.length > 0) { | ||||||
|  |       this.bubbleChartData = [...this.originalBubbleChartData]; | ||||||
|  |       console.log('Restored original data'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('After reset - data:', this.bubbleChartData); | ||||||
|  |      | ||||||
|  |     // Re-fetch original data
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Navigate back to previous drilldown level
 | ||||||
|  |   navigateBack(): void { | ||||||
|  |     console.log('Navigating back, current stack:', this.drilldownStack); | ||||||
|  |     console.log('Current level:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       // Remove the last entry from the stack
 | ||||||
|  |       const removedEntry = this.drilldownStack.pop(); | ||||||
|  |       console.log('Removed entry from stack:', removedEntry); | ||||||
|  |        | ||||||
|  |       // Update the current drilldown level
 | ||||||
|  |       this.currentDrilldownLevel = this.drilldownStack.length; | ||||||
|  |       console.log('New level after pop:', this.currentDrilldownLevel); | ||||||
|  |       console.log('Stack after pop:', this.drilldownStack); | ||||||
|  |        | ||||||
|  |       if (this.drilldownStack.length > 0) { | ||||||
|  |         // Fetch data for the previous level
 | ||||||
|  |         console.log('Fetching data for previous level'); | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         // Back to base level
 | ||||||
|  |         console.log('Back to base level, resetting to original data'); | ||||||
|  |         this.resetToOriginalData(); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // Already at base level, reset to original data
 | ||||||
|  |       console.log('Already at base level, resetting to original data'); | ||||||
|  |       this.resetToOriginalData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private transformToBubbleData(labels: any[], chartData: any[]): ChartDataset[] { | ||||||
|  |     // Transform the API data into bubble chart format
 | ||||||
|  |     const datasets: ChartDataset[] = []; | ||||||
|  |      | ||||||
|  |     // Create a dataset for each data series
 | ||||||
|  |     chartData.forEach((series, index) => { | ||||||
|  |       // For bubble charts, we need x, y, and r values
 | ||||||
|  |       // We'll use the labels as x values and the data as y values
 | ||||||
|  |       // For radius (r), we'll use a default value or derive it from the data
 | ||||||
|  |        | ||||||
|  |       const bubbleData = labels.map((label, i) => { | ||||||
|  |         const xValue = isNaN(Number(label)) ? i : Number(label); | ||||||
|  |         const yValue = series.data && series.data[i] !== undefined ?  | ||||||
|  |           (isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0; | ||||||
|  |         // Use a default radius or derive from data
 | ||||||
|  |         const radius = Math.abs(yValue) > 0 ? Math.abs(yValue) / 10 : 5; | ||||||
|  |          | ||||||
|  |         return { | ||||||
|  |           x: xValue, | ||||||
|  |           y: yValue, | ||||||
|  |           r: radius | ||||||
|  |         }; | ||||||
|  |       }); | ||||||
|  |        | ||||||
|  |       datasets.push({ | ||||||
|  |         data: bubbleData, | ||||||
|  |         label: series.label || `Series ${index + 1}`, | ||||||
|  |         backgroundColor: this.getBackgroundColor(index), | ||||||
|  |         borderColor: this.getBorderColor(index), | ||||||
|  |         hoverBackgroundColor: this.getHoverBackgroundColor(index), | ||||||
|  |         hoverBorderColor: this.getHoverBorderColor(index), | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  |     return datasets; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getBackgroundColor(index: number): string { | ||||||
|  |     const colors = [ | ||||||
|  |       'rgba(255, 0, 0, 0.6)',    // Red
 | ||||||
|  |       'rgba(0, 255, 0, 0.6)',    // Green
 | ||||||
|  |       'rgba(0, 0, 255, 0.6)',    // Blue
 | ||||||
|  |       'rgba(255, 255, 0, 0.6)',  // Yellow
 | ||||||
|  |       'rgba(255, 0, 255, 0.6)',  // Magenta
 | ||||||
|  |       'rgba(0, 255, 255, 0.6)',  // Cyan
 | ||||||
|  |     ]; | ||||||
|  |     return colors[index % colors.length]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getBorderColor(index: number): string { | ||||||
|  |     const colors = ['blue', 'green', 'red', 'orange', 'purple', 'cyan']; | ||||||
|  |     return colors[index % colors.length]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getHoverBackgroundColor(index: number): string { | ||||||
|  |     const colors = ['purple', 'yellow', 'orange', 'red', 'blue', 'green']; | ||||||
|  |     return colors[index % colors.length]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private getHoverBorderColor(index: number): string { | ||||||
|  |     const colors = ['red', 'blue', 'green', 'purple', 'yellow', 'orange']; | ||||||
|  |     return colors[index % colors.length]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // events
 | ||||||
|  |   public chartClicked(e: any): void { | ||||||
|  |     console.log('Bubble chart clicked:', e); | ||||||
|  |      | ||||||
|  |     // If drilldown is enabled and we have a valid click event
 | ||||||
|  |     if (this.drilldownEnabled && e.active && e.active.length > 0) { | ||||||
|  |       // Get the index of the clicked element
 | ||||||
|  |       const clickedIndex = e.active[0].index; | ||||||
|  |        | ||||||
|  |       // Get the dataset index
 | ||||||
|  |       const datasetIndex = e.active[0].datasetIndex; | ||||||
|  |        | ||||||
|  |       // Get the data point
 | ||||||
|  |       const dataPoint = this.bubbleChartData[datasetIndex].data[clickedIndex]; | ||||||
|  |        | ||||||
|  |       console.log('Clicked on bubble:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint }); | ||||||
|  |        | ||||||
|  |       // If we're not at the base level, store original data
 | ||||||
|  |       if (this.currentDrilldownLevel === 0) { | ||||||
|  |         // Store original data before entering drilldown mode
 | ||||||
|  |         this.originalBubbleChartData = JSON.parse(JSON.stringify(this.bubbleChartData)); | ||||||
|  |         console.log('Stored original data for drilldown'); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       // Determine the next drilldown level
 | ||||||
|  |       const nextDrilldownLevel = this.currentDrilldownLevel + 1; | ||||||
|  |        | ||||||
|  |       console.log('Next drilldown level will be:', nextDrilldownLevel); | ||||||
|  |        | ||||||
|  |       // Check if there's a drilldown configuration for this level
 | ||||||
|  |       let hasDrilldownConfig = false; | ||||||
|  |       let drilldownConfig; | ||||||
|  |        | ||||||
|  |       if (nextDrilldownLevel === 1) { | ||||||
|  |         // Base drilldown level
 | ||||||
|  |         drilldownConfig = { | ||||||
|  |           apiUrl: this.drilldownApiUrl, | ||||||
|  |           xAxis: this.drilldownXAxis, | ||||||
|  |           yAxis: this.drilldownYAxis, | ||||||
|  |           parameter: this.drilldownParameter | ||||||
|  |         }; | ||||||
|  |         hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; | ||||||
|  |       } else { | ||||||
|  |         // Multi-layer drilldown level
 | ||||||
|  |         const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |         if (layerIndex < this.drilldownLayers.length) { | ||||||
|  |           drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |           hasDrilldownConfig = drilldownConfig.enabled && | ||||||
|  |                               !!drilldownConfig.apiUrl &&  | ||||||
|  |                               !!drilldownConfig.xAxis &&  | ||||||
|  |                               !!drilldownConfig.yAxis; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       console.log('Drilldown config for next level:', drilldownConfig); | ||||||
|  |       console.log('Has drilldown config:', hasDrilldownConfig); | ||||||
|  |        | ||||||
|  |       // If there's a drilldown configuration for the next level, proceed
 | ||||||
|  |       if (hasDrilldownConfig) { | ||||||
|  |         // For bubble charts, we'll use the x value as the clicked value
 | ||||||
|  |         const clickedValue = dataPoint && (dataPoint as any).x !== undefined ?  | ||||||
|  |           (dataPoint as any).x.toString() : ''; | ||||||
|  |          | ||||||
|  |         // Add this click to the drilldown stack
 | ||||||
|  |         const stackEntry = { | ||||||
|  |           level: nextDrilldownLevel, | ||||||
|  |           datasetIndex: datasetIndex, | ||||||
|  |           clickedIndex: clickedIndex, | ||||||
|  |           clickedValue: clickedValue | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         this.drilldownStack.push(stackEntry); | ||||||
|  |          | ||||||
|  |         console.log('Added to drilldown stack:', stackEntry); | ||||||
|  |         console.log('Current drilldown stack:', this.drilldownStack); | ||||||
|  |          | ||||||
|  |         // Update the current drilldown level
 | ||||||
|  |         this.currentDrilldownLevel = nextDrilldownLevel; | ||||||
|  |          | ||||||
|  |         console.log('Entering drilldown level:', this.currentDrilldownLevel); | ||||||
|  |          | ||||||
|  |         // Fetch drilldown data for the new level
 | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         console.log('No drilldown configuration for level:', nextDrilldownLevel); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log('Drilldown not enabled or invalid click event'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public chartHovered(e: any): void { | ||||||
|  |     console.log(e); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,8 +1,44 @@ | |||||||
| <div style="display: block"> | <div class="doughnut-chart-container"> | ||||||
| 	<canvas baseChart  |   <!-- Drilldown mode indicator --> | ||||||
| 	[data]="doughnutChartData"  |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
| 	[labels]="doughnutChartLabels"  |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
| 	[type]="doughnutChartType"  |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| 	(chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
| 	(chartClick)="chartClicked($event)"></canvas> |     </button> | ||||||
| </div> |     <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> | ||||||
|  |   </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({ | @Component({ | ||||||
|   selector: 'app-doughnut-chart', |   selector: 'app-doughnut-chart', | ||||||
|   templateUrl: './doughnut-chart.component.html', |   templateUrl: './doughnut-chart.component.html', | ||||||
|   styleUrls: ['./doughnut-chart.component.scss'] |   styleUrls: ['./doughnut-chart.component.scss'] | ||||||
| }) | }) | ||||||
| export class DoughnutChartComponent implements OnInit { | export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewChecked { | ||||||
|   public doughnutChartLabels: string[] = [ |   @Input() xAxis: string; | ||||||
| 		"Download Sales", |   @Input() yAxis: string | string[]; | ||||||
| 		"In-Store Sales", |   @Input() table: string; | ||||||
| 		"Mail-Order Sales" |   @Input() datastore: string; | ||||||
| 	]; |   @Input() charttitle: string; | ||||||
| 	public doughnutChartData: number[] = [350, 450, 100]; |   @Input() chartlegend: boolean = true; | ||||||
| 	public doughnutChartType: string = "doughnut"; |   @Input() showlabel: boolean = true; | ||||||
|  |   @Input() chartcolor: boolean; | ||||||
|  |   @Input() slices: boolean; | ||||||
|  |   @Input() donut: boolean; | ||||||
|  |   @Input() charturl: string; | ||||||
|  |   @Input() chartparameter: string; | ||||||
|  |   @Input() datasource: string; | ||||||
|  |   @Input() fieldName: string; | ||||||
|  |   @Input() connection: number; // Add connection input
 | ||||||
|  |   // Drilldown configuration inputs
 | ||||||
|  |   @Input() drilldownEnabled: boolean = false; | ||||||
|  |   @Input() drilldownApiUrl: string; | ||||||
|  |   @Input() drilldownXAxis: string; | ||||||
|  |   @Input() drilldownYAxis: string; | ||||||
|  |   @Input() drilldownParameter: string; // Add drilldown parameter input
 | ||||||
|  |   // Multi-layer drilldown configuration inputs
 | ||||||
|  |   @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
 | ||||||
| 
 | 
 | ||||||
| 	// events
 |   public doughnutChartLabels: string[] = ["Category A", "Category B", "Category C"]; | ||||||
| 	public chartClicked(e: any): void { |   public doughnutChartData: number[] = [30, 50, 20]; | ||||||
| 		console.log(e); |   public doughnutChartType: string = "doughnut"; | ||||||
| 	} |   public doughnutChartOptions: any = { | ||||||
|  |     responsive: true, | ||||||
|  |     maintainAspectRatio: false, | ||||||
|  |     cutout: '60%', // This creates the doughnut effect (Chart.js v3+ syntax)
 | ||||||
|  |     plugins: { | ||||||
|  |       legend: { | ||||||
|  |         display: false // We'll create our own legend
 | ||||||
|  |       }, | ||||||
|  |       tooltip: { | ||||||
|  |         enabled: true, | ||||||
|  |         mode: 'index', | ||||||
|  |         intersect: false, | ||||||
|  |         backgroundColor: 'rgba(0, 0, 0, 0.8)', | ||||||
|  |         titleFont: { | ||||||
|  |           size: 16, | ||||||
|  |           color: '#fff' | ||||||
|  |         }, | ||||||
|  |         bodyFont: { | ||||||
|  |           size: 14, | ||||||
|  |           color: '#fff' | ||||||
|  |         }, | ||||||
|  |         cornerRadius: 4, | ||||||
|  |         displayColors: false | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     animation: { | ||||||
|  |       animateRotate: true, | ||||||
|  |       animateScale: false | ||||||
|  |     }, | ||||||
|  |     elements: { | ||||||
|  |       arc: { | ||||||
|  |         borderWidth: 2, | ||||||
|  |         borderColor: '#fff' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |    | ||||||
|  |   // Chart colors for consistent styling
 | ||||||
|  |   private chartColors: string[] = [ | ||||||
|  |     '#FF6384', | ||||||
|  |     '#36A2EB', | ||||||
|  |     '#FFCE56', | ||||||
|  |     '#4BC0C0', | ||||||
|  |     '#9966FF', | ||||||
|  |     '#FF9F40', | ||||||
|  |     '#FF6384', | ||||||
|  |     '#C9CBCF' | ||||||
|  |   ]; | ||||||
|  |    | ||||||
|  |   // Multi-layer drilldown state tracking
 | ||||||
|  |   drilldownStack: any[] = []; // Stack to track drilldown navigation history
 | ||||||
|  |   currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
 | ||||||
|  |   originalDoughnutChartLabels: string[] = []; | ||||||
|  |   originalDoughnutChartData: number[] = []; | ||||||
|  |    | ||||||
|  |   // No data state
 | ||||||
|  |   noDataAvailable: boolean = false; | ||||||
| 
 | 
 | ||||||
| 	public chartHovered(e: any): void { |   constructor(private dashboardService: Dashboard3Service) { } | ||||||
| 		console.log(e); |  | ||||||
| 	} |  | ||||||
|   constructor() { } |  | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  |     // Validate initial data
 | ||||||
|  |     this.validateChartData(); | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Validate and sanitize chart data | ||||||
|  |    */ | ||||||
|  |   private validateChartData(): void { | ||||||
|  |     // Ensure we have valid arrays
 | ||||||
|  |     if (!Array.isArray(this.doughnutChartLabels)) { | ||||||
|  |       this.doughnutChartLabels = []; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (!Array.isArray(this.doughnutChartData)) { | ||||||
|  |       this.doughnutChartData = []; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Ensure we have some data to display
 | ||||||
|  |     if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) { | ||||||
|  |       // Add default data to ensure chart visibility
 | ||||||
|  |       this.doughnutChartLabels = ["Category A", "Category B", "Category C"]; | ||||||
|  |       this.doughnutChartData = [30, 50, 20]; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Ensure we have matching arrays
 | ||||||
|  |     if (this.doughnutChartLabels.length !== this.doughnutChartData.length) { | ||||||
|  |       const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length); | ||||||
|  |       while (this.doughnutChartLabels.length < maxLength) { | ||||||
|  |         this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`); | ||||||
|  |       } | ||||||
|  |       while (this.doughnutChartData.length < maxLength) { | ||||||
|  |         this.doughnutChartData.push(0); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Force chart redraw | ||||||
|  |    */ | ||||||
|  |   public redrawChart(): void { | ||||||
|  |     // This method can be called to force a chart redraw if needed
 | ||||||
|  |     console.log('Redrawing doughnut chart'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| } |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|  |     console.log('DoughnutChartComponent input changes:', changes); | ||||||
|  |      | ||||||
|  |     // Check if any of the key properties have changed
 | ||||||
|  |     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; | ||||||
|  |     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; | ||||||
|  |     const tableChanged = changes.table && !changes.table.firstChange; | ||||||
|  |     const connectionChanged = changes.connection && !changes.connection.firstChange; | ||||||
|  |     // Drilldown configuration changes
 | ||||||
|  |     const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; | ||||||
|  |     const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; | ||||||
|  |     const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; | ||||||
|  |     const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; | ||||||
|  |     const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; | ||||||
|  |      | ||||||
|  |     // Respond to input changes
 | ||||||
|  |     if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||  | ||||||
|  |         drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || | ||||||
|  |         drilldownLayersChanged) { | ||||||
|  |       console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); | ||||||
|  |       this.fetchChartData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   ngAfterViewChecked() { | ||||||
|  |     // Debugging: Log component state after view checks
 | ||||||
|  |     console.log('DoughnutChartComponent state:', { | ||||||
|  |       labels: this.doughnutChartLabels, | ||||||
|  |       data: this.doughnutChartData, | ||||||
|  |       hasData: this.doughnutChartLabels.length > 0 && this.doughnutChartData.length > 0 | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetchChartData(): void { | ||||||
|  |     // If we're in drilldown mode, fetch the appropriate drilldown data
 | ||||||
|  |     if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { | ||||||
|  |       this.fetchDrilldownData(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // If we have the necessary data, fetch chart data from the service
 | ||||||
|  |     if (this.table && this.xAxis && this.yAxis) { | ||||||
|  |       console.log('Fetching doughnut chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |        | ||||||
|  |       // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  |        | ||||||
|  |       // Log the URL that will be called
 | ||||||
|  |       const url = `chart/getdashjson/doughnut?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |       console.log('Doughnut chart data URL:', url); | ||||||
|  |        | ||||||
|  |       // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |       // For base level, we pass empty parameter and value
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'doughnut', this.xAxis, yAxisString, this.connection, '', '').subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |           console.log('Received doughnut chart data:', data); | ||||||
|  |           if (data === null) { | ||||||
|  |             console.warn('Doughnut chart API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.doughnutChartLabels = []; | ||||||
|  |             this.doughnutChartData = []; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Handle the actual data structure returned by the API
 | ||||||
|  |           if (data && data.chartLabels && data.chartData) { | ||||||
|  |             // For doughnut charts, we need to extract the data differently
 | ||||||
|  |             // The first dataset's data array contains the values for the doughnut chart
 | ||||||
|  |             this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |             this.doughnutChartLabels = data.chartLabels || []; | ||||||
|  |             if (data.chartData && data.chartData.length > 0) { | ||||||
|  |               this.doughnutChartData = data.chartData[0].data.map(value => { | ||||||
|  |                 // Convert to number if it's not already
 | ||||||
|  |                 const numValue = Number(value); | ||||||
|  |                 return isNaN(numValue) ? 0 : numValue; | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               this.doughnutChartData = []; | ||||||
|  |             } | ||||||
|  |             // Ensure labels and data arrays have the same length
 | ||||||
|  |             this.syncLabelAndDataArrays(); | ||||||
|  |             // Validate and sanitize data
 | ||||||
|  |             this.validateChartData(); | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.doughnutChartData = [...this.doughnutChartData]; | ||||||
|  |             console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||||
|  |           } else if (data && data.labels && data.data) { | ||||||
|  |             // Handle the original expected format as fallback
 | ||||||
|  |             this.noDataAvailable = data.labels.length === 0; | ||||||
|  |             this.doughnutChartLabels = data.labels || []; | ||||||
|  |             this.doughnutChartData = data.data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               const numValue = Number(value); | ||||||
|  |               return isNaN(numValue) ? 0 : numValue; | ||||||
|  |             }); | ||||||
|  |             // Ensure labels and data arrays have the same length
 | ||||||
|  |             this.syncLabelAndDataArrays(); | ||||||
|  |             // Validate and sanitize data
 | ||||||
|  |             this.validateChartData(); | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.doughnutChartData = [...this.doughnutChartData]; | ||||||
|  |             console.log('Updated doughnut chart with legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||||
|  |           } else { | ||||||
|  |             console.warn('Doughnut chart received data does not have expected structure', data); | ||||||
|  |             // Reset to default data
 | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.doughnutChartLabels = []; | ||||||
|  |             this.doughnutChartData = []; | ||||||
|  |             // Validate and sanitize data
 | ||||||
|  |             this.validateChartData(); | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Log final state for debugging
 | ||||||
|  |           console.log('Final doughnut chart state:', {  | ||||||
|  |             labels: this.doughnutChartLabels,  | ||||||
|  |             data: this.doughnutChartData,  | ||||||
|  |             labelsLength: this.doughnutChartLabels.length,  | ||||||
|  |             dataLength: this.doughnutChartData.length  | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |         (error) => { | ||||||
|  |           console.error('Error fetching doughnut chart data:', error); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.doughnutChartLabels = []; | ||||||
|  |           this.doughnutChartData = []; | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       console.log('Missing required data for doughnut chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |       // Don't set noDataAvailable to true when there's no required data
 | ||||||
|  |       // This allows static data to be displayed
 | ||||||
|  |       // Only validate the chart data to ensure we have some data to display
 | ||||||
|  |       this.validateChartData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Fetch drilldown data based on current drilldown level
 | ||||||
|  |   fetchDrilldownData(): void { | ||||||
|  |     console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); | ||||||
|  |     console.log('Drilldown stack:', this.drilldownStack); | ||||||
|  |      | ||||||
|  |     // Get the current drilldown configuration based on the current level
 | ||||||
|  |     let drilldownConfig; | ||||||
|  |     if (this.currentDrilldownLevel === 1) { | ||||||
|  |       // Base drilldown level
 | ||||||
|  |       drilldownConfig = { | ||||||
|  |         apiUrl: this.drilldownApiUrl, | ||||||
|  |         xAxis: this.drilldownXAxis, | ||||||
|  |         yAxis: this.drilldownYAxis, | ||||||
|  |         parameter: this.drilldownParameter | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       // Multi-layer drilldown level
 | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { | ||||||
|  |         drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |       } else { | ||||||
|  |         console.warn('Invalid drilldown layer index:', layerIndex); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.doughnutChartLabels = []; | ||||||
|  |         this.doughnutChartData = []; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); | ||||||
|  |      | ||||||
|  |     // Check if we have valid drilldown configuration
 | ||||||
|  |     if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { | ||||||
|  |       console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.doughnutChartLabels = []; | ||||||
|  |       this.doughnutChartData = []; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter value from the drilldown stack
 | ||||||
|  |     let parameterValue = ''; | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; | ||||||
|  |       parameterValue = lastEntry.clickedValue || ''; | ||||||
|  |       console.log('Parameter value from last click:', parameterValue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter field from drilldown config
 | ||||||
|  |     const parameterField = drilldownConfig.parameter || ''; | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { | ||||||
|  |       apiUrl: drilldownConfig.apiUrl, | ||||||
|  |       xAxis: drilldownConfig.xAxis, | ||||||
|  |       yAxis: drilldownConfig.yAxis, | ||||||
|  |       parameterField: parameterField, | ||||||
|  |       parameterValue: parameterValue, | ||||||
|  |       connection: this.connection | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  |     // Build the actual API URL with parameter replacement
 | ||||||
|  |     let actualApiUrl = drilldownConfig.apiUrl; | ||||||
|  |     console.log('Original API URL:', actualApiUrl); | ||||||
|  |     console.log('Parameter value to use:', parameterValue); | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     // Check if the URL contains angle brackets for parameter replacement
 | ||||||
|  |     const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); | ||||||
|  |      | ||||||
|  |     if (hasAngleBrackets && parameterValue) { | ||||||
|  |       // Replace angle brackets placeholder with actual value
 | ||||||
|  |       console.log('Replacing angle brackets with parameter value'); | ||||||
|  |       const encodedValue = encodeURIComponent(parameterValue); | ||||||
|  |       actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); | ||||||
|  |       console.log('URL after angle bracket replacement:', actualApiUrl); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Log the URL that will be called
 | ||||||
|  |     const url = `chart/getdashjson/doughnut?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |     console.log('Drilldown data URL:', url); | ||||||
|  |      | ||||||
|  |     // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |     // Backend handles filtering, we just pass the parameter field and value
 | ||||||
|  |     this.dashboardService.getChartData(actualApiUrl, 'doughnut', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( | ||||||
|  |       (data: any) => { | ||||||
|  |         console.log('Received drilldown data:', data); | ||||||
|  |         if (data === null) { | ||||||
|  |           console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.doughnutChartLabels = []; | ||||||
|  |           this.doughnutChartData = []; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Handle the actual data structure returned by the API
 | ||||||
|  |         if (data && data.chartLabels && data.chartData) { | ||||||
|  |           // For doughnut charts, we need to extract the data differently
 | ||||||
|  |           // The first dataset's data array contains the values for the doughnut chart
 | ||||||
|  |           this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |           this.doughnutChartLabels = data.chartLabels || []; | ||||||
|  |           if (data.chartData && data.chartData.length > 0) { | ||||||
|  |             this.doughnutChartData = data.chartData[0].data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               const numValue = Number(value); | ||||||
|  |               return isNaN(numValue) ? 0 : numValue; | ||||||
|  |             }); | ||||||
|  |           } else { | ||||||
|  |             this.doughnutChartData = []; | ||||||
|  |           } | ||||||
|  |           // Ensure labels and data arrays have the same length
 | ||||||
|  |           this.syncLabelAndDataArrays(); | ||||||
|  |           // Validate and sanitize data
 | ||||||
|  |           this.validateChartData(); | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.doughnutChartData = [...this.doughnutChartData]; | ||||||
|  |           console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||||
|  |         } else if (data && data.labels && data.data) { | ||||||
|  |           // Handle the original expected format as fallback
 | ||||||
|  |           this.noDataAvailable = data.labels.length === 0; | ||||||
|  |           this.doughnutChartLabels = data.labels || []; | ||||||
|  |           this.doughnutChartData = data.data.map(value => { | ||||||
|  |             // Convert to number if it's not already
 | ||||||
|  |             const numValue = Number(value); | ||||||
|  |             return isNaN(numValue) ? 0 : numValue; | ||||||
|  |           }); | ||||||
|  |           // Ensure labels and data arrays have the same length
 | ||||||
|  |           this.syncLabelAndDataArrays(); | ||||||
|  |           // Validate and sanitize data
 | ||||||
|  |           this.validateChartData(); | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.doughnutChartData = [...this.doughnutChartData]; | ||||||
|  |           console.log('Updated doughnut chart with drilldown legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData }); | ||||||
|  |         } else { | ||||||
|  |           console.warn('Drilldown received data does not have expected structure', data); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.doughnutChartLabels = []; | ||||||
|  |           this.doughnutChartData = []; | ||||||
|  |           // Validate and sanitize data
 | ||||||
|  |           this.validateChartData(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Log final state for debugging
 | ||||||
|  |         console.log('Final doughnut chart state:', {  | ||||||
|  |           labels: this.doughnutChartLabels,  | ||||||
|  |           data: this.doughnutChartData,  | ||||||
|  |           labelsLength: this.doughnutChartLabels.length,  | ||||||
|  |           dataLength: this.doughnutChartData.length  | ||||||
|  |         }); | ||||||
|  |       }, | ||||||
|  |       (error) => { | ||||||
|  |         console.error('Error fetching drilldown data:', error); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.doughnutChartLabels = []; | ||||||
|  |         this.doughnutChartData = []; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Reset to original data (go back to base level)
 | ||||||
|  |   resetToOriginalData(): void { | ||||||
|  |     console.log('Resetting to original data'); | ||||||
|  |     console.log('Current stack before reset:', this.drilldownStack); | ||||||
|  |     console.log('Current level before reset:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     this.currentDrilldownLevel = 0; | ||||||
|  |     this.drilldownStack = []; | ||||||
|  |      | ||||||
|  |     if (this.originalDoughnutChartLabels.length > 0) { | ||||||
|  |       this.doughnutChartLabels = [...this.originalDoughnutChartLabels]; | ||||||
|  |       console.log('Restored original labels'); | ||||||
|  |     } | ||||||
|  |     if (this.originalDoughnutChartData.length > 0) { | ||||||
|  |       this.doughnutChartData = [...this.originalDoughnutChartData]; | ||||||
|  |       console.log('Restored original data'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('After reset - labels:', this.doughnutChartLabels); | ||||||
|  |     console.log('After reset - data:', this.doughnutChartData); | ||||||
|  |      | ||||||
|  |     // Re-fetch original data
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Navigate back to previous drilldown level
 | ||||||
|  |   navigateBack(): void { | ||||||
|  |     console.log('Navigating back, current stack:', this.drilldownStack); | ||||||
|  |     console.log('Current level:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       // Remove the last entry from the stack
 | ||||||
|  |       const removedEntry = this.drilldownStack.pop(); | ||||||
|  |       console.log('Removed entry from stack:', removedEntry); | ||||||
|  |        | ||||||
|  |       // Update the current drilldown level
 | ||||||
|  |       this.currentDrilldownLevel = this.drilldownStack.length; | ||||||
|  |       console.log('New level after pop:', this.currentDrilldownLevel); | ||||||
|  |       console.log('Stack after pop:', this.drilldownStack); | ||||||
|  |        | ||||||
|  |       if (this.drilldownStack.length > 0) { | ||||||
|  |         // Fetch data for the previous level
 | ||||||
|  |         console.log('Fetching data for previous level'); | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         // Back to base level
 | ||||||
|  |         console.log('Back to base level, resetting to original data'); | ||||||
|  |         this.resetToOriginalData(); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // Already at base level, reset to original data
 | ||||||
|  |       console.log('Already at base level, resetting to original data'); | ||||||
|  |       this.resetToOriginalData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Get color for legend item | ||||||
|  |    * @param index Index of the legend item | ||||||
|  |    */ | ||||||
|  |   public getLegendColor(index: number): string { | ||||||
|  |     return this.chartColors[index % this.chartColors.length]; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Ensure labels and data arrays have the same length | ||||||
|  |    */ | ||||||
|  |   private syncLabelAndDataArrays(): void { | ||||||
|  |     // Handle empty arrays
 | ||||||
|  |     if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length); | ||||||
|  |      | ||||||
|  |     // Pad the shorter array with default values
 | ||||||
|  |     while (this.doughnutChartLabels.length < maxLength) { | ||||||
|  |       this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     while (this.doughnutChartData.length < maxLength) { | ||||||
|  |       this.doughnutChartData.push(0); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Truncate the longer array if needed
 | ||||||
|  |     if (this.doughnutChartLabels.length > maxLength) { | ||||||
|  |       this.doughnutChartLabels = this.doughnutChartLabels.slice(0, maxLength); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     if (this.doughnutChartData.length > maxLength) { | ||||||
|  |       this.doughnutChartData = this.doughnutChartData.slice(0, maxLength); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // events
 | ||||||
|  |   public chartClicked(e: any): void { | ||||||
|  |     console.log('Doughnut chart clicked:', e); | ||||||
|  |      | ||||||
|  |     // If drilldown is enabled and we have a valid click event
 | ||||||
|  |     if (this.drilldownEnabled && e.active && e.active.length > 0) { | ||||||
|  |       // Get the index of the clicked element
 | ||||||
|  |       const clickedIndex = e.active[0].index; | ||||||
|  |        | ||||||
|  |       // Get the label of the clicked element
 | ||||||
|  |       const clickedLabel = this.doughnutChartLabels[clickedIndex]; | ||||||
|  |        | ||||||
|  |       console.log('Clicked on doughnut slice:', { index: clickedIndex, label: clickedLabel }); | ||||||
|  |        | ||||||
|  |       // If we're not at the base level, store original data
 | ||||||
|  |       if (this.currentDrilldownLevel === 0) { | ||||||
|  |         // Store original data before entering drilldown mode
 | ||||||
|  |         this.originalDoughnutChartLabels = [...this.doughnutChartLabels]; | ||||||
|  |         this.originalDoughnutChartData = [...this.doughnutChartData]; | ||||||
|  |         console.log('Stored original data for drilldown'); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       // Determine the next drilldown level
 | ||||||
|  |       const nextDrilldownLevel = this.currentDrilldownLevel + 1; | ||||||
|  |        | ||||||
|  |       console.log('Next drilldown level will be:', nextDrilldownLevel); | ||||||
|  |        | ||||||
|  |       // Check if there's a drilldown configuration for this level
 | ||||||
|  |       let hasDrilldownConfig = false; | ||||||
|  |       let drilldownConfig; | ||||||
|  |        | ||||||
|  |       if (nextDrilldownLevel === 1) { | ||||||
|  |         // Base drilldown level
 | ||||||
|  |         drilldownConfig = { | ||||||
|  |           apiUrl: this.drilldownApiUrl, | ||||||
|  |           xAxis: this.drilldownXAxis, | ||||||
|  |           yAxis: this.drilldownYAxis, | ||||||
|  |           parameter: this.drilldownParameter | ||||||
|  |         }; | ||||||
|  |         hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; | ||||||
|  |       } else { | ||||||
|  |         // Multi-layer drilldown level
 | ||||||
|  |         const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |         if (layerIndex < this.drilldownLayers.length) { | ||||||
|  |           drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |           hasDrilldownConfig = drilldownConfig.enabled && | ||||||
|  |                               !!drilldownConfig.apiUrl &&  | ||||||
|  |                               !!drilldownConfig.xAxis &&  | ||||||
|  |                               !!drilldownConfig.yAxis; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       console.log('Drilldown config for next level:', drilldownConfig); | ||||||
|  |       console.log('Has drilldown config:', hasDrilldownConfig); | ||||||
|  |        | ||||||
|  |       // If there's a drilldown configuration for the next level, proceed
 | ||||||
|  |       if (hasDrilldownConfig) { | ||||||
|  |         // Add this click to the drilldown stack
 | ||||||
|  |         const stackEntry = { | ||||||
|  |           level: nextDrilldownLevel, | ||||||
|  |           clickedIndex: clickedIndex, | ||||||
|  |           clickedLabel: clickedLabel, | ||||||
|  |           clickedValue: clickedLabel // Using label as value for now
 | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         this.drilldownStack.push(stackEntry); | ||||||
|  |          | ||||||
|  |         console.log('Added to drilldown stack:', stackEntry); | ||||||
|  |         console.log('Current drilldown stack:', this.drilldownStack); | ||||||
|  |          | ||||||
|  |         // Update the current drilldown level
 | ||||||
|  |         this.currentDrilldownLevel = nextDrilldownLevel; | ||||||
|  |          | ||||||
|  |         console.log('Entering drilldown level:', this.currentDrilldownLevel); | ||||||
|  |          | ||||||
|  |         // Fetch drilldown data for the new level
 | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         console.log('No drilldown configuration for level:', nextDrilldownLevel); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log('Drilldown not enabled or invalid click event'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public chartHovered(e: any): void { | ||||||
|  |     console.log(e); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,10 +1,38 @@ | |||||||
| <div style="display: block"> | <div class="dynamic-chart-container"> | ||||||
|   <canvas baseChart [datasets]="dynamicChartData" |   <!-- Drilldown mode indicator --> | ||||||
|   [options]="barChartOptions" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|     [type]="barChartType" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|     [labels]="dynamicChartLabels" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|     (chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|     (chartClick)="chartClicked($event)"> |     </button> | ||||||
|   </canvas> |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| </div> |       Back to Main View | ||||||
| <button class="btn btn-primary" (click)="randomize()">Update</button> |     </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" | ||||||
|  |       (chartHover)="chartHovered($event)" | ||||||
|  |       (chartClick)="chartClicked($event)"> | ||||||
|  |     </canvas> | ||||||
|  |   </div> | ||||||
|  |   <button class="btn btn-primary" (click)="randomize()">Update</button> | ||||||
|  | </div> | ||||||
| @ -1,18 +1,71 @@ | |||||||
| import { Component, OnInit, ViewChild } from '@angular/core'; | import { Component, OnInit, ViewChild, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
| import { ChartConfiguration, ChartData,  } from 'chart.js'; | import { ChartConfiguration, ChartData, ChartDataset } from 'chart.js'; | ||||||
| import { BaseChartDirective } from 'ng2-charts'; | import { BaseChartDirective } from 'ng2-charts'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
|  | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-dynamic-chart', |   selector: 'app-dynamic-chart', | ||||||
|   templateUrl: './dynamic-chart.component.html', |   templateUrl: './dynamic-chart.component.html', | ||||||
|   styleUrls: ['./dynamic-chart.component.scss'] |   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 { |   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'] = { |   public barChartOptions: ChartConfiguration['options'] = { | ||||||
|     elements: { |     elements: { | ||||||
| @ -34,37 +87,369 @@ export class DynamicChartComponent implements OnInit { | |||||||
|   public dynamicChartLabels: string[] = [ '2006', '2007', '2008', '2009', '2010', '2011', '2012' ]; |   public dynamicChartLabels: string[] = [ '2006', '2007', '2008', '2009', '2010', '2011', '2012' ]; | ||||||
|   public barChartType: string = 'bar'; |   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 = [ | 	public dynamicChartData: any = [ | ||||||
| 		{ data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, | 		{ data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, | ||||||
| 		{ data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } | 		{ 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
 |   // events
 | ||||||
|   public chartClicked(e: any): void { |   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 { | 	public chartHovered(e: any): void { | ||||||
| 		console.log(e); | 		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 { |   public randomize(): void { | ||||||
|     this.barChartType = this.barChartType === 'bar' ? 'line' : 'bar'; |     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({ | @Component({ | ||||||
|   selector: 'app-financial-chart', |   selector: 'app-financial-chart', | ||||||
|   templateUrl: './financial-chart.component.html', |   templateUrl: './financial-chart.component.html', | ||||||
|   styleUrls: ['./financial-chart.component.scss'] |   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 { |   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); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,62 +1,29 @@ | |||||||
| <div style="display: block;"> | <div style="display: block;"> | ||||||
| <div class="dg-wrapper"> |   <div class="dg-wrapper"> | ||||||
|     <div class="clr-row"> |     <div class="clr-row"> | ||||||
|       <div class="clr-col-8"> |       <div class="clr-col-8"> | ||||||
|         <h3>User Group Maintenance</h3> |         <h3>{{charttitle || 'Data Grid'}}</h3> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <clr-datagrid [clrDgLoading]="loading"> |     <clr-datagrid [clrDgLoading]="loading"> | ||||||
|       <clr-dg-placeholder> <ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template> |       <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 |       <!-- Dynamic columns based on response keys --> | ||||||
|       </ng-container></clr-dg-column> |       <clr-dg-column *ngFor="let header of dynamicHeaders" [clrDgField]="header.key">  | ||||||
|       <clr-dg-column [clrDgField]="'groupName'"> <ng-container *clrDgHideableColumn="{hidden: false}"> |         <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|         Group Name |           {{header.displayName}} | ||||||
|       </ng-container></clr-dg-column> |         </ng-container> | ||||||
|       <clr-dg-column [clrDgField]="'groupDesc'"><ng-container *clrDgHideableColumn="{hidden: false}"> |       </clr-dg-column> | ||||||
|         Description | 
 | ||||||
|         </ng-container></clr-dg-column > |       <clr-dg-row *clrDgItems="let item of givendata" [clrDgItem]="item"> | ||||||
|       <clr-dg-column [clrDgField]="'groupLevel'"> <ng-container *clrDgHideableColumn="{hidden: false}"> |         <!-- Dynamic cells based on response keys --> | ||||||
|         Group Level |         <clr-dg-cell *ngFor="let header of dynamicHeaders"> | ||||||
|       </ng-container></clr-dg-column> |           {{item[header.key]}} | ||||||
|       <clr-dg-column [clrDgField]="'status'"> <ng-container *clrDgHideableColumn="{hidden: false}"> |         </clr-dg-cell> | ||||||
|         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> |  | ||||||
|    |  | ||||||
|    |  | ||||||
|       <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> |       </clr-dg-row> | ||||||
|    | 
 | ||||||
|       <clr-dg-footer> |       <clr-dg-footer> | ||||||
|         <clr-dg-pagination #pagination [clrDgPageSize]="10"> |         <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||||
|           <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size> |           <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Record per page</clr-dg-page-size> | ||||||
| @ -65,5 +32,5 @@ | |||||||
|         </clr-dg-pagination> |         </clr-dg-pagination> | ||||||
|       </clr-dg-footer> |       </clr-dg-footer> | ||||||
|     </clr-datagrid> |     </clr-datagrid> | ||||||
| </div> |   </div> | ||||||
|   </div> | </div> | ||||||
| @ -1,54 +1,178 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit, Input, OnChanges, SimpleChanges } 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 { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service'; | import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service'; | ||||||
| import { ToastrService } from 'ngx-toastr'; | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
| import { MenuGroupService } from 'src/app/services/admin/menu-group.service'; | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-grid-view', |   selector: 'app-grid-view', | ||||||
|   templateUrl: './grid-view.component.html', |   templateUrl: './grid-view.component.html', | ||||||
|   styleUrls: ['./grid-view.component.scss'] |   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; |   loading = false; | ||||||
|   public entryForm: FormGroup; |   givendata: any[] = []; | ||||||
|   givendata; |  | ||||||
|   orders; |   orders; | ||||||
|   error; |   error: string; | ||||||
|   modalAdd= false; |   modalAdd = false; | ||||||
|   modaledit=false; |   modaledit = false; | ||||||
|   modaldelete=false; |   modaldelete = false; | ||||||
|   rowSelected :any= {}; |   rowSelected: any = {}; | ||||||
|   mcreate; |   mcreate; | ||||||
|   medit; |   medit; | ||||||
|   showdata; |   showdata; | ||||||
|   submitted=false; |   submitted = false; | ||||||
|  |   dynamicHeaders: any[] = []; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private excel: ExcelService, | 
 | ||||||
|     private toastr:ToastrService, |     private mainservice: UsergrpmaintainceService, | ||||||
|     private _fb: FormBuilder, |     private dashboardService: Dashboard3Service, | ||||||
|     private router: Router, |  | ||||||
|     private route: ActivatedRoute, |  | ||||||
|     private menuGroupService: MenuGroupService, |  | ||||||
|     private mainservice:UsergrpmaintainceService, |  | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.mainservice.getAll().subscribe((data) => { |     this.fetchGridData(); | ||||||
|       console.log(data); |  | ||||||
|       this.givendata = data; |  | ||||||
|       if(this.givendata.length==0){ |  | ||||||
|         this.error="No data Available"; |  | ||||||
|         console.log(this.error) |  | ||||||
|       } |  | ||||||
|     },(error) => { |  | ||||||
|       console.log(error); |  | ||||||
|       if(error){ |  | ||||||
|        this.error="Server Error"; |  | ||||||
|      } |  | ||||||
|     }); |  | ||||||
|   } |   } | ||||||
| } | 
 | ||||||
|  |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|  |     console.log('GridViewComponent input changes:', changes); | ||||||
|  | 
 | ||||||
|  |     // Check if any of the key properties have changed
 | ||||||
|  |     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; | ||||||
|  |     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; | ||||||
|  |     const tableChanged = changes.table && !changes.table.firstChange; | ||||||
|  |     const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
 | ||||||
|  | 
 | ||||||
|  |     // Respond to input changes
 | ||||||
|  |     if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) { | ||||||
|  |       console.log('X or Y axis or table or connection changed, fetching new data'); | ||||||
|  |       // Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
 | ||||||
|  |       this.fetchGridData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Dynamic headers for the grid
 | ||||||
|  |    | ||||||
|  |   fetchGridData(): void { | ||||||
|  |     // If we have the necessary data, fetch grid data from the service
 | ||||||
|  |     if (this.table && this.xAxis) { | ||||||
|  |       console.log('Fetching grid data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  | 
 | ||||||
|  |       // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  | 
 | ||||||
|  |       // Fetch data from the dashboard service, similar to other chart components
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |           console.log('Received grid data:', data); | ||||||
|  |           if (data === null) { | ||||||
|  |             console.warn('Grid API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |             this.error = "No data Available"; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Handle the actual data structure returned by the API
 | ||||||
|  |           if (data && data.chartData) { | ||||||
|  |             this.givendata = data.chartData; | ||||||
|  |             this.extractDynamicHeaders(data.chartData); | ||||||
|  |             this.error = this.givendata.length === 0 ? "No data Available" : undefined; | ||||||
|  |             console.log('Updated grid with data:', this.givendata); | ||||||
|  |           } else if (data && data.data) { | ||||||
|  |             // Handle the original expected format as fallback
 | ||||||
|  |             this.givendata = data.data; | ||||||
|  |             this.extractDynamicHeaders(data.data); | ||||||
|  |             this.error = this.givendata.length === 0 ? "No data Available" : undefined; | ||||||
|  |             console.log('Updated grid with legacy data format:', this.givendata); | ||||||
|  |           } else if (Array.isArray(data)) { | ||||||
|  |             // Handle case where data is directly an array
 | ||||||
|  |             this.givendata = data; | ||||||
|  |             this.extractDynamicHeaders(data); | ||||||
|  |             this.error = this.givendata.length === 0 ? "No data Available" : undefined; | ||||||
|  |             console.log('Updated grid with array data:', this.givendata); | ||||||
|  |           } else { | ||||||
|  |             console.warn('Grid received data does not have expected structure', data); | ||||||
|  |             this.error = "No valid data received"; | ||||||
|  |             this.givendata = []; | ||||||
|  |           } | ||||||
|  |         }, (error) => { | ||||||
|  |           console.log('Error fetching grid data:', error); | ||||||
|  |           this.error = "Server Error"; | ||||||
|  |         }); | ||||||
|  |     } else if (this.table) { | ||||||
|  |       console.log('Missing xAxis, falling back to default data fetching'); | ||||||
|  |       // Fallback to default data fetching when only table is provided
 | ||||||
|  |        // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  | 
 | ||||||
|  |       // Fetch data from the dashboard service, similar to other chart components
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |       // this.mainservice.getAll().subscribe((data: any) => {
 | ||||||
|  |         console.log('recv data ', data); | ||||||
|  |         this.givendata = Array.isArray(data) ? data : []; | ||||||
|  |         this.extractDynamicHeaders(data); | ||||||
|  |         this.error = this.givendata && this.givendata.length === 0 ? "No data Available" : undefined; | ||||||
|  |       }, (error) => { | ||||||
|  |         console.log(error); | ||||||
|  |         this.error = "Server Error"; | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       console.log('Missing required data for grid:', { table: this.table }); | ||||||
|  |       this.error = "Table name is required"; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Extract dynamic headers from the data | ||||||
|  |    * @param data Array of data objects | ||||||
|  |    */ | ||||||
|  |   private extractDynamicHeaders(data: any): void { | ||||||
|  |     // Ensure data is an array
 | ||||||
|  |     const dataArray = Array.isArray(data) ? data : []; | ||||||
|  |      | ||||||
|  |     if (dataArray && dataArray.length > 0) { | ||||||
|  |       // Get all unique keys from the data objects
 | ||||||
|  |       const allKeys = new Set<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,12 +1,31 @@ | |||||||
| <div style="display: block;"> | <div style="display: block"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|               [datasets]="lineChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|               [labels]="lineChartLabels" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|               [options]="lineChartOptions" |     <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}} | ||||||
|               [legend]="lineChartLegend" |     </button> | ||||||
|               [type]="lineChartType" |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|               (chartHover)="chartHovered($event)" |       Back to Main View | ||||||
|               (chartClick)="chartClicked($event)"></canvas> |     </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" | ||||||
|  |                 [options]="lineChartOptions" | ||||||
|  |             | ||||||
|  |                 [legend]="lineChartLegend" | ||||||
|  |                 [type]="lineChartType" | ||||||
|  |                 (chartHover)="chartHovered($event)" | ||||||
|  |                 (chartClick)="chartClicked($event)"></canvas> | ||||||
|  |   </div> | ||||||
| </div> | </div> | ||||||
| <!--     [color]="lineChartColors"--> | <!--     [color]="lineChartColors"--> | ||||||
| @ -1,71 +1,474 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-line-chart', |   selector: 'app-line-chart', | ||||||
|   templateUrl: './line-chart.component.html', |   templateUrl: './line-chart.component.html', | ||||||
|   styleUrls: ['./line-chart.component.scss'] |   styleUrls: ['./line-chart.component.scss'] | ||||||
| }) | }) | ||||||
| export class LineChartComponent implements OnInit { | export class LineChartComponent implements OnInit, OnChanges { | ||||||
|   public lineChartData:Array<any> = [ |   @Input() xAxis: string; | ||||||
| 		{data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'}, |   @Input() yAxis: string | string[]; | ||||||
| 		{data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'}, |   @Input() table: string; | ||||||
| 		{data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'} |   @Input() datastore: string; | ||||||
| 	  ]; |   @Input() charttitle: string; | ||||||
| 	  public lineChartLabels:Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; |   @Input() chartlegend: boolean = true; | ||||||
| 	  public lineChartOptions:any = { |   @Input() showlabel: boolean = true; | ||||||
| 		responsive: true |   @Input() chartcolor: boolean; | ||||||
| 	  }; |   @Input() slices: boolean; | ||||||
| 	  public lineChartColors:Array<any> = [ |   @Input() donut: boolean; | ||||||
| 		{ // grey
 |   @Input() charturl: string; | ||||||
| 		  backgroundColor: 'rgba(148,159,177,0.2)', |   @Input() chartparameter: string; | ||||||
| 		  borderColor: 'rgba(148,159,177,1)', |   @Input() datasource: string; | ||||||
| 		  pointBackgroundColor: 'rgba(148,159,177,1)', |   @Input() fieldName: string; | ||||||
| 		  pointBorderColor: '#fff', |   @Input() connection: number; // Add connection input
 | ||||||
| 		  pointHoverBackgroundColor: '#fff', |   // Drilldown configuration inputs
 | ||||||
| 		  pointHoverBorderColor: 'rgba(148,159,177,0.8)' |   @Input() drilldownEnabled: boolean = false; | ||||||
| 		}, |   @Input() drilldownApiUrl: string; | ||||||
| 		{ // dark grey
 |   @Input() drilldownXAxis: string; | ||||||
| 		  backgroundColor: 'rgba(77,83,96,0.2)', |   @Input() drilldownYAxis: string; | ||||||
| 		  borderColor: 'rgba(77,83,96,1)', |   @Input() drilldownParameter: string; // Add drilldown parameter input
 | ||||||
| 		  pointBackgroundColor: 'rgba(77,83,96,1)', |   // Multi-layer drilldown configuration inputs
 | ||||||
| 		  pointBorderColor: '#fff', |   @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
 | ||||||
| 		  pointHoverBackgroundColor: '#fff', |  | ||||||
| 		  pointHoverBorderColor: 'rgba(77,83,96,1)' |  | ||||||
| 		}, |  | ||||||
| 		{ // grey
 |  | ||||||
| 		  backgroundColor: 'rgba(148,159,177,0.2)', |  | ||||||
| 		  borderColor: 'rgba(148,159,177,1)', |  | ||||||
| 		  pointBackgroundColor: 'rgba(148,159,177,1)', |  | ||||||
| 		  pointBorderColor: '#fff', |  | ||||||
| 		  pointHoverBackgroundColor: '#fff', |  | ||||||
| 		  pointHoverBorderColor: 'rgba(148,159,177,0.8)' |  | ||||||
| 		} |  | ||||||
| 	  ]; |  | ||||||
| 	  public lineChartLegend:boolean = true; |  | ||||||
| 	  public lineChartType:string = 'line'; |  | ||||||
| 
 | 
 | ||||||
| 	  public randomize():void { |   public lineChartData: Array<any> = [ | ||||||
| 		let _lineChartData:Array<any> = new Array(this.lineChartData.length); |     {data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'}, | ||||||
| 		for (let i = 0; i < this.lineChartData.length; i++) { |     {data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'}, | ||||||
| 		  _lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label}; |     {data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'} | ||||||
| 		  for (let j = 0; j < this.lineChartData[i].data.length; j++) { |   ]; | ||||||
| 			_lineChartData[i].data[j] = Math.floor((Math.random() * 100) + 1); |   public lineChartLabels: Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; | ||||||
| 		  } |   public lineChartOptions: any = { | ||||||
| 		} |     responsive: true | ||||||
| 		this.lineChartData = _lineChartData; |   }; | ||||||
| 	  } |   public lineChartColors: Array<any> = [ | ||||||
|  |     { // grey
 | ||||||
|  |       backgroundColor: 'rgba(148,159,177,0.2)', | ||||||
|  |       borderColor: 'rgba(148,159,177,1)', | ||||||
|  |       pointBackgroundColor: 'rgba(148,159,177,1)', | ||||||
|  |       pointBorderColor: '#fff', | ||||||
|  |       pointHoverBackgroundColor: '#fff', | ||||||
|  |       pointHoverBorderColor: 'rgba(148,159,177,0.8)' | ||||||
|  |     }, | ||||||
|  |     { // dark grey
 | ||||||
|  |       backgroundColor: 'rgba(77,83,96,0.2)', | ||||||
|  |       borderColor: 'rgba(77,83,96,1)', | ||||||
|  |       pointBackgroundColor: 'rgba(77,83,96,1)', | ||||||
|  |       pointBorderColor: '#fff', | ||||||
|  |       pointHoverBackgroundColor: '#fff', | ||||||
|  |       pointHoverBorderColor: 'rgba(77,83,96,1)' | ||||||
|  |     }, | ||||||
|  |     { // grey
 | ||||||
|  |       backgroundColor: 'rgba(148,159,177,0.2)', | ||||||
|  |       borderColor: 'rgba(148,159,177,1)', | ||||||
|  |       pointBackgroundColor: 'rgba(148,159,177,1)', | ||||||
|  |       pointBorderColor: '#fff', | ||||||
|  |       pointHoverBackgroundColor: '#fff', | ||||||
|  |       pointHoverBorderColor: 'rgba(148,159,177,0.8)' | ||||||
|  |     } | ||||||
|  |   ]; | ||||||
|  |   public lineChartLegend: boolean = true; | ||||||
|  |   public lineChartType: string = 'line'; | ||||||
|  |    | ||||||
|  |   // Multi-layer drilldown state tracking
 | ||||||
|  |   drilldownStack: any[] = []; // Stack to track drilldown navigation history
 | ||||||
|  |   currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
 | ||||||
|  |   originalLineChartLabels: Array<any> = []; | ||||||
|  |   originalLineChartData: Array<any> = []; | ||||||
|  |    | ||||||
|  |   // No data state
 | ||||||
|  |   noDataAvailable: boolean = false; | ||||||
| 
 | 
 | ||||||
| 	  // events
 |   constructor(private dashboardService: Dashboard3Service) { } | ||||||
| 	  public chartClicked(e:any):void { |  | ||||||
| 		console.log(e); |  | ||||||
| 	  } |  | ||||||
| 
 |  | ||||||
| 	  public chartHovered(e:any):void { |  | ||||||
| 		console.log(e); |  | ||||||
| 	  } |  | ||||||
|   constructor() { } |  | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   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++) { | ||||||
|  |       _lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label}; | ||||||
|  |       for (let j = 0; j < this.lineChartData[i].data.length; j++) { | ||||||
|  |         _lineChartData[i].data[j] = Math.floor((Math.random() * 100) + 1); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     this.lineChartData = _lineChartData; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // events
 | ||||||
|  |   public chartClicked(e: any): void { | ||||||
|  |     console.log('Line chart clicked:', e); | ||||||
|  |      | ||||||
|  |     // If drilldown is enabled and we have a valid click event
 | ||||||
|  |     if (this.drilldownEnabled && e.active && e.active.length > 0) { | ||||||
|  |       // Get the index of the clicked element
 | ||||||
|  |       const clickedIndex = e.active[0].index; | ||||||
|  |        | ||||||
|  |       // Get the label of the clicked element
 | ||||||
|  |       const clickedLabel = this.lineChartLabels[clickedIndex]; | ||||||
|  |        | ||||||
|  |       console.log('Clicked on line point:', { index: clickedIndex, label: clickedLabel }); | ||||||
|  |        | ||||||
|  |       // If we're not at the base level, store original data
 | ||||||
|  |       if (this.currentDrilldownLevel === 0) { | ||||||
|  |         // Store original data before entering drilldown mode
 | ||||||
|  |         this.originalLineChartLabels = [...this.lineChartLabels]; | ||||||
|  |         this.originalLineChartData = [...this.lineChartData]; | ||||||
|  |         console.log('Stored original data for drilldown'); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       // Determine the next drilldown level
 | ||||||
|  |       const nextDrilldownLevel = this.currentDrilldownLevel + 1; | ||||||
|  |        | ||||||
|  |       console.log('Next drilldown level will be:', nextDrilldownLevel); | ||||||
|  |        | ||||||
|  |       // Check if there's a drilldown configuration for this level
 | ||||||
|  |       let hasDrilldownConfig = false; | ||||||
|  |       let drilldownConfig; | ||||||
|  |        | ||||||
|  |       if (nextDrilldownLevel === 1) { | ||||||
|  |         // Base drilldown level
 | ||||||
|  |         drilldownConfig = { | ||||||
|  |           apiUrl: this.drilldownApiUrl, | ||||||
|  |           xAxis: this.drilldownXAxis, | ||||||
|  |           yAxis: this.drilldownYAxis, | ||||||
|  |           parameter: this.drilldownParameter | ||||||
|  |         }; | ||||||
|  |         hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; | ||||||
|  |       } else { | ||||||
|  |         // Multi-layer drilldown level
 | ||||||
|  |         const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |         if (layerIndex < this.drilldownLayers.length) { | ||||||
|  |           drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |           hasDrilldownConfig = drilldownConfig.enabled && | ||||||
|  |                               !!drilldownConfig.apiUrl &&  | ||||||
|  |                               !!drilldownConfig.xAxis &&  | ||||||
|  |                               !!drilldownConfig.yAxis; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       console.log('Drilldown config for next level:', drilldownConfig); | ||||||
|  |       console.log('Has drilldown config:', hasDrilldownConfig); | ||||||
|  |        | ||||||
|  |       // If there's a drilldown configuration for the next level, proceed
 | ||||||
|  |       if (hasDrilldownConfig) { | ||||||
|  |         // Add this click to the drilldown stack
 | ||||||
|  |         const stackEntry = { | ||||||
|  |           level: nextDrilldownLevel, | ||||||
|  |           clickedIndex: clickedIndex, | ||||||
|  |           clickedLabel: clickedLabel, | ||||||
|  |           clickedValue: clickedLabel // Using label as value for now
 | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         this.drilldownStack.push(stackEntry); | ||||||
|  |          | ||||||
|  |         console.log('Added to drilldown stack:', stackEntry); | ||||||
|  |         console.log('Current drilldown stack:', this.drilldownStack); | ||||||
|  |          | ||||||
|  |         // Update the current drilldown level
 | ||||||
|  |         this.currentDrilldownLevel = nextDrilldownLevel; | ||||||
|  |          | ||||||
|  |         console.log('Entering drilldown level:', this.currentDrilldownLevel); | ||||||
|  |          | ||||||
|  |         // Fetch drilldown data for the new level
 | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         console.log('No drilldown configuration for level:', nextDrilldownLevel); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log('Drilldown not enabled or invalid click event'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public chartHovered(e: any): void { | ||||||
|  |     console.log(e); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,9 +1,44 @@ | |||||||
| <div style="display: block;"> | <div class="pie-chart-container"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|   [data]="pieChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|   [labels]="pieChartLabels" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|   [type]="pieChartType" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|   (chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|   (chartClick)="chartClicked($event)"> |     </button> | ||||||
| </canvas> |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| </div> |       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({ | @Component({ | ||||||
|   selector: 'app-pie-chart', |   selector: 'app-pie-chart', | ||||||
|   templateUrl: './pie-chart.component.html', |   templateUrl: './pie-chart.component.html', | ||||||
|   styleUrls: ['./pie-chart.component.scss'] |   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() { } |   public pieChartLabels: string[] = ['Category A', 'Category B', 'Category C']; | ||||||
| 
 |  | ||||||
|   ngOnInit(): void { |  | ||||||
|   } |  | ||||||
|   public pieChartLabels: string[] = ['SciFi', 'Drama', 'Comedy']; |  | ||||||
|   public pieChartData: number[] = [30, 50, 20]; |   public pieChartData: number[] = [30, 50, 20]; | ||||||
|   public pieChartType: string = 'pie'; |   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
 |   // events
 | ||||||
| 	public chartClicked(e: any): void { |   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 { |   public chartHovered(e: any): void { | ||||||
| 		console.log(e); |     console.log(e); | ||||||
| 	} |   } | ||||||
| 
 | } | ||||||
| } |  | ||||||
| @ -1,10 +1,28 @@ | |||||||
| 
 |  | ||||||
| <div style="display: block"> | <div style="display: block"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|     [datasets]="polarAreaChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|     [labels]="polarAreaChartLabels" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|     [type]="polarAreaChartType" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|     (chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|    (chartClick)="chartClicked($event)"> |     </button> | ||||||
|   </canvas> |     <button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| </div> |       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 | ||||||
|  |       [data]="polarAreaChartData" | ||||||
|  |       [labels]="polarAreaChartLabels" | ||||||
|  |       [type]="polarAreaChartType" | ||||||
|  |       (chartHover)="chartHovered($event)" | ||||||
|  |       (chartClick)="chartClicked($event)"> | ||||||
|  |     </canvas> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
| @ -1,37 +1,449 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-polar-chart', |   selector: 'app-polar-chart', | ||||||
|   templateUrl: './polar-chart.component.html', |   templateUrl: './polar-chart.component.html', | ||||||
|   styleUrls: ['./polar-chart.component.scss'] |   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 { |   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 polarAreaChartLabels: string[] = [ 'Download Sales', 'In-Store Sales', 'Mail Sales', 'Telesales', 'Corporate Sales' ]; | ||||||
|   public polarAreaChartData: any = [ |   public polarAreaChartData: any = [ | ||||||
|     { data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'} |     { data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'} | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   public polarAreaChartType: string = 'polarArea'; |   public polarAreaChartType: string = 'polarArea'; | ||||||
|  |    | ||||||
|  |   // Multi-layer drilldown state tracking
 | ||||||
|  |   drilldownStack: any[] = []; // Stack to track drilldown navigation history
 | ||||||
|  |   currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
 | ||||||
|  |   originalPolarAreaChartLabels: string[] = []; | ||||||
|  |   originalPolarAreaChartData: any = []; | ||||||
|  |    | ||||||
|  |   // No data state
 | ||||||
|  |   noDataAvailable: boolean = false; | ||||||
|  |    | ||||||
|  |   fetchChartData(): void { | ||||||
|  |     // If we're in drilldown mode, fetch the appropriate drilldown data
 | ||||||
|  |     if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { | ||||||
|  |       this.fetchDrilldownData(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // If we have the necessary data, fetch chart data from the service
 | ||||||
|  |     if (this.table && this.xAxis && this.yAxis) { | ||||||
|  |       console.log('Fetching polar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |        | ||||||
|  |       // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  |        | ||||||
|  |       // Log the URL that will be called
 | ||||||
|  |       const url = `chart/getdashjson/polar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |       console.log('Polar chart data URL:', url); | ||||||
|  |        | ||||||
|  |       // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |       // For base level, we pass empty parameter and value
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'polar', this.xAxis, yAxisString, this.connection, '', '').subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |           console.log('Received polar chart data:', data); | ||||||
|  |           if (data === null) { | ||||||
|  |             console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.polarAreaChartLabels = []; | ||||||
|  |             this.polarAreaChartData = []; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Handle the actual data structure returned by the API
 | ||||||
|  |           if (data && data.chartLabels && data.chartData) { | ||||||
|  |             // For polar charts, we need to extract the data differently
 | ||||||
|  |             // The first dataset's data array contains the values for the polar chart
 | ||||||
|  |             this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |             this.polarAreaChartLabels = data.chartLabels; | ||||||
|  |             if (data.chartData && data.chartData.length > 0) { | ||||||
|  |               this.polarAreaChartData = data.chartData[0].data.map(value => { | ||||||
|  |                 // Convert to number if it's not already
 | ||||||
|  |                 return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               this.polarAreaChartData = []; | ||||||
|  |             } | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.polarAreaChartData = [...this.polarAreaChartData]; | ||||||
|  |             console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); | ||||||
|  |           } else if (data && data.labels && data.data) { | ||||||
|  |             // Handle the original expected format as fallback
 | ||||||
|  |             this.noDataAvailable = data.labels.length === 0; | ||||||
|  |             this.polarAreaChartLabels = data.labels; | ||||||
|  |             this.polarAreaChartData = data.data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |             }); | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.polarAreaChartData = [...this.polarAreaChartData]; | ||||||
|  |             console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); | ||||||
|  |           } else { | ||||||
|  |             console.warn('Polar chart received data does not have expected structure', data); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.polarAreaChartLabels = []; | ||||||
|  |             this.polarAreaChartData = []; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         (error) => { | ||||||
|  |           console.error('Error fetching polar chart data:', error); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.polarAreaChartLabels = []; | ||||||
|  |           this.polarAreaChartData = []; | ||||||
|  |           // Keep default data in case of error
 | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.polarAreaChartLabels = []; | ||||||
|  |       this.polarAreaChartData = []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Fetch drilldown data based on current drilldown level
 | ||||||
|  |   fetchDrilldownData(): void { | ||||||
|  |     console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); | ||||||
|  |     console.log('Drilldown stack:', this.drilldownStack); | ||||||
|  |      | ||||||
|  |     // Get the current drilldown configuration based on the current level
 | ||||||
|  |     let drilldownConfig; | ||||||
|  |     if (this.currentDrilldownLevel === 1) { | ||||||
|  |       // Base drilldown level
 | ||||||
|  |       drilldownConfig = { | ||||||
|  |         apiUrl: this.drilldownApiUrl, | ||||||
|  |         xAxis: this.drilldownXAxis, | ||||||
|  |         yAxis: this.drilldownYAxis, | ||||||
|  |         parameter: this.drilldownParameter | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       // Multi-layer drilldown level
 | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { | ||||||
|  |         drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |       } else { | ||||||
|  |         console.warn('Invalid drilldown layer index:', layerIndex); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.polarAreaChartLabels = []; | ||||||
|  |         this.polarAreaChartData = []; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); | ||||||
|  |      | ||||||
|  |     // Check if we have valid drilldown configuration
 | ||||||
|  |     if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { | ||||||
|  |       console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.polarAreaChartLabels = []; | ||||||
|  |       this.polarAreaChartData = []; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter value from the drilldown stack
 | ||||||
|  |     let parameterValue = ''; | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; | ||||||
|  |       parameterValue = lastEntry.clickedValue || ''; | ||||||
|  |       console.log('Parameter value from last click:', parameterValue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter field from drilldown config
 | ||||||
|  |     const parameterField = drilldownConfig.parameter || ''; | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { | ||||||
|  |       apiUrl: drilldownConfig.apiUrl, | ||||||
|  |       xAxis: drilldownConfig.xAxis, | ||||||
|  |       yAxis: drilldownConfig.yAxis, | ||||||
|  |       parameterField: parameterField, | ||||||
|  |       parameterValue: parameterValue, | ||||||
|  |       connection: this.connection | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  |     // Build the actual API URL with parameter replacement
 | ||||||
|  |     let actualApiUrl = drilldownConfig.apiUrl; | ||||||
|  |     console.log('Original API URL:', actualApiUrl); | ||||||
|  |     console.log('Parameter value to use:', parameterValue); | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     // Check if the URL contains angle brackets for parameter replacement
 | ||||||
|  |     const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); | ||||||
|  |      | ||||||
|  |     if (hasAngleBrackets && parameterValue) { | ||||||
|  |       // Replace angle brackets placeholder with actual value
 | ||||||
|  |       console.log('Replacing angle brackets with parameter value'); | ||||||
|  |       const encodedValue = encodeURIComponent(parameterValue); | ||||||
|  |       actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); | ||||||
|  |       console.log('URL after angle bracket replacement:', actualApiUrl); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Log the URL that will be called
 | ||||||
|  |     const url = `chart/getdashjson/polar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |     console.log('Drilldown data URL:', url); | ||||||
|  |      | ||||||
|  |     // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |     // Backend handles filtering, we just pass the parameter field and value
 | ||||||
|  |     this.dashboardService.getChartData(actualApiUrl, 'polar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( | ||||||
|  |       (data: any) => { | ||||||
|  |         console.log('Received drilldown data:', data); | ||||||
|  |         if (data === null) { | ||||||
|  |           console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.polarAreaChartLabels = []; | ||||||
|  |           this.polarAreaChartData = []; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Handle the actual data structure returned by the API
 | ||||||
|  |         if (data && data.chartLabels && data.chartData) { | ||||||
|  |           // For polar charts, we need to extract the data differently
 | ||||||
|  |           // The first dataset's data array contains the values for the polar chart
 | ||||||
|  |           this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |           this.polarAreaChartLabels = data.chartLabels; | ||||||
|  |           if (data.chartData && data.chartData.length > 0) { | ||||||
|  |             this.polarAreaChartData = data.chartData[0].data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |             }); | ||||||
|  |           } else { | ||||||
|  |             this.polarAreaChartData = []; | ||||||
|  |           } | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.polarAreaChartData = [...this.polarAreaChartData]; | ||||||
|  |           console.log('Updated polar chart with drilldown data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); | ||||||
|  |         } else if (data && data.labels && data.data) { | ||||||
|  |           // Handle the original expected format as fallback
 | ||||||
|  |           this.noDataAvailable = data.labels.length === 0; | ||||||
|  |           this.polarAreaChartLabels = data.labels; | ||||||
|  |           this.polarAreaChartData = data.data.map(value => { | ||||||
|  |             // Convert to number if it's not already
 | ||||||
|  |             return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |           }); | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.polarAreaChartData = [...this.polarAreaChartData]; | ||||||
|  |           console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData }); | ||||||
|  |         } else { | ||||||
|  |           console.warn('Drilldown received data does not have expected structure', data); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.polarAreaChartLabels = []; | ||||||
|  |           this.polarAreaChartData = []; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       (error) => { | ||||||
|  |         console.error('Error fetching drilldown data:', error); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.polarAreaChartLabels = []; | ||||||
|  |         this.polarAreaChartData = []; | ||||||
|  |         // Keep current data in case of error
 | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Reset to original data (go back to base level)
 | ||||||
|  |   resetToOriginalData(): void { | ||||||
|  |     console.log('Resetting to original data'); | ||||||
|  |     console.log('Current stack before reset:', this.drilldownStack); | ||||||
|  |     console.log('Current level before reset:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     this.currentDrilldownLevel = 0; | ||||||
|  |     this.drilldownStack = []; | ||||||
|  |      | ||||||
|  |     if (this.originalPolarAreaChartLabels.length > 0) { | ||||||
|  |       this.polarAreaChartLabels = [...this.originalPolarAreaChartLabels]; | ||||||
|  |       console.log('Restored original labels'); | ||||||
|  |     } | ||||||
|  |     if (this.originalPolarAreaChartData.length > 0) { | ||||||
|  |       this.polarAreaChartData = [...this.originalPolarAreaChartData]; | ||||||
|  |       console.log('Restored original data'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('After reset - labels:', this.polarAreaChartLabels); | ||||||
|  |     console.log('After reset - data:', this.polarAreaChartData); | ||||||
|  |      | ||||||
|  |     // Re-fetch original data
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
| 
 |   // Navigate back to previous drilldown level
 | ||||||
| 
 |   navigateBack(): void { | ||||||
| 	// public radarChartData: any = [
 |     console.log('Navigating back, current stack:', this.drilldownStack); | ||||||
| 	// 	{ data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" },
 |     console.log('Current level:', this.currentDrilldownLevel); | ||||||
| 	// 	{ data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" }
 |      | ||||||
| 	// ];
 |     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
 |   // events
 | ||||||
| 	public chartClicked(e: any): void { | 	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 { | 	public chartHovered(e: any): void { | ||||||
| 		console.log(e); | 		console.log(e); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -1,8 +1,28 @@ | |||||||
| <div style="display: block"> | <div style="display: block"> | ||||||
| 	<canvas baseChart  |   <!-- Drilldown mode indicator --> | ||||||
| 	[datasets]="radarChartData"  |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
| 	[labels]="radarChartLabels"  |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
| 	[type]="radarChartType"  |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
| 	(chartHover)="chartHovered($event)" |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
| 	(chartClick)="chartClicked($event)"></canvas> |     </button> | ||||||
| </div> |     <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> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
| @ -1,39 +1,460 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-radar-chart', |   selector: 'app-radar-chart', | ||||||
|   templateUrl: './radar-chart.component.html', |   templateUrl: './radar-chart.component.html', | ||||||
|   styleUrls: ['./radar-chart.component.scss'] |   styleUrls: ['./radar-chart.component.scss'] | ||||||
| }) | }) | ||||||
| export class RadarChartComponent implements OnInit { | export class RadarChartComponent implements OnInit, OnChanges { | ||||||
| // Radar
 |   @Input() xAxis: string; | ||||||
| public radarChartLabels: string[] = [ |   @Input() yAxis: string | string[]; | ||||||
|   "Eating", |   @Input() table: string; | ||||||
|   "Drinking", |   @Input() datastore: string; | ||||||
|   "Sleeping", |   @Input() charttitle: string; | ||||||
|   "Designing", |   @Input() chartlegend: boolean = true; | ||||||
|   "Coding", |   @Input() showlabel: boolean = true; | ||||||
|   "Cycling", |   @Input() chartcolor: boolean; | ||||||
|   "Running" |   @Input() slices: boolean; | ||||||
| ]; |   @Input() donut: boolean; | ||||||
|  |   @Input() charturl: string; | ||||||
|  |   @Input() chartparameter: string; | ||||||
|  |   @Input() datasource: string; | ||||||
|  |   @Input() fieldName: string; | ||||||
|  |   @Input() connection: number; // Add connection input
 | ||||||
|  |   // Drilldown configuration inputs
 | ||||||
|  |   @Input() drilldownEnabled: boolean = false; | ||||||
|  |   @Input() drilldownApiUrl: string; | ||||||
|  |   @Input() drilldownXAxis: string; | ||||||
|  |   @Input() drilldownYAxis: string; | ||||||
|  |   @Input() drilldownParameter: string; // Add drilldown parameter input
 | ||||||
|  |   // Multi-layer drilldown configuration inputs
 | ||||||
|  |   @Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
 | ||||||
| 
 | 
 | ||||||
| public radarChartData: any = [ |   // Radar
 | ||||||
|   { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, |   public radarChartLabels: string[] = [ | ||||||
|   { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } |     "Eating", | ||||||
| ]; |     "Drinking", | ||||||
| public radarChartType: string = "radar"; |     "Sleeping", | ||||||
|  |     "Designing", | ||||||
|  |     "Coding", | ||||||
|  |     "Cycling", | ||||||
|  |     "Running" | ||||||
|  |   ]; | ||||||
| 
 | 
 | ||||||
| // events
 |   public radarChartData: any = [ | ||||||
| public chartClicked(e: any): void { |     { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" }, | ||||||
|   console.log(e); |     { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" } | ||||||
| } |   ]; | ||||||
|  |   public radarChartType: string = "radar"; | ||||||
|  |    | ||||||
|  |   // Multi-layer drilldown state tracking
 | ||||||
|  |   drilldownStack: any[] = []; // Stack to track drilldown navigation history
 | ||||||
|  |   currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
 | ||||||
|  |   originalRadarChartLabels: string[] = []; | ||||||
|  |   originalRadarChartData: any = []; | ||||||
|  |    | ||||||
|  |   // No data state
 | ||||||
|  |   noDataAvailable: boolean = false; | ||||||
| 
 | 
 | ||||||
| public chartHovered(e: any): void { |   constructor(private dashboardService: Dashboard3Service) { } | ||||||
|   console.log(e); |  | ||||||
| } |  | ||||||
|   constructor() { } |  | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  |     this.fetchChartData(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| } |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|  |     console.log('RadarChartComponent input changes:', changes); | ||||||
|  |      | ||||||
|  |     // Check if any of the key properties have changed
 | ||||||
|  |     const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange; | ||||||
|  |     const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange; | ||||||
|  |     const tableChanged = changes.table && !changes.table.firstChange; | ||||||
|  |     const connectionChanged = changes.connection && !changes.connection.firstChange; | ||||||
|  |     // Drilldown configuration changes
 | ||||||
|  |     const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange; | ||||||
|  |     const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange; | ||||||
|  |     const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange; | ||||||
|  |     const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange; | ||||||
|  |     const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange; | ||||||
|  |      | ||||||
|  |     // Respond to input changes
 | ||||||
|  |     if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||  | ||||||
|  |         drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged || | ||||||
|  |         drilldownLayersChanged) { | ||||||
|  |       console.log('X or Y axis or table or connection or drilldown config changed, fetching new data'); | ||||||
|  |       this.fetchChartData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetchChartData(): void { | ||||||
|  |     // If we're in drilldown mode, fetch the appropriate drilldown data
 | ||||||
|  |     if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) { | ||||||
|  |       this.fetchDrilldownData(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // If we have the necessary data, fetch chart data from the service
 | ||||||
|  |     if (this.table && this.xAxis && this.yAxis) { | ||||||
|  |       console.log('Fetching radar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |        | ||||||
|  |       // Convert yAxis to string if it's an array
 | ||||||
|  |       const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis; | ||||||
|  |        | ||||||
|  |       // Log the URL that will be called
 | ||||||
|  |       const url = `chart/getdashjson/radar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |       console.log('Radar chart data URL:', url); | ||||||
|  |        | ||||||
|  |       // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |       // For base level, we pass empty parameter and value
 | ||||||
|  |       this.dashboardService.getChartData(this.table, 'radar', this.xAxis, yAxisString, this.connection, '', '').subscribe( | ||||||
|  |         (data: any) => { | ||||||
|  |           console.log('Received radar chart data:', data); | ||||||
|  |           if (data === null) { | ||||||
|  |             console.warn('Radar chart API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.radarChartLabels = []; | ||||||
|  |             this.radarChartData = []; | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // Handle the actual data structure returned by the API
 | ||||||
|  |           if (data && data.chartLabels && data.chartData) { | ||||||
|  |             // Map the API response to the format expected by the chart
 | ||||||
|  |             this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |             this.radarChartLabels = data.chartLabels; | ||||||
|  |             // For radar charts, we need to ensure the data is properly formatted
 | ||||||
|  |             // Each dataset should have a data array with numeric values
 | ||||||
|  |             this.radarChartData = data.chartData.map(dataset => ({ | ||||||
|  |               ...dataset, | ||||||
|  |               data: dataset.data ? dataset.data.map(value => { | ||||||
|  |                 // Convert to number if it's not already
 | ||||||
|  |                 return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |               }) : [] | ||||||
|  |             })); | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.radarChartData = [...this.radarChartData]; | ||||||
|  |             console.log('Updated radar chart with data:', { labels: this.radarChartLabels, data: this.radarChartData }); | ||||||
|  |           } else if (data && data.labels && data.datasets) { | ||||||
|  |             // Handle the original expected format as fallback
 | ||||||
|  |             this.noDataAvailable = data.labels.length === 0; | ||||||
|  |             this.radarChartLabels = data.labels; | ||||||
|  |             this.radarChartData = data.datasets.map(dataset => ({ | ||||||
|  |               ...dataset, | ||||||
|  |               data: dataset.data ? dataset.data.map(value => { | ||||||
|  |                 // Convert to number if it's not already
 | ||||||
|  |                 return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |               }) : [] | ||||||
|  |             })); | ||||||
|  |             // Trigger change detection
 | ||||||
|  |             this.radarChartData = [...this.radarChartData]; | ||||||
|  |             console.log('Updated radar chart with legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData }); | ||||||
|  |           } else { | ||||||
|  |             console.warn('Radar chart received data does not have expected structure', data); | ||||||
|  |             this.noDataAvailable = true; | ||||||
|  |             this.radarChartLabels = []; | ||||||
|  |             this.radarChartData = []; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         (error) => { | ||||||
|  |           console.error('Error fetching radar chart data:', error); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.radarChartLabels = []; | ||||||
|  |           this.radarChartData = []; | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       console.log('Missing required data for radar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection }); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.radarChartLabels = []; | ||||||
|  |       this.radarChartData = []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Fetch drilldown data based on current drilldown level
 | ||||||
|  |   fetchDrilldownData(): void { | ||||||
|  |     console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel); | ||||||
|  |     console.log('Drilldown stack:', this.drilldownStack); | ||||||
|  |      | ||||||
|  |     // Get the current drilldown configuration based on the current level
 | ||||||
|  |     let drilldownConfig; | ||||||
|  |     if (this.currentDrilldownLevel === 1) { | ||||||
|  |       // Base drilldown level
 | ||||||
|  |       drilldownConfig = { | ||||||
|  |         apiUrl: this.drilldownApiUrl, | ||||||
|  |         xAxis: this.drilldownXAxis, | ||||||
|  |         yAxis: this.drilldownYAxis, | ||||||
|  |         parameter: this.drilldownParameter | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       // Multi-layer drilldown level
 | ||||||
|  |       const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |       if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) { | ||||||
|  |         drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |       } else { | ||||||
|  |         console.warn('Invalid drilldown layer index:', layerIndex); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.radarChartLabels = []; | ||||||
|  |         this.radarChartData = []; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig); | ||||||
|  |      | ||||||
|  |     // Check if we have valid drilldown configuration
 | ||||||
|  |     if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) { | ||||||
|  |       console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel); | ||||||
|  |       this.noDataAvailable = true; | ||||||
|  |       this.radarChartLabels = []; | ||||||
|  |       this.radarChartData = []; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter value from the drilldown stack
 | ||||||
|  |     let parameterValue = ''; | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       const lastEntry = this.drilldownStack[this.drilldownStack.length - 1]; | ||||||
|  |       parameterValue = lastEntry.clickedValue || ''; | ||||||
|  |       console.log('Parameter value from last click:', parameterValue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Get the parameter field from drilldown config
 | ||||||
|  |     const parameterField = drilldownConfig.parameter || ''; | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, { | ||||||
|  |       apiUrl: drilldownConfig.apiUrl, | ||||||
|  |       xAxis: drilldownConfig.xAxis, | ||||||
|  |       yAxis: drilldownConfig.yAxis, | ||||||
|  |       parameterField: parameterField, | ||||||
|  |       parameterValue: parameterValue, | ||||||
|  |       connection: this.connection | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  |     // Build the actual API URL with parameter replacement
 | ||||||
|  |     let actualApiUrl = drilldownConfig.apiUrl; | ||||||
|  |     console.log('Original API URL:', actualApiUrl); | ||||||
|  |     console.log('Parameter value to use:', parameterValue); | ||||||
|  |     console.log('Parameter field:', parameterField); | ||||||
|  |      | ||||||
|  |     // Check if the URL contains angle brackets for parameter replacement
 | ||||||
|  |     const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl); | ||||||
|  |      | ||||||
|  |     if (hasAngleBrackets && parameterValue) { | ||||||
|  |       // Replace angle brackets placeholder with actual value
 | ||||||
|  |       console.log('Replacing angle brackets with parameter value'); | ||||||
|  |       const encodedValue = encodeURIComponent(parameterValue); | ||||||
|  |       actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue); | ||||||
|  |       console.log('URL after angle bracket replacement:', actualApiUrl); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Log the URL that will be called
 | ||||||
|  |     const url = `chart/getdashjson/radar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`; | ||||||
|  |     console.log('Drilldown data URL:', url); | ||||||
|  |      | ||||||
|  |     // Fetch data from the dashboard service with parameter field and value
 | ||||||
|  |     // Backend handles filtering, we just pass the parameter field and value
 | ||||||
|  |     this.dashboardService.getChartData(actualApiUrl, 'radar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe( | ||||||
|  |       (data: any) => { | ||||||
|  |         console.log('Received drilldown data:', data); | ||||||
|  |         if (data === null) { | ||||||
|  |           console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.'); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.radarChartLabels = []; | ||||||
|  |           this.radarChartData = []; | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Handle the actual data structure returned by the API
 | ||||||
|  |         if (data && data.chartLabels && data.chartData) { | ||||||
|  |           // Map the API response to the format expected by the chart
 | ||||||
|  |           this.noDataAvailable = data.chartLabels.length === 0; | ||||||
|  |           this.radarChartLabels = data.chartLabels; | ||||||
|  |           // For radar charts, we need to ensure the data is properly formatted
 | ||||||
|  |           // Each dataset should have a data array with numeric values
 | ||||||
|  |           this.radarChartData = data.chartData.map(dataset => ({ | ||||||
|  |             ...dataset, | ||||||
|  |             data: dataset.data ? dataset.data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |             }) : [] | ||||||
|  |           })); | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.radarChartData = [...this.radarChartData]; | ||||||
|  |           console.log('Updated radar chart with drilldown data:', { labels: this.radarChartLabels, data: this.radarChartData }); | ||||||
|  |         } else if (data && data.labels && data.datasets) { | ||||||
|  |           // Handle the original expected format as fallback
 | ||||||
|  |           this.noDataAvailable = data.labels.length === 0; | ||||||
|  |           this.radarChartLabels = data.labels; | ||||||
|  |           this.radarChartData = data.datasets.map(dataset => ({ | ||||||
|  |             ...dataset, | ||||||
|  |             data: dataset.data ? dataset.data.map(value => { | ||||||
|  |               // Convert to number if it's not already
 | ||||||
|  |               return isNaN(Number(value)) ? 0 : Number(value); | ||||||
|  |             }) : [] | ||||||
|  |           })); | ||||||
|  |           // Trigger change detection
 | ||||||
|  |           this.radarChartData = [...this.radarChartData]; | ||||||
|  |           console.log('Updated radar chart with drilldown legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData }); | ||||||
|  |         } else { | ||||||
|  |           console.warn('Drilldown received data does not have expected structure', data); | ||||||
|  |           this.noDataAvailable = true; | ||||||
|  |           this.radarChartLabels = []; | ||||||
|  |           this.radarChartData = []; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       (error) => { | ||||||
|  |         console.error('Error fetching drilldown data:', error); | ||||||
|  |         this.noDataAvailable = true; | ||||||
|  |         this.radarChartLabels = []; | ||||||
|  |         this.radarChartData = []; | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Reset to original data (go back to base level)
 | ||||||
|  |   resetToOriginalData(): void { | ||||||
|  |     console.log('Resetting to original data'); | ||||||
|  |     console.log('Current stack before reset:', this.drilldownStack); | ||||||
|  |     console.log('Current level before reset:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     this.currentDrilldownLevel = 0; | ||||||
|  |     this.drilldownStack = []; | ||||||
|  |      | ||||||
|  |     if (this.originalRadarChartLabels.length > 0) { | ||||||
|  |       this.radarChartLabels = [...this.originalRadarChartLabels]; | ||||||
|  |       console.log('Restored original labels'); | ||||||
|  |     } | ||||||
|  |     if (this.originalRadarChartData.length > 0) { | ||||||
|  |       this.radarChartData = [...this.originalRadarChartData]; | ||||||
|  |       console.log('Restored original data'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('After reset - labels:', this.radarChartLabels); | ||||||
|  |     console.log('After reset - data:', this.radarChartData); | ||||||
|  |      | ||||||
|  |     // Re-fetch original data
 | ||||||
|  |     this.fetchChartData(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Navigate back to previous drilldown level
 | ||||||
|  |   navigateBack(): void { | ||||||
|  |     console.log('Navigating back, current stack:', this.drilldownStack); | ||||||
|  |     console.log('Current level:', this.currentDrilldownLevel); | ||||||
|  |      | ||||||
|  |     if (this.drilldownStack.length > 0) { | ||||||
|  |       // Remove the last entry from the stack
 | ||||||
|  |       const removedEntry = this.drilldownStack.pop(); | ||||||
|  |       console.log('Removed entry from stack:', removedEntry); | ||||||
|  |        | ||||||
|  |       // Update the current drilldown level
 | ||||||
|  |       this.currentDrilldownLevel = this.drilldownStack.length; | ||||||
|  |       console.log('New level after pop:', this.currentDrilldownLevel); | ||||||
|  |       console.log('Stack after pop:', this.drilldownStack); | ||||||
|  |        | ||||||
|  |       if (this.drilldownStack.length > 0) { | ||||||
|  |         // Fetch data for the previous level
 | ||||||
|  |         console.log('Fetching data for previous level'); | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         // Back to base level
 | ||||||
|  |         console.log('Back to base level, resetting to original data'); | ||||||
|  |         this.resetToOriginalData(); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // Already at base level, reset to original data
 | ||||||
|  |       console.log('Already at base level, resetting to original data'); | ||||||
|  |       this.resetToOriginalData(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // events
 | ||||||
|  |   public chartClicked(e: any): void { | ||||||
|  |     console.log('Radar chart clicked:', e); | ||||||
|  |      | ||||||
|  |     // If drilldown is enabled and we have a valid click event
 | ||||||
|  |     if (this.drilldownEnabled && e.active && e.active.length > 0) { | ||||||
|  |       // Get the index of the clicked element
 | ||||||
|  |       const clickedIndex = e.active[0].index; | ||||||
|  |        | ||||||
|  |       // Get the label of the clicked element
 | ||||||
|  |       const clickedLabel = this.radarChartLabels[clickedIndex]; | ||||||
|  |        | ||||||
|  |       console.log('Clicked on radar point:', { index: clickedIndex, label: clickedLabel }); | ||||||
|  |        | ||||||
|  |       // If we're not at the base level, store original data
 | ||||||
|  |       if (this.currentDrilldownLevel === 0) { | ||||||
|  |         // Store original data before entering drilldown mode
 | ||||||
|  |         this.originalRadarChartLabels = [...this.radarChartLabels]; | ||||||
|  |         this.originalRadarChartData = [...this.radarChartData]; | ||||||
|  |         console.log('Stored original data for drilldown'); | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       // Determine the next drilldown level
 | ||||||
|  |       const nextDrilldownLevel = this.currentDrilldownLevel + 1; | ||||||
|  |        | ||||||
|  |       console.log('Next drilldown level will be:', nextDrilldownLevel); | ||||||
|  |        | ||||||
|  |       // Check if there's a drilldown configuration for this level
 | ||||||
|  |       let hasDrilldownConfig = false; | ||||||
|  |       let drilldownConfig; | ||||||
|  |        | ||||||
|  |       if (nextDrilldownLevel === 1) { | ||||||
|  |         // Base drilldown level
 | ||||||
|  |         drilldownConfig = { | ||||||
|  |           apiUrl: this.drilldownApiUrl, | ||||||
|  |           xAxis: this.drilldownXAxis, | ||||||
|  |           yAxis: this.drilldownYAxis, | ||||||
|  |           parameter: this.drilldownParameter | ||||||
|  |         }; | ||||||
|  |         hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis; | ||||||
|  |       } else { | ||||||
|  |         // Multi-layer drilldown level
 | ||||||
|  |         const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
 | ||||||
|  |         if (layerIndex < this.drilldownLayers.length) { | ||||||
|  |           drilldownConfig = this.drilldownLayers[layerIndex]; | ||||||
|  |           hasDrilldownConfig = drilldownConfig.enabled && | ||||||
|  |                               !!drilldownConfig.apiUrl &&  | ||||||
|  |                               !!drilldownConfig.xAxis &&  | ||||||
|  |                               !!drilldownConfig.yAxis; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       console.log('Drilldown config for next level:', drilldownConfig); | ||||||
|  |       console.log('Has drilldown config:', hasDrilldownConfig); | ||||||
|  |        | ||||||
|  |       // If there's a drilldown configuration for the next level, proceed
 | ||||||
|  |       if (hasDrilldownConfig) { | ||||||
|  |         // Add this click to the drilldown stack
 | ||||||
|  |         const stackEntry = { | ||||||
|  |           level: nextDrilldownLevel, | ||||||
|  |           clickedIndex: clickedIndex, | ||||||
|  |           clickedLabel: clickedLabel, | ||||||
|  |           clickedValue: clickedLabel // Using label as value for now
 | ||||||
|  |         }; | ||||||
|  |          | ||||||
|  |         this.drilldownStack.push(stackEntry); | ||||||
|  |          | ||||||
|  |         console.log('Added to drilldown stack:', stackEntry); | ||||||
|  |         console.log('Current drilldown stack:', this.drilldownStack); | ||||||
|  |          | ||||||
|  |         // Update the current drilldown level
 | ||||||
|  |         this.currentDrilldownLevel = nextDrilldownLevel; | ||||||
|  |          | ||||||
|  |         console.log('Entering drilldown level:', this.currentDrilldownLevel); | ||||||
|  |          | ||||||
|  |         // Fetch drilldown data for the new level
 | ||||||
|  |         this.fetchDrilldownData(); | ||||||
|  |       } else { | ||||||
|  |         console.log('No drilldown configuration for level:', nextDrilldownLevel); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       console.log('Drilldown not enabled or invalid click event'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public chartHovered(e: any): void { | ||||||
|  |     console.log(e); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,8 +1,27 @@ | |||||||
| <div style="display: block"> | <div style="display: block"> | ||||||
|   <canvas baseChart |   <!-- Drilldown mode indicator --> | ||||||
|     [datasets]="scatterChartData" |   <div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;"> | ||||||
|     [type]="scatterChartType" |     <span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span> | ||||||
|     (chartHover)="chartHovered($event)" |     <button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;"> | ||||||
|   (chartClick)="chartClicked($event)"> |       Back to Level {{currentDrilldownLevel - 1}} | ||||||
|   </canvas> |     </button> | ||||||
| </div> |     <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" | ||||||
|  |       (chartHover)="chartHovered($event)" | ||||||
|  |       (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 { ChartData,ChartDataset } from 'chart.js'; | ||||||
|  | import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-scatter-chart', |   selector: 'app-scatter-chart', | ||||||
|   templateUrl: './scatter-chart.component.html', |   templateUrl: './scatter-chart.component.html', | ||||||
|   styleUrls: ['./scatter-chart.component.scss'] |   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 { |   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 scatterChartLabels: string[] = [ 'Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running' ]; | ||||||
| 
 | 
 | ||||||
|   public scatterChartData: ChartDataset[] = [ |   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: [ |       data: [ | ||||||
|         { x: 1, y: 1 }, |         { x: 1, y: 1 }, | ||||||
| @ -64,14 +92,378 @@ export class ScatterChartComponent implements OnInit { | |||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
|   public scatterChartType: string = 'scatter'; |   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
 |    // events
 | ||||||
| 	public chartClicked(e: any): void { | 	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 { | 	public chartHovered(e: any): void { | ||||||
| 		console.log(e); | 		console.log(e); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -1,27 +1,76 @@ | |||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-to-do-chart', |   selector: 'app-to-do-chart', | ||||||
|   templateUrl: './to-do-chart.component.html', |   templateUrl: './to-do-chart.component.html', | ||||||
|   styleUrls: ['./to-do-chart.component.scss'] |   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() { } |   constructor() { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   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; |   data: any; | ||||||
|   todo: string; |   todo: string; | ||||||
|   todoList = ['todo 1']; |   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) { |   public addTodo(todo: string) { | ||||||
|     this.todoList.push(todo); |     this.todoList.push(todo); | ||||||
| } |   } | ||||||
| 
 | 
 | ||||||
| public removeTodo(todoIx: number) { |   public removeTodo(todoIx: number) { | ||||||
|     if (this.todoList.length) { |     if (this.todoList.length) { | ||||||
|         this.todoList.splice(todoIx, 1); |         this.todoList.splice(todoIx, 1); | ||||||
|     } |     } | ||||||
| } |   } | ||||||
| } | } | ||||||
| @ -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