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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,9 +16,9 @@
|
|||||||
<button class="btn btn-primary" (click)="onSchedule()">Schedule</button> -->
|
<button class="btn btn-primary" (click)="onSchedule()">Schedule</button> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-container">
|
<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">
|
||||||
@ -47,7 +47,8 @@
|
|||||||
</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"
|
||||||
|
(change)="toggleAddToDashboard(item)" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- <label for="workflow_name">Add to Dasboard</label>
|
<!-- <label for="workflow_name">Add to Dasboard</label>
|
||||||
@ -58,37 +59,50 @@
|
|||||||
</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)"
|
||||||
|
(moduleInfo)="display($event)"></ndc-dynamic>
|
||||||
<!-- </ng-container> -->
|
<!-- </ng-container> -->
|
||||||
|
|
||||||
</gridster-item>
|
</gridster-item>
|
||||||
</gridster>
|
</gridster>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<button class="btn btn-outline" (click)="goBack()">Back</button>
|
<button class="btn btn-outline" (click)="goBack()">Back</button>
|
||||||
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()" >
|
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()">
|
||||||
<b>Update</b>
|
<b>Update</b>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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,11 +119,13 @@
|
|||||||
<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>
|
||||||
@ -126,10 +142,12 @@
|
|||||||
</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"
|
||||||
|
[(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>
|
<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>
|
||||||
@ -137,9 +155,11 @@
|
|||||||
</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>
|
||||||
@ -147,7 +167,8 @@
|
|||||||
</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>
|
||||||
@ -170,7 +191,8 @@
|
|||||||
</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">
|
||||||
@ -180,6 +202,165 @@
|
|||||||
</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>
|
||||||
@ -188,11 +369,10 @@
|
|||||||
</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>
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +20,8 @@ import { GridViewComponent } from '../gadgets/grid-view/grid-view.component';
|
|||||||
import { DatastoreService } from 'src/app/services/fnd/datastore.service';
|
import { DatastoreService } from 'src/app/services/fnd/datastore.service';
|
||||||
import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
||||||
import { isArray } from 'highcharts';
|
import { isArray } from 'highcharts';
|
||||||
// import { ChartItem } from '../chartitem';
|
// Add the SureconnectService import
|
||||||
|
import { SureconnectService } from '../sureconnect/sureconnect.service';
|
||||||
|
|
||||||
function isNullArray(arr) {
|
function isNullArray(arr) {
|
||||||
return !Array.isArray(arr) || arr.length === 0;
|
return !Array.isArray(arr) || arr.length === 0;
|
||||||
@ -36,9 +37,9 @@ function isNullArray(arr) {
|
|||||||
|
|
||||||
export class EditnewdashComponent implements OnInit {
|
export class EditnewdashComponent implements OnInit {
|
||||||
|
|
||||||
editId:number;
|
editId: number;
|
||||||
toggle:boolean;
|
toggle: boolean;
|
||||||
modeledit:boolean = false;
|
modeledit: boolean = false;
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
|
|
||||||
WidgetsMock: WidgetModel[] = [
|
WidgetsMock: WidgetModel[] = [
|
||||||
@ -90,7 +91,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
name: 'Grid View',
|
name: 'Grid View',
|
||||||
identifier: 'grid_view'
|
identifier: 'grid_view'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
public options: GridsterConfig;
|
public options: GridsterConfig;
|
||||||
protected dashboardId: number;
|
protected dashboardId: number;
|
||||||
@ -98,7 +99,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
//dashboardCollection:any;
|
//dashboardCollection:any;
|
||||||
protected dashboardCollection1: DashboardModel[];
|
protected dashboardCollection1: DashboardModel[];
|
||||||
public dashboardArray: DashboardContentModel[];
|
public dashboardArray: DashboardContentModel[];
|
||||||
public dashArr:[];
|
public dashArr: [];
|
||||||
|
|
||||||
protected componentCollection = [
|
protected componentCollection = [
|
||||||
{ name: "Line Chart", componentInstance: LineChartComponent },
|
{ name: "Line Chart", componentInstance: LineChartComponent },
|
||||||
@ -114,34 +115,49 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
|
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
|
||||||
{ name: "Grid View", componentInstance: GridViewComponent },
|
{ name: "Grid View", componentInstance: GridViewComponent },
|
||||||
];
|
];
|
||||||
model:any;
|
model: any;
|
||||||
linesdata:any;
|
linesdata: any;
|
||||||
id:any;
|
id: any;
|
||||||
gadgetsEditdata = {
|
gadgetsEditdata = {
|
||||||
donut : '',
|
donut: '',
|
||||||
chartlegend: '',
|
chartlegend: '',
|
||||||
showlabel : '',
|
showlabel: '',
|
||||||
charturl: '',
|
charturl: '',
|
||||||
chartparameter : '',
|
chartparameter: '',
|
||||||
datastore : '',
|
datastore: '',
|
||||||
table:'',
|
table: '',
|
||||||
datasource : '',
|
datasource: '',
|
||||||
charttitle:'',
|
charttitle: '',
|
||||||
id:'',
|
id: '',
|
||||||
fieldName:'',
|
fieldName: '',
|
||||||
chartcolor:'',
|
chartcolor: '',
|
||||||
slices:'',
|
slices: '',
|
||||||
yAxis:'',
|
yAxis: '',
|
||||||
xAxis:''
|
xAxis: '',
|
||||||
|
connection: '', // Add connection field
|
||||||
|
// Drilldown configuration properties (base level)
|
||||||
|
drilldownEnabled: false,
|
||||||
|
drilldownApiUrl: '',
|
||||||
|
// Removed drilldownParameterKey since we're using URL templates
|
||||||
|
drilldownXAxis: '',
|
||||||
|
drilldownYAxis: '',
|
||||||
|
drilldownParameter: '', // Add drilldown parameter property
|
||||||
|
// Multi-layer drilldown configurations
|
||||||
|
drilldownLayers: [] as any[]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add sureconnect data property
|
||||||
|
sureconnectData: any[] = [];
|
||||||
|
layerColumnData: { [key: number]: any[] } = {}; // Add layer column data property
|
||||||
|
|
||||||
};
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private router : Router,
|
private router: Router,
|
||||||
private dashboardService: Dashboard3Service,
|
private dashboardService: Dashboard3Service,
|
||||||
private toastr:ToastrService,
|
private toastr: ToastrService,
|
||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
private datastoreService: DatastoreService,
|
private datastoreService: DatastoreService,
|
||||||
private alertService:AlertsService,) { }
|
private alertService: AlertsService,
|
||||||
|
private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
@ -166,37 +182,61 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
minCols: 10,
|
minCols: 10,
|
||||||
minRows: 10
|
minRows: 10
|
||||||
};
|
};
|
||||||
this.getData();
|
|
||||||
|
|
||||||
this.editId = this.route.snapshot.params.id;
|
this.editId = this.route.snapshot.params.id;
|
||||||
console.log(this.editId);
|
console.log(this.editId);
|
||||||
this.dashboardService.getById(this.editId).subscribe((data)=>{
|
this.dashboardService.getById(this.editId).subscribe((data) => {
|
||||||
console.log("ngOnInit",data);
|
console.log("ngOnInit", data);
|
||||||
this.linesdata = data;
|
this.linesdata = data;
|
||||||
this.id = data.dashbord1_Line[0].id;
|
this.id = data.dashbord1_Line[0].id;
|
||||||
console.log("this.id ",this.id);
|
console.log("this.id ", this.id);
|
||||||
},
|
},
|
||||||
(error: any)=>{
|
(error: any) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.entryForm = this._fb.group({
|
this.entryForm = this._fb.group({
|
||||||
donut : [null],
|
donut: [null],
|
||||||
chartlegend: [null],
|
chartlegend: [null],
|
||||||
showlabel : [null],
|
showlabel: [null],
|
||||||
charturl: [null],
|
charturl: [null],
|
||||||
chartparameter : [null],
|
chartparameter: [null],
|
||||||
datastore:[null],
|
datastore: [null],
|
||||||
table:[null],
|
table: [null],
|
||||||
fieldName: [null],
|
fieldName: [null],
|
||||||
datasource : [null],
|
datasource: [null],
|
||||||
charttitle:[null],
|
charttitle: [null],
|
||||||
id:[null],
|
id: [null],
|
||||||
chartcolor:[null],
|
chartcolor: [null],
|
||||||
slices:[null],
|
slices: [null],
|
||||||
yAxis:[null],
|
yAxis: [null],
|
||||||
xAxis: [null],
|
xAxis: [null],
|
||||||
|
connection: [null], // Add connection to form group
|
||||||
|
// Base drilldown configuration form controls
|
||||||
|
drilldownEnabled: [null],
|
||||||
|
drilldownApiUrl: [null],
|
||||||
|
drilldownXAxis: [null],
|
||||||
|
drilldownYAxis: [null],
|
||||||
|
drilldownParameter: [null] // Add drilldown parameter to form group
|
||||||
|
// Note: Dynamic drilldown layers will be handled separately since they're complex objects
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load sureconnect data first, then load dashboard data
|
||||||
|
this.loadSureconnectData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to load sureconnect data
|
||||||
|
loadSureconnectData() {
|
||||||
|
this.sureconnectService.getAll().subscribe((data: any[]) => {
|
||||||
|
this.sureconnectData = data;
|
||||||
|
console.log('Sureconnect data loaded:', this.sureconnectData);
|
||||||
|
// Now that sureconnect data is loaded, we can safely load dashboard data
|
||||||
|
this.getData();
|
||||||
|
}, (error) => {
|
||||||
|
console.log('Error loading sureconnect data:', error);
|
||||||
|
// Even if there's an error loading sureconnect data, we still need to load dashboard data
|
||||||
|
this.getData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,13 +245,13 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDrag(event, identifier) {
|
onDrag(event, identifier) {
|
||||||
console.log("on drag",identifier);
|
console.log("on drag", identifier);
|
||||||
console.log("on drag ",event);
|
console.log("on drag ", event);
|
||||||
event.dataTransfer.setData('widgetIdentifier', identifier);
|
event.dataTransfer.setData('widgetIdentifier', identifier);
|
||||||
}
|
}
|
||||||
datagadgets:any;
|
datagadgets: any;
|
||||||
dashboardLine:any;
|
dashboardLine: any;
|
||||||
dashboardName:any;
|
dashboardName: any;
|
||||||
getData() {
|
getData() {
|
||||||
// We get the id in get current router dashboard/:id
|
// We get the id in get current router dashboard/:id
|
||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
@ -224,21 +264,29 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
this.datagadgets = dashboard;
|
this.datagadgets = dashboard;
|
||||||
this.dashboardLine = dashboard.dashbord1_Line;
|
this.dashboardLine = dashboard.dashbord1_Line;
|
||||||
//this.dashboardCollection = dashboard.dashbord1_Line.model;
|
//this.dashboardCollection = dashboard.dashbord1_Line.model;
|
||||||
console.log("this.datagadgets",this.datagadgets);
|
console.log("this.datagadgets", this.datagadgets);
|
||||||
console.log("this.dashboardLine",this.dashboardLine);
|
console.log("this.dashboardLine", this.dashboardLine);
|
||||||
this.dashboardCollection =JSON.parse(this.dashboardLine[0].model) ;
|
this.dashboardCollection = JSON.parse(this.dashboardLine[0].model);
|
||||||
//this.dashboardCollection =this.dashboardLine[0].model ;
|
//this.dashboardCollection =this.dashboardLine[0].model ;
|
||||||
console.log("this.dasboard ",this.dashboardCollection );
|
console.log("this.dasboard ", this.dashboardCollection);
|
||||||
console.log(this.dashboardCollection);
|
console.log(this.dashboardCollection);
|
||||||
// We parse serialized Json to generate components on the fly
|
// We parse serialized Json to generate components on the fly
|
||||||
this.parseJson(this.dashboardCollection);
|
this.parseJson(this.dashboardCollection);
|
||||||
|
|
||||||
|
// Set default connections for all gadgets if sureconnect data is available
|
||||||
|
if (this.sureconnectData && this.sureconnectData.length > 0) {
|
||||||
|
this.dashboardCollection.dashboard.forEach(item => {
|
||||||
|
if (!item['connection'] || item['connection'] === '') {
|
||||||
|
item['connection'] = this.sureconnectData[0].id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// We copy array without reference
|
// We copy array without reference
|
||||||
this.dashboardArray = this.dashboardCollection.dashboard.slice();
|
this.dashboardArray = this.dashboardCollection.dashboard.slice();
|
||||||
console.log("this.dashboardArray",this.dashboardArray);
|
console.log("this.dashboardArray", this.dashboardArray);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super TOKENIZER 2.0 POWERED BY NATCHOIN
|
// Super TOKENIZER 2.0 POWERED BY NATCHOIN
|
||||||
@ -273,15 +321,15 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
|
|
||||||
itemChange() {
|
itemChange() {
|
||||||
this.dashboardCollection.dashboard = this.dashboardArray;
|
this.dashboardCollection.dashboard = this.dashboardArray;
|
||||||
console.log("itemChange this.dashboardCollection.dashboard ",this.dashboardCollection.dashboard);
|
console.log("itemChange this.dashboardCollection.dashboard ", this.dashboardCollection.dashboard);
|
||||||
console.log("itemChange this.dashboardCollection ",this.dashboardCollection);
|
console.log("itemChange this.dashboardCollection ", this.dashboardCollection);
|
||||||
console.log("itemChange this.dashboardCollection type",typeof this.dashboardCollection);
|
console.log("itemChange this.dashboardCollection type", typeof this.dashboardCollection);
|
||||||
console.log("itemChange this.dashboardArray ",this.dashboardArray);
|
console.log("itemChange this.dashboardArray ", this.dashboardArray);
|
||||||
let tmp = JSON.stringify(this.dashboardCollection);
|
let tmp = JSON.stringify(this.dashboardCollection);
|
||||||
console.log("temp data",tmp);
|
console.log("temp data", tmp);
|
||||||
let parsed: DashboardModel = JSON.parse(tmp);
|
let parsed: DashboardModel = JSON.parse(tmp);
|
||||||
console.log("parsed data",parsed);
|
console.log("parsed data", parsed);
|
||||||
console.log("let parsed ",typeof parsed);
|
console.log("let parsed ", typeof parsed);
|
||||||
this.serialize(parsed.dashboard);
|
this.serialize(parsed.dashboard);
|
||||||
console.log("item chnage function ", typeof this.dashboardArray);
|
console.log("item chnage function ", typeof this.dashboardArray);
|
||||||
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
|
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
|
||||||
@ -297,7 +345,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: RadarChartComponent,
|
component: RadarChartComponent,
|
||||||
name: "Radar Chart"
|
name: "Radar Chart"
|
||||||
});
|
});
|
||||||
@ -307,7 +355,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 7,
|
rows: 7,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: LineChartComponent,
|
component: LineChartComponent,
|
||||||
name: "Line Chart"
|
name: "Line Chart"
|
||||||
});
|
});
|
||||||
@ -317,7 +365,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: DoughnutChartComponent,
|
component: DoughnutChartComponent,
|
||||||
name: "Doughnut Chart"
|
name: "Doughnut Chart"
|
||||||
});
|
});
|
||||||
@ -327,7 +375,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: BarChartComponent,
|
component: BarChartComponent,
|
||||||
name: "Bar Chart"
|
name: "Bar Chart"
|
||||||
});
|
});
|
||||||
@ -337,7 +385,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: PieChartComponent,
|
component: PieChartComponent,
|
||||||
name: "Pie Chart"
|
name: "Pie Chart"
|
||||||
});
|
});
|
||||||
@ -347,7 +395,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: PolarChartComponent,
|
component: PolarChartComponent,
|
||||||
name: "Polar Area Chart"
|
name: "Polar Area Chart"
|
||||||
});
|
});
|
||||||
@ -357,7 +405,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: BubbleChartComponent,
|
component: BubbleChartComponent,
|
||||||
name: "Bubble Chart"
|
name: "Bubble Chart"
|
||||||
});
|
});
|
||||||
@ -367,7 +415,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: ScatterChartComponent,
|
component: ScatterChartComponent,
|
||||||
name: "Scatter Chart"
|
name: "Scatter Chart"
|
||||||
});
|
});
|
||||||
@ -377,7 +425,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: DynamicChartComponent,
|
component: DynamicChartComponent,
|
||||||
name: "Dynamic Chart"
|
name: "Dynamic Chart"
|
||||||
});
|
});
|
||||||
@ -387,7 +435,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 6,
|
rows: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: FinancialChartComponent,
|
component: FinancialChartComponent,
|
||||||
name: "Financial Chart"
|
name: "Financial Chart"
|
||||||
});
|
});
|
||||||
@ -397,7 +445,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 5,
|
rows: 5,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: ToDoChartComponent,
|
component: ToDoChartComponent,
|
||||||
name: "To Do Chart"
|
name: "To Do Chart"
|
||||||
});
|
});
|
||||||
@ -407,7 +455,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
rows: 5,
|
rows: 5,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
chartid:maxChartId + 1,
|
chartid: maxChartId + 1,
|
||||||
component: GridViewComponent,
|
component: GridViewComponent,
|
||||||
name: "Grid View"
|
name: "Grid View"
|
||||||
});
|
});
|
||||||
@ -425,29 +473,70 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
this.options.api.optionsChanged();
|
this.options.api.optionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
modelid:number ;
|
modelid: number;
|
||||||
editGadget(item)
|
editGadget(item) {
|
||||||
{
|
|
||||||
this.modeledit = true;
|
this.modeledit = true;
|
||||||
this.modelid = item.chartid;
|
this.modelid = item.chartid;
|
||||||
console.log(this.modelid);
|
console.log(this.modelid);
|
||||||
this.gadgetsEditdata = item;
|
this.gadgetsEditdata = item;
|
||||||
this.gadgetsEditdata.fieldName = item.name;
|
this.gadgetsEditdata.fieldName = item.name;
|
||||||
if(item.showlabel === undefined){ item.showlabel = true; }
|
if (item.showlabel === undefined) { item.showlabel = true; }
|
||||||
if(item.chartcolor === undefined ){ item.chartcolor = true;}
|
if (item.chartcolor === undefined) { item.chartcolor = true; }
|
||||||
if(item.chartlegend === undefined){ item.chartlegend = true; }
|
if (item.chartlegend === undefined) { item.chartlegend = true; }
|
||||||
this.getStores();
|
this.getStores();
|
||||||
if(item.datastore !== undefined || '' || null){
|
|
||||||
|
// Set default connection if none is set and we have connections
|
||||||
|
if ((!item['connection'] || item['connection'] === '') && this.sureconnectData && this.sureconnectData.length > 0) {
|
||||||
|
this.gadgetsEditdata['connection'] = this.sureconnectData[0].id;
|
||||||
|
// Also update the form control
|
||||||
|
this.entryForm.patchValue({ connection: this.sureconnectData[0].id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize base drilldown properties if not present
|
||||||
|
if (item['drilldownEnabled'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownEnabled'] = false;
|
||||||
|
}
|
||||||
|
if (item['drilldownApiUrl'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownApiUrl'] = '';
|
||||||
|
}
|
||||||
|
// Removed drilldownParameterKey initialization
|
||||||
|
if (item['drilldownXAxis'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownXAxis'] = '';
|
||||||
|
}
|
||||||
|
if (item['drilldownYAxis'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownYAxis'] = '';
|
||||||
|
}
|
||||||
|
if (item['drilldownParameter'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownParameter'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize drilldown layers if not present
|
||||||
|
if (item['drilldownLayers'] === undefined) {
|
||||||
|
this.gadgetsEditdata['drilldownLayers'] = [];
|
||||||
|
} else {
|
||||||
|
// Ensure each layer has proper structure (removed parameterKey, added parameter)
|
||||||
|
this.gadgetsEditdata['drilldownLayers'].forEach(layer => {
|
||||||
|
// Initialize parameter if not present
|
||||||
|
if (layer['parameter'] === undefined) {
|
||||||
|
layer['parameter'] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset drilldown column data
|
||||||
|
this.drilldownColumnData = [];
|
||||||
|
|
||||||
|
if (item.datastore !== undefined || '' || null) {
|
||||||
const datastore = item.datastore;
|
const datastore = item.datastore;
|
||||||
this.getTables(datastore);
|
this.getTables(datastore);
|
||||||
const table = item.table;
|
const table = item.table;
|
||||||
this.getColumns(datastore,table);
|
this.getColumns(datastore, table);
|
||||||
console.log(item.yAxis);
|
console.log(item.yAxis);
|
||||||
if(isArray(item.yAxis)){
|
if (isArray(item.yAxis)) {
|
||||||
this.selectedyAxis = item.yAxis;
|
this.selectedyAxis = item.yAxis;
|
||||||
console.log( this.selectedyAxis);
|
console.log(this.selectedyAxis);
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
this.selectedyAxis = [];
|
this.selectedyAxis = [];
|
||||||
}
|
}
|
||||||
console.log(item);
|
console.log(item);
|
||||||
@ -455,12 +544,11 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
|
|
||||||
dashbord1_Line = {
|
dashbord1_Line = {
|
||||||
//model:JSON.stringify(this.da),
|
//model:JSON.stringify(this.da),
|
||||||
model:''
|
model: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UpdateLine()
|
UpdateLine() {
|
||||||
{
|
|
||||||
console.log('Add button clicked.......');
|
console.log('Add button clicked.......');
|
||||||
console.log(this.dashboardArray);
|
console.log(this.dashboardArray);
|
||||||
console.log(this.dashboardCollection);
|
console.log(this.dashboardCollection);
|
||||||
@ -470,13 +558,14 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
|
|
||||||
//https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring
|
//https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring
|
||||||
|
|
||||||
let cmp=this.dashboardCollection.dashboard.forEach(dashboard=>{
|
let cmp = this.dashboardCollection.dashboard.forEach(dashboard => {
|
||||||
this.componentCollection.forEach(component=>{
|
this.componentCollection.forEach(component => {
|
||||||
if (dashboard.name === component.name) {
|
if (dashboard.name === component.name) {
|
||||||
dashboard.component = component.name;
|
dashboard.component = component.name;
|
||||||
} })
|
}
|
||||||
})
|
})
|
||||||
console.log(cmp);
|
})
|
||||||
|
console.log(cmp);
|
||||||
|
|
||||||
let tmp = JSON.stringify(this.dashboardCollection);
|
let tmp = JSON.stringify(this.dashboardCollection);
|
||||||
// var merged = this.dashboardArray.reduce((current, value, index) => {
|
// var merged = this.dashboardArray.reduce((current, value, index) => {
|
||||||
@ -487,9 +576,9 @@ console.log(cmp);
|
|||||||
// }, '');
|
// }, '');
|
||||||
|
|
||||||
//console.log(merged);
|
//console.log(merged);
|
||||||
console.log("temp data",typeof tmp);
|
console.log("temp data", typeof tmp);
|
||||||
console.log(tmp);
|
console.log(tmp);
|
||||||
let parsed= JSON.parse(tmp);
|
let parsed = JSON.parse(tmp);
|
||||||
this.serialize(parsed.dashboard);
|
this.serialize(parsed.dashboard);
|
||||||
this.dashbord1_Line.model = tmp;
|
this.dashbord1_Line.model = tmp;
|
||||||
|
|
||||||
@ -498,12 +587,12 @@ console.log(cmp);
|
|||||||
// let myJSON = JSON.stringify(obj);
|
// let myJSON = JSON.stringify(obj);
|
||||||
// this.dashbord1_Line.model = myJSON;
|
// this.dashbord1_Line.model = myJSON;
|
||||||
|
|
||||||
console.log("line data in addgadget ",this.dashbord1_Line);
|
console.log("line data in addgadget ", this.dashbord1_Line);
|
||||||
console.log("line data in addgadget type ",typeof this.dashbord1_Line);
|
console.log("line data in addgadget type ", typeof this.dashbord1_Line);
|
||||||
console.log("line model data ",this.dashbord1_Line.model);
|
console.log("line model data ", this.dashbord1_Line.model);
|
||||||
console.log("line model data type",typeof this.dashbord1_Line.model);
|
console.log("line model data type", typeof this.dashbord1_Line.model);
|
||||||
this.dashboardService.UpdateLineData(this.id,this.dashbord1_Line).subscribe(
|
this.dashboardService.UpdateLineData(this.id, this.dashbord1_Line).subscribe(
|
||||||
(data: any)=>{
|
(data: any) => {
|
||||||
console.log('Updation Successful...');
|
console.log('Updation Successful...');
|
||||||
this.ngOnInit();
|
this.ngOnInit();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
@ -515,8 +604,7 @@ console.log(cmp);
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(id)
|
onSubmit(id) {
|
||||||
{
|
|
||||||
console.log(id);
|
console.log(id);
|
||||||
if (!isNullArray(this.selectedyAxis)) {
|
if (!isNullArray(this.selectedyAxis)) {
|
||||||
console.log("get y-axis array", this.selectedyAxis);
|
console.log("get y-axis array", this.selectedyAxis);
|
||||||
@ -526,92 +614,309 @@ console.log(cmp);
|
|||||||
let num = id;
|
let num = id;
|
||||||
console.log(this.entryForm.value);
|
console.log(this.entryForm.value);
|
||||||
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
||||||
if(item.chartid == num)
|
if (item.chartid == num) {
|
||||||
{
|
|
||||||
//item["product_id"] = "thisistest";
|
//item["product_id"] = "thisistest";
|
||||||
const xyz = {...item,...formdata}
|
const xyz = { ...item, ...formdata }
|
||||||
|
|
||||||
|
// Explicitly ensure drilldown properties are preserved
|
||||||
|
xyz.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled;
|
||||||
|
xyz.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl;
|
||||||
|
xyz.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
|
||||||
|
xyz.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
|
||||||
|
xyz.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
|
||||||
|
xyz.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
|
||||||
|
|
||||||
console.log(xyz);
|
console.log(xyz);
|
||||||
return xyz;
|
return xyz;
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
});
|
});
|
||||||
console.log(this.dashboardCollection.dashboard);
|
console.log('dashboard collection ', this.dashboardCollection.dashboard);
|
||||||
this.modeledit = false;
|
this.modeledit = false;
|
||||||
|
|
||||||
// this.entryForm.reset();
|
// this.entryForm.reset();
|
||||||
|
|
||||||
}
|
}
|
||||||
goBack(){
|
|
||||||
|
/**
|
||||||
|
* Extract only the relevant chart configuration properties to pass to chart components
|
||||||
|
* This prevents errors when trying to set properties that don't exist on the components
|
||||||
|
*/
|
||||||
|
getChartInputs(item: any): any {
|
||||||
|
// Only pass properties that are relevant to chart components
|
||||||
|
const chartInputs = {
|
||||||
|
xAxis: item.xAxis,
|
||||||
|
yAxis: item.yAxis,
|
||||||
|
table: item.table,
|
||||||
|
datastore: item.datastore,
|
||||||
|
charttitle: item.charttitle,
|
||||||
|
chartlegend: item.chartlegend,
|
||||||
|
showlabel: item.showlabel,
|
||||||
|
chartcolor: item.chartcolor,
|
||||||
|
slices: item.slices,
|
||||||
|
donut: item.donut,
|
||||||
|
charturl: item.charturl,
|
||||||
|
chartparameter: item.chartparameter,
|
||||||
|
datasource: item.datasource,
|
||||||
|
fieldName: item.name, // Using item.name as fieldName
|
||||||
|
connection: item['connection'], // Add connection field using bracket notation
|
||||||
|
// Base drilldown configuration properties
|
||||||
|
drilldownEnabled: item['drilldownEnabled'],
|
||||||
|
drilldownApiUrl: item['drilldownApiUrl'],
|
||||||
|
// Removed drilldownParameterKey since we're using URL templates
|
||||||
|
drilldownXAxis: item['drilldownXAxis'],
|
||||||
|
drilldownYAxis: item['drilldownYAxis'],
|
||||||
|
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
|
||||||
|
// Multi-layer drilldown configurations
|
||||||
|
drilldownLayers: item['drilldownLayers'] || []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove undefined properties to avoid passing unnecessary data
|
||||||
|
Object.keys(chartInputs).forEach(key => {
|
||||||
|
if (chartInputs[key] === undefined) {
|
||||||
|
delete chartInputs[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return chartInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyChanges(id) {
|
||||||
|
console.log('Apply changes for chart ID:', id);
|
||||||
|
|
||||||
|
// Check if ID is valid
|
||||||
|
if (id === null || id === undefined) {
|
||||||
|
console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid);
|
||||||
|
id = this.modelid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the form with selected Y-axis values if it's an array
|
||||||
|
if (!isNullArray(this.selectedyAxis)) {
|
||||||
|
console.log("get y-axis array", this.selectedyAxis);
|
||||||
|
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get form data
|
||||||
|
let formdata = this.entryForm.value;
|
||||||
|
let num = id;
|
||||||
|
console.log('Form data:', this.entryForm.value);
|
||||||
|
|
||||||
|
// Update the dashboard collection with the new configuration
|
||||||
|
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
||||||
|
if (item.chartid == num) {
|
||||||
|
// Merge the existing item with the new form data
|
||||||
|
const updatedItem = { ...item, ...formdata }
|
||||||
|
|
||||||
|
// Explicitly ensure drilldown properties are preserved
|
||||||
|
updatedItem.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled;
|
||||||
|
updatedItem.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl;
|
||||||
|
updatedItem.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
|
||||||
|
updatedItem.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
|
||||||
|
updatedItem.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
|
||||||
|
updatedItem.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
|
||||||
|
|
||||||
|
console.log('Updated item:', updatedItem);
|
||||||
|
return updatedItem;
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Updated dashboard collection:', this.dashboardCollection.dashboard);
|
||||||
|
|
||||||
|
// Update the dashboardArray to reflect changes immediately
|
||||||
|
// Create a new array with new object references to ensure change detection
|
||||||
|
this.dashboardArray = this.dashboardCollection.dashboard.map(item => ({ ...item }));
|
||||||
|
|
||||||
|
// Force gridster to refresh
|
||||||
|
if (this.options && this.options.api) {
|
||||||
|
this.options.api.optionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: We don't close the modal here, allowing the user to make additional changes
|
||||||
|
// The user can click "Save" when they're done with all changes
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
this.router.navigate(["../../all"], { relativeTo: this.route })
|
this.router.navigate(["../../all"], { relativeTo: this.route })
|
||||||
}
|
}
|
||||||
|
|
||||||
onSchedule(){
|
onSchedule() {
|
||||||
this.router.navigate(['../../schedule/'+ this.editId],{relativeTo:this.route});
|
this.router.navigate(['../../schedule/' + this.editId], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////
|
///////
|
||||||
storedata;
|
storedata;
|
||||||
getStores(){
|
getStores() {
|
||||||
this.datastoreService.getAll().subscribe((data) => {
|
this.datastoreService.getAll().subscribe((data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
this.storedata = data;
|
this.storedata = data;
|
||||||
},(error) => {
|
}, (error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedStoreId;
|
selectedStoreId;
|
||||||
storename(val){
|
storename(val) {
|
||||||
console.log(val);
|
console.log(val);
|
||||||
this.selectedStoreId = val;
|
this.selectedStoreId = val;
|
||||||
this.getTables(this.selectedStoreId);
|
this.getTables(this.selectedStoreId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TableData;
|
TableData;
|
||||||
getTables(id){
|
getTables(id) {
|
||||||
this.alertService.getTablefromstore(id).subscribe(gateway =>{
|
this.alertService.getTablefromstore(id).subscribe(gateway => {
|
||||||
console.log(gateway);
|
console.log(gateway);
|
||||||
this.TableData = gateway;
|
this.TableData = gateway;
|
||||||
},(error)=>{
|
}, (error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tablename(val){
|
callApi(val) {
|
||||||
console.log(val);
|
console.log(' api value ', val);
|
||||||
this.getColumns(this.selectedStoreId,val);
|
this.getColumns(this.selectedStoreId, val);
|
||||||
}
|
}
|
||||||
selectedyAxis;
|
selectedyAxis;
|
||||||
columnData;
|
columnData;
|
||||||
getColumns(id,table){
|
drilldownColumnData = []; // Add drilldown column data property
|
||||||
this.alertService.getColumnfromurl(table).subscribe(data =>{
|
|
||||||
console.log(data);
|
getColumns(id, table) {
|
||||||
|
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||||
|
this.alertService.getColumnfromurl(table, connectionId).subscribe(data => {
|
||||||
|
console.log(' api data ', data);
|
||||||
this.columnData = data;
|
this.columnData = data;
|
||||||
},(error)=>{
|
}, (error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add method to refresh drilldown columns
|
||||||
|
refreshDrilldownColumns() {
|
||||||
|
if (this.gadgetsEditdata.drilldownApiUrl) {
|
||||||
|
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||||
|
this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => {
|
||||||
|
console.log('Drilldown column data:', data);
|
||||||
|
this.drilldownColumnData = data;
|
||||||
|
}, (error) => {
|
||||||
|
console.log('Error fetching drilldown columns:', error);
|
||||||
|
this.drilldownColumnData = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// toggleAddToDashboard(item) {
|
// Add method to reset drilldown configuration
|
||||||
// item.addToDashboard = item.addToDashboard;
|
resetDrilldownConfiguration() {
|
||||||
// }
|
this.gadgetsEditdata.drilldownApiUrl = '';
|
||||||
|
// Removed drilldownParameterKey since we're using URL templates
|
||||||
|
this.gadgetsEditdata.drilldownXAxis = '';
|
||||||
|
this.gadgetsEditdata.drilldownYAxis = '';
|
||||||
|
this.gadgetsEditdata.drilldownParameter = ''; // Reset drilldown parameter
|
||||||
|
// Reset drilldown layers but preserve the array structure
|
||||||
|
this.gadgetsEditdata.drilldownLayers = this.gadgetsEditdata.drilldownLayers.map(layer => ({
|
||||||
|
...layer,
|
||||||
|
enabled: false,
|
||||||
|
apiUrl: '',
|
||||||
|
xAxis: '',
|
||||||
|
yAxis: '',
|
||||||
|
parameter: '' // Reset parameter property
|
||||||
|
}));
|
||||||
|
this.drilldownColumnData = [];
|
||||||
|
}
|
||||||
|
|
||||||
// getChartDataForToggleSwitchTrue() {
|
// Add method to add a new drilldown layer
|
||||||
// for (let i = 0; i < this.dashArr.length; i++) {
|
addDrilldownLayer() {
|
||||||
// if (this.dashArr[i].addToDashboard) {
|
const newLayer = {
|
||||||
// this.dashboardService.getChartData(
|
enabled: false,
|
||||||
// this.dashArr[i].charturl, // Assuming charturl is the correct property to pass as a string
|
apiUrl: '',
|
||||||
// true // Pass true to indicate fetching charts with toggle switch set to true
|
// Removed parameterKey since we're using URL templates
|
||||||
// ).subscribe(tData => {
|
xAxis: '',
|
||||||
// console.log(tData);
|
yAxis: '',
|
||||||
// // this.dashArr[i].featchData = tData;
|
parameter: '' // Add parameter property
|
||||||
// });
|
};
|
||||||
// }
|
this.gadgetsEditdata.drilldownLayers.push(newLayer);
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
|
// Add method to remove a drilldown layer
|
||||||
|
removeDrilldownLayer(index: number) {
|
||||||
|
this.gadgetsEditdata.drilldownLayers.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to refresh drilldown columns for a specific layer
|
||||||
|
refreshDrilldownLayerColumns(layerIndex: number) {
|
||||||
|
const layer = this.gadgetsEditdata.drilldownLayers[layerIndex];
|
||||||
|
if (layer && layer.apiUrl) {
|
||||||
|
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||||
|
this.alertService.getColumnfromurl(layer.apiUrl, connectionId).subscribe(data => {
|
||||||
|
console.log(`Drilldown layer ${layerIndex} column data:`, data);
|
||||||
|
// Store layer column data in a separate property
|
||||||
|
if (!this.layerColumnData) {
|
||||||
|
this.layerColumnData = {};
|
||||||
|
}
|
||||||
|
this.layerColumnData[layerIndex] = data;
|
||||||
|
}, (error) => {
|
||||||
|
console.log(`Error fetching drilldown layer ${layerIndex} columns:`, error);
|
||||||
|
if (!this.layerColumnData) {
|
||||||
|
this.layerColumnData = {};
|
||||||
|
}
|
||||||
|
this.layerColumnData[layerIndex] = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to refresh base drilldown columns
|
||||||
|
refreshBaseDrilldownColumns() {
|
||||||
|
if (this.gadgetsEditdata.drilldownApiUrl) {
|
||||||
|
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||||
|
this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => {
|
||||||
|
console.log('Base drilldown column data:', data);
|
||||||
|
this.drilldownColumnData = data;
|
||||||
|
}, (error) => {
|
||||||
|
console.log('Error fetching base drilldown columns:', error);
|
||||||
|
this.drilldownColumnData = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to build drilldown URL with template parameters using angle brackets
|
||||||
|
buildDrilldownUrl(baseUrl: string, parameterValue: string): string {
|
||||||
|
// If no base URL, return empty string
|
||||||
|
if (!baseUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no parameter value, return the base URL as-is
|
||||||
|
if (!parameterValue) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the URL contains angle brackets for parameter replacement
|
||||||
|
const hasAngleBrackets = /<[^>]+>/.test(baseUrl);
|
||||||
|
|
||||||
|
if (hasAngleBrackets) {
|
||||||
|
// Replace angle brackets placeholder with actual value
|
||||||
|
// Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/<country>
|
||||||
|
// becomes: http://localhost:9292/State_ListFilter1/State_ListFilter11/india
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
const urlWithReplacedParam = baseUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
return urlWithReplacedParam;
|
||||||
|
} else {
|
||||||
|
// No angle brackets, return the base URL as-is
|
||||||
|
// This handles normal API endpoints without parameter replacement
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to get the parameter key from URL template using angle brackets
|
||||||
|
getParameterKeyFromUrl(baseUrl: string): string {
|
||||||
|
if (!baseUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameter key from angle brackets
|
||||||
|
// Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/<country>
|
||||||
|
// returns: country
|
||||||
|
const match = baseUrl.match(/<([^>]+)>/);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,283 @@
|
|||||||
|
# Drilldown Configuration Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document describes the drilldown configuration implementation applied to all chart components in the dashboard system. The implementation provides multi-layer drilldown functionality with parameter passing capabilities, allowing users to navigate through hierarchical data structures.
|
||||||
|
|
||||||
|
## Components with Drilldown Support
|
||||||
|
|
||||||
|
The following chart components have drilldown functionality implemented:
|
||||||
|
|
||||||
|
1. Bar Chart (`bar-chart`)
|
||||||
|
2. Line Chart (`line-chart`)
|
||||||
|
3. Pie Chart (`pie-chart`)
|
||||||
|
4. Bubble Chart (`bubble-chart`)
|
||||||
|
5. Doughnut Chart (`doughnut-chart`)
|
||||||
|
6. Polar Chart (`polar-chart`)
|
||||||
|
7. Radar Chart (`radar-chart`)
|
||||||
|
8. Scatter Chart (`scatter-chart`)
|
||||||
|
9. Financial Chart (`financial-chart`)
|
||||||
|
10. Dynamic Chart (`dynamic-chart`)
|
||||||
|
|
||||||
|
## Drilldown Configuration Properties
|
||||||
|
|
||||||
|
Each chart component includes the following drilldown configuration inputs:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Drilldown configuration inputs
|
||||||
|
@Input() drilldownEnabled: boolean = false;
|
||||||
|
@Input() drilldownApiUrl: string;
|
||||||
|
@Input() drilldownXAxis: string;
|
||||||
|
@Input() drilldownYAxis: string;
|
||||||
|
@Input() drilldownParameter: string;
|
||||||
|
|
||||||
|
// Multi-layer drilldown configuration inputs
|
||||||
|
@Input() drilldownLayers: any[] = [];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. State Management
|
||||||
|
|
||||||
|
Each component maintains drilldown state through the following properties:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Multi-layer drilldown state tracking
|
||||||
|
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||||
|
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||||
|
|
||||||
|
// Original data storage for navigation
|
||||||
|
originalChartLabels: string[] = []; // Stores original labels
|
||||||
|
originalChartData: any[] = []; // Stores original data
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Core Methods
|
||||||
|
|
||||||
|
#### fetchDrilldownData()
|
||||||
|
Fetches data for the current drilldown level based on configuration:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
fetchDrilldownData(): void {
|
||||||
|
// Determine drilldown configuration based on current level
|
||||||
|
let drilldownConfig;
|
||||||
|
if (this.currentDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = this.currentDrilldownLevel - 2;
|
||||||
|
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get parameter value from drilldown stack
|
||||||
|
let parameterValue = '';
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||||
|
parameterValue = lastEntry.clickedValue || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace parameter placeholders in API URL
|
||||||
|
let actualApiUrl = drilldownConfig.apiUrl;
|
||||||
|
if (parameterValue) {
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data from service
|
||||||
|
this.dashboardService.getChartData(
|
||||||
|
actualApiUrl,
|
||||||
|
chartType,
|
||||||
|
drilldownConfig.xAxis,
|
||||||
|
drilldownConfig.yAxis,
|
||||||
|
this.connection,
|
||||||
|
drilldownConfig.parameter,
|
||||||
|
parameterValue
|
||||||
|
).subscribe(...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### chartClicked()
|
||||||
|
Handles chart click events to initiate drilldown navigation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
public chartClicked(e: any): void {
|
||||||
|
// Check if drilldown is enabled and we have a valid click event
|
||||||
|
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||||
|
// Get clicked element details
|
||||||
|
const clickedIndex = e.active[0].index;
|
||||||
|
const clickedLabel = this.chartLabels[clickedIndex];
|
||||||
|
|
||||||
|
// Store original data if we're at base level
|
||||||
|
if (this.currentDrilldownLevel === 0) {
|
||||||
|
this.originalChartLabels = [...this.chartLabels];
|
||||||
|
this.originalChartData = [...this.chartData];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine next drilldown level
|
||||||
|
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||||
|
|
||||||
|
// Check if there's a drilldown configuration for this level
|
||||||
|
let hasDrilldownConfig = false;
|
||||||
|
let drilldownConfig;
|
||||||
|
|
||||||
|
if (nextDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = nextDrilldownLevel - 2;
|
||||||
|
if (layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||||
|
!!drilldownConfig.apiUrl &&
|
||||||
|
!!drilldownConfig.xAxis &&
|
||||||
|
!!drilldownConfig.yAxis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with drilldown if configuration exists
|
||||||
|
if (hasDrilldownConfig) {
|
||||||
|
// Add click to drilldown stack
|
||||||
|
const stackEntry = {
|
||||||
|
level: nextDrilldownLevel,
|
||||||
|
clickedIndex: clickedIndex,
|
||||||
|
clickedLabel: clickedLabel,
|
||||||
|
clickedValue: clickedLabel
|
||||||
|
};
|
||||||
|
|
||||||
|
this.drilldownStack.push(stackEntry);
|
||||||
|
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||||
|
|
||||||
|
// Fetch drilldown data
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### navigateBack()
|
||||||
|
Navigates back to the previous drilldown level:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
navigateBack(): void {
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Remove last entry from stack
|
||||||
|
this.drilldownStack.pop();
|
||||||
|
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Fetch data for previous level
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
// Back to base level
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Already at base level
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### resetToOriginalData()
|
||||||
|
Resets the chart to its original data:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
resetToOriginalData(): void {
|
||||||
|
this.currentDrilldownLevel = 0;
|
||||||
|
this.drilldownStack = [];
|
||||||
|
|
||||||
|
if (this.originalChartLabels.length > 0) {
|
||||||
|
this.chartLabels = [...this.originalChartLabels];
|
||||||
|
}
|
||||||
|
if (this.originalChartData.length > 0) {
|
||||||
|
this.chartData = [...this.originalChartData];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch original data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-Layer Drilldown Support
|
||||||
|
|
||||||
|
The implementation supports multiple drilldown layers through the `drilldownLayers` array. Each layer can have its own configuration:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
drilldownLayers: [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
apiUrl: "second-level-endpoint/<parameter>",
|
||||||
|
xAxis: "column1",
|
||||||
|
yAxis: "column2",
|
||||||
|
parameter: "selectedColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
apiUrl: "third-level-endpoint/<parameter>",
|
||||||
|
xAxis: "column3",
|
||||||
|
yAxis: "column4",
|
||||||
|
parameter: "selectedColumn"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameter Passing
|
||||||
|
|
||||||
|
The drilldown implementation supports parameter passing by replacing placeholders in the API URL:
|
||||||
|
|
||||||
|
1. URL templates use angle brackets for parameter placeholders: `endpoint/<parameter>`
|
||||||
|
2. When navigating, the clicked value replaces the placeholder
|
||||||
|
3. Parameters are properly encoded using `encodeURIComponent`
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
1. **Initial Load**: Chart loads with base data using `fetchChartData()`
|
||||||
|
2. **Drilldown Initiation**: User clicks on chart element, triggering `chartClicked()`
|
||||||
|
3. **Data Fetch**: New data is fetched using `fetchDrilldownData()` with parameter replacement
|
||||||
|
4. **Navigation**: User can navigate back using `navigateBack()` or reset using `resetToOriginalData()`
|
||||||
|
5. **State Management**: All navigation is tracked in `drilldownStack` with level management
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The implementation includes error handling for:
|
||||||
|
|
||||||
|
1. Missing drilldown configuration
|
||||||
|
2. API call failures
|
||||||
|
3. Invalid data structures
|
||||||
|
4. Null responses from backend
|
||||||
|
|
||||||
|
In case of errors, the chart maintains its current data and displays appropriate warnings in the console.
|
||||||
|
|
||||||
|
## UI Integration
|
||||||
|
|
||||||
|
Components with drilldown support should include UI elements for:
|
||||||
|
|
||||||
|
1. **Back Button**: To navigate to previous drilldown level
|
||||||
|
2. **Reset Button**: To return to original data
|
||||||
|
3. **Navigation Indicators**: To show current drilldown level
|
||||||
|
|
||||||
|
Example HTML structure:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div *ngIf="drilldownEnabled && currentDrilldownLevel > 0" class="drilldown-controls">
|
||||||
|
<button (click)="navigateBack()" class="btn btn-secondary">
|
||||||
|
← Back to Level {{ currentDrilldownLevel - 1 }}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" class="btn btn-outline">
|
||||||
|
↺ Reset to Original
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
@ -1,9 +1,28 @@
|
|||||||
<div style="display: block">
|
<div style="display: block">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="barChartData"
|
[datasets]="barChartData"
|
||||||
[labels]="barChartLabels"
|
[labels]="barChartLabels"
|
||||||
[type]="barChartType"
|
[type]="barChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
</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">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="bubbleChartData"
|
[datasets]="bubbleChartData"
|
||||||
[type]="bubbleChartType"
|
[type]="bubbleChartType"
|
||||||
[options]="bubbleChartOptions"
|
[options]="bubbleChartOptions"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
</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,35 +81,429 @@ 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;
|
||||||
|
|
||||||
|
constructor(private dashboardService: Dashboard3Service) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
console.log('BubbleChartComponent input changes:', changes);
|
||||||
|
|
||||||
|
// Check if any of the key properties have changed
|
||||||
|
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||||
|
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||||
|
const tableChanged = changes.table && !changes.table.firstChange;
|
||||||
|
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||||
|
// Drilldown configuration changes
|
||||||
|
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||||
|
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||||
|
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||||
|
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||||
|
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||||
|
|
||||||
|
// Respond to input changes
|
||||||
|
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||||
|
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||||
|
drilldownLayersChanged) {
|
||||||
|
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchChartData(): void {
|
||||||
|
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||||
|
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have the necessary data, fetch chart data from the service
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
|
console.log('Fetching bubble chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
|
||||||
|
// Convert yAxis to string if it's an array
|
||||||
|
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/bubble?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Bubble chart data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// For base level, we pass empty parameter and value
|
||||||
|
this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received bubble chart data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Bubble chart API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For bubble charts, we need to transform the data into bubble format
|
||||||
|
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||||
|
console.log('Updated bubble chart with data:', this.bubbleChartData);
|
||||||
|
} else if (data && data.labels && data.datasets) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.bubbleChartData = data.datasets;
|
||||||
|
console.log('Updated bubble chart with legacy data format:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.warn('Bubble chart received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching bubble chart data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch drilldown data based on current drilldown level
|
||||||
|
fetchDrilldownData(): void {
|
||||||
|
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||||
|
console.log('Drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Get the current drilldown configuration based on the current level
|
||||||
|
let drilldownConfig;
|
||||||
|
if (this.currentDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
} else {
|
||||||
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||||
|
|
||||||
|
// Check if we have valid drilldown configuration
|
||||||
|
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||||
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter value from the drilldown stack
|
||||||
|
let parameterValue = '';
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||||
|
parameterValue = lastEntry.clickedValue || '';
|
||||||
|
console.log('Parameter value from last click:', parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter field from drilldown config
|
||||||
|
const parameterField = drilldownConfig.parameter || '';
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||||
|
apiUrl: drilldownConfig.apiUrl,
|
||||||
|
xAxis: drilldownConfig.xAxis,
|
||||||
|
yAxis: drilldownConfig.yAxis,
|
||||||
|
parameterField: parameterField,
|
||||||
|
parameterValue: parameterValue,
|
||||||
|
connection: this.connection
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the actual API URL with parameter replacement
|
||||||
|
let actualApiUrl = drilldownConfig.apiUrl;
|
||||||
|
console.log('Original API URL:', actualApiUrl);
|
||||||
|
console.log('Parameter value to use:', parameterValue);
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
// Check if the URL contains angle brackets for parameter replacement
|
||||||
|
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||||
|
|
||||||
|
if (hasAngleBrackets && parameterValue) {
|
||||||
|
// Replace angle brackets placeholder with actual value
|
||||||
|
console.log('Replacing angle brackets with parameter value');
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/bubble?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Drilldown data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// Backend handles filtering, we just pass the parameter field and value
|
||||||
|
this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received drilldown data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For bubble charts, we need to transform the data into bubble format
|
||||||
|
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||||
|
console.log('Updated bubble chart with drilldown data:', this.bubbleChartData);
|
||||||
|
} else if (data && data.labels && data.datasets) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.bubbleChartData = data.datasets;
|
||||||
|
console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching drilldown data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.bubbleChartData = [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to original data (go back to base level)
|
||||||
|
resetToOriginalData(): void {
|
||||||
|
console.log('Resetting to original data');
|
||||||
|
console.log('Current stack before reset:', this.drilldownStack);
|
||||||
|
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
this.currentDrilldownLevel = 0;
|
||||||
|
this.drilldownStack = [];
|
||||||
|
|
||||||
|
if (this.originalBubbleChartData.length > 0) {
|
||||||
|
this.bubbleChartData = [...this.originalBubbleChartData];
|
||||||
|
console.log('Restored original data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('After reset - data:', this.bubbleChartData);
|
||||||
|
|
||||||
|
// Re-fetch original data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate back to previous drilldown level
|
||||||
|
navigateBack(): void {
|
||||||
|
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||||
|
console.log('Current level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Remove the last entry from the stack
|
||||||
|
const removedEntry = this.drilldownStack.pop();
|
||||||
|
console.log('Removed entry from stack:', removedEntry);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||||
|
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||||
|
console.log('Stack after pop:', this.drilldownStack);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Fetch data for the previous level
|
||||||
|
console.log('Fetching data for previous level');
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
// Back to base level
|
||||||
|
console.log('Back to base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Already at base level, reset to original data
|
||||||
|
console.log('Already at base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private transformToBubbleData(labels: any[], chartData: any[]): ChartDataset[] {
|
||||||
|
// Transform the API data into bubble chart format
|
||||||
|
const datasets: ChartDataset[] = [];
|
||||||
|
|
||||||
|
// Create a dataset for each data series
|
||||||
|
chartData.forEach((series, index) => {
|
||||||
|
// For bubble charts, we need x, y, and r values
|
||||||
|
// We'll use the labels as x values and the data as y values
|
||||||
|
// For radius (r), we'll use a default value or derive it from the data
|
||||||
|
|
||||||
|
const bubbleData = labels.map((label, i) => {
|
||||||
|
const xValue = isNaN(Number(label)) ? i : Number(label);
|
||||||
|
const yValue = series.data && series.data[i] !== undefined ?
|
||||||
|
(isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0;
|
||||||
|
// Use a default radius or derive from data
|
||||||
|
const radius = Math.abs(yValue) > 0 ? Math.abs(yValue) / 10 : 5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: xValue,
|
||||||
|
y: yValue,
|
||||||
|
r: radius
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
datasets.push({
|
||||||
|
data: bubbleData,
|
||||||
|
label: series.label || `Series ${index + 1}`,
|
||||||
|
backgroundColor: this.getBackgroundColor(index),
|
||||||
|
borderColor: this.getBorderColor(index),
|
||||||
|
hoverBackgroundColor: this.getHoverBackgroundColor(index),
|
||||||
|
hoverBorderColor: this.getHoverBorderColor(index),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return datasets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBackgroundColor(index: number): string {
|
||||||
|
const colors = [
|
||||||
|
'rgba(255, 0, 0, 0.6)', // Red
|
||||||
|
'rgba(0, 255, 0, 0.6)', // Green
|
||||||
|
'rgba(0, 0, 255, 0.6)', // Blue
|
||||||
|
'rgba(255, 255, 0, 0.6)', // Yellow
|
||||||
|
'rgba(255, 0, 255, 0.6)', // Magenta
|
||||||
|
'rgba(0, 255, 255, 0.6)', // Cyan
|
||||||
|
];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBorderColor(index: number): string {
|
||||||
|
const colors = ['blue', 'green', 'red', 'orange', 'purple', 'cyan'];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHoverBackgroundColor(index: number): string {
|
||||||
|
const colors = ['purple', 'yellow', 'orange', 'red', 'blue', 'green'];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHoverBorderColor(index: number): string {
|
||||||
|
const colors = ['red', 'blue', 'green', 'purple', 'yellow', 'orange'];
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
public chartClicked(e: any): void {
|
public chartClicked(e: any): void {
|
||||||
console.log(e);
|
console.log('Bubble chart clicked:', e);
|
||||||
|
|
||||||
|
// If drilldown is enabled and we have a valid click event
|
||||||
|
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||||
|
// Get the index of the clicked element
|
||||||
|
const clickedIndex = e.active[0].index;
|
||||||
|
|
||||||
|
// Get the dataset index
|
||||||
|
const datasetIndex = e.active[0].datasetIndex;
|
||||||
|
|
||||||
|
// Get the data point
|
||||||
|
const dataPoint = this.bubbleChartData[datasetIndex].data[clickedIndex];
|
||||||
|
|
||||||
|
console.log('Clicked on bubble:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint });
|
||||||
|
|
||||||
|
// If we're not at the base level, store original data
|
||||||
|
if (this.currentDrilldownLevel === 0) {
|
||||||
|
// Store original data before entering drilldown mode
|
||||||
|
this.originalBubbleChartData = JSON.parse(JSON.stringify(this.bubbleChartData));
|
||||||
|
console.log('Stored original data for drilldown');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the next drilldown level
|
||||||
|
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||||
|
|
||||||
|
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||||
|
|
||||||
|
// Check if there's a drilldown configuration for this level
|
||||||
|
let hasDrilldownConfig = false;
|
||||||
|
let drilldownConfig;
|
||||||
|
|
||||||
|
if (nextDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||||
|
!!drilldownConfig.apiUrl &&
|
||||||
|
!!drilldownConfig.xAxis &&
|
||||||
|
!!drilldownConfig.yAxis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for next level:', drilldownConfig);
|
||||||
|
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||||
|
|
||||||
|
// If there's a drilldown configuration for the next level, proceed
|
||||||
|
if (hasDrilldownConfig) {
|
||||||
|
// For bubble charts, we'll use the x value as the clicked value
|
||||||
|
const clickedValue = dataPoint && (dataPoint as any).x !== undefined ?
|
||||||
|
(dataPoint as any).x.toString() : '';
|
||||||
|
|
||||||
|
// Add this click to the drilldown stack
|
||||||
|
const stackEntry = {
|
||||||
|
level: nextDrilldownLevel,
|
||||||
|
datasetIndex: datasetIndex,
|
||||||
|
clickedIndex: clickedIndex,
|
||||||
|
clickedValue: clickedValue
|
||||||
|
};
|
||||||
|
|
||||||
|
this.drilldownStack.push(stackEntry);
|
||||||
|
|
||||||
|
console.log('Added to drilldown stack:', stackEntry);
|
||||||
|
console.log('Current drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||||
|
|
||||||
|
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
// Fetch drilldown data for the new level
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Drilldown not enabled or invalid click event');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public chartHovered(e: any): void {
|
public chartHovered(e: any): void {
|
||||||
|
|||||||
@ -1,8 +1,44 @@
|
|||||||
<div style="display: block">
|
<div class="doughnut-chart-container">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<!-- Show loading indicator -->
|
||||||
|
<div class="loading-indicator" *ngIf="doughnutChartLabels.length === 0 && doughnutChartData.length === 0 && !noDataAvailable">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading chart data...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show no data message -->
|
||||||
|
<div class="no-data-message" *ngIf="noDataAvailable">
|
||||||
|
<p>No chart data available</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show chart when data is available -->
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
|
*ngIf="!noDataAvailable && doughnutChartLabels.length > 0 && doughnutChartData.length > 0"
|
||||||
[data]="doughnutChartData"
|
[data]="doughnutChartData"
|
||||||
[labels]="doughnutChartLabels"
|
[labels]="doughnutChartLabels"
|
||||||
[type]="doughnutChartType"
|
[type]="doughnutChartType"
|
||||||
|
[options]="doughnutChartOptions"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)"></canvas>
|
(chartClick)="chartClicked($event)">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
|
<div class="chart-legend" *ngIf="!noDataAvailable && showlabel && doughnutChartLabels && doughnutChartLabels.length > 0">
|
||||||
|
<div class="legend-item" *ngFor="let label of doughnutChartLabels; let i = index">
|
||||||
|
<span class="legend-color" [style.background-color]="getLegendColor(i)"></span>
|
||||||
|
<span class="legend-label">{{ label }}</span>
|
||||||
|
<span class="legend-value">{{ doughnutChartData && doughnutChartData[i] !== undefined ? doughnutChartData[i] : 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</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;
|
||||||
|
@Input() showlabel: boolean = true;
|
||||||
|
@Input() chartcolor: boolean;
|
||||||
|
@Input() slices: boolean;
|
||||||
|
@Input() donut: boolean;
|
||||||
|
@Input() charturl: string;
|
||||||
|
@Input() chartparameter: string;
|
||||||
|
@Input() datasource: string;
|
||||||
|
@Input() fieldName: string;
|
||||||
|
@Input() connection: number; // Add connection input
|
||||||
|
// Drilldown configuration inputs
|
||||||
|
@Input() drilldownEnabled: boolean = false;
|
||||||
|
@Input() drilldownApiUrl: string;
|
||||||
|
@Input() drilldownXAxis: string;
|
||||||
|
@Input() drilldownYAxis: string;
|
||||||
|
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||||
|
// Multi-layer drilldown configuration inputs
|
||||||
|
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||||
|
|
||||||
|
public doughnutChartLabels: string[] = ["Category A", "Category B", "Category C"];
|
||||||
|
public doughnutChartData: number[] = [30, 50, 20];
|
||||||
public doughnutChartType: string = "doughnut";
|
public doughnutChartType: string = "doughnut";
|
||||||
|
public doughnutChartOptions: any = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
cutout: '60%', // This creates the doughnut effect (Chart.js v3+ syntax)
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false // We'll create our own legend
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
titleFont: {
|
||||||
|
size: 16,
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
bodyFont: {
|
||||||
|
size: 14,
|
||||||
|
color: '#fff'
|
||||||
|
},
|
||||||
|
cornerRadius: 4,
|
||||||
|
displayColors: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
animateRotate: true,
|
||||||
|
animateScale: false
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
arc: {
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chart colors for consistent styling
|
||||||
|
private chartColors: string[] = [
|
||||||
|
'#FF6384',
|
||||||
|
'#36A2EB',
|
||||||
|
'#FFCE56',
|
||||||
|
'#4BC0C0',
|
||||||
|
'#9966FF',
|
||||||
|
'#FF9F40',
|
||||||
|
'#FF6384',
|
||||||
|
'#C9CBCF'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Multi-layer drilldown state tracking
|
||||||
|
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||||
|
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||||
|
originalDoughnutChartLabels: string[] = [];
|
||||||
|
originalDoughnutChartData: number[] = [];
|
||||||
|
|
||||||
|
// No data state
|
||||||
|
noDataAvailable: boolean = false;
|
||||||
|
|
||||||
|
constructor(private dashboardService: Dashboard3Service) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Validate initial data
|
||||||
|
this.validateChartData();
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and sanitize chart data
|
||||||
|
*/
|
||||||
|
private validateChartData(): void {
|
||||||
|
// Ensure we have valid arrays
|
||||||
|
if (!Array.isArray(this.doughnutChartLabels)) {
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(this.doughnutChartData)) {
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have some data to display
|
||||||
|
if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) {
|
||||||
|
// Add default data to ensure chart visibility
|
||||||
|
this.doughnutChartLabels = ["Category A", "Category B", "Category C"];
|
||||||
|
this.doughnutChartData = [30, 50, 20];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have matching arrays
|
||||||
|
if (this.doughnutChartLabels.length !== this.doughnutChartData.length) {
|
||||||
|
const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length);
|
||||||
|
while (this.doughnutChartLabels.length < maxLength) {
|
||||||
|
this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`);
|
||||||
|
}
|
||||||
|
while (this.doughnutChartData.length < maxLength) {
|
||||||
|
this.doughnutChartData.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force chart redraw
|
||||||
|
*/
|
||||||
|
public redrawChart(): void {
|
||||||
|
// This method can be called to force a chart redraw if needed
|
||||||
|
console.log('Redrawing doughnut chart');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
console.log('DoughnutChartComponent input changes:', changes);
|
||||||
|
|
||||||
|
// Check if any of the key properties have changed
|
||||||
|
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||||
|
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||||
|
const tableChanged = changes.table && !changes.table.firstChange;
|
||||||
|
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||||
|
// Drilldown configuration changes
|
||||||
|
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||||
|
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||||
|
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||||
|
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||||
|
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||||
|
|
||||||
|
// Respond to input changes
|
||||||
|
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||||
|
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||||
|
drilldownLayersChanged) {
|
||||||
|
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewChecked() {
|
||||||
|
// Debugging: Log component state after view checks
|
||||||
|
console.log('DoughnutChartComponent state:', {
|
||||||
|
labels: this.doughnutChartLabels,
|
||||||
|
data: this.doughnutChartData,
|
||||||
|
hasData: this.doughnutChartLabels.length > 0 && this.doughnutChartData.length > 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchChartData(): void {
|
||||||
|
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||||
|
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have the necessary data, fetch chart data from the service
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
|
console.log('Fetching doughnut chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
|
||||||
|
// Convert yAxis to string if it's an array
|
||||||
|
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/doughnut?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Doughnut chart data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// For base level, we pass empty parameter and value
|
||||||
|
this.dashboardService.getChartData(this.table, 'doughnut', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received doughnut chart data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Doughnut chart API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For doughnut charts, we need to extract the data differently
|
||||||
|
// The first dataset's data array contains the values for the doughnut chart
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.doughnutChartLabels = data.chartLabels || [];
|
||||||
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
|
this.doughnutChartData = data.chartData[0].data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
const numValue = Number(value);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
}
|
||||||
|
// Ensure labels and data arrays have the same length
|
||||||
|
this.syncLabelAndDataArrays();
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
// Trigger change detection
|
||||||
|
this.doughnutChartData = [...this.doughnutChartData];
|
||||||
|
console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
|
} else if (data && data.labels && data.data) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.doughnutChartLabels = data.labels || [];
|
||||||
|
this.doughnutChartData = data.data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
const numValue = Number(value);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
});
|
||||||
|
// Ensure labels and data arrays have the same length
|
||||||
|
this.syncLabelAndDataArrays();
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
// Trigger change detection
|
||||||
|
this.doughnutChartData = [...this.doughnutChartData];
|
||||||
|
console.log('Updated doughnut chart with legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Doughnut chart received data does not have expected structure', data);
|
||||||
|
// Reset to default data
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log final state for debugging
|
||||||
|
console.log('Final doughnut chart state:', {
|
||||||
|
labels: this.doughnutChartLabels,
|
||||||
|
data: this.doughnutChartData,
|
||||||
|
labelsLength: this.doughnutChartLabels.length,
|
||||||
|
dataLength: this.doughnutChartData.length
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching doughnut chart data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Missing required data for doughnut chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
// Don't set noDataAvailable to true when there's no required data
|
||||||
|
// This allows static data to be displayed
|
||||||
|
// Only validate the chart data to ensure we have some data to display
|
||||||
|
this.validateChartData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch drilldown data based on current drilldown level
|
||||||
|
fetchDrilldownData(): void {
|
||||||
|
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||||
|
console.log('Drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Get the current drilldown configuration based on the current level
|
||||||
|
let drilldownConfig;
|
||||||
|
if (this.currentDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
} else {
|
||||||
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||||
|
|
||||||
|
// Check if we have valid drilldown configuration
|
||||||
|
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||||
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter value from the drilldown stack
|
||||||
|
let parameterValue = '';
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||||
|
parameterValue = lastEntry.clickedValue || '';
|
||||||
|
console.log('Parameter value from last click:', parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter field from drilldown config
|
||||||
|
const parameterField = drilldownConfig.parameter || '';
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||||
|
apiUrl: drilldownConfig.apiUrl,
|
||||||
|
xAxis: drilldownConfig.xAxis,
|
||||||
|
yAxis: drilldownConfig.yAxis,
|
||||||
|
parameterField: parameterField,
|
||||||
|
parameterValue: parameterValue,
|
||||||
|
connection: this.connection
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the actual API URL with parameter replacement
|
||||||
|
let actualApiUrl = drilldownConfig.apiUrl;
|
||||||
|
console.log('Original API URL:', actualApiUrl);
|
||||||
|
console.log('Parameter value to use:', parameterValue);
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
// Check if the URL contains angle brackets for parameter replacement
|
||||||
|
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||||
|
|
||||||
|
if (hasAngleBrackets && parameterValue) {
|
||||||
|
// Replace angle brackets placeholder with actual value
|
||||||
|
console.log('Replacing angle brackets with parameter value');
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/doughnut?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Drilldown data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// Backend handles filtering, we just pass the parameter field and value
|
||||||
|
this.dashboardService.getChartData(actualApiUrl, 'doughnut', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received drilldown data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For doughnut charts, we need to extract the data differently
|
||||||
|
// The first dataset's data array contains the values for the doughnut chart
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.doughnutChartLabels = data.chartLabels || [];
|
||||||
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
|
this.doughnutChartData = data.chartData[0].data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
const numValue = Number(value);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
}
|
||||||
|
// Ensure labels and data arrays have the same length
|
||||||
|
this.syncLabelAndDataArrays();
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
// Trigger change detection
|
||||||
|
this.doughnutChartData = [...this.doughnutChartData];
|
||||||
|
console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
|
} else if (data && data.labels && data.data) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.doughnutChartLabels = data.labels || [];
|
||||||
|
this.doughnutChartData = data.data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
const numValue = Number(value);
|
||||||
|
return isNaN(numValue) ? 0 : numValue;
|
||||||
|
});
|
||||||
|
// Ensure labels and data arrays have the same length
|
||||||
|
this.syncLabelAndDataArrays();
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
// Trigger change detection
|
||||||
|
this.doughnutChartData = [...this.doughnutChartData];
|
||||||
|
console.log('Updated doughnut chart with drilldown legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
// Validate and sanitize data
|
||||||
|
this.validateChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log final state for debugging
|
||||||
|
console.log('Final doughnut chart state:', {
|
||||||
|
labels: this.doughnutChartLabels,
|
||||||
|
data: this.doughnutChartData,
|
||||||
|
labelsLength: this.doughnutChartLabels.length,
|
||||||
|
dataLength: this.doughnutChartData.length
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching drilldown data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.doughnutChartLabels = [];
|
||||||
|
this.doughnutChartData = [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to original data (go back to base level)
|
||||||
|
resetToOriginalData(): void {
|
||||||
|
console.log('Resetting to original data');
|
||||||
|
console.log('Current stack before reset:', this.drilldownStack);
|
||||||
|
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
this.currentDrilldownLevel = 0;
|
||||||
|
this.drilldownStack = [];
|
||||||
|
|
||||||
|
if (this.originalDoughnutChartLabels.length > 0) {
|
||||||
|
this.doughnutChartLabels = [...this.originalDoughnutChartLabels];
|
||||||
|
console.log('Restored original labels');
|
||||||
|
}
|
||||||
|
if (this.originalDoughnutChartData.length > 0) {
|
||||||
|
this.doughnutChartData = [...this.originalDoughnutChartData];
|
||||||
|
console.log('Restored original data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('After reset - labels:', this.doughnutChartLabels);
|
||||||
|
console.log('After reset - data:', this.doughnutChartData);
|
||||||
|
|
||||||
|
// Re-fetch original data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate back to previous drilldown level
|
||||||
|
navigateBack(): void {
|
||||||
|
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||||
|
console.log('Current level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Remove the last entry from the stack
|
||||||
|
const removedEntry = this.drilldownStack.pop();
|
||||||
|
console.log('Removed entry from stack:', removedEntry);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||||
|
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||||
|
console.log('Stack after pop:', this.drilldownStack);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Fetch data for the previous level
|
||||||
|
console.log('Fetching data for previous level');
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
// Back to base level
|
||||||
|
console.log('Back to base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Already at base level, reset to original data
|
||||||
|
console.log('Already at base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get color for legend item
|
||||||
|
* @param index Index of the legend item
|
||||||
|
*/
|
||||||
|
public getLegendColor(index: number): string {
|
||||||
|
return this.chartColors[index % this.chartColors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure labels and data arrays have the same length
|
||||||
|
*/
|
||||||
|
private syncLabelAndDataArrays(): void {
|
||||||
|
// Handle empty arrays
|
||||||
|
if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length);
|
||||||
|
|
||||||
|
// Pad the shorter array with default values
|
||||||
|
while (this.doughnutChartLabels.length < maxLength) {
|
||||||
|
this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.doughnutChartData.length < maxLength) {
|
||||||
|
this.doughnutChartData.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate the longer array if needed
|
||||||
|
if (this.doughnutChartLabels.length > maxLength) {
|
||||||
|
this.doughnutChartLabels = this.doughnutChartLabels.slice(0, maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.doughnutChartData.length > maxLength) {
|
||||||
|
this.doughnutChartData = this.doughnutChartData.slice(0, maxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
public chartClicked(e: any): void {
|
public chartClicked(e: any): void {
|
||||||
console.log(e);
|
console.log('Doughnut chart clicked:', e);
|
||||||
|
|
||||||
|
// If drilldown is enabled and we have a valid click event
|
||||||
|
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||||
|
// Get the index of the clicked element
|
||||||
|
const clickedIndex = e.active[0].index;
|
||||||
|
|
||||||
|
// Get the label of the clicked element
|
||||||
|
const clickedLabel = this.doughnutChartLabels[clickedIndex];
|
||||||
|
|
||||||
|
console.log('Clicked on doughnut slice:', { index: clickedIndex, label: clickedLabel });
|
||||||
|
|
||||||
|
// If we're not at the base level, store original data
|
||||||
|
if (this.currentDrilldownLevel === 0) {
|
||||||
|
// Store original data before entering drilldown mode
|
||||||
|
this.originalDoughnutChartLabels = [...this.doughnutChartLabels];
|
||||||
|
this.originalDoughnutChartData = [...this.doughnutChartData];
|
||||||
|
console.log('Stored original data for drilldown');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the next drilldown level
|
||||||
|
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||||
|
|
||||||
|
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||||
|
|
||||||
|
// Check if there's a drilldown configuration for this level
|
||||||
|
let hasDrilldownConfig = false;
|
||||||
|
let drilldownConfig;
|
||||||
|
|
||||||
|
if (nextDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||||
|
!!drilldownConfig.apiUrl &&
|
||||||
|
!!drilldownConfig.xAxis &&
|
||||||
|
!!drilldownConfig.yAxis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for next level:', drilldownConfig);
|
||||||
|
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||||
|
|
||||||
|
// If there's a drilldown configuration for the next level, proceed
|
||||||
|
if (hasDrilldownConfig) {
|
||||||
|
// Add this click to the drilldown stack
|
||||||
|
const stackEntry = {
|
||||||
|
level: nextDrilldownLevel,
|
||||||
|
clickedIndex: clickedIndex,
|
||||||
|
clickedLabel: clickedLabel,
|
||||||
|
clickedValue: clickedLabel // Using label as value for now
|
||||||
|
};
|
||||||
|
|
||||||
|
this.drilldownStack.push(stackEntry);
|
||||||
|
|
||||||
|
console.log('Added to drilldown stack:', stackEntry);
|
||||||
|
console.log('Current drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||||
|
|
||||||
|
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
// Fetch drilldown data for the new level
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Drilldown not enabled or invalid click event');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public chartHovered(e: any): void {
|
public chartHovered(e: any): void {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,10 +1,38 @@
|
|||||||
<div style="display: block">
|
<div class="dynamic-chart-container">
|
||||||
<canvas baseChart [datasets]="dynamicChartData"
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<!-- Show loading indicator -->
|
||||||
|
<div class="loading-indicator" *ngIf="dynamicChartLabels.length === 0 && dynamicChartData.length === 0 && !noDataAvailable">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading chart data...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show no data message -->
|
||||||
|
<div class="no-data-message" *ngIf="noDataAvailable">
|
||||||
|
<p>No chart data available</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show chart when data is available -->
|
||||||
|
<canvas baseChart
|
||||||
|
*ngIf="!noDataAvailable && dynamicChartLabels.length > 0 && dynamicChartData.length > 0"
|
||||||
|
[datasets]="dynamicChartData"
|
||||||
[options]="barChartOptions"
|
[options]="barChartOptions"
|
||||||
[type]="barChartType"
|
[type]="barChartType"
|
||||||
[labels]="dynamicChartLabels"
|
[labels]="dynamicChartLabels"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" (click)="randomize()">Update</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" (click)="randomize()">Update</button>
|
|
||||||
|
|||||||
@ -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,35 +87,367 @@ 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,60 +1,27 @@
|
|||||||
<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}">
|
<!-- Dynamic columns based on response keys -->
|
||||||
User Group No
|
<clr-dg-column *ngFor="let header of dynamicHeaders" [clrDgField]="header.key">
|
||||||
</ng-container></clr-dg-column>
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-dg-column [clrDgField]="'groupName'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
{{header.displayName}}
|
||||||
Group Name
|
</ng-container>
|
||||||
</ng-container></clr-dg-column>
|
</clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'groupDesc'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Description
|
|
||||||
</ng-container></clr-dg-column >
|
|
||||||
<clr-dg-column [clrDgField]="'groupLevel'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Group Level
|
|
||||||
</ng-container></clr-dg-column>
|
|
||||||
<clr-dg-column [clrDgField]="'status'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Status
|
|
||||||
</ng-container></clr-dg-column>
|
|
||||||
<!-- <clr-dg-column [clrDgField]="'usrGrp'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
User Group
|
|
||||||
</ng-container></clr-dg-column> -->
|
|
||||||
<clr-dg-column [clrDgField]="'updateDateFormated'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
|
||||||
Updated Date
|
|
||||||
</ng-container></clr-dg-column>
|
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let item of givendata" [clrDgItem]="item">
|
||||||
<clr-dg-row *clrDgItems="let user of givendata?.slice()?.reverse();let i = index" [clrDgItem]="user">
|
<!-- Dynamic cells based on response keys -->
|
||||||
<clr-dg-cell>{{user.usrGrp}}</clr-dg-cell>
|
<clr-dg-cell *ngFor="let header of dynamicHeaders">
|
||||||
<clr-dg-cell>{{user.groupName}}</clr-dg-cell>
|
{{item[header.key]}}
|
||||||
<clr-dg-cell >{{user.groupDesc}}</clr-dg-cell>
|
</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>
|
||||||
@ -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) => {
|
|
||||||
|
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);
|
console.log(error);
|
||||||
if(error){
|
this.error = "Server Error";
|
||||||
this.error="Server Error";
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Missing required data for grid:', { table: this.table });
|
||||||
|
this.error = "Table name is required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract dynamic headers from the data
|
||||||
|
* @param data Array of data objects
|
||||||
|
*/
|
||||||
|
private extractDynamicHeaders(data: any): void {
|
||||||
|
// Ensure data is an array
|
||||||
|
const dataArray = Array.isArray(data) ? data : [];
|
||||||
|
|
||||||
|
if (dataArray && dataArray.length > 0) {
|
||||||
|
// Get all unique keys from the data objects
|
||||||
|
const allKeys = new Set<string>();
|
||||||
|
dataArray.forEach(item => {
|
||||||
|
if (item && typeof item === 'object') {
|
||||||
|
Object.keys(item).forEach(key => allKeys.add(key));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Convert to array of header objects with key and display name
|
||||||
|
this.dynamicHeaders = Array.from(allKeys).map(key => ({
|
||||||
|
key: key,
|
||||||
|
displayName: this.formatHeader(key)
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Dynamic headers extracted:', this.dynamicHeaders);
|
||||||
|
} else {
|
||||||
|
this.dynamicHeaders = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format header name for better display
|
||||||
|
* @param key The key to format
|
||||||
|
*/
|
||||||
|
private formatHeader(key: string): string {
|
||||||
|
// Convert camelCase to Title Case
|
||||||
|
return key
|
||||||
|
.replace(/([A-Z])/g, ' $1')
|
||||||
|
.replace(/^./, str => str.toUpperCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,22 @@
|
|||||||
<div style="display: block;">
|
<div style="display: block">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="lineChartData"
|
[datasets]="lineChartData"
|
||||||
[labels]="lineChartLabels"
|
[labels]="lineChartLabels"
|
||||||
@ -8,5 +26,6 @@
|
|||||||
[type]="lineChartType"
|
[type]="lineChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)"></canvas>
|
(chartClick)="chartClicked($event)"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- [color]="lineChartColors"-->
|
<!-- [color]="lineChartColors"-->
|
||||||
@ -1,21 +1,46 @@
|
|||||||
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;
|
||||||
|
@Input() yAxis: string | string[];
|
||||||
|
@Input() table: string;
|
||||||
|
@Input() datastore: string;
|
||||||
|
@Input() charttitle: string;
|
||||||
|
@Input() chartlegend: boolean = true;
|
||||||
|
@Input() showlabel: boolean = true;
|
||||||
|
@Input() chartcolor: boolean;
|
||||||
|
@Input() slices: boolean;
|
||||||
|
@Input() donut: boolean;
|
||||||
|
@Input() charturl: string;
|
||||||
|
@Input() chartparameter: string;
|
||||||
|
@Input() datasource: string;
|
||||||
|
@Input() fieldName: string;
|
||||||
|
@Input() connection: number; // Add connection input
|
||||||
|
// Drilldown configuration inputs
|
||||||
|
@Input() drilldownEnabled: boolean = false;
|
||||||
|
@Input() drilldownApiUrl: string;
|
||||||
|
@Input() drilldownXAxis: string;
|
||||||
|
@Input() drilldownYAxis: string;
|
||||||
|
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||||
|
// Multi-layer drilldown configuration inputs
|
||||||
|
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||||
|
|
||||||
|
public lineChartData: Array<any> = [
|
||||||
{data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'},
|
{data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'},
|
||||||
{data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'},
|
{data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'},
|
||||||
{data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'}
|
{data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'}
|
||||||
];
|
];
|
||||||
public lineChartLabels:Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
public lineChartLabels: Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||||
public lineChartOptions:any = {
|
public lineChartOptions: any = {
|
||||||
responsive: true
|
responsive: true
|
||||||
};
|
};
|
||||||
public lineChartColors:Array<any> = [
|
public lineChartColors: Array<any> = [
|
||||||
{ // grey
|
{ // grey
|
||||||
backgroundColor: 'rgba(148,159,177,0.2)',
|
backgroundColor: 'rgba(148,159,177,0.2)',
|
||||||
borderColor: 'rgba(148,159,177,1)',
|
borderColor: 'rgba(148,159,177,1)',
|
||||||
@ -41,11 +66,314 @@ export class LineChartComponent implements OnInit {
|
|||||||
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
|
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
public lineChartLegend:boolean = true;
|
public lineChartLegend: boolean = true;
|
||||||
public lineChartType:string = 'line';
|
public lineChartType: string = 'line';
|
||||||
|
|
||||||
public randomize():void {
|
// Multi-layer drilldown state tracking
|
||||||
let _lineChartData:Array<any> = new Array(this.lineChartData.length);
|
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||||
|
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||||
|
originalLineChartLabels: Array<any> = [];
|
||||||
|
originalLineChartData: Array<any> = [];
|
||||||
|
|
||||||
|
// No data state
|
||||||
|
noDataAvailable: boolean = false;
|
||||||
|
|
||||||
|
constructor(private dashboardService: Dashboard3Service) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Initialize with default data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
console.log('LineChartComponent input changes:', changes);
|
||||||
|
|
||||||
|
// Check if any of the key properties have changed
|
||||||
|
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||||
|
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||||
|
const tableChanged = changes.table && !changes.table.firstChange;
|
||||||
|
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||||
|
// Drilldown configuration changes
|
||||||
|
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||||
|
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||||
|
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||||
|
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||||
|
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||||
|
|
||||||
|
// Respond to input changes
|
||||||
|
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||||
|
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||||
|
drilldownLayersChanged) {
|
||||||
|
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||||
|
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update legend visibility if it changed
|
||||||
|
if (changes.chartlegend !== undefined) {
|
||||||
|
this.lineChartLegend = changes.chartlegend.currentValue;
|
||||||
|
console.log('Chart legend changed to:', this.lineChartLegend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchChartData(): void {
|
||||||
|
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||||
|
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have the necessary data, fetch chart data from the service
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
|
console.log('Fetching chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
|
||||||
|
// Convert yAxis to string if it's an array
|
||||||
|
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/line?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Chart data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// For base level, we pass empty parameter and value
|
||||||
|
this.dashboardService.getChartData(this.table, 'line', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received chart data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// Backend has already filtered the data, just display it
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.lineChartLabels = data.chartLabels;
|
||||||
|
this.lineChartData = data.chartData;
|
||||||
|
// Trigger change detection
|
||||||
|
this.lineChartData = [...this.lineChartData];
|
||||||
|
console.log('Updated line chart with data:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||||
|
} else if (data && data.labels && data.datasets) {
|
||||||
|
// Backend has already filtered the data, just display it
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.lineChartLabels = data.labels;
|
||||||
|
this.lineChartData = data.datasets;
|
||||||
|
// Trigger change detection
|
||||||
|
this.lineChartData = [...this.lineChartData];
|
||||||
|
console.log('Updated line chart with legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching chart data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
// Keep default data in case of error
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Missing required data for chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch drilldown data based on current drilldown level
|
||||||
|
fetchDrilldownData(): void {
|
||||||
|
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||||
|
console.log('Drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Get the current drilldown configuration based on the current level
|
||||||
|
let drilldownConfig;
|
||||||
|
if (this.currentDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
} else {
|
||||||
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||||
|
|
||||||
|
// Check if we have valid drilldown configuration
|
||||||
|
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||||
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter value from the drilldown stack
|
||||||
|
let parameterValue = '';
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||||
|
parameterValue = lastEntry.clickedValue || '';
|
||||||
|
console.log('Parameter value from last click:', parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter field from drilldown config
|
||||||
|
const parameterField = drilldownConfig.parameter || '';
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||||
|
apiUrl: drilldownConfig.apiUrl,
|
||||||
|
xAxis: drilldownConfig.xAxis,
|
||||||
|
yAxis: drilldownConfig.yAxis,
|
||||||
|
parameterField: parameterField,
|
||||||
|
parameterValue: parameterValue,
|
||||||
|
connection: this.connection
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the actual API URL with parameter replacement
|
||||||
|
let actualApiUrl = drilldownConfig.apiUrl;
|
||||||
|
console.log('Original API URL:', actualApiUrl);
|
||||||
|
console.log('Parameter value to use:', parameterValue);
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
// Check if the URL contains angle brackets for parameter replacement
|
||||||
|
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||||
|
|
||||||
|
if (hasAngleBrackets && parameterValue) {
|
||||||
|
// Replace angle brackets placeholder with actual value
|
||||||
|
console.log('Replacing angle brackets with parameter value');
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/line?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Drilldown data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// Backend handles filtering, we just pass the parameter field and value
|
||||||
|
this.dashboardService.getChartData(actualApiUrl, 'line', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received drilldown data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// Backend has already filtered the data, just display it
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.lineChartLabels = data.chartLabels;
|
||||||
|
this.lineChartData = data.chartData;
|
||||||
|
// Trigger change detection
|
||||||
|
this.lineChartData = [...this.lineChartData];
|
||||||
|
console.log('Updated line chart with drilldown data:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||||
|
} else if (data && data.labels && data.datasets) {
|
||||||
|
// Backend has already filtered the data, just display it
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.lineChartLabels = data.labels;
|
||||||
|
this.lineChartData = data.datasets;
|
||||||
|
// Trigger change detection
|
||||||
|
this.lineChartData = [...this.lineChartData];
|
||||||
|
console.log('Updated line chart with drilldown legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching drilldown data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.lineChartLabels = [];
|
||||||
|
this.lineChartData = [];
|
||||||
|
// Keep current data in case of error
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to original data (go back to base level)
|
||||||
|
resetToOriginalData(): void {
|
||||||
|
console.log('Resetting to original data');
|
||||||
|
console.log('Current stack before reset:', this.drilldownStack);
|
||||||
|
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
this.currentDrilldownLevel = 0;
|
||||||
|
this.drilldownStack = [];
|
||||||
|
|
||||||
|
if (this.originalLineChartLabels.length > 0) {
|
||||||
|
this.lineChartLabels = [...this.originalLineChartLabels];
|
||||||
|
console.log('Restored original labels');
|
||||||
|
}
|
||||||
|
if (this.originalLineChartData.length > 0) {
|
||||||
|
this.lineChartData = [...this.originalLineChartData];
|
||||||
|
console.log('Restored original data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('After reset - labels:', this.lineChartLabels);
|
||||||
|
console.log('After reset - data:', this.lineChartData);
|
||||||
|
|
||||||
|
// Re-fetch original data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate back to previous drilldown level
|
||||||
|
navigateBack(): void {
|
||||||
|
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||||
|
console.log('Current level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Remove the last entry from the stack
|
||||||
|
const removedEntry = this.drilldownStack.pop();
|
||||||
|
console.log('Removed entry from stack:', removedEntry);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||||
|
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||||
|
console.log('Stack after pop:', this.drilldownStack);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Fetch data for the previous level
|
||||||
|
console.log('Fetching data for previous level');
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
// Back to base level
|
||||||
|
console.log('Back to base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Already at base level, reset to original data
|
||||||
|
console.log('Already at base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public randomize(): void {
|
||||||
|
let _lineChartData: Array<any> = new Array(this.lineChartData.length);
|
||||||
for (let i = 0; i < this.lineChartData.length; i++) {
|
for (let i = 0; i < this.lineChartData.length; i++) {
|
||||||
_lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label};
|
_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++) {
|
for (let j = 0; j < this.lineChartData[i].data.length; j++) {
|
||||||
@ -56,16 +384,91 @@ export class LineChartComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
public chartClicked(e:any):void {
|
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);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public chartHovered(e:any):void {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,9 +1,44 @@
|
|||||||
<div style="display: block;">
|
<div class="pie-chart-container">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<!-- Show loading indicator -->
|
||||||
|
<div class="loading-indicator" *ngIf="pieChartLabels.length === 0 && pieChartData.length === 0 && !noDataAvailable">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading chart data...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show no data message -->
|
||||||
|
<div class="no-data-message" *ngIf="noDataAvailable && pieChartLabels.length === 0">
|
||||||
|
<p>No chart data available</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Show chart when data is available -->
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
|
*ngIf="pieChartLabels.length > 0 && pieChartData.length > 0"
|
||||||
[data]="pieChartData"
|
[data]="pieChartData"
|
||||||
[labels]="pieChartLabels"
|
[labels]="pieChartLabels"
|
||||||
[type]="pieChartType"
|
[type]="pieChartType"
|
||||||
|
[options]="pieChartOptions"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</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>
|
</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">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="polarAreaChartData"
|
[data]="polarAreaChartData"
|
||||||
[labels]="polarAreaChartLabels"
|
[labels]="polarAreaChartLabels"
|
||||||
[type]="polarAreaChartType"
|
[type]="polarAreaChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,16 +1,68 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||||
|
|
||||||
@Component({
|
@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'}
|
||||||
@ -18,16 +70,376 @@ export class PolarChartComponent implements OnInit {
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
// public radarChartData: any = [
|
fetchChartData(): void {
|
||||||
// { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" },
|
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||||
// { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" }
|
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||||
// ];
|
this.fetchDrilldownData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have the necessary data, fetch chart data from the service
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
|
console.log('Fetching polar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
|
||||||
|
// Convert yAxis to string if it's an array
|
||||||
|
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/polar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Polar chart data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// For base level, we pass empty parameter and value
|
||||||
|
this.dashboardService.getChartData(this.table, 'polar', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received polar chart data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For polar charts, we need to extract the data differently
|
||||||
|
// The first dataset's data array contains the values for the polar chart
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.polarAreaChartLabels = data.chartLabels;
|
||||||
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
|
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
}
|
||||||
|
// Trigger change detection
|
||||||
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
|
console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
|
} else if (data && data.labels && data.data) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.polarAreaChartLabels = data.labels;
|
||||||
|
this.polarAreaChartData = data.data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
|
});
|
||||||
|
// Trigger change detection
|
||||||
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
|
console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Polar chart received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching polar chart data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
// Keep default data in case of error
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch drilldown data based on current drilldown level
|
||||||
|
fetchDrilldownData(): void {
|
||||||
|
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||||
|
console.log('Drilldown stack:', this.drilldownStack);
|
||||||
|
|
||||||
|
// Get the current drilldown configuration based on the current level
|
||||||
|
let drilldownConfig;
|
||||||
|
if (this.currentDrilldownLevel === 1) {
|
||||||
|
// Base drilldown level
|
||||||
|
drilldownConfig = {
|
||||||
|
apiUrl: this.drilldownApiUrl,
|
||||||
|
xAxis: this.drilldownXAxis,
|
||||||
|
yAxis: this.drilldownYAxis,
|
||||||
|
parameter: this.drilldownParameter
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Multi-layer drilldown level
|
||||||
|
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||||
|
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||||
|
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||||
|
} else {
|
||||||
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||||
|
|
||||||
|
// Check if we have valid drilldown configuration
|
||||||
|
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||||
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter value from the drilldown stack
|
||||||
|
let parameterValue = '';
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||||
|
parameterValue = lastEntry.clickedValue || '';
|
||||||
|
console.log('Parameter value from last click:', parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameter field from drilldown config
|
||||||
|
const parameterField = drilldownConfig.parameter || '';
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||||
|
apiUrl: drilldownConfig.apiUrl,
|
||||||
|
xAxis: drilldownConfig.xAxis,
|
||||||
|
yAxis: drilldownConfig.yAxis,
|
||||||
|
parameterField: parameterField,
|
||||||
|
parameterValue: parameterValue,
|
||||||
|
connection: this.connection
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the actual API URL with parameter replacement
|
||||||
|
let actualApiUrl = drilldownConfig.apiUrl;
|
||||||
|
console.log('Original API URL:', actualApiUrl);
|
||||||
|
console.log('Parameter value to use:', parameterValue);
|
||||||
|
console.log('Parameter field:', parameterField);
|
||||||
|
|
||||||
|
// Check if the URL contains angle brackets for parameter replacement
|
||||||
|
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||||
|
|
||||||
|
if (hasAngleBrackets && parameterValue) {
|
||||||
|
// Replace angle brackets placeholder with actual value
|
||||||
|
console.log('Replacing angle brackets with parameter value');
|
||||||
|
const encodedValue = encodeURIComponent(parameterValue);
|
||||||
|
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||||
|
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the URL that will be called
|
||||||
|
const url = `chart/getdashjson/polar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||||
|
console.log('Drilldown data URL:', url);
|
||||||
|
|
||||||
|
// Fetch data from the dashboard service with parameter field and value
|
||||||
|
// Backend handles filtering, we just pass the parameter field and value
|
||||||
|
this.dashboardService.getChartData(actualApiUrl, 'polar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
console.log('Received drilldown data:', data);
|
||||||
|
if (data === null) {
|
||||||
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the actual data structure returned by the API
|
||||||
|
if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For polar charts, we need to extract the data differently
|
||||||
|
// The first dataset's data array contains the values for the polar chart
|
||||||
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
|
this.polarAreaChartLabels = data.chartLabels;
|
||||||
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
|
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
}
|
||||||
|
// Trigger change detection
|
||||||
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
|
console.log('Updated polar chart with drilldown data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
|
} else if (data && data.labels && data.data) {
|
||||||
|
// Handle the original expected format as fallback
|
||||||
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
|
this.polarAreaChartLabels = data.labels;
|
||||||
|
this.polarAreaChartData = data.data.map(value => {
|
||||||
|
// Convert to number if it's not already
|
||||||
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
|
});
|
||||||
|
// Trigger change detection
|
||||||
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
|
console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
|
} else {
|
||||||
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching drilldown data:', error);
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
this.polarAreaChartLabels = [];
|
||||||
|
this.polarAreaChartData = [];
|
||||||
|
// Keep current data in case of error
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to original data (go back to base level)
|
||||||
|
resetToOriginalData(): void {
|
||||||
|
console.log('Resetting to original data');
|
||||||
|
console.log('Current stack before reset:', this.drilldownStack);
|
||||||
|
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
this.currentDrilldownLevel = 0;
|
||||||
|
this.drilldownStack = [];
|
||||||
|
|
||||||
|
if (this.originalPolarAreaChartLabels.length > 0) {
|
||||||
|
this.polarAreaChartLabels = [...this.originalPolarAreaChartLabels];
|
||||||
|
console.log('Restored original labels');
|
||||||
|
}
|
||||||
|
if (this.originalPolarAreaChartData.length > 0) {
|
||||||
|
this.polarAreaChartData = [...this.originalPolarAreaChartData];
|
||||||
|
console.log('Restored original data');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('After reset - labels:', this.polarAreaChartLabels);
|
||||||
|
console.log('After reset - data:', this.polarAreaChartData);
|
||||||
|
|
||||||
|
// Re-fetch original data
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate back to previous drilldown level
|
||||||
|
navigateBack(): void {
|
||||||
|
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||||
|
console.log('Current level:', this.currentDrilldownLevel);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Remove the last entry from the stack
|
||||||
|
const removedEntry = this.drilldownStack.pop();
|
||||||
|
console.log('Removed entry from stack:', removedEntry);
|
||||||
|
|
||||||
|
// Update the current drilldown level
|
||||||
|
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||||
|
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||||
|
console.log('Stack after pop:', this.drilldownStack);
|
||||||
|
|
||||||
|
if (this.drilldownStack.length > 0) {
|
||||||
|
// Fetch data for the previous level
|
||||||
|
console.log('Fetching data for previous level');
|
||||||
|
this.fetchDrilldownData();
|
||||||
|
} else {
|
||||||
|
// Back to base level
|
||||||
|
console.log('Back to base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Already at base level, reset to original data
|
||||||
|
console.log('Already at base level, resetting to original data');
|
||||||
|
this.resetToOriginalData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// events
|
// 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 {
|
||||||
|
|||||||
@ -1,8 +1,28 @@
|
|||||||
<div style="display: block">
|
<div style="display: block">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="radarChartData"
|
[datasets]="radarChartData"
|
||||||
[labels]="radarChartLabels"
|
[labels]="radarChartLabels"
|
||||||
[type]="radarChartType"
|
[type]="radarChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)"></canvas>
|
(chartClick)="chartClicked($event)">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,13 +1,38 @@
|
|||||||
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[];
|
||||||
|
@Input() table: string;
|
||||||
|
@Input() datastore: string;
|
||||||
|
@Input() charttitle: string;
|
||||||
|
@Input() chartlegend: boolean = true;
|
||||||
|
@Input() showlabel: boolean = true;
|
||||||
|
@Input() chartcolor: boolean;
|
||||||
|
@Input() slices: boolean;
|
||||||
|
@Input() donut: boolean;
|
||||||
|
@Input() charturl: string;
|
||||||
|
@Input() chartparameter: string;
|
||||||
|
@Input() datasource: string;
|
||||||
|
@Input() fieldName: string;
|
||||||
|
@Input() connection: number; // Add connection input
|
||||||
|
// Drilldown configuration inputs
|
||||||
|
@Input() drilldownEnabled: boolean = false;
|
||||||
|
@Input() drilldownApiUrl: string;
|
||||||
|
@Input() drilldownXAxis: string;
|
||||||
|
@Input() drilldownYAxis: string;
|
||||||
|
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||||
|
// Multi-layer drilldown configuration inputs
|
||||||
|
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||||
|
|
||||||
|
// Radar
|
||||||
|
public radarChartLabels: string[] = [
|
||||||
"Eating",
|
"Eating",
|
||||||
"Drinking",
|
"Drinking",
|
||||||
"Sleeping",
|
"Sleeping",
|
||||||
@ -15,25 +40,421 @@ public radarChartLabels: string[] = [
|
|||||||
"Coding",
|
"Coding",
|
||||||
"Cycling",
|
"Cycling",
|
||||||
"Running"
|
"Running"
|
||||||
];
|
];
|
||||||
|
|
||||||
public radarChartData: any = [
|
public radarChartData: 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" }
|
||||||
];
|
];
|
||||||
public radarChartType: string = "radar";
|
public radarChartType: string = "radar";
|
||||||
|
|
||||||
// events
|
// Multi-layer drilldown state tracking
|
||||||
public chartClicked(e: any): void {
|
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||||
console.log(e);
|
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||||
}
|
originalRadarChartLabels: string[] = [];
|
||||||
|
originalRadarChartData: any = [];
|
||||||
|
|
||||||
public chartHovered(e: any): void {
|
// No data state
|
||||||
console.log(e);
|
noDataAvailable: boolean = false;
|
||||||
}
|
|
||||||
constructor() { }
|
constructor(private dashboardService: Dashboard3Service) { }
|
||||||
|
|
||||||
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">
|
||||||
|
<!-- Drilldown mode indicator -->
|
||||||
|
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||||
|
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||||
|
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Level {{currentDrilldownLevel - 1}}
|
||||||
|
</button>
|
||||||
|
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||||
|
Back to Main View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No data message -->
|
||||||
|
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||||
|
No data available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart display -->
|
||||||
|
<div *ngIf="!noDataAvailable">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="scatterChartData"
|
[datasets]="scatterChartData"
|
||||||
[type]="scatterChartType"
|
[type]="scatterChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
</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 },
|
||||||
@ -65,10 +93,374 @@ 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 {
|
||||||
|
|||||||
@ -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