dash
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,542 @@
|
||||
<ol class="breadcrumb breadcrumb-arrow font-trirong">
|
||||
<li><a href="javascript://"> Data lake</a></li>
|
||||
</ol>
|
||||
<div class="dg-wrapper">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-8">
|
||||
<h3>Data lake </h3>
|
||||
</div>
|
||||
<div class="clr-col-4" style="text-align: right;">
|
||||
<button *ngIf="cardButton" id="add" class="btn btn-primary btn-icon" (click)="changeView()">
|
||||
<clr-icon *ngIf="!isCardview" shape="grid-view"></clr-icon> <clr-icon *ngIf="isCardview"
|
||||
shape="bars"></clr-icon>
|
||||
</button>
|
||||
<!-- button -->
|
||||
<button id="add" class="btn btn-primary" (click)="goToAdd(product)">
|
||||
<clr-icon shape="plus"></clr-icon>ADD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="!isCardview"> <!-- GET ALL --> <clr-datagrid [clrDgLoading]="loading"
|
||||
[(clrDgSelected)]="selected">
|
||||
<clr-dg-placeholder>
|
||||
<ng-template #loadingSpinner>
|
||||
<clr-spinner>Loading ... </clr-spinner>
|
||||
</ng-template>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
||||
</clr-dg-placeholder>
|
||||
|
||||
<clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'schedule'"> <ng-container *clrDgHideableColumn="{hidden: false}"> schedule
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'url_endpoint'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url Endpoint
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
<clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
<clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}"> SureConnect
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
<!-- who column -->
|
||||
<clr-dg-column> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
<clr-icon shape="bars"></clr-icon> Action
|
||||
</ng-container></clr-dg-column>
|
||||
<!-- end -->
|
||||
|
||||
<clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user">
|
||||
|
||||
<clr-dg-cell>{{user.name }}</clr-dg-cell>
|
||||
|
||||
|
||||
<clr-dg-cell (click)="goTourlUrl(user.url)"
|
||||
style="cursor: pointer; color: rgb(108, 108, 194);">{{user.url}}</clr-dg-cell>
|
||||
|
||||
|
||||
<clr-dg-cell>{{user.schedule }}</clr-dg-cell>
|
||||
|
||||
|
||||
<clr-dg-cell>{{user.cron_job }}</clr-dg-cell>
|
||||
|
||||
|
||||
<clr-dg-cell (click)="goToReplaceStringjson(user.json)" style="cursor: pointer; align-items: center;"><clr-icon
|
||||
shape="details"></clr-icon></clr-dg-cell>
|
||||
|
||||
|
||||
<clr-dg-cell>
|
||||
<div style="display: flex; align-items: center; max-width: 200px;">
|
||||
<span style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;"
|
||||
title="{{user.url}}" (click)="showFullUrl(user.url)">
|
||||
{{user.url | slice:0:30}}{{user.url.length > 30 ? '...' : ''}}
|
||||
</span>
|
||||
<button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url)" title="Copy URL">
|
||||
<clr-icon shape="copy-to-clipboard"></clr-icon>
|
||||
</button>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.batch_volume}}</clr-dg-cell>
|
||||
|
||||
<clr-dg-cell>{{getSureConnectNameById(user.sure_connect_id)}}</clr-dg-cell>
|
||||
|
||||
<!-- who column -->
|
||||
<clr-dg-cell>
|
||||
<clr-signpost>
|
||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
||||
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
||||
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||
<h5 style="margin-top: 0">Who Column</h5>
|
||||
<div>Account ID: <code class="clr-code">{{user.accountId}}</code></div>
|
||||
<div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div>
|
||||
<div>Created By: <code class="clr-code">{{user.createdBy}}</code></div>
|
||||
<div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div>
|
||||
<div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
|
||||
<!-- New JSON Update button -->
|
||||
<button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</button>
|
||||
</clr-dg-cell>
|
||||
|
||||
<!-- who colmn -->
|
||||
|
||||
<!-- <clr-dg-cell>
|
||||
<button class="btn btn-icon" (click)="onEdit(user)">
|
||||
<clr-icon shape="pencil"></clr-icon>
|
||||
</button>
|
||||
<button class="btn btn-icon" (click)="onDelete(user)">
|
||||
<clr-icon shape="trash"></clr-icon>
|
||||
</button>
|
||||
|
||||
</clr-dg-cell> -->
|
||||
|
||||
<clr-dg-action-overflow>
|
||||
<button class="action-item" (click)="onEdit(user)">Edit</button>
|
||||
<button class="action-item" (click)="onDelete(user)">Delete</button>
|
||||
</clr-dg-action-overflow>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||
of {{pagination.totalItems}} users
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid> </ng-container>
|
||||
<ng-template #showInfo>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert-items">
|
||||
<div class="alert-item static">
|
||||
<span class="alert-text">
|
||||
<clr-icon class="alert-icon" shape="info-circle"></clr-icon>
|
||||
Data could be found. Loading..
|
||||
<clr-spinner [clrMedium]="true">Loading ...</clr-spinner>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template><ng-container *ngIf="isCardview">
|
||||
<div *ngIf="product; else showInfo" class="clr-row clr-align-items-start clr-justify-content-start">
|
||||
<div *ngFor="let app of product| filter:search; let index = i" class="clr-col-auto">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-lg-12 clr-col-md-4 clr-col-sm-4 clr-col-12" style="width: 410px;">
|
||||
<div class="card" style="padding: 10px; "
|
||||
[style.background-color]="cardmodal.cardColor !== '' ? cardmodal.cardColor : 'white'">
|
||||
<div class="card-body"
|
||||
style="display: grid; grid-template-columns: repeat(13, 1fr); grid-template-rows: repeat(7, 1fr); gap: 5px;">
|
||||
<ng-container *ngFor="let item of dashboardArray">
|
||||
<div [style.gridColumn]="item.x + 1" [style.gridRow]="item.y + 1"
|
||||
[style.gridColumnEnd]="item.x + item.cols + 1" [style.gridRowEnd]="item.y + item.rows + 1">
|
||||
<div *ngIf="item.name === 'textField'" class="title-card card-title"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'"
|
||||
[style.background-color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor"
|
||||
[style.color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor">
|
||||
{{beforeText(item.fieldtext)}}
|
||||
{{ app[transform(item.fieldtext) ] }}
|
||||
{{afterText(item.fieldtext)}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="item.name === 'dateField'" class="title-card card-title"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'"
|
||||
[style.background-color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor"
|
||||
[style.color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor">
|
||||
{{beforeText(item.fieldtext)}}
|
||||
{{ app[transform(item.fieldtext) ] | date}}
|
||||
{{afterText(item.fieldtext)}}
|
||||
</div>
|
||||
<div *ngIf="item.name === 'numberField'" class="title-card card-title"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'"
|
||||
[style.background-color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor"
|
||||
[style.color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor">
|
||||
{{beforeText(item.fieldtext)}}
|
||||
{{ app[transform(item.fieldtext) ]}}
|
||||
{{afterText(item.fieldtext)}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="item.name === 'Line'" class="title-card card-title"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'">
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
|
||||
<div *ngIf="item.name === 'Icon'" class="icon-card"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'">
|
||||
<clr-icon [attr.shape]="item.iconName"></clr-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="item.name == 'Image'"
|
||||
[style.text-align]="item.alignment !== '' ? item.alignment : 'left'"
|
||||
[style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
[style.font-size]="item.fontSize !== '' ? item.fontSize : '100%'"
|
||||
[style.font-style]="item.italic == true ? 'Italic' : 'normal'"
|
||||
[style.font-weight]="item.bold == true ? 'bold' : 'normal'" [style.text-decoration]="(item.underline && item.strikethough) ? 'underline line-through' :
|
||||
(item.underline ? 'underline' : (item.strikethough ? 'line-through' : 'none'))"
|
||||
[style.background-color]="item.backgroundcolor !== '' ? item.backgroundcolor : 'white'"
|
||||
[style.color]="item.textcolor !== '' ? item.textcolor : 'black'"
|
||||
[style.background-color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditionbackgroundcolor : item.backgroundcolor"
|
||||
[style.color]="item.conditionValue == app[transform(item.fieldtext) ] ? item.conditiontextcolor : item.textcolor">
|
||||
<img id="filePreview" [src]="item.imageURL" alt="File Preview"
|
||||
[style.width]="item.imagewidth !== '' ? item.imagewidth + 'px' : '100px'"
|
||||
[style.height]="item.imagewidth !== '' ? item.imagewidth + 'px' : '100px'"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<clr-modal [(clrModalOpen)]="rsModaljson" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true">
|
||||
<div class="modal-body">
|
||||
<textarea class="form-control" style="width:100%; height: 400px;" readonly>{{rowSelected}}</textarea>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
||||
|
||||
|
||||
<!-- // EDIT DATA......... -->
|
||||
<clr-modal [(clrModalOpen)]="modalEdit" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">Update Data lake
|
||||
<!--update button -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</h3>
|
||||
<div class="modal-body" *ngIf="rowSelected.id">
|
||||
<h2 class="heading">{{rowSelected.id}}</h2>
|
||||
<!-- button -->
|
||||
<form>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label>Name</label>
|
||||
<input class="clr-input" type="text" [(ngModel)]="rowSelected.name" name="name" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> Url</label>
|
||||
<input type="url" name="url" [(ngModel)]="rowSelected.url" placeholder="Enter URL" class="clr-input"
|
||||
pattern="https?://.+">
|
||||
<div *ngIf="!isValidurl(rowSelected.url )" class="error_mess"> * Please enter a valid URL. </div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>schedule</label>
|
||||
<input class="clr-input" type="text" [(ngModel)]="rowSelected.schedule" name="schedule" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>cron job</label>
|
||||
<input class="clr-input" type="text" [(ngModel)]="rowSelected.cron_job" name="cron_job" />
|
||||
<app-cron-job-builder [cronExpression]="editCronExpression" (cronExpressionChange)="onEditCronExpressionChange($event)" instanceId="edit-form-{{rowSelected.id}}"></app-cron-job-builder>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> json</label>
|
||||
<input id="name" type="Text" class="form-control" style="border: none; outline: none; height:33px !important;"
|
||||
[(ngModel)]="rowSelected.json" name=" json " />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>Url</label>
|
||||
<input class="clr-input" type="text" [(ngModel)]="rowSelected.url_endpoint" name="url_endpoint" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>Batch Volume</label>
|
||||
<input class="clr-input" type="number" [(ngModel)]="rowSelected.batch_volume" name="batch_volume" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>SureConnect</label>
|
||||
<select class="clr-input" [(ngModel)]="rowSelected.sure_connect_id" name="sure_connect_id" (change)="onEditSureConnectChange($event)">
|
||||
<option value="">Select SureConnect</option>
|
||||
<option *ngFor="let sureConnect of sureConnectList" [value]="sureConnect.id">{{sureConnect.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- form code start -->
|
||||
<div *ngIf="checkFormCode">
|
||||
<h4 style="font-weight: 300;display: inline;">Extension</h4>
|
||||
<br>
|
||||
<hr>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4" *ngFor="let field of additionalFieldsFromBackend">
|
||||
<ng-container *ngIf="field.formCode === formcode" [ngSwitch]="field.fieldType">
|
||||
<!-- Text Input --> <label *ngSwitchCase="'text'">{{ field.fieldName }}</label>
|
||||
<input *ngSwitchCase="'text'" [type]="field.fieldType" name="{{ field.extValue }}"
|
||||
[(ngModel)]="rowSelected[field.extValue]" class="clr-input" />
|
||||
|
||||
<!-- Date Input --> <label *ngSwitchCase="'date'">{{ field.fieldName }}</label>
|
||||
<input *ngSwitchCase="'date'" [type]="field.fieldType" name="{{ field.extValue }}"
|
||||
[(ngModel)]="rowSelected[field.extValue]" class="clr-input" />
|
||||
|
||||
<!-- Textarea --> <label *ngSwitchCase="'textarea'">{{ field.fieldName }}</label>
|
||||
<textarea *ngSwitchCase="'textarea'" name="{{ field.extValue }}" [(ngModel)]="rowSelected[field.extValue]"
|
||||
col="10" row="2"></textarea>
|
||||
|
||||
<!-- Checkbox --> <label *ngSwitchCase="'checkbox'">{{ field.fieldName }}</label><br>
|
||||
<input *ngSwitchCase="'checkbox'" [type]="field.fieldType" name="{{ field.extValue }}"
|
||||
[(ngModel)]="rowSelected[field.extValue]" class="clr-checkbox" />
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- form code end -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="modalEdit = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="onUpdate(rowSelected.id)">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||
<div class="modal-body" *ngIf="rowSelected.id">
|
||||
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||
<h2 class="heading">{{rowSelected.id}}</h2>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
||||
<button type="button" (click)="delete(rowSelected.id)" class="btn btn-primary">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-modal>
|
||||
<!-- ADD FORM ..... -->
|
||||
<clr-modal [(clrModalOpen)]="modalAdd" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||
<h3 class="modal-title">Add Data lake
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- aeroplane icon -->
|
||||
|
||||
<a *ngIf="userrole?.includes('ADMIN')" style="float: right;" href="javascript:void(0)" role="tooltip"
|
||||
aria-haspopup="true" class="tooltip tooltip-sm tooltip-bottom-left">
|
||||
<a id="build_extension" [routerLink]="['../extension/all']" [queryParams]="{ formCode: 'Data_lake_formCode' }">
|
||||
<clr-icon shape="airplane" size="32"></clr-icon>
|
||||
</a>
|
||||
<span class="tooltip-content">Form Extension</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="entryForm">
|
||||
<div class="clr-row" style="height: fit-content;">
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> Name</label>
|
||||
<input class="clr-input" type="text" formControlName="name" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> Url </label>
|
||||
<input type="url" formControlName="url" class="clr-input" pattern="https?://.+">
|
||||
<div *ngIf="entryForm.controls['url'].errors" class="error_mess">
|
||||
<div *ngIf="entryForm.controls['url'].hasError('pattern')" class="error_mess"> * Please enter a valid URL.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> schedule</label>
|
||||
<input class="clr-input" type="text" formControlName="schedule" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> cron job</label>
|
||||
<input class="clr-input" type="text" formControlName="cron_job" />
|
||||
<app-cron-job-builder [cronExpression]="addCronExpression" (cronExpressionChange)="onAddCronExpressionChange($event)" instanceId="add-form"></app-cron-job-builder>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>json</label>
|
||||
<input class="form-control" type="Text" formControlName="json"
|
||||
style="border: none; outline: none; height:33px !important;" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> Url Endpoint</label>
|
||||
<input class="clr-input" type="text" formControlName="url_endpoint" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label> Batch Volume</label>
|
||||
<input class="clr-input" type="number" formControlName="batch_volume" />
|
||||
</div>
|
||||
|
||||
<div class="clr-col-sm-12">
|
||||
<label>SureConnect</label>
|
||||
<select class="clr-input" formControlName="sure_connect_id" (change)="onAddSureConnectChange($event)">
|
||||
<option value="">Select SureConnect</option>
|
||||
<option *ngFor="let sureConnect of sureConnectList" [value]="sureConnect.id">{{sureConnect.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- form code start -->
|
||||
<div *ngIf="checkFormCode">
|
||||
<h4 style="font-weight: 300;display: inline;">Extension</h4>
|
||||
<br>
|
||||
<hr>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-4" *ngFor="let field of additionalFieldsFromBackend">
|
||||
<ng-container *ngIf="field.formCode === formcode" [ngSwitch]="field.fieldType">
|
||||
<!-- Text Input --> <label *ngSwitchCase="'text'">{{ field.fieldName }}</label>
|
||||
<input *ngSwitchCase="'text'" [type]="field.fieldType" [formControlName]="field.extValue"
|
||||
class="clr-input" />
|
||||
<!-- Date Input --> <label *ngSwitchCase="'date'">{{ field.fieldName }}</label>
|
||||
<input *ngSwitchCase="'date'" [type]="field.fieldType" [formControlName]="field.extValue"
|
||||
class="clr-input" />
|
||||
<!-- Textarea --> <label *ngSwitchCase="'textarea'">{{ field.fieldName }}</label>
|
||||
<textarea *ngSwitchCase="'textarea'" [formControlName]="field.extValue" col="10" row="2"></textarea>
|
||||
<!-- Checkbox --> <label *ngSwitchCase="'checkbox'">{{ field.fieldName }}</label><br>
|
||||
<input *ngSwitchCase="'checkbox'" [type]="field.fieldType" [formControlName]="field.extValue"
|
||||
class="clr-checkbox" />
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- form code end -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="modalAdd = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="onSubmit()">ADD</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- htmlpopup -->
|
||||
@@ -0,0 +1,85 @@
|
||||
//@import "../../../../assets/scss/var";
|
||||
.s-info-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.delete,.heading{
|
||||
text-align: center;
|
||||
color: red;
|
||||
}
|
||||
.entry-pg {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.button1::after {
|
||||
content: none;
|
||||
}
|
||||
.button1:hover::after {
|
||||
content: "ADD ROWS";
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #dddddd;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section p {
|
||||
//color: white;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clr-input {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clr-file {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
//padding: 0.6rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
select{
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
input[type=text],[type=date],[type=number],textarea {
|
||||
width: 100%;
|
||||
padding: 15px 15px;
|
||||
background-color:rgb(255, 255, 255);
|
||||
// margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.error_mess {
|
||||
color: red;
|
||||
}
|
||||
.universal-section-header {
|
||||
margin: 24px 0 10px 0;
|
||||
font-weight: 600;
|
||||
color: #1a237e;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { AlertService } from 'src/app/services/alert.service';
|
||||
import { Data_lakeservice } from './Data_lake.service';
|
||||
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators, ValidationErrors } from '@angular/forms';
|
||||
import { ExtensionService } from 'src/app/services/fnd/extension.service';
|
||||
import { DashboardContentModel2 } from 'src/app/models/builder/dashboard';
|
||||
import { Data_lakecardvariable } from './Data_lake_cardvariable';
|
||||
import { UserInfoService } from 'src/app/services/user-info.service';
|
||||
import { SureconnectService } from '../sureconnect/sureconnect.service';
|
||||
declare var JsBarcode: any;
|
||||
@Component({
|
||||
selector: 'app-Data_lake',
|
||||
templateUrl: './Data_lake.component.html',
|
||||
styleUrls: ['./Data_lake.component.scss']
|
||||
})
|
||||
export class Data_lakeComponent implements OnInit {
|
||||
cardButton = Data_lakecardvariable.cardButton;
|
||||
cardmodeldata = Data_lakecardvariable.cardmodeldata;
|
||||
public dashboardArray: DashboardContentModel2[];
|
||||
isCardview = Data_lakecardvariable.cardButton;
|
||||
cardmodal; changeView() {
|
||||
this.isCardview = !this.isCardview;
|
||||
}
|
||||
beforeText(fieldtext: string): string { // Extract the text before the first '<'
|
||||
const index = fieldtext.indexOf('<');
|
||||
return index !== -1 ? fieldtext.substring(0, index) : fieldtext;
|
||||
}
|
||||
afterText(fieldtext: string): string { // Extract the text after the last '>'
|
||||
const index = fieldtext.lastIndexOf('>');
|
||||
return index !== -1 ? fieldtext.substring(index + 1) : '';
|
||||
}
|
||||
transform(fieldtext: string): string {
|
||||
const match = fieldtext.match(/<([^>]*)>/);
|
||||
return match ? match[1] : ''; // Extract the text between '<' and '>'
|
||||
}
|
||||
userrole;
|
||||
rowSelected: any = {};
|
||||
modaldelete = false;
|
||||
modalEdit = false;
|
||||
modalAdd = false;
|
||||
public entryForm: FormGroup;
|
||||
loading = false;
|
||||
product;
|
||||
modalOpenedforNewLine = false;
|
||||
newLine: any;
|
||||
additionalFieldsFromBackend: any[] = [];
|
||||
formcode = 'Data_lake_formCode'
|
||||
tableName = 'Data_lake'; checkFormCode; selected: any[] = [];
|
||||
|
||||
// New properties for URL handling
|
||||
urlModalOpen = false;
|
||||
selectedUrl = '';
|
||||
|
||||
// Cron job builder properties
|
||||
addCronExpression = '';
|
||||
editCronExpression = '';
|
||||
|
||||
// SureConnect properties
|
||||
sureConnectList;
|
||||
selectedSureConnect: any = null;
|
||||
|
||||
constructor(
|
||||
private extensionService: ExtensionService,
|
||||
private userInfoService: UserInfoService,
|
||||
private mainService: Data_lakeservice,
|
||||
private alertService: AlertService,
|
||||
private toastr: ToastrService,
|
||||
private _fb: FormBuilder,
|
||||
private sureConnectService: SureconnectService
|
||||
) { }
|
||||
private editInterval: any;
|
||||
// component button
|
||||
ngOnInit(): void {
|
||||
if (this.cardmodeldata !== '') {
|
||||
this.cardmodal = JSON.parse(this.cardmodeldata);
|
||||
this.dashboardArray = this.cardmodal.dashboard.slice();
|
||||
console.log(this.dashboardArray)
|
||||
}
|
||||
this.userrole = this.userInfoService.getRoles();
|
||||
this.getData();
|
||||
this.getSureConnectList(); // Fetch SureConnect list
|
||||
this.entryForm = this._fb.group({
|
||||
name: [null],
|
||||
|
||||
url: [null],
|
||||
|
||||
schedule: [null],
|
||||
|
||||
cron_job: [null],
|
||||
|
||||
json: [null],
|
||||
|
||||
url_endpoint: [null],
|
||||
|
||||
batch_volume: [null],
|
||||
|
||||
sure_connect_id: [null], // Add SureConnect field
|
||||
|
||||
}); // component_button200
|
||||
// form code start
|
||||
this.extensionService.getJsonObjectsByFormCodeList(this.formcode).subscribe(data => {
|
||||
console.log(data);
|
||||
const jsonArray = data.map((str) => JSON.parse(str));
|
||||
this.additionalFieldsFromBackend = jsonArray;
|
||||
this.checkFormCode = this.additionalFieldsFromBackend.some(field => field.formCode === "Data_lake_formCode");
|
||||
console.log(this.checkFormCode);
|
||||
console.log(this.additionalFieldsFromBackend);
|
||||
if (this.additionalFieldsFromBackend && this.additionalFieldsFromBackend.length > 0) {
|
||||
this.additionalFieldsFromBackend.forEach(field => {
|
||||
if (field.formCode === this.formcode) {
|
||||
if (!this.entryForm.contains(field.extValue)) {
|
||||
// Add the control only if it doesn't exist in the form
|
||||
this.entryForm.addControl(field.extValue, this._fb.control(field.fieldValue));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(this.entryForm.value);
|
||||
// form code end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
if (this.editInterval) {
|
||||
clearInterval(this.editInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
error;
|
||||
getData() {
|
||||
this.mainService.getAll().subscribe((data) => {
|
||||
console.log(data);
|
||||
this.product = data;
|
||||
this.product = [...this.product].reverse(); if (this.product.length == 0) {
|
||||
this.error = "No Data Available"
|
||||
}
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if (error) {
|
||||
this.error = "Server Error";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch SureConnect list
|
||||
getSureConnectList() {
|
||||
this.sureConnectService.getAll().subscribe((data) => {
|
||||
this.sureConnectList = data;
|
||||
console.log('SureConnect List:', this.sureConnectList);
|
||||
}, (error) => {
|
||||
console.log('Error fetching SureConnect list:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Get SureConnect name by ID for display in grid
|
||||
getSureConnectNameById(id: number): string {
|
||||
if (!id || !this.sureConnectList || this.sureConnectList.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const sureConnect = this.sureConnectList.find(item => item.id === id);
|
||||
return sureConnect ? sureConnect.name : '';
|
||||
}
|
||||
|
||||
onEdit(row) {
|
||||
this.rowSelected = row;
|
||||
this.editCronExpression = row.cron_job || '';
|
||||
// Set the selected SureConnect for edit form
|
||||
this.selectedSureConnect = row.sure_connect_id || null;
|
||||
// Use setTimeout to ensure the component has time to initialize
|
||||
setTimeout(() => {
|
||||
this.modalEdit = true;
|
||||
}, 0);
|
||||
}
|
||||
onDelete(row) {
|
||||
this.rowSelected = row;
|
||||
this.modaldelete = true;
|
||||
}
|
||||
delete(id) {
|
||||
this.modaldelete = false;
|
||||
console.log("in delete " + id);
|
||||
this.mainService.delete(id).subscribe(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
this.ngOnInit();
|
||||
if (data) { this.toastr.success('Deleted successfully'); }
|
||||
});
|
||||
}
|
||||
onUpdate(id) {
|
||||
this.modalEdit = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//console.log("in update");
|
||||
console.log("id " + id);
|
||||
console.log(this.rowSelected);
|
||||
//console.log("out update");
|
||||
this.mainService.update(id, this.rowSelected).subscribe(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
if (data || data.status >= 200 && data.status <= 299) {
|
||||
this.toastr.success("Update Successfully");
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if (error.status >= 200 && error.status <= 299) {
|
||||
// this.toastr.success("update Succesfully");
|
||||
}
|
||||
if (error.status >= 400 && error.status <= 499) {
|
||||
this.toastr.error("Not Updated");
|
||||
}
|
||||
if (error.status >= 500 && error.status <= 599) {
|
||||
this.toastr.error("Not Updated");
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// New method for updating JSON
|
||||
updateJson(id: number) {
|
||||
this.mainService.updateJson(id).subscribe(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
if (data || data.status >= 200 && data.status <= 299) {
|
||||
this.toastr.success("JSON Updated Successfully");
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if (error.status >= 200 && error.status <= 299) {
|
||||
this.toastr.success("JSON Updated Successfully");
|
||||
}
|
||||
if (error.status >= 400 && error.status <= 499) {
|
||||
this.toastr.error("JSON Not Updated");
|
||||
}
|
||||
if (error.status >= 500 && error.status <= 599) {
|
||||
this.toastr.error("JSON Not Updated");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// New method to show full URL in a modal
|
||||
showFullUrl(url: string) {
|
||||
this.selectedUrl = url;
|
||||
this.urlModalOpen = true;
|
||||
}
|
||||
|
||||
// New method to copy text to clipboard
|
||||
copyToClipboard(text: string) {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
this.toastr.success('Copied to clipboard');
|
||||
}
|
||||
|
||||
// Handle cron expression change for add form
|
||||
onAddCronExpressionChange(expression: string) {
|
||||
this.addCronExpression = expression;
|
||||
// Update the form control
|
||||
if (this.entryForm && this.entryForm.get('cron_job')) {
|
||||
this.entryForm.get('cron_job')?.setValue(expression);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cron expression change for edit form
|
||||
onEditCronExpressionChange(expression: string) {
|
||||
this.editCronExpression = expression;
|
||||
// Update the rowSelected
|
||||
if (this.rowSelected) {
|
||||
this.rowSelected.cron_job = expression;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle SureConnect selection change in add form
|
||||
onAddSureConnectChange(event: any) {
|
||||
const selectedId = event.target.value;
|
||||
if (this.entryForm && this.entryForm.get('sure_connect_id')) {
|
||||
this.entryForm.get('sure_connect_id')?.setValue(selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle SureConnect selection change in edit form
|
||||
onEditSureConnectChange(event: any) {
|
||||
const selectedId = event.target.value;
|
||||
if (this.rowSelected) {
|
||||
this.rowSelected.sure_connect_id = selectedId;
|
||||
}
|
||||
}
|
||||
|
||||
onCreate() {
|
||||
this.modalAdd = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
this.mainService.create(this.entryForm.value).subscribe(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
if (data || data.status >= 200 && data.status <= 299) {
|
||||
this.toastr.success("Added Successfully");
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if (error.status >= 200 && error.status <= 299) {
|
||||
// this.toastr.success("Added Succesfully");
|
||||
}
|
||||
if (error.status >= 400 && error.status <= 499) {
|
||||
this.toastr.error("Not Added");
|
||||
}
|
||||
if (error.status >= 500 && error.status <= 599) {
|
||||
this.toastr.error("Not Added");
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
}
|
||||
goToAdd(row) {
|
||||
this.modalAdd = true;
|
||||
this.addCronExpression = '';
|
||||
this.selectedSureConnect = null; // Reset SureConnect selection
|
||||
this.submitted = false;
|
||||
// Reset the form control for cron_job and sure_connect_id
|
||||
if (this.entryForm) {
|
||||
if (this.entryForm.get('cron_job')) {
|
||||
this.entryForm.get('cron_job')?.setValue('');
|
||||
}
|
||||
if (this.entryForm.get('sure_connect_id')) {
|
||||
this.entryForm.get('sure_connect_id')?.setValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
submitted = false;
|
||||
onSubmit() {
|
||||
console.log(this.entryForm.value);
|
||||
this.submitted = true;
|
||||
if (this.entryForm.invalid) {
|
||||
return;
|
||||
} this.onCreate();
|
||||
|
||||
}
|
||||
|
||||
|
||||
isValidurl(url: string): boolean {
|
||||
return /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/.test(url);
|
||||
}
|
||||
goTourlUrl(val) { window.open(val) }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
rsModaljson = false;
|
||||
goToReplaceStringjson(row) {
|
||||
this.rowSelected = row; this.rsModaljson = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// updateaction
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from "rxjs";
|
||||
import { HttpClient, HttpHeaders, HttpParams, } from "@angular/common/http";
|
||||
import { ApiRequestService } from "src/app/services/api/api-request.service";
|
||||
import { environment } from 'src/environments/environment';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class Data_lakeservice{
|
||||
private baseURL = "Data_lake/Data_lake" ; constructor(
|
||||
private http: HttpClient,
|
||||
private apiRequest: ApiRequestService,
|
||||
) { }
|
||||
getAll(page?: number, size?: number): Observable<any> {
|
||||
return this.apiRequest.get(this.baseURL);
|
||||
}
|
||||
getById(id: number): Observable<any> {
|
||||
const _http = this.baseURL + "/" + id;
|
||||
return this.apiRequest.get(_http);
|
||||
}
|
||||
create(data: any): Observable<any> {
|
||||
return this.apiRequest.post(this.baseURL, data);
|
||||
}
|
||||
update(id: number, data: any): Observable<any> {
|
||||
const _http = this.baseURL + "/" + id;
|
||||
return this.apiRequest.put(_http, data);
|
||||
}
|
||||
delete(id: number): Observable<any> {
|
||||
const _http = this.baseURL + "/" + id;
|
||||
return this.apiRequest.delete(_http);
|
||||
}
|
||||
|
||||
// New method for updating JSON
|
||||
updateJson(id: number): Observable<any> {
|
||||
const _http = this.baseURL + "/json/" + id;
|
||||
return this.apiRequest.put(_http, {});
|
||||
}
|
||||
|
||||
// updateaction
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export const Data_lakecardvariable = {
|
||||
"cardButton": false,
|
||||
"cardmodeldata": ``
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<div class="cron-job-builder">
|
||||
<h4>Cron Job Builder</h4>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Minute</label>
|
||||
<select class="form-control" [(ngModel)]="minute" (change)="onFieldChange()">
|
||||
<option *ngFor="let option of minuteOptions" [value]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Hour</label>
|
||||
<select class="form-control" [(ngModel)]="hour" (change)="onFieldChange()">
|
||||
<option *ngFor="let option of hourOptions" [value]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Day of Month</label>
|
||||
<select class="form-control" [(ngModel)]="dayOfMonth" (change)="onFieldChange()">
|
||||
<option *ngFor="let option of dayOfMonthOptions" [value]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-6 clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Month</label>
|
||||
<select class="form-control" [(ngModel)]="month" (change)="onFieldChange()">
|
||||
<option *ngFor="let option of monthOptions" [value]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-12 clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Day of Week</label>
|
||||
<select class="form-control" [(ngModel)]="dayOfWeek" (change)="onFieldChange()">
|
||||
<option *ngFor="let option of dayOfWeekOptions" [value]="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-12">
|
||||
<div class="form-group">
|
||||
<label>Generated Cron Expression:</label>
|
||||
<input type="text" class="form-control" [value]="buildCronExpression()" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="cronDescription">
|
||||
<div class="clr-col-12">
|
||||
<div class="cron-description">
|
||||
<strong>Schedule Description:</strong> {{ cronDescription }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
.cron-job-builder {
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f8f8f8;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
select, input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.cron-description {
|
||||
padding: 10px;
|
||||
background-color: #e6f7ff;
|
||||
border: 1px solid #91d5ff;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
strong {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cron-job-builder',
|
||||
templateUrl: './cron-job-builder.component.html',
|
||||
styleUrls: ['./cron-job-builder.component.scss']
|
||||
})
|
||||
export class CronJobBuilderComponent implements OnInit, OnChanges {
|
||||
@Input() cronExpression: string = '';
|
||||
@Input() instanceId: string = ''; // Unique identifier for each instance
|
||||
@Output() cronExpressionChange = new EventEmitter<string>();
|
||||
|
||||
// Cron job fields
|
||||
minute: string = '*';
|
||||
hour: string = '*';
|
||||
dayOfMonth: string = '*';
|
||||
month: string = '*';
|
||||
dayOfWeek: string = '*';
|
||||
|
||||
// Human readable description
|
||||
cronDescription: string = '';
|
||||
|
||||
// Options for selectors
|
||||
minuteOptions: any[] = [];
|
||||
hourOptions: any[] = [];
|
||||
dayOfMonthOptions: any[] = [];
|
||||
monthOptions: any[] = [];
|
||||
dayOfWeekOptions: any[] = [];
|
||||
|
||||
ngOnInit() {
|
||||
// Initialize options for each instance
|
||||
this.minuteOptions = this.generateOptions(0, 59);
|
||||
this.hourOptions = this.generateOptions(0, 23);
|
||||
this.dayOfMonthOptions = this.generateOptions(1, 31);
|
||||
this.monthOptions = [
|
||||
{ value: '*', label: 'Every month' },
|
||||
{ value: '1', label: 'January' },
|
||||
{ value: '2', label: 'February' },
|
||||
{ value: '3', label: 'March' },
|
||||
{ value: '4', label: 'April' },
|
||||
{ value: '5', label: 'May' },
|
||||
{ value: '6', label: 'June' },
|
||||
{ value: '7', label: 'July' },
|
||||
{ value: '8', label: 'August' },
|
||||
{ value: '9', label: 'September' },
|
||||
{ value: '10', label: 'October' },
|
||||
{ value: '11', label: 'November' },
|
||||
{ value: '12', label: 'December' }
|
||||
];
|
||||
this.dayOfWeekOptions = [
|
||||
{ value: '*', label: 'Every day' },
|
||||
{ value: '0', label: 'Sunday' },
|
||||
{ value: '1', label: 'Monday' },
|
||||
{ value: '2', label: 'Tuesday' },
|
||||
{ value: '3', label: 'Wednesday' },
|
||||
{ value: '4', label: 'Thursday' },
|
||||
{ value: '5', label: 'Friday' },
|
||||
{ value: '6', label: 'Saturday' }
|
||||
];
|
||||
|
||||
if (this.cronExpression) {
|
||||
this.parseCronExpression(this.cronExpression);
|
||||
}
|
||||
// Generate initial description
|
||||
this.generateDescription();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
// When cronExpression input changes, update the component state
|
||||
if (changes['cronExpression'] && !changes['cronExpression'].firstChange) {
|
||||
this.parseCronExpression(changes['cronExpression'].currentValue);
|
||||
this.generateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
generateOptions(start: number, end: number): any[] {
|
||||
const options = [{ value: '*', label: `Every (${start === 0 ? '0' : start}-${end})` }];
|
||||
for (let i = start; i <= end; i++) {
|
||||
options.push({ value: i.toString(), label: i.toString() });
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
parseCronExpression(expression: string) {
|
||||
const parts = expression.split(' ');
|
||||
if (parts.length >= 5) {
|
||||
this.minute = parts[0];
|
||||
this.hour = parts[1];
|
||||
this.dayOfMonth = parts[2];
|
||||
this.month = parts[3];
|
||||
this.dayOfWeek = parts[4];
|
||||
}
|
||||
}
|
||||
|
||||
generateDescription() {
|
||||
let description = 'Runs ';
|
||||
|
||||
// Minute description
|
||||
if (this.minute === '*') {
|
||||
description += 'every minute';
|
||||
} else if (this.minute.includes('/')) {
|
||||
const interval = this.minute.split('/')[1];
|
||||
description += `every ${interval} minutes`;
|
||||
} else {
|
||||
description += `at minute ${this.minute}`;
|
||||
}
|
||||
|
||||
// Hour description
|
||||
if (this.hour === '*') {
|
||||
if (this.minute === '*') {
|
||||
description += '';
|
||||
} else {
|
||||
description += ' past every hour';
|
||||
}
|
||||
} else if (this.hour.includes('/')) {
|
||||
const interval = this.hour.split('/')[1];
|
||||
description += ` past every ${interval} hours`;
|
||||
} else {
|
||||
description += ` past hour ${this.hour}`;
|
||||
}
|
||||
|
||||
// Day of month description
|
||||
if (this.dayOfMonth !== '*' && this.dayOfMonth !== '?') {
|
||||
description += ` on day ${this.dayOfMonth}`;
|
||||
}
|
||||
|
||||
// Month description
|
||||
if (this.month !== '*') {
|
||||
const monthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
if (this.month.match(/^\d+$/)) {
|
||||
description += ` in ${monthNames[parseInt(this.month)]}`;
|
||||
} else {
|
||||
description += ` in ${this.month}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Day of week description
|
||||
if (this.dayOfWeek !== '*' && this.dayOfWeek !== '?') {
|
||||
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
if (this.dayOfWeek.match(/^\d+$/)) {
|
||||
description += ` on ${dayNames[parseInt(this.dayOfWeek)]}`;
|
||||
} else {
|
||||
description += ` on ${this.dayOfWeek}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for simple patterns
|
||||
if (this.minute === '0' && this.hour === '0' && this.dayOfMonth === '1' && this.month === '*' && this.dayOfWeek === '*') {
|
||||
description = 'Runs at midnight on the first day of every month';
|
||||
} else if (this.minute === '0' && this.hour === '0' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||
description = 'Runs at midnight every day';
|
||||
} else if (this.minute === '0' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||
description = 'Runs at the top of every hour';
|
||||
} else if (this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||
description = 'Runs every minute';
|
||||
}
|
||||
|
||||
this.cronDescription = description;
|
||||
}
|
||||
|
||||
buildCronExpression() {
|
||||
const expression = `${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`;
|
||||
this.cronExpressionChange.emit(expression);
|
||||
this.generateDescription(); // Update description when expression changes
|
||||
return expression;
|
||||
}
|
||||
|
||||
onFieldChange() {
|
||||
this.buildCronExpression();
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,8 @@
|
||||
</button>
|
||||
|
||||
<button class="btn btn-icon" style="margin-top: 10px; float: right;">
|
||||
<input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch" (change)="toggleAddToDashboard(item)" />
|
||||
<input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch"
|
||||
(change)="toggleAddToDashboard(item)" />
|
||||
</button>
|
||||
|
||||
<!-- <label for="workflow_name">Add to Dasboard</label>
|
||||
@@ -58,8 +59,10 @@
|
||||
</button>
|
||||
|
||||
<h4 style="margin-top: 0px; margin-left: 10px;">{{item.name}}</h4>
|
||||
<ndc-dynamic class="no-drag" [ndcDynamicComponent]="item.component" (moduleInfo)="display($event)"></ndc-dynamic>
|
||||
<ndc-dynamic class="no-drag" [ndcDynamicComponent]="item.component" [ndcDynamicInputs]="getChartInputs(item)"
|
||||
(moduleInfo)="display($event)"></ndc-dynamic>
|
||||
<!-- </ng-container> -->
|
||||
|
||||
</gridster-item>
|
||||
</gridster>
|
||||
</div>
|
||||
@@ -79,16 +82,27 @@
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="charttitle">Chart Title</label>
|
||||
<input id="chartparameter" type="text" formControlName="charttitle" class="clr-input" [(ngModel)]="gadgetsEditdata.charttitle" >
|
||||
<input id="chartparameter" type="text" formControlName="charttitle" class="clr-input"
|
||||
[(ngModel)]="gadgetsEditdata.charttitle">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="clr-row">
|
||||
|
||||
<!-- Add Connection Selection Field -->
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="id">ID</label>
|
||||
<input id="datasource" type="text" formControlName="id" class="clr-input" [(ngModel)]="gadgetsEditdata.id">
|
||||
<label for="connection">Connection</label>
|
||||
<select id="connection" formControlName="connection" [(ngModel)]="gadgetsEditdata.connection" class="clr-select">
|
||||
<option value="">Select Connection</option>
|
||||
<option *ngFor="let conn of sureconnectData" [value]="conn.id">
|
||||
{{conn.connection_name || conn.id}}
|
||||
</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select a SureConnect connection to use for this chart</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Grid View' && gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
</div>
|
||||
|
||||
<div class="clr-row"
|
||||
*ngIf="gadgetsEditdata?.fieldName !== 'Grid View' && gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<div class="clr-form-control" style="margin-top: 5px;margin-bottom: 10px;">
|
||||
<div class="clr-control-container">
|
||||
@@ -105,11 +119,13 @@
|
||||
<label for="donut" class="clr-control-label">Show donut</label>
|
||||
</div> -->
|
||||
<div class="clr-checkbox-wrapper">
|
||||
<input type="checkbox" id="chartlegend" formControlName="chartlegend" [(ngModel)]="gadgetsEditdata.chartlegend" class="clr-checkbox" />
|
||||
<input type="checkbox" id="chartlegend" formControlName="chartlegend"
|
||||
[(ngModel)]="gadgetsEditdata.chartlegend" class="clr-checkbox" />
|
||||
<label for="chartlegend" class="clr-control-label">Show Chart Legend</label>
|
||||
</div>
|
||||
<div class="clr-checkbox-wrapper">
|
||||
<input type="checkbox" id="showlabel" formControlName="showlabel" [(ngModel)]="gadgetsEditdata.showlabel" class="clr-checkbox" />
|
||||
<input type="checkbox" id="showlabel" formControlName="showlabel"
|
||||
[(ngModel)]="gadgetsEditdata.showlabel" class="clr-checkbox" />
|
||||
<label for="showlabel" class="clr-control-label">Show Chart Label</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,8 +144,10 @@
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="table">Table Name</label>
|
||||
<div><input type="urk" id="table" formControlName="table" class="clr-input" [(ngModel)]="gadgetsEditdata.table" style="width:90%"> <span><button class="btn btn-icon btn-primary" style="margin: 0px;" (click)="tablename(gadgetsEditdata.table)">
|
||||
<label for="table">Api Url</label>
|
||||
<div><input type="urk" id="table" formControlName="table" class="clr-input"
|
||||
[(ngModel)]="gadgetsEditdata.table" style="width:90%"> <span><button class="btn btn-icon btn-primary"
|
||||
style="margin: 0px;" (click)="callApi(gadgetsEditdata.table)">
|
||||
<clr-icon shape="redo"></clr-icon> </button></span></div>
|
||||
<!-- <select id="table" formControlName="table" [(ngModel)]="gadgetsEditdata.table" (change)="tablename($event.target.value)">
|
||||
<option value="null">choose Table</option>
|
||||
@@ -137,9 +155,11 @@
|
||||
</select> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Grid View'&& gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-row"
|
||||
*ngIf="gadgetsEditdata?.fieldName !== 'Grid View'&& gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="xAxis">X-Axis <span *ngIf="gadgetsEditdata?.fieldName === 'Bubble Chart' || gadgetsEditdata?.fieldName === 'Scatter Chart' ">(Numeric)</span></label>
|
||||
<label for="xAxis">X-Axis <span
|
||||
*ngIf="gadgetsEditdata?.fieldName === 'Bubble Chart' || gadgetsEditdata?.fieldName === 'Scatter Chart' ">(Numeric)</span></label>
|
||||
<!-- <input id="xAxis" type="text" formControlName="xAxis" class="clr-input" [(ngModel)]="gadgetsEditdata.xAxis"> -->
|
||||
<select id="xAxis" formControlName="xAxis" [(ngModel)]="gadgetsEditdata.xAxis">
|
||||
<option value="null">choose Column</option>
|
||||
@@ -147,7 +167,8 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' && gadgetsEditdata?.fieldName !== 'Polar Area Chart' && gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-row"
|
||||
*ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' && gadgetsEditdata?.fieldName !== 'Polar Area Chart' && gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="yAxis" *ngIf="gadgetsEditdata?.fieldName === 'Grid View'; else yaxislable">Columns</label>
|
||||
<ng-template #yaxislable>
|
||||
@@ -170,7 +191,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata?.fieldName == 'Pie Chart' || gadgetsEditdata?.fieldName == 'Polar Area Chart' || gadgetsEditdata?.fieldName == 'To Do Chart'">
|
||||
<div class="clr-row"
|
||||
*ngIf="gadgetsEditdata?.fieldName == 'Pie Chart' || gadgetsEditdata?.fieldName == 'Polar Area Chart' || gadgetsEditdata?.fieldName == 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="yAxis">Y-Axis (Numeric)</label>
|
||||
<select id="yAxis" formControlName="yAxis" [(ngModel)]="gadgetsEditdata.yAxis">
|
||||
@@ -180,6 +202,165 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Base Drilldown Configuration Section -->
|
||||
<div class="clr-row" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
|
||||
<div class="clr-col-sm-12">
|
||||
<h4>Base Drilldown Configuration</h4>
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-checkbox-wrapper">
|
||||
<input type="checkbox" id="drilldownEnabled" formControlName="drilldownEnabled"
|
||||
[(ngModel)]="gadgetsEditdata.drilldownEnabled" class="clr-checkbox"
|
||||
(change)="gadgetsEditdata.drilldownEnabled ? null : resetDrilldownConfiguration()" />
|
||||
<label for="drilldownEnabled" class="clr-control-label">Enable Base Drilldown</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="drilldownApiUrl">Base Drilldown API URL</label>
|
||||
<div>
|
||||
<input type="text" id="drilldownApiUrl" formControlName="drilldownApiUrl" class="clr-input"
|
||||
[(ngModel)]="gadgetsEditdata.drilldownApiUrl" style="width:90%" [ngModelOptions]="{standalone: true}">
|
||||
<span>
|
||||
<button class="btn btn-icon btn-primary" style="margin: 0px;"
|
||||
(click)="refreshBaseDrilldownColumns()" [disabled]="!gadgetsEditdata.drilldownApiUrl">
|
||||
<clr-icon shape="redo"></clr-icon>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="clr-subtext">Enter the API URL for base drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<country></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="drilldownXAxis">Base Drilldown X-Axis</label>
|
||||
<select id="drilldownXAxis" formControlName="drilldownXAxis" [(ngModel)]="gadgetsEditdata.drilldownXAxis" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select X-Axis Column</option>
|
||||
<option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use for X-axis in base drilldown view</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled &&
|
||||
gadgetsEditdata?.fieldName !== 'Pie Chart' &&
|
||||
gadgetsEditdata?.fieldName !== 'Polar Area Chart' &&
|
||||
gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="drilldownYAxis">Base Drilldown Y-Axis</label>
|
||||
<select id="drilldownYAxis" formControlName="drilldownYAxis" [(ngModel)]="gadgetsEditdata.drilldownYAxis" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select Y-Axis Column</option>
|
||||
<option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use for Y-axis in base drilldown view</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Base Drilldown Parameter Configuration -->
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="drilldownParameter">Base Drilldown Parameter</label>
|
||||
<select id="drilldownParameter" [(ngModel)]="gadgetsEditdata.drilldownParameter" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select Parameter Column</option>
|
||||
<option *ngFor="let column of drilldownColumnData" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use as parameter for URL template replacement in base drilldown</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Multi-Layer Drilldown Configurations -->
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata.drilldownEnabled" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
|
||||
<div class="clr-col-sm-12">
|
||||
<h4>Multi-Layer Drilldown Configurations</h4>
|
||||
<button class="btn btn-sm btn-primary" (click)="addDrilldownLayer()">
|
||||
<clr-icon shape="plus"></clr-icon> Add Drilldown Layer
|
||||
</button>
|
||||
<div class="clr-subtext">Add additional drilldown layers for multi-level navigation</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Drilldown Layers -->
|
||||
<div class="clr-row" *ngFor="let layer of gadgetsEditdata.drilldownLayers; let i = index">
|
||||
<div class="clr-col-sm-12" style="margin-top: 15px; padding: 10px; border: 1px solid #eee; border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h5>Drilldown Layer {{i + 1}}</h5>
|
||||
<button class="btn btn-icon btn-danger btn-sm" (click)="removeDrilldownLayer(i)">
|
||||
<clr-icon shape="trash"></clr-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="clr-form-control">
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-checkbox-wrapper">
|
||||
<input type="checkbox" [id]="'layerEnabled' + i" [(ngModel)]="layer.enabled" class="clr-checkbox" [ngModelOptions]="{standalone: true}" />
|
||||
<label [for]="'layerEnabled' + i" class="clr-control-label">Enable Layer {{i + 1}} Drilldown</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label [for]="'layerApiUrl' + i">Layer {{i + 1}} API URL</label>
|
||||
<div>
|
||||
<input type="text" [id]="'layerApiUrl' + i" class="clr-input"
|
||||
[(ngModel)]="layer.apiUrl" style="width:90%" [ngModelOptions]="{standalone: true}">
|
||||
<span>
|
||||
<button class="btn btn-icon btn-primary" style="margin: 0px;"
|
||||
(click)="refreshDrilldownLayerColumns(i)" [disabled]="!layer.apiUrl">
|
||||
<clr-icon shape="redo"></clr-icon>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="clr-subtext">Enter the API URL for layer {{i + 1}} drilldown data. Use angle brackets for parameters, e.g., http://api.example.com/data/<state></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label [for]="'layerXAxis' + i">Layer {{i + 1}} X-Axis</label>
|
||||
<select [id]="'layerXAxis' + i" [(ngModel)]="layer.xAxis" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select X-Axis Column</option>
|
||||
<option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use for X-axis in layer {{i + 1}} drilldown view</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row" *ngIf="gadgetsEditdata?.fieldName !== 'Pie Chart' &&
|
||||
gadgetsEditdata?.fieldName !== 'Polar Area Chart' &&
|
||||
gadgetsEditdata?.fieldName !== 'To Do Chart'">
|
||||
<div class="clr-col-sm-12">
|
||||
<label [for]="'layerYAxis' + i">Layer {{i + 1}} Y-Axis</label>
|
||||
<select [id]="'layerYAxis' + i" [(ngModel)]="layer.yAxis" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select Y-Axis Column</option>
|
||||
<option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use for Y-axis in layer {{i + 1}} drilldown view</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Parameter Selection for Drilldown Layer -->
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label [for]="'layerParameter' + i">Layer {{i + 1}} Parameter</label>
|
||||
<select [id]="'layerParameter' + i" [(ngModel)]="layer.parameter" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Select Parameter Column</option>
|
||||
<option *ngFor="let column of layerColumnData[i] || []" [value]="column">{{column}}</option>
|
||||
</select>
|
||||
<div class="clr-subtext">Select the column to use as parameter for URL template replacement in layer {{i + 1}} drilldown</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer Parameter Configuration -->
|
||||
<!-- Removed parameter key input since we're using URL templates -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="clr-row">
|
||||
<div class="clr-col-sm-12">
|
||||
<label for="chartparameter">API parameter</label>
|
||||
@@ -188,11 +369,10 @@
|
||||
</div> -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="modeledit = false">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="onSubmit(modelid)" >save</button>
|
||||
<button type="button" class="btn btn-primary" (click)="applyChanges(modelid)">Apply</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="onSubmit(modelid)">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ import { GridViewComponent } from '../gadgets/grid-view/grid-view.component';
|
||||
import { DatastoreService } from 'src/app/services/fnd/datastore.service';
|
||||
import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
||||
import { isArray } from 'highcharts';
|
||||
// import { ChartItem } from '../chartitem';
|
||||
// Add the SureconnectService import
|
||||
import { SureconnectService } from '../sureconnect/sureconnect.service';
|
||||
|
||||
function isNullArray(arr) {
|
||||
return !Array.isArray(arr) || arr.length === 0;
|
||||
@@ -132,16 +133,31 @@ export class EditnewdashComponent implements OnInit {
|
||||
chartcolor: '',
|
||||
slices: '',
|
||||
yAxis: '',
|
||||
xAxis:''
|
||||
|
||||
xAxis: '',
|
||||
connection: '', // Add connection field
|
||||
// Drilldown configuration properties (base level)
|
||||
drilldownEnabled: false,
|
||||
drilldownApiUrl: '',
|
||||
// Removed drilldownParameterKey since we're using URL templates
|
||||
drilldownXAxis: '',
|
||||
drilldownYAxis: '',
|
||||
drilldownParameter: '', // Add drilldown parameter property
|
||||
// Multi-layer drilldown configurations
|
||||
drilldownLayers: [] as any[]
|
||||
};
|
||||
|
||||
// Add sureconnect data property
|
||||
sureconnectData: any[] = [];
|
||||
layerColumnData: { [key: number]: any[] } = {}; // Add layer column data property
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private dashboardService: Dashboard3Service,
|
||||
private toastr: ToastrService,
|
||||
private _fb: FormBuilder,
|
||||
private datastoreService: DatastoreService,
|
||||
private alertService:AlertsService,) { }
|
||||
private alertService: AlertsService,
|
||||
private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
@@ -166,7 +182,6 @@ export class EditnewdashComponent implements OnInit {
|
||||
minCols: 10,
|
||||
minRows: 10
|
||||
};
|
||||
this.getData();
|
||||
|
||||
this.editId = this.route.snapshot.params.id;
|
||||
console.log(this.editId);
|
||||
@@ -197,6 +212,31 @@ export class EditnewdashComponent implements OnInit {
|
||||
slices: [null],
|
||||
yAxis: [null],
|
||||
xAxis: [null],
|
||||
connection: [null], // Add connection to form group
|
||||
// Base drilldown configuration form controls
|
||||
drilldownEnabled: [null],
|
||||
drilldownApiUrl: [null],
|
||||
drilldownXAxis: [null],
|
||||
drilldownYAxis: [null],
|
||||
drilldownParameter: [null] // Add drilldown parameter to form group
|
||||
// Note: Dynamic drilldown layers will be handled separately since they're complex objects
|
||||
});
|
||||
|
||||
// Load sureconnect data first, then load dashboard data
|
||||
this.loadSureconnectData();
|
||||
}
|
||||
|
||||
// Add method to load sureconnect data
|
||||
loadSureconnectData() {
|
||||
this.sureconnectService.getAll().subscribe((data: any[]) => {
|
||||
this.sureconnectData = data;
|
||||
console.log('Sureconnect data loaded:', this.sureconnectData);
|
||||
// Now that sureconnect data is loaded, we can safely load dashboard data
|
||||
this.getData();
|
||||
}, (error) => {
|
||||
console.log('Error loading sureconnect data:', error);
|
||||
// Even if there's an error loading sureconnect data, we still need to load dashboard data
|
||||
this.getData();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -232,13 +272,21 @@ export class EditnewdashComponent implements OnInit {
|
||||
console.log(this.dashboardCollection);
|
||||
// We parse serialized Json to generate components on the fly
|
||||
this.parseJson(this.dashboardCollection);
|
||||
|
||||
// Set default connections for all gadgets if sureconnect data is available
|
||||
if (this.sureconnectData && this.sureconnectData.length > 0) {
|
||||
this.dashboardCollection.dashboard.forEach(item => {
|
||||
if (!item['connection'] || item['connection'] === '') {
|
||||
item['connection'] = this.sureconnectData[0].id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We copy array without reference
|
||||
this.dashboardArray = this.dashboardCollection.dashboard.slice();
|
||||
console.log("this.dashboardArray", this.dashboardArray);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Super TOKENIZER 2.0 POWERED BY NATCHOIN
|
||||
@@ -426,8 +474,7 @@ export class EditnewdashComponent implements OnInit {
|
||||
}
|
||||
|
||||
modelid: number;
|
||||
editGadget(item)
|
||||
{
|
||||
editGadget(item) {
|
||||
this.modeledit = true;
|
||||
this.modelid = item.chartid;
|
||||
console.log(this.modelid);
|
||||
@@ -437,6 +484,48 @@ export class EditnewdashComponent implements OnInit {
|
||||
if (item.chartcolor === undefined) { item.chartcolor = true; }
|
||||
if (item.chartlegend === undefined) { item.chartlegend = true; }
|
||||
this.getStores();
|
||||
|
||||
// Set default connection if none is set and we have connections
|
||||
if ((!item['connection'] || item['connection'] === '') && this.sureconnectData && this.sureconnectData.length > 0) {
|
||||
this.gadgetsEditdata['connection'] = this.sureconnectData[0].id;
|
||||
// Also update the form control
|
||||
this.entryForm.patchValue({ connection: this.sureconnectData[0].id });
|
||||
}
|
||||
|
||||
// Initialize base drilldown properties if not present
|
||||
if (item['drilldownEnabled'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownEnabled'] = false;
|
||||
}
|
||||
if (item['drilldownApiUrl'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownApiUrl'] = '';
|
||||
}
|
||||
// Removed drilldownParameterKey initialization
|
||||
if (item['drilldownXAxis'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownXAxis'] = '';
|
||||
}
|
||||
if (item['drilldownYAxis'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownYAxis'] = '';
|
||||
}
|
||||
if (item['drilldownParameter'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownParameter'] = '';
|
||||
}
|
||||
|
||||
// Initialize drilldown layers if not present
|
||||
if (item['drilldownLayers'] === undefined) {
|
||||
this.gadgetsEditdata['drilldownLayers'] = [];
|
||||
} else {
|
||||
// Ensure each layer has proper structure (removed parameterKey, added parameter)
|
||||
this.gadgetsEditdata['drilldownLayers'].forEach(layer => {
|
||||
// Initialize parameter if not present
|
||||
if (layer['parameter'] === undefined) {
|
||||
layer['parameter'] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset drilldown column data
|
||||
this.drilldownColumnData = [];
|
||||
|
||||
if (item.datastore !== undefined || '' || null) {
|
||||
const datastore = item.datastore;
|
||||
this.getTables(datastore);
|
||||
@@ -459,8 +548,7 @@ export class EditnewdashComponent implements OnInit {
|
||||
}
|
||||
|
||||
|
||||
UpdateLine()
|
||||
{
|
||||
UpdateLine() {
|
||||
console.log('Add button clicked.......');
|
||||
console.log(this.dashboardArray);
|
||||
console.log(this.dashboardCollection);
|
||||
@@ -474,7 +562,8 @@ let cmp=this.dashboardCollection.dashboard.forEach(dashboard=>{
|
||||
this.componentCollection.forEach(component => {
|
||||
if (dashboard.name === component.name) {
|
||||
dashboard.component = component.name;
|
||||
} })
|
||||
}
|
||||
})
|
||||
})
|
||||
console.log(cmp);
|
||||
|
||||
@@ -515,8 +604,7 @@ console.log(cmp);
|
||||
// }
|
||||
}
|
||||
|
||||
onSubmit(id)
|
||||
{
|
||||
onSubmit(id) {
|
||||
console.log(id);
|
||||
if (!isNullArray(this.selectedyAxis)) {
|
||||
console.log("get y-axis array", this.selectedyAxis);
|
||||
@@ -526,21 +614,128 @@ console.log(cmp);
|
||||
let num = id;
|
||||
console.log(this.entryForm.value);
|
||||
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
||||
if(item.chartid == num)
|
||||
{
|
||||
if (item.chartid == num) {
|
||||
//item["product_id"] = "thisistest";
|
||||
const xyz = { ...item, ...formdata }
|
||||
|
||||
// Explicitly ensure drilldown properties are preserved
|
||||
xyz.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled;
|
||||
xyz.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl;
|
||||
xyz.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
|
||||
xyz.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
|
||||
xyz.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
|
||||
xyz.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
|
||||
|
||||
console.log(xyz);
|
||||
return xyz;
|
||||
}
|
||||
return item
|
||||
});
|
||||
console.log(this.dashboardCollection.dashboard);
|
||||
console.log('dashboard collection ', this.dashboardCollection.dashboard);
|
||||
this.modeledit = false;
|
||||
|
||||
// this.entryForm.reset();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract only the relevant chart configuration properties to pass to chart components
|
||||
* This prevents errors when trying to set properties that don't exist on the components
|
||||
*/
|
||||
getChartInputs(item: any): any {
|
||||
// Only pass properties that are relevant to chart components
|
||||
const chartInputs = {
|
||||
xAxis: item.xAxis,
|
||||
yAxis: item.yAxis,
|
||||
table: item.table,
|
||||
datastore: item.datastore,
|
||||
charttitle: item.charttitle,
|
||||
chartlegend: item.chartlegend,
|
||||
showlabel: item.showlabel,
|
||||
chartcolor: item.chartcolor,
|
||||
slices: item.slices,
|
||||
donut: item.donut,
|
||||
charturl: item.charturl,
|
||||
chartparameter: item.chartparameter,
|
||||
datasource: item.datasource,
|
||||
fieldName: item.name, // Using item.name as fieldName
|
||||
connection: item['connection'], // Add connection field using bracket notation
|
||||
// Base drilldown configuration properties
|
||||
drilldownEnabled: item['drilldownEnabled'],
|
||||
drilldownApiUrl: item['drilldownApiUrl'],
|
||||
// Removed drilldownParameterKey since we're using URL templates
|
||||
drilldownXAxis: item['drilldownXAxis'],
|
||||
drilldownYAxis: item['drilldownYAxis'],
|
||||
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
|
||||
// Multi-layer drilldown configurations
|
||||
drilldownLayers: item['drilldownLayers'] || []
|
||||
};
|
||||
|
||||
// Remove undefined properties to avoid passing unnecessary data
|
||||
Object.keys(chartInputs).forEach(key => {
|
||||
if (chartInputs[key] === undefined) {
|
||||
delete chartInputs[key];
|
||||
}
|
||||
});
|
||||
|
||||
return chartInputs;
|
||||
}
|
||||
|
||||
applyChanges(id) {
|
||||
console.log('Apply changes for chart ID:', id);
|
||||
|
||||
// Check if ID is valid
|
||||
if (id === null || id === undefined) {
|
||||
console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid);
|
||||
id = this.modelid;
|
||||
}
|
||||
|
||||
// Update the form with selected Y-axis values if it's an array
|
||||
if (!isNullArray(this.selectedyAxis)) {
|
||||
console.log("get y-axis array", this.selectedyAxis);
|
||||
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
||||
}
|
||||
|
||||
// Get form data
|
||||
let formdata = this.entryForm.value;
|
||||
let num = id;
|
||||
console.log('Form data:', this.entryForm.value);
|
||||
|
||||
// Update the dashboard collection with the new configuration
|
||||
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
||||
if (item.chartid == num) {
|
||||
// Merge the existing item with the new form data
|
||||
const updatedItem = { ...item, ...formdata }
|
||||
|
||||
// Explicitly ensure drilldown properties are preserved
|
||||
updatedItem.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled;
|
||||
updatedItem.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl;
|
||||
updatedItem.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
|
||||
updatedItem.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
|
||||
updatedItem.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
|
||||
updatedItem.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
|
||||
|
||||
console.log('Updated item:', updatedItem);
|
||||
return updatedItem;
|
||||
}
|
||||
return item
|
||||
});
|
||||
|
||||
console.log('Updated dashboard collection:', this.dashboardCollection.dashboard);
|
||||
|
||||
// Update the dashboardArray to reflect changes immediately
|
||||
// Create a new array with new object references to ensure change detection
|
||||
this.dashboardArray = this.dashboardCollection.dashboard.map(item => ({ ...item }));
|
||||
|
||||
// Force gridster to refresh
|
||||
if (this.options && this.options.api) {
|
||||
this.options.api.optionsChanged();
|
||||
}
|
||||
|
||||
// Note: We don't close the modal here, allowing the user to make additional changes
|
||||
// The user can click "Save" when they're done with all changes
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.router.navigate(["../../all"], { relativeTo: this.route })
|
||||
}
|
||||
@@ -578,40 +773,150 @@ console.log(cmp);
|
||||
});
|
||||
}
|
||||
|
||||
tablename(val){
|
||||
console.log(val);
|
||||
callApi(val) {
|
||||
console.log(' api value ', val);
|
||||
this.getColumns(this.selectedStoreId, val);
|
||||
}
|
||||
selectedyAxis;
|
||||
columnData;
|
||||
drilldownColumnData = []; // Add drilldown column data property
|
||||
|
||||
getColumns(id, table) {
|
||||
this.alertService.getColumnfromurl(table).subscribe(data =>{
|
||||
console.log(data);
|
||||
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||
this.alertService.getColumnfromurl(table, connectionId).subscribe(data => {
|
||||
console.log(' api data ', data);
|
||||
this.columnData = data;
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// toggleAddToDashboard(item) {
|
||||
// item.addToDashboard = item.addToDashboard;
|
||||
// }
|
||||
|
||||
// getChartDataForToggleSwitchTrue() {
|
||||
// for (let i = 0; i < this.dashArr.length; i++) {
|
||||
// if (this.dashArr[i].addToDashboard) {
|
||||
// this.dashboardService.getChartData(
|
||||
// this.dashArr[i].charturl, // Assuming charturl is the correct property to pass as a string
|
||||
// true // Pass true to indicate fetching charts with toggle switch set to true
|
||||
// ).subscribe(tData => {
|
||||
// console.log(tData);
|
||||
// // this.dashArr[i].featchData = tData;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// Add method to refresh drilldown columns
|
||||
refreshDrilldownColumns() {
|
||||
if (this.gadgetsEditdata.drilldownApiUrl) {
|
||||
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||
this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => {
|
||||
console.log('Drilldown column data:', data);
|
||||
this.drilldownColumnData = data;
|
||||
}, (error) => {
|
||||
console.log('Error fetching drilldown columns:', error);
|
||||
this.drilldownColumnData = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add method to reset drilldown configuration
|
||||
resetDrilldownConfiguration() {
|
||||
this.gadgetsEditdata.drilldownApiUrl = '';
|
||||
// Removed drilldownParameterKey since we're using URL templates
|
||||
this.gadgetsEditdata.drilldownXAxis = '';
|
||||
this.gadgetsEditdata.drilldownYAxis = '';
|
||||
this.gadgetsEditdata.drilldownParameter = ''; // Reset drilldown parameter
|
||||
// Reset drilldown layers but preserve the array structure
|
||||
this.gadgetsEditdata.drilldownLayers = this.gadgetsEditdata.drilldownLayers.map(layer => ({
|
||||
...layer,
|
||||
enabled: false,
|
||||
apiUrl: '',
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
parameter: '' // Reset parameter property
|
||||
}));
|
||||
this.drilldownColumnData = [];
|
||||
}
|
||||
|
||||
// Add method to add a new drilldown layer
|
||||
addDrilldownLayer() {
|
||||
const newLayer = {
|
||||
enabled: false,
|
||||
apiUrl: '',
|
||||
// Removed parameterKey since we're using URL templates
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
parameter: '' // Add parameter property
|
||||
};
|
||||
this.gadgetsEditdata.drilldownLayers.push(newLayer);
|
||||
}
|
||||
|
||||
// Add method to remove a drilldown layer
|
||||
removeDrilldownLayer(index: number) {
|
||||
this.gadgetsEditdata.drilldownLayers.splice(index, 1);
|
||||
}
|
||||
|
||||
// Add method to refresh drilldown columns for a specific layer
|
||||
refreshDrilldownLayerColumns(layerIndex: number) {
|
||||
const layer = this.gadgetsEditdata.drilldownLayers[layerIndex];
|
||||
if (layer && layer.apiUrl) {
|
||||
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||
this.alertService.getColumnfromurl(layer.apiUrl, connectionId).subscribe(data => {
|
||||
console.log(`Drilldown layer ${layerIndex} column data:`, data);
|
||||
// Store layer column data in a separate property
|
||||
if (!this.layerColumnData) {
|
||||
this.layerColumnData = {};
|
||||
}
|
||||
this.layerColumnData[layerIndex] = data;
|
||||
}, (error) => {
|
||||
console.log(`Error fetching drilldown layer ${layerIndex} columns:`, error);
|
||||
if (!this.layerColumnData) {
|
||||
this.layerColumnData = {};
|
||||
}
|
||||
this.layerColumnData[layerIndex] = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add method to refresh base drilldown columns
|
||||
refreshBaseDrilldownColumns() {
|
||||
if (this.gadgetsEditdata.drilldownApiUrl) {
|
||||
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
|
||||
this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => {
|
||||
console.log('Base drilldown column data:', data);
|
||||
this.drilldownColumnData = data;
|
||||
}, (error) => {
|
||||
console.log('Error fetching base drilldown columns:', error);
|
||||
this.drilldownColumnData = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add method to build drilldown URL with template parameters using angle brackets
|
||||
buildDrilldownUrl(baseUrl: string, parameterValue: string): string {
|
||||
// If no base URL, return empty string
|
||||
if (!baseUrl) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If no parameter value, return the base URL as-is
|
||||
if (!parameterValue) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(baseUrl);
|
||||
|
||||
if (hasAngleBrackets) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
// Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/<country>
|
||||
// becomes: http://localhost:9292/State_ListFilter1/State_ListFilter11/india
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
const urlWithReplacedParam = baseUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
return urlWithReplacedParam;
|
||||
} else {
|
||||
// No angle brackets, return the base URL as-is
|
||||
// This handles normal API endpoints without parameter replacement
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Add method to get the parameter key from URL template using angle brackets
|
||||
getParameterKeyFromUrl(baseUrl: string): string {
|
||||
if (!baseUrl) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Extract parameter key from angle brackets
|
||||
// Example: http://localhost:9292/State_ListFilter1/State_ListFilter11/<country>
|
||||
// returns: country
|
||||
const match = baseUrl.match(/<([^>]+)>/);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
# Drilldown Configuration Implementation
|
||||
|
||||
## Overview
|
||||
This document describes the drilldown configuration implementation applied to all chart components in the dashboard system. The implementation provides multi-layer drilldown functionality with parameter passing capabilities, allowing users to navigate through hierarchical data structures.
|
||||
|
||||
## Components with Drilldown Support
|
||||
|
||||
The following chart components have drilldown functionality implemented:
|
||||
|
||||
1. Bar Chart (`bar-chart`)
|
||||
2. Line Chart (`line-chart`)
|
||||
3. Pie Chart (`pie-chart`)
|
||||
4. Bubble Chart (`bubble-chart`)
|
||||
5. Doughnut Chart (`doughnut-chart`)
|
||||
6. Polar Chart (`polar-chart`)
|
||||
7. Radar Chart (`radar-chart`)
|
||||
8. Scatter Chart (`scatter-chart`)
|
||||
9. Financial Chart (`financial-chart`)
|
||||
10. Dynamic Chart (`dynamic-chart`)
|
||||
|
||||
## Drilldown Configuration Properties
|
||||
|
||||
Each chart component includes the following drilldown configuration inputs:
|
||||
|
||||
```typescript
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string;
|
||||
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = [];
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. State Management
|
||||
|
||||
Each component maintains drilldown state through the following properties:
|
||||
|
||||
```typescript
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
|
||||
// Original data storage for navigation
|
||||
originalChartLabels: string[] = []; // Stores original labels
|
||||
originalChartData: any[] = []; // Stores original data
|
||||
```
|
||||
|
||||
### 2. Core Methods
|
||||
|
||||
#### fetchDrilldownData()
|
||||
Fetches data for the current drilldown level based on configuration:
|
||||
|
||||
```typescript
|
||||
fetchDrilldownData(): void {
|
||||
// Determine drilldown configuration based on current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2;
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Get parameter value from drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
}
|
||||
|
||||
// Replace parameter placeholders in API URL
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
if (parameterValue) {
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
}
|
||||
|
||||
// Fetch data from service
|
||||
this.dashboardService.getChartData(
|
||||
actualApiUrl,
|
||||
chartType,
|
||||
drilldownConfig.xAxis,
|
||||
drilldownConfig.yAxis,
|
||||
this.connection,
|
||||
drilldownConfig.parameter,
|
||||
parameterValue
|
||||
).subscribe(...);
|
||||
}
|
||||
```
|
||||
|
||||
#### chartClicked()
|
||||
Handles chart click events to initiate drilldown navigation:
|
||||
|
||||
```typescript
|
||||
public chartClicked(e: any): void {
|
||||
// Check if drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get clicked element details
|
||||
const clickedIndex = e.active[0].index;
|
||||
const clickedLabel = this.chartLabels[clickedIndex];
|
||||
|
||||
// Store original data if we're at base level
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
this.originalChartLabels = [...this.chartLabels];
|
||||
this.originalChartData = [...this.chartData];
|
||||
}
|
||||
|
||||
// Determine next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2;
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
// Proceed with drilldown if configuration exists
|
||||
if (hasDrilldownConfig) {
|
||||
// Add click to drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
// Fetch drilldown data
|
||||
this.fetchDrilldownData();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### navigateBack()
|
||||
Navigates back to the previous drilldown level:
|
||||
|
||||
```typescript
|
||||
navigateBack(): void {
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove last entry from stack
|
||||
this.drilldownStack.pop();
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for previous level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### resetToOriginalData()
|
||||
Resets the chart to its original data:
|
||||
|
||||
```typescript
|
||||
resetToOriginalData(): void {
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalChartLabels.length > 0) {
|
||||
this.chartLabels = [...this.originalChartLabels];
|
||||
}
|
||||
if (this.originalChartData.length > 0) {
|
||||
this.chartData = [...this.originalChartData];
|
||||
}
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Layer Drilldown Support
|
||||
|
||||
The implementation supports multiple drilldown layers through the `drilldownLayers` array. Each layer can have its own configuration:
|
||||
|
||||
```typescript
|
||||
drilldownLayers: [
|
||||
{
|
||||
enabled: true,
|
||||
apiUrl: "second-level-endpoint/<parameter>",
|
||||
xAxis: "column1",
|
||||
yAxis: "column2",
|
||||
parameter: "selectedColumn"
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
apiUrl: "third-level-endpoint/<parameter>",
|
||||
xAxis: "column3",
|
||||
yAxis: "column4",
|
||||
parameter: "selectedColumn"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Parameter Passing
|
||||
|
||||
The drilldown implementation supports parameter passing by replacing placeholders in the API URL:
|
||||
|
||||
1. URL templates use angle brackets for parameter placeholders: `endpoint/<parameter>`
|
||||
2. When navigating, the clicked value replaces the placeholder
|
||||
3. Parameters are properly encoded using `encodeURIComponent`
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. **Initial Load**: Chart loads with base data using `fetchChartData()`
|
||||
2. **Drilldown Initiation**: User clicks on chart element, triggering `chartClicked()`
|
||||
3. **Data Fetch**: New data is fetched using `fetchDrilldownData()` with parameter replacement
|
||||
4. **Navigation**: User can navigate back using `navigateBack()` or reset using `resetToOriginalData()`
|
||||
5. **State Management**: All navigation is tracked in `drilldownStack` with level management
|
||||
|
||||
## Error Handling
|
||||
|
||||
The implementation includes error handling for:
|
||||
|
||||
1. Missing drilldown configuration
|
||||
2. API call failures
|
||||
3. Invalid data structures
|
||||
4. Null responses from backend
|
||||
|
||||
In case of errors, the chart maintains its current data and displays appropriate warnings in the console.
|
||||
|
||||
## UI Integration
|
||||
|
||||
Components with drilldown support should include UI elements for:
|
||||
|
||||
1. **Back Button**: To navigate to previous drilldown level
|
||||
2. **Reset Button**: To return to original data
|
||||
3. **Navigation Indicators**: To show current drilldown level
|
||||
|
||||
Example HTML structure:
|
||||
|
||||
```html
|
||||
<div *ngIf="drilldownEnabled && currentDrilldownLevel > 0" class="drilldown-controls">
|
||||
<button (click)="navigateBack()" class="btn btn-secondary">
|
||||
← Back to Level {{ currentDrilldownLevel - 1 }}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" class="btn btn-outline">
|
||||
↺ Reset to Original
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
@@ -1,4 +1,22 @@
|
||||
<div style="display: block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="barChartData"
|
||||
[labels]="barChartLabels"
|
||||
@@ -7,3 +25,4 @@
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,33 +1,436 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bar-chart',
|
||||
templateUrl: './bar-chart.component.html',
|
||||
styleUrls: ['./bar-chart.component.scss']
|
||||
})
|
||||
export class BarChartComponent implements OnInit {
|
||||
export class BarChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
barChartLabels: string[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'];
|
||||
barChartType: string = 'bar';
|
||||
// barChartLegend = true;
|
||||
barChartPlugins = [];
|
||||
barChartData: any[] = [
|
||||
{ data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' }
|
||||
];
|
||||
barChartLegend: boolean = true;
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalBarChartLabels: string[] = [];
|
||||
originalBarChartData: any[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('BarChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Update legend visibility if it changed
|
||||
if (changes.chartlegend !== undefined) {
|
||||
this.barChartLegend = changes.chartlegend.currentValue;
|
||||
console.log('Chart legend changed to:', this.barChartLegend);
|
||||
}
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching bar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Get the parameter value from the drilldown stack for base level (should be empty)
|
||||
let parameterValue = '';
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/bar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Bar chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'bar', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received bar chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Bar chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.barChartLabels = data.chartLabels;
|
||||
this.barChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.barChartData = [...this.barChartData];
|
||||
console.log('Updated bar chart with data:', { labels: this.barChartLabels, data: this.barChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.barChartLabels = data.labels;
|
||||
this.barChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.barChartData = [...this.barChartData];
|
||||
console.log('Updated bar chart with legacy data format:', { labels: this.barChartLabels, data: this.barChartData });
|
||||
} else {
|
||||
console.warn('Bar chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching bar chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for bar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/bar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'bar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.barChartLabels = data.chartLabels;
|
||||
this.barChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.barChartData = [...this.barChartData];
|
||||
console.log('Updated bar chart with drilldown data:', { labels: this.barChartLabels, data: this.barChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.barChartLabels = data.labels;
|
||||
this.barChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.barChartData = [...this.barChartData];
|
||||
console.log('Updated bar chart with drilldown legacy data format:', { labels: this.barChartLabels, data: this.barChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.barChartLabels = [];
|
||||
this.barChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalBarChartLabels.length > 0) {
|
||||
this.barChartLabels = [...this.originalBarChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalBarChartData.length > 0) {
|
||||
this.barChartData = [...this.originalBarChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.barChartLabels);
|
||||
console.log('After reset - data:', this.barChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Bar chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.barChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on bar:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalBarChartLabels = [...this.barChartLabels];
|
||||
this.originalBarChartData = [...this.barChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,22 @@
|
||||
<div style="display:block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="bubbleChartData"
|
||||
[type]="bubbleChartType"
|
||||
@@ -7,3 +25,4 @@
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,16 +1,37 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChartConfiguration, ChartDataset, ChartOptions } from 'chart.js';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bubble-chart',
|
||||
templateUrl: './bubble-chart.component.html',
|
||||
styleUrls: ['./bubble-chart.component.scss']
|
||||
})
|
||||
export class BubbleChartComponent implements OnInit {
|
||||
export class BubbleChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
public bubbleChartOptions: ChartConfiguration['options'] = {
|
||||
// scales: {
|
||||
// x: {
|
||||
@@ -33,7 +54,6 @@ export class BubbleChartComponent implements OnInit {
|
||||
};
|
||||
|
||||
public bubbleChartType: string = 'bubble';
|
||||
// public bubbleChartLegend = true;
|
||||
public bubbleChartData: ChartDataset[] = [
|
||||
{
|
||||
data: [
|
||||
@@ -61,35 +81,429 @@ export class BubbleChartComponent implements OnInit {
|
||||
hoverBackgroundColor: 'yellow',
|
||||
hoverBorderColor: 'blue',
|
||||
},
|
||||
// {
|
||||
// data: [
|
||||
// { x: 10, y: 10, r: 10 },
|
||||
// { x: 15, y: 5, r: 15 },
|
||||
// { x: 26, y: 12, r: 23 },
|
||||
// { x: 7, y: 8, r: 8 },
|
||||
// ],
|
||||
// label: 'Investment Equities',
|
||||
// backgroundColor: [
|
||||
// 'red',
|
||||
// 'green',
|
||||
// 'blue',
|
||||
// 'purple',
|
||||
// 'yellow',
|
||||
// 'brown',
|
||||
// 'magenta',
|
||||
// 'cyan',
|
||||
// 'orange',
|
||||
// 'pink'
|
||||
// ],
|
||||
// borderColor: 'blue',
|
||||
// hoverBackgroundColor: 'purple',
|
||||
// hoverBorderColor: 'red',
|
||||
// },
|
||||
];
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalBubbleChartData: ChartDataset[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('BubbleChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching bubble chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/bubble?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Bubble chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received bubble chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Bubble chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For bubble charts, we need to transform the data into bubble format
|
||||
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||
console.log('Updated bubble chart with data:', this.bubbleChartData);
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.bubbleChartData = data.datasets;
|
||||
console.log('Updated bubble chart with legacy data format:', this.bubbleChartData);
|
||||
} else {
|
||||
console.warn('Bubble chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching bubble chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/bubble?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For bubble charts, we need to transform the data into bubble format
|
||||
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||
console.log('Updated bubble chart with drilldown data:', this.bubbleChartData);
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.bubbleChartData = data.datasets;
|
||||
console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData);
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.bubbleChartData = [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalBubbleChartData.length > 0) {
|
||||
this.bubbleChartData = [...this.originalBubbleChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - data:', this.bubbleChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
private transformToBubbleData(labels: any[], chartData: any[]): ChartDataset[] {
|
||||
// Transform the API data into bubble chart format
|
||||
const datasets: ChartDataset[] = [];
|
||||
|
||||
// Create a dataset for each data series
|
||||
chartData.forEach((series, index) => {
|
||||
// For bubble charts, we need x, y, and r values
|
||||
// We'll use the labels as x values and the data as y values
|
||||
// For radius (r), we'll use a default value or derive it from the data
|
||||
|
||||
const bubbleData = labels.map((label, i) => {
|
||||
const xValue = isNaN(Number(label)) ? i : Number(label);
|
||||
const yValue = series.data && series.data[i] !== undefined ?
|
||||
(isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0;
|
||||
// Use a default radius or derive from data
|
||||
const radius = Math.abs(yValue) > 0 ? Math.abs(yValue) / 10 : 5;
|
||||
|
||||
return {
|
||||
x: xValue,
|
||||
y: yValue,
|
||||
r: radius
|
||||
};
|
||||
});
|
||||
|
||||
datasets.push({
|
||||
data: bubbleData,
|
||||
label: series.label || `Series ${index + 1}`,
|
||||
backgroundColor: this.getBackgroundColor(index),
|
||||
borderColor: this.getBorderColor(index),
|
||||
hoverBackgroundColor: this.getHoverBackgroundColor(index),
|
||||
hoverBorderColor: this.getHoverBorderColor(index),
|
||||
});
|
||||
});
|
||||
|
||||
return datasets;
|
||||
}
|
||||
|
||||
private getBackgroundColor(index: number): string {
|
||||
const colors = [
|
||||
'rgba(255, 0, 0, 0.6)', // Red
|
||||
'rgba(0, 255, 0, 0.6)', // Green
|
||||
'rgba(0, 0, 255, 0.6)', // Blue
|
||||
'rgba(255, 255, 0, 0.6)', // Yellow
|
||||
'rgba(255, 0, 255, 0.6)', // Magenta
|
||||
'rgba(0, 255, 255, 0.6)', // Cyan
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
private getBorderColor(index: number): string {
|
||||
const colors = ['blue', 'green', 'red', 'orange', 'purple', 'cyan'];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
private getHoverBackgroundColor(index: number): string {
|
||||
const colors = ['purple', 'yellow', 'orange', 'red', 'blue', 'green'];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
private getHoverBorderColor(index: number): string {
|
||||
const colors = ['red', 'blue', 'green', 'purple', 'yellow', 'orange'];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Bubble chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the dataset index
|
||||
const datasetIndex = e.active[0].datasetIndex;
|
||||
|
||||
// Get the data point
|
||||
const dataPoint = this.bubbleChartData[datasetIndex].data[clickedIndex];
|
||||
|
||||
console.log('Clicked on bubble:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalBubbleChartData = JSON.parse(JSON.stringify(this.bubbleChartData));
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// For bubble charts, we'll use the x value as the clicked value
|
||||
const clickedValue = dataPoint && (dataPoint as any).x !== undefined ?
|
||||
(dataPoint as any).x.toString() : '';
|
||||
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
datasetIndex: datasetIndex,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedValue: clickedValue
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
<div style="display: block">
|
||||
<div class="doughnut-chart-container">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<!-- Show loading indicator -->
|
||||
<div class="loading-indicator" *ngIf="doughnutChartLabels.length === 0 && doughnutChartData.length === 0 && !noDataAvailable">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading chart data...</p>
|
||||
</div>
|
||||
|
||||
<!-- Show no data message -->
|
||||
<div class="no-data-message" *ngIf="noDataAvailable">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
|
||||
<!-- Show chart when data is available -->
|
||||
<canvas baseChart
|
||||
*ngIf="!noDataAvailable && doughnutChartLabels.length > 0 && doughnutChartData.length > 0"
|
||||
[data]="doughnutChartData"
|
||||
[labels]="doughnutChartLabels"
|
||||
[type]="doughnutChartType"
|
||||
[options]="doughnutChartOptions"
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)"></canvas>
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="chart-legend" *ngIf="!noDataAvailable && showlabel && doughnutChartLabels && doughnutChartLabels.length > 0">
|
||||
<div class="legend-item" *ngFor="let label of doughnutChartLabels; let i = index">
|
||||
<span class="legend-color" [style.background-color]="getLegendColor(i)"></span>
|
||||
<span class="legend-label">{{ label }}</span>
|
||||
<span class="legend-value">{{ doughnutChartData && doughnutChartData[i] !== undefined ? doughnutChartData[i] : 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,184 @@
|
||||
.doughnut-chart-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
min-height: 400px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.doughnut-chart-container:hover {
|
||||
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.chart-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 250px;
|
||||
margin: 15px 0;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-wrapper canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.chart-wrapper canvas:hover {
|
||||
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15));
|
||||
transform: scale(1.02);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #eaeaea;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legend-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
border-color: #3498db;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
display: inline-block;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-right: 15px;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.legend-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #3498db;
|
||||
background: linear-gradient(135deg, #e9ecef 0%, #dde1e5 100%);
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-indicator, .no-data-message {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-indicator p, .no-data-message p {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.doughnut-chart-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,620 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewChecked } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-doughnut-chart',
|
||||
templateUrl: './doughnut-chart.component.html',
|
||||
styleUrls: ['./doughnut-chart.component.scss']
|
||||
})
|
||||
export class DoughnutChartComponent implements OnInit {
|
||||
public doughnutChartLabels: string[] = [
|
||||
"Download Sales",
|
||||
"In-Store Sales",
|
||||
"Mail-Order Sales"
|
||||
];
|
||||
public doughnutChartData: number[] = [350, 450, 100];
|
||||
export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewChecked {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
public doughnutChartLabels: string[] = ["Category A", "Category B", "Category C"];
|
||||
public doughnutChartData: number[] = [30, 50, 20];
|
||||
public doughnutChartType: string = "doughnut";
|
||||
public doughnutChartOptions: any = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
cutout: '60%', // This creates the doughnut effect (Chart.js v3+ syntax)
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false // We'll create our own legend
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: {
|
||||
size: 16,
|
||||
color: '#fff'
|
||||
},
|
||||
bodyFont: {
|
||||
size: 14,
|
||||
color: '#fff'
|
||||
},
|
||||
cornerRadius: 4,
|
||||
displayColors: false
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
animateRotate: true,
|
||||
animateScale: false
|
||||
},
|
||||
elements: {
|
||||
arc: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Chart colors for consistent styling
|
||||
private chartColors: string[] = [
|
||||
'#FF6384',
|
||||
'#36A2EB',
|
||||
'#FFCE56',
|
||||
'#4BC0C0',
|
||||
'#9966FF',
|
||||
'#FF9F40',
|
||||
'#FF6384',
|
||||
'#C9CBCF'
|
||||
];
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalDoughnutChartLabels: string[] = [];
|
||||
originalDoughnutChartData: number[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Validate initial data
|
||||
this.validateChartData();
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and sanitize chart data
|
||||
*/
|
||||
private validateChartData(): void {
|
||||
// Ensure we have valid arrays
|
||||
if (!Array.isArray(this.doughnutChartLabels)) {
|
||||
this.doughnutChartLabels = [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.doughnutChartData)) {
|
||||
this.doughnutChartData = [];
|
||||
}
|
||||
|
||||
// Ensure we have some data to display
|
||||
if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) {
|
||||
// Add default data to ensure chart visibility
|
||||
this.doughnutChartLabels = ["Category A", "Category B", "Category C"];
|
||||
this.doughnutChartData = [30, 50, 20];
|
||||
}
|
||||
|
||||
// Ensure we have matching arrays
|
||||
if (this.doughnutChartLabels.length !== this.doughnutChartData.length) {
|
||||
const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length);
|
||||
while (this.doughnutChartLabels.length < maxLength) {
|
||||
this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`);
|
||||
}
|
||||
while (this.doughnutChartData.length < maxLength) {
|
||||
this.doughnutChartData.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force chart redraw
|
||||
*/
|
||||
public redrawChart(): void {
|
||||
// This method can be called to force a chart redraw if needed
|
||||
console.log('Redrawing doughnut chart');
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('DoughnutChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
// Debugging: Log component state after view checks
|
||||
console.log('DoughnutChartComponent state:', {
|
||||
labels: this.doughnutChartLabels,
|
||||
data: this.doughnutChartData,
|
||||
hasData: this.doughnutChartLabels.length > 0 && this.doughnutChartData.length > 0
|
||||
});
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching doughnut chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/doughnut?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Doughnut chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'doughnut', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received doughnut chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Doughnut chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For doughnut charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the doughnut chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.doughnutChartLabels = data.chartLabels || [];
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.doughnutChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
} else {
|
||||
this.doughnutChartData = [];
|
||||
}
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.doughnutChartData = [...this.doughnutChartData];
|
||||
console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.doughnutChartLabels = data.labels || [];
|
||||
this.doughnutChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.doughnutChartData = [...this.doughnutChartData];
|
||||
console.log('Updated doughnut chart with legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||
} else {
|
||||
console.warn('Doughnut chart received data does not have expected structure', data);
|
||||
// Reset to default data
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
}
|
||||
|
||||
// Log final state for debugging
|
||||
console.log('Final doughnut chart state:', {
|
||||
labels: this.doughnutChartLabels,
|
||||
data: this.doughnutChartData,
|
||||
labelsLength: this.doughnutChartLabels.length,
|
||||
dataLength: this.doughnutChartData.length
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching doughnut chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for doughnut chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
// Don't set noDataAvailable to true when there's no required data
|
||||
// This allows static data to be displayed
|
||||
// Only validate the chart data to ensure we have some data to display
|
||||
this.validateChartData();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/doughnut?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'doughnut', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For doughnut charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the doughnut chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.doughnutChartLabels = data.chartLabels || [];
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.doughnutChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
} else {
|
||||
this.doughnutChartData = [];
|
||||
}
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.doughnutChartData = [...this.doughnutChartData];
|
||||
console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.doughnutChartLabels = data.labels || [];
|
||||
this.doughnutChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.doughnutChartData = [...this.doughnutChartData];
|
||||
console.log('Updated doughnut chart with drilldown legacy data format:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
}
|
||||
|
||||
// Log final state for debugging
|
||||
console.log('Final doughnut chart state:', {
|
||||
labels: this.doughnutChartLabels,
|
||||
data: this.doughnutChartData,
|
||||
labelsLength: this.doughnutChartLabels.length,
|
||||
dataLength: this.doughnutChartData.length
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.doughnutChartLabels = [];
|
||||
this.doughnutChartData = [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalDoughnutChartLabels.length > 0) {
|
||||
this.doughnutChartLabels = [...this.originalDoughnutChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalDoughnutChartData.length > 0) {
|
||||
this.doughnutChartData = [...this.originalDoughnutChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.doughnutChartLabels);
|
||||
console.log('After reset - data:', this.doughnutChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for legend item
|
||||
* @param index Index of the legend item
|
||||
*/
|
||||
public getLegendColor(index: number): string {
|
||||
return this.chartColors[index % this.chartColors.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure labels and data arrays have the same length
|
||||
*/
|
||||
private syncLabelAndDataArrays(): void {
|
||||
// Handle empty arrays
|
||||
if (this.doughnutChartLabels.length === 0 && this.doughnutChartData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxLength = Math.max(this.doughnutChartLabels.length, this.doughnutChartData.length);
|
||||
|
||||
// Pad the shorter array with default values
|
||||
while (this.doughnutChartLabels.length < maxLength) {
|
||||
this.doughnutChartLabels.push(`Label ${this.doughnutChartLabels.length + 1}`);
|
||||
}
|
||||
|
||||
while (this.doughnutChartData.length < maxLength) {
|
||||
this.doughnutChartData.push(0);
|
||||
}
|
||||
|
||||
// Truncate the longer array if needed
|
||||
if (this.doughnutChartLabels.length > maxLength) {
|
||||
this.doughnutChartLabels = this.doughnutChartLabels.slice(0, maxLength);
|
||||
}
|
||||
|
||||
if (this.doughnutChartData.length > maxLength) {
|
||||
this.doughnutChartData = this.doughnutChartData.slice(0, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Doughnut chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.doughnutChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on doughnut slice:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalDoughnutChartLabels = [...this.doughnutChartLabels];
|
||||
this.originalDoughnutChartData = [...this.doughnutChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,32 @@
|
||||
<div style="display: block">
|
||||
<canvas baseChart [datasets]="dynamicChartData"
|
||||
<div class="dynamic-chart-container">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<!-- Show loading indicator -->
|
||||
<div class="loading-indicator" *ngIf="dynamicChartLabels.length === 0 && dynamicChartData.length === 0 && !noDataAvailable">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading chart data...</p>
|
||||
</div>
|
||||
|
||||
<!-- Show no data message -->
|
||||
<div class="no-data-message" *ngIf="noDataAvailable">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
|
||||
<!-- Show chart when data is available -->
|
||||
<canvas baseChart
|
||||
*ngIf="!noDataAvailable && dynamicChartLabels.length > 0 && dynamicChartData.length > 0"
|
||||
[datasets]="dynamicChartData"
|
||||
[options]="barChartOptions"
|
||||
[type]="barChartType"
|
||||
[labels]="dynamicChartLabels"
|
||||
@@ -8,3 +35,4 @@
|
||||
</canvas>
|
||||
</div>
|
||||
<button class="btn btn-primary" (click)="randomize()">Update</button>
|
||||
</div>
|
||||
@@ -1,18 +1,71 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChartConfiguration, ChartData, } from 'chart.js';
|
||||
import { Component, OnInit, ViewChild, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChartConfiguration, ChartData, ChartDataset } from 'chart.js';
|
||||
import { BaseChartDirective } from 'ng2-charts';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dynamic-chart',
|
||||
templateUrl: './dynamic-chart.component.html',
|
||||
styleUrls: ['./dynamic-chart.component.scss']
|
||||
})
|
||||
export class DynamicChartComponent implements OnInit {
|
||||
export class DynamicChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
@ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('DynamicChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
@ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;
|
||||
|
||||
public barChartOptions: ChartConfiguration['options'] = {
|
||||
elements: {
|
||||
@@ -34,35 +87,367 @@ export class DynamicChartComponent implements OnInit {
|
||||
public dynamicChartLabels: string[] = [ '2006', '2007', '2008', '2009', '2010', '2011', '2012' ];
|
||||
public barChartType: string = 'bar';
|
||||
|
||||
// public barChartData: ChartData<'bar'> = {
|
||||
// labels: this.barChartLabels,
|
||||
// datasets: [
|
||||
// { data: [ 65, 59, 80, 81, 56, 55, 40 ], label: 'Series A' },
|
||||
// { data: [ 28, 48, 40, 19, 86, 27, 90 ], label: 'Series B' }
|
||||
// ]
|
||||
// };
|
||||
|
||||
|
||||
public dynamicChartData: any = [
|
||||
{ data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" },
|
||||
{ data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" }
|
||||
];
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalDynamicChartLabels: string[] = [];
|
||||
originalDynamicChartData: any = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching dynamic chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/dynamic?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Dynamic chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'dynamic', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received dynamic chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Dynamic chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.dynamicChartLabels = data.chartLabels;
|
||||
this.dynamicChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.dynamicChartData = [...this.dynamicChartData];
|
||||
console.log('Updated dynamic chart with data:', { labels: this.dynamicChartLabels, data: this.dynamicChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.dynamicChartLabels = data.labels;
|
||||
this.dynamicChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.dynamicChartData = [...this.dynamicChartData];
|
||||
console.log('Updated dynamic chart with legacy data format:', { labels: this.dynamicChartLabels, data: this.dynamicChartData });
|
||||
} else {
|
||||
console.warn('Dynamic chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching dynamic chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for dynamic chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/dynamic?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'dynamic', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.dynamicChartLabels = data.chartLabels;
|
||||
this.dynamicChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.dynamicChartData = [...this.dynamicChartData];
|
||||
console.log('Updated dynamic chart with drilldown data:', { labels: this.dynamicChartLabels, data: this.dynamicChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.dynamicChartLabels = data.labels;
|
||||
this.dynamicChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.dynamicChartData = [...this.dynamicChartData];
|
||||
console.log('Updated dynamic chart with drilldown legacy data format:', { labels: this.dynamicChartLabels, data: this.dynamicChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.dynamicChartLabels = [];
|
||||
this.dynamicChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalDynamicChartLabels.length > 0) {
|
||||
this.dynamicChartLabels = [...this.originalDynamicChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalDynamicChartData.length > 0) {
|
||||
this.dynamicChartData = [...this.originalDynamicChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.dynamicChartLabels);
|
||||
console.log('After reset - data:', this.dynamicChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Dynamic chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element (if available)
|
||||
let clickedLabel = '';
|
||||
if (this.dynamicChartLabels && this.dynamicChartLabels[clickedIndex]) {
|
||||
clickedLabel = this.dynamicChartLabels[clickedIndex];
|
||||
}
|
||||
|
||||
console.log('Clicked on dynamic chart element:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalDynamicChartLabels = [...this.dynamicChartLabels];
|
||||
this.originalDynamicChartData = [...this.dynamicChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
// public chartClicked({ event, active }: { event?: ChartEvent, active?: {}[] }): void {
|
||||
// console.log(event, active);
|
||||
// }
|
||||
|
||||
// public chartHovered({ event, active }: { event?: ChartEvent, active?: {}[] }): void {
|
||||
// console.log(event, active);
|
||||
// }
|
||||
|
||||
public randomize(): void {
|
||||
this.barChartType = this.barChartType === 'bar' ? 'line' : 'bar';
|
||||
|
||||
@@ -1 +1,36 @@
|
||||
<p>financial-chart works!</p>
|
||||
<div class="financial-chart-container">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<!-- Show loading indicator -->
|
||||
<div class="loading-indicator" *ngIf="financialChartLabels.length === 0 && financialChartData.length === 0 && !noDataAvailable">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading chart data...</p>
|
||||
</div>
|
||||
|
||||
<!-- Show no data message -->
|
||||
<div class="no-data-message" *ngIf="noDataAvailable">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
|
||||
<!-- Show chart when data is available -->
|
||||
<canvas baseChart
|
||||
*ngIf="!noDataAvailable && financialChartLabels.length > 0 && financialChartData.length > 0"
|
||||
[datasets]="financialChartData"
|
||||
[labels]="financialChartLabels"
|
||||
[type]="financialChartType"
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,108 @@
|
||||
.financial-chart-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
min-height: 400px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.financial-chart-container:hover {
|
||||
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 250px;
|
||||
margin: 15px 0;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-wrapper canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.chart-wrapper canvas:hover {
|
||||
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15));
|
||||
transform: scale(1.02);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.loading-indicator, .no-data-message {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-indicator p, .no-data-message p {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.financial-chart-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,454 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-financial-chart',
|
||||
templateUrl: './financial-chart.component.html',
|
||||
styleUrls: ['./financial-chart.component.scss']
|
||||
})
|
||||
export class FinancialChartComponent implements OnInit {
|
||||
export class FinancialChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('FinancialChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
// Default financial chart data
|
||||
public financialChartLabels: string[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||
public financialChartData: any[] = [
|
||||
{ data: [65, 59, 80, 81, 56, 55, 40], label: 'Revenue' },
|
||||
{ data: [28, 48, 40, 19, 86, 27, 90], label: 'Expenses' }
|
||||
];
|
||||
public financialChartType: string = 'line';
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalFinancialChartLabels: string[] = [];
|
||||
originalFinancialChartData: any[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching financial chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/financial?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Financial chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'financial', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received financial chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Financial chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.financialChartLabels = data.chartLabels;
|
||||
this.financialChartData = data.chartData.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.financialChartData = [...this.financialChartData];
|
||||
console.log('Updated financial chart with data:', { labels: this.financialChartLabels, data: this.financialChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.financialChartLabels = data.labels;
|
||||
this.financialChartData = data.datasets.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.financialChartData = [...this.financialChartData];
|
||||
console.log('Updated financial chart with legacy data format:', { labels: this.financialChartLabels, data: this.financialChartData });
|
||||
} else {
|
||||
console.warn('Financial chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching financial chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for financial chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/financial?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'financial', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.financialChartLabels = data.chartLabels;
|
||||
this.financialChartData = data.chartData.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.financialChartData = [...this.financialChartData];
|
||||
console.log('Updated financial chart with drilldown data:', { labels: this.financialChartLabels, data: this.financialChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.financialChartLabels = data.labels;
|
||||
this.financialChartData = data.datasets.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.financialChartData = [...this.financialChartData];
|
||||
console.log('Updated financial chart with drilldown legacy data format:', { labels: this.financialChartLabels, data: this.financialChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.financialChartLabels = [];
|
||||
this.financialChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalFinancialChartLabels.length > 0) {
|
||||
this.financialChartLabels = [...this.originalFinancialChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalFinancialChartData.length > 0) {
|
||||
this.financialChartData = [...this.originalFinancialChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.financialChartLabels);
|
||||
console.log('After reset - data:', this.financialChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log('Financial chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element (if available)
|
||||
let clickedLabel = '';
|
||||
if (this.financialChartLabels && this.financialChartLabels[clickedIndex]) {
|
||||
clickedLabel = this.financialChartLabels[clickedIndex];
|
||||
}
|
||||
|
||||
console.log('Clicked on financial chart element:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalFinancialChartLabels = [...this.financialChartLabels];
|
||||
this.originalFinancialChartData = [...this.financialChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
@@ -2,59 +2,26 @@
|
||||
<div class="dg-wrapper">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-8">
|
||||
<h3>User Group Maintenance</h3>
|
||||
<h3>{{charttitle || 'Data Grid'}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid [clrDgLoading]="loading">
|
||||
<clr-dg-placeholder> <ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
||||
</clr-dg-placeholder>
|
||||
|
||||
<clr-dg-column [clrDgField]="'usrGrp'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
User Group No
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'groupName'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Group Name
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'groupDesc'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Description
|
||||
</ng-container></clr-dg-column >
|
||||
<clr-dg-column [clrDgField]="'groupLevel'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Group Level
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'status'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Status
|
||||
</ng-container></clr-dg-column>
|
||||
<!-- <clr-dg-column [clrDgField]="'usrGrp'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
User Group
|
||||
</ng-container></clr-dg-column> -->
|
||||
<clr-dg-column [clrDgField]="'updateDateFormated'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Updated Date
|
||||
</ng-container></clr-dg-column>
|
||||
<!-- Dynamic columns based on response keys -->
|
||||
<clr-dg-column *ngFor="let header of dynamicHeaders" [clrDgField]="header.key">
|
||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
{{header.displayName}}
|
||||
</ng-container>
|
||||
</clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-row *clrDgItems="let user of givendata?.slice()?.reverse();let i = index" [clrDgItem]="user">
|
||||
<clr-dg-cell>{{user.usrGrp}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.groupName}}</clr-dg-cell>
|
||||
<clr-dg-cell >{{user.groupDesc}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.groupLevel}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.status}}</clr-dg-cell>
|
||||
<!-- <clr-dg-cell>{{user.usrGrp}}</clr-dg-cell> -->
|
||||
<clr-dg-cell>{{user.updateDateFormated}}</clr-dg-cell>
|
||||
|
||||
<!-- <clr-dg-action-overflow>
|
||||
|
||||
</clr-dg-action-overflow> -->
|
||||
|
||||
<!-- <clr-dg-row-detail *clrIfExpanded>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="td-title">username</td>
|
||||
<td class="td-content">{{user.groupName}}</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</table>
|
||||
</clr-dg-row-detail> -->
|
||||
<clr-dg-row *clrDgItems="let item of givendata" [clrDgItem]="item">
|
||||
<!-- Dynamic cells based on response keys -->
|
||||
<clr-dg-cell *ngFor="let header of dynamicHeaders">
|
||||
{{item[header.key]}}
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
|
||||
<clr-dg-footer>
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ExcelService } from 'src/app/services/excel.service';
|
||||
import * as moment from 'moment';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-grid-view',
|
||||
templateUrl: './grid-view.component.html',
|
||||
styleUrls: ['./grid-view.component.scss']
|
||||
})
|
||||
export class GridViewComponent implements OnInit {
|
||||
export class GridViewComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
|
||||
loading = false;
|
||||
public entryForm: FormGroup;
|
||||
givendata;
|
||||
givendata: any[] = [];
|
||||
orders;
|
||||
error;
|
||||
error: string;
|
||||
modalAdd = false;
|
||||
modaledit = false;
|
||||
modaldelete = false;
|
||||
@@ -25,30 +36,143 @@ export class GridViewComponent implements OnInit {
|
||||
medit;
|
||||
showdata;
|
||||
submitted = false;
|
||||
dynamicHeaders: any[] = [];
|
||||
|
||||
constructor(
|
||||
private excel: ExcelService,
|
||||
private toastr:ToastrService,
|
||||
private _fb: FormBuilder,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private menuGroupService: MenuGroupService,
|
||||
|
||||
private mainservice: UsergrpmaintainceService,
|
||||
private dashboardService: Dashboard3Service,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.mainservice.getAll().subscribe((data) => {
|
||||
console.log(data);
|
||||
this.givendata = data;
|
||||
if(this.givendata.length==0){
|
||||
this.fetchGridData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('GridViewComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) {
|
||||
console.log('X or Y axis or table or connection changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
|
||||
this.fetchGridData();
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic headers for the grid
|
||||
|
||||
fetchGridData(): void {
|
||||
// If we have the necessary data, fetch grid data from the service
|
||||
if (this.table && this.xAxis) {
|
||||
console.log('Fetching grid data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Fetch data from the dashboard service, similar to other chart components
|
||||
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received grid data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Grid API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.error = "No data Available";
|
||||
console.log(this.error)
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartData) {
|
||||
this.givendata = data.chartData;
|
||||
this.extractDynamicHeaders(data.chartData);
|
||||
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
|
||||
console.log('Updated grid with data:', this.givendata);
|
||||
} else if (data && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.givendata = data.data;
|
||||
this.extractDynamicHeaders(data.data);
|
||||
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
|
||||
console.log('Updated grid with legacy data format:', this.givendata);
|
||||
} else if (Array.isArray(data)) {
|
||||
// Handle case where data is directly an array
|
||||
this.givendata = data;
|
||||
this.extractDynamicHeaders(data);
|
||||
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
|
||||
console.log('Updated grid with array data:', this.givendata);
|
||||
} else {
|
||||
console.warn('Grid received data does not have expected structure', data);
|
||||
this.error = "No valid data received";
|
||||
this.givendata = [];
|
||||
}
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if(error){
|
||||
console.log('Error fetching grid data:', error);
|
||||
this.error = "Server Error";
|
||||
});
|
||||
} else if (this.table) {
|
||||
console.log('Missing xAxis, falling back to default data fetching');
|
||||
// Fallback to default data fetching when only table is provided
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Fetch data from the dashboard service, similar to other chart components
|
||||
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe(
|
||||
(data: any) => {
|
||||
// this.mainservice.getAll().subscribe((data: any) => {
|
||||
console.log('recv data ', data);
|
||||
this.givendata = Array.isArray(data) ? data : [];
|
||||
this.extractDynamicHeaders(data);
|
||||
this.error = this.givendata && this.givendata.length === 0 ? "No data Available" : undefined;
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
this.error = "Server Error";
|
||||
});
|
||||
} else {
|
||||
console.log('Missing required data for grid:', { table: this.table });
|
||||
this.error = "Table name is required";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract dynamic headers from the data
|
||||
* @param data Array of data objects
|
||||
*/
|
||||
private extractDynamicHeaders(data: any): void {
|
||||
// Ensure data is an array
|
||||
const dataArray = Array.isArray(data) ? data : [];
|
||||
|
||||
if (dataArray && dataArray.length > 0) {
|
||||
// Get all unique keys from the data objects
|
||||
const allKeys = new Set<string>();
|
||||
dataArray.forEach(item => {
|
||||
if (item && typeof item === 'object') {
|
||||
Object.keys(item).forEach(key => allKeys.add(key));
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to array of header objects with key and display name
|
||||
this.dynamicHeaders = Array.from(allKeys).map(key => ({
|
||||
key: key,
|
||||
displayName: this.formatHeader(key)
|
||||
}));
|
||||
|
||||
console.log('Dynamic headers extracted:', this.dynamicHeaders);
|
||||
} else {
|
||||
this.dynamicHeaders = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format header name for better display
|
||||
* @param key The key to format
|
||||
*/
|
||||
private formatHeader(key: string): string {
|
||||
// Convert camelCase to Title Case
|
||||
return key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, str => str.toUpperCase());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,22 @@
|
||||
<div style="display: block;">
|
||||
<div style="display: block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="lineChartData"
|
||||
[labels]="lineChartLabels"
|
||||
@@ -9,4 +27,5 @@
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- [color]="lineChartColors"-->
|
||||
@@ -1,11 +1,36 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-line-chart',
|
||||
templateUrl: './line-chart.component.html',
|
||||
styleUrls: ['./line-chart.component.scss']
|
||||
})
|
||||
export class LineChartComponent implements OnInit {
|
||||
export class LineChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
public lineChartData: Array<any> = [
|
||||
{data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A'},
|
||||
{data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B'},
|
||||
@@ -44,6 +69,309 @@ export class LineChartComponent implements OnInit {
|
||||
public lineChartLegend: boolean = true;
|
||||
public lineChartType: string = 'line';
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalLineChartLabels: Array<any> = [];
|
||||
originalLineChartData: Array<any> = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('LineChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Update legend visibility if it changed
|
||||
if (changes.chartlegend !== undefined) {
|
||||
this.lineChartLegend = changes.chartlegend.currentValue;
|
||||
console.log('Chart legend changed to:', this.lineChartLegend);
|
||||
}
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/line?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'line', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.lineChartLabels = data.chartLabels;
|
||||
this.lineChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.lineChartData = [...this.lineChartData];
|
||||
console.log('Updated line chart with data:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.lineChartLabels = data.labels;
|
||||
this.lineChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.lineChartData = [...this.lineChartData];
|
||||
console.log('Updated line chart with legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||
} else {
|
||||
console.warn('Received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/line?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'line', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.lineChartLabels = data.chartLabels;
|
||||
this.lineChartData = data.chartData;
|
||||
// Trigger change detection
|
||||
this.lineChartData = [...this.lineChartData];
|
||||
console.log('Updated line chart with drilldown data:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Backend has already filtered the data, just display it
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.lineChartLabels = data.labels;
|
||||
this.lineChartData = data.datasets;
|
||||
// Trigger change detection
|
||||
this.lineChartData = [...this.lineChartData];
|
||||
console.log('Updated line chart with drilldown legacy data format:', { labels: this.lineChartLabels, data: this.lineChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.lineChartLabels = [];
|
||||
this.lineChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalLineChartLabels.length > 0) {
|
||||
this.lineChartLabels = [...this.originalLineChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalLineChartData.length > 0) {
|
||||
this.lineChartData = [...this.originalLineChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.lineChartLabels);
|
||||
console.log('After reset - data:', this.lineChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
public randomize(): void {
|
||||
let _lineChartData: Array<any> = new Array(this.lineChartData.length);
|
||||
for (let i = 0; i < this.lineChartData.length; i++) {
|
||||
@@ -57,15 +385,90 @@ export class LineChartComponent implements OnInit {
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Line chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.lineChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on line point:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalLineChartLabels = [...this.lineChartLabels];
|
||||
this.originalLineChartData = [...this.lineChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,44 @@
|
||||
<div style="display: block;">
|
||||
<div class="pie-chart-container">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 class="chart-title" *ngIf="charttitle">{{ charttitle }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<!-- Show loading indicator -->
|
||||
<div class="loading-indicator" *ngIf="pieChartLabels.length === 0 && pieChartData.length === 0 && !noDataAvailable">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading chart data...</p>
|
||||
</div>
|
||||
|
||||
<!-- Show no data message -->
|
||||
<div class="no-data-message" *ngIf="noDataAvailable && pieChartLabels.length === 0">
|
||||
<p>No chart data available</p>
|
||||
</div>
|
||||
|
||||
<!-- Show chart when data is available -->
|
||||
<canvas baseChart
|
||||
*ngIf="pieChartLabels.length > 0 && pieChartData.length > 0"
|
||||
[data]="pieChartData"
|
||||
[labels]="pieChartLabels"
|
||||
[type]="pieChartType"
|
||||
[options]="pieChartOptions"
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="chart-legend" *ngIf="showlabel && pieChartLabels && pieChartLabels.length > 0">
|
||||
<div class="legend-item" *ngFor="let label of pieChartLabels; let i = index">
|
||||
<span class="legend-color" [style.background-color]="getLegendColor(i)"></span>
|
||||
<span class="legend-label">{{ label }}</span>
|
||||
<span class="legend-value">{{ pieChartData && pieChartData[i] !== undefined ? pieChartData[i] : 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,182 @@
|
||||
.pie-chart-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
min-height: 400px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.pie-chart-container:hover {
|
||||
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 250px;
|
||||
margin: 15px 0;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-wrapper canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
|
||||
}
|
||||
|
||||
.chart-wrapper canvas:hover {
|
||||
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.15));
|
||||
transform: scale(1.02);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #eaeaea;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legend-item:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
border-color: #3498db;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
display: inline-block;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-right: 15px;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.legend-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #3498db;
|
||||
background: linear-gradient(135deg, #e9ecef 0%, #dde1e5 100%);
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-indicator, .no-data-message {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-indicator p, .no-data-message p {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.pie-chart-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,613 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewChecked } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pie-chart',
|
||||
templateUrl: './pie-chart.component.html',
|
||||
styleUrls: ['./pie-chart.component.scss']
|
||||
})
|
||||
export class PieChartComponent implements OnInit {
|
||||
export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
public pieChartLabels: string[] = ['SciFi', 'Drama', 'Comedy'];
|
||||
public pieChartLabels: string[] = ['Category A', 'Category B', 'Category C'];
|
||||
public pieChartData: number[] = [30, 50, 20];
|
||||
public pieChartType: string = 'pie';
|
||||
public pieChartOptions: any = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false // We'll create our own legend
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
titleFont: {
|
||||
size: 16,
|
||||
color: '#fff'
|
||||
},
|
||||
bodyFont: {
|
||||
size: 14,
|
||||
color: '#fff'
|
||||
},
|
||||
cornerRadius: 4,
|
||||
displayColors: false
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
animateRotate: true,
|
||||
animateScale: false
|
||||
},
|
||||
elements: {
|
||||
arc: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Chart colors for consistent styling
|
||||
private chartColors: string[] = [
|
||||
'#FF6384',
|
||||
'#36A2EB',
|
||||
'#FFCE56',
|
||||
'#4BC0C0',
|
||||
'#9966FF',
|
||||
'#FF9F40',
|
||||
'#FF6384',
|
||||
'#C9CBCF'
|
||||
];
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalPieChartLabels: string[] = [];
|
||||
originalPieChartData: number[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
/**
|
||||
* Force chart redraw
|
||||
*/
|
||||
public redrawChart(): void {
|
||||
// This method can be called to force a chart redraw if needed
|
||||
console.log('Redrawing pie chart');
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('PieChartComponent initialized with default data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
// Validate initial data
|
||||
this.validateChartData();
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('PieChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching pie chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/pie?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Pie chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'pie', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received pie chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Pie chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
// Validate and sanitize data to show default data
|
||||
this.validateChartData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For pie charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the pie chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.pieChartLabels = data.chartLabels || [];
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.pieChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
} else {
|
||||
this.pieChartData = [];
|
||||
}
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
console.log('Updated pie chart with data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.pieChartLabels = data.labels || [];
|
||||
this.pieChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
console.log('Updated pie chart with legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
} else {
|
||||
console.warn('Pie chart received data does not have expected structure', data);
|
||||
// Reset to default data
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
// Validate and sanitize data to show default data
|
||||
this.validateChartData();
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching pie chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
// Validate and sanitize data to show default data
|
||||
this.validateChartData();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for pie chart, showing default data:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
// Don't set noDataAvailable to true when there's no required data
|
||||
// This allows static data to be displayed
|
||||
this.noDataAvailable = false;
|
||||
// Validate the chart data to ensure we have some data to display
|
||||
this.validateChartData();
|
||||
// Force a redraw to ensure the chart displays
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/pie?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'pie', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For pie charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the pie chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.pieChartLabels = data.chartLabels || [];
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.pieChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
} else {
|
||||
this.pieChartData = [];
|
||||
}
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
console.log('Updated pie chart with drilldown data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.pieChartLabels = data.labels || [];
|
||||
this.pieChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
// Trigger change detection
|
||||
this.pieChartData = [...this.pieChartData];
|
||||
console.log('Updated pie chart with drilldown legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
// Validate and sanitize data
|
||||
this.validateChartData();
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.pieChartLabels = [];
|
||||
this.pieChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalPieChartLabels.length > 0) {
|
||||
this.pieChartLabels = [...this.originalPieChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalPieChartData.length > 0) {
|
||||
this.pieChartData = [...this.originalPieChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.pieChartLabels);
|
||||
console.log('After reset - data:', this.pieChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for legend item
|
||||
* @param index Index of the legend item
|
||||
*/
|
||||
public getLegendColor(index: number): string {
|
||||
return this.chartColors[index % this.chartColors.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure labels and data arrays have the same length
|
||||
*/
|
||||
private syncLabelAndDataArrays(): void {
|
||||
// Ensure we have matching arrays
|
||||
if (this.pieChartLabels.length !== this.pieChartData.length) {
|
||||
const maxLength = Math.max(this.pieChartLabels.length, this.pieChartData.length);
|
||||
while (this.pieChartLabels.length < maxLength) {
|
||||
this.pieChartLabels.push(`Label ${this.pieChartLabels.length + 1}`);
|
||||
}
|
||||
while (this.pieChartData.length < maxLength) {
|
||||
this.pieChartData.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and sanitize chart data
|
||||
*/
|
||||
private validateChartData(): void {
|
||||
console.log('Validating chart data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
|
||||
// Ensure we have valid arrays
|
||||
if (!Array.isArray(this.pieChartLabels)) {
|
||||
this.pieChartLabels = [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.pieChartData)) {
|
||||
this.pieChartData = [];
|
||||
}
|
||||
|
||||
// Ensure we have some data to display
|
||||
if (this.pieChartLabels.length === 0 && this.pieChartData.length === 0) {
|
||||
// Add default data to ensure chart visibility
|
||||
this.pieChartLabels = ['Category A', 'Category B', 'Category C'];
|
||||
this.pieChartData = [30, 50, 20];
|
||||
console.log('Added default data for chart display');
|
||||
}
|
||||
|
||||
// Ensure labels and data arrays have the same length
|
||||
this.syncLabelAndDataArrays();
|
||||
|
||||
// Ensure all data values are numbers
|
||||
this.pieChartData = this.pieChartData.map(value => {
|
||||
const numValue = Number(value);
|
||||
return isNaN(numValue) ? 0 : numValue;
|
||||
});
|
||||
|
||||
console.log('After validation:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||
}
|
||||
|
||||
ngAfterViewChecked() {
|
||||
// Debugging: Log component state after view checks
|
||||
console.log('PieChartComponent state:', {
|
||||
labels: this.pieChartLabels,
|
||||
data: this.pieChartData,
|
||||
hasData: this.pieChartLabels.length > 0 && this.pieChartData.length > 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if chart data is valid and ready to display
|
||||
*/
|
||||
public isChartDataValid(): boolean {
|
||||
return this.pieChartLabels && this.pieChartData &&
|
||||
Array.isArray(this.pieChartLabels) && Array.isArray(this.pieChartData) &&
|
||||
this.pieChartLabels.length > 0 && this.pieChartData.length > 0 &&
|
||||
this.pieChartLabels.length === this.pieChartData.length;
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Pie chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.pieChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on pie slice:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalPieChartLabels = [...this.pieChartLabels];
|
||||
this.originalPieChartData = [...this.pieChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,28 @@
|
||||
|
||||
<div style="display: block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="polarAreaChartData"
|
||||
[data]="polarAreaChartData"
|
||||
[labels]="polarAreaChartLabels"
|
||||
[type]="polarAreaChartType"
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,16 +1,68 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-polar-chart',
|
||||
templateUrl: './polar-chart.component.html',
|
||||
styleUrls: ['./polar-chart.component.scss']
|
||||
})
|
||||
export class PolarChartComponent implements OnInit {
|
||||
export class PolarChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('PolarChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
public polarAreaChartLabels: string[] = [ 'Download Sales', 'In-Store Sales', 'Mail Sales', 'Telesales', 'Corporate Sales' ];
|
||||
public polarAreaChartData: any = [
|
||||
{ data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'}
|
||||
@@ -18,16 +70,376 @@ export class PolarChartComponent implements OnInit {
|
||||
|
||||
public polarAreaChartType: string = 'polarArea';
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalPolarAreaChartLabels: string[] = [];
|
||||
originalPolarAreaChartData: any = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
// public radarChartData: any = [
|
||||
// { data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" },
|
||||
// { data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" }
|
||||
// ];
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching polar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/polar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Polar chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'polar', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received polar chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For polar charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the polar chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.polarAreaChartLabels = data.chartLabels;
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
});
|
||||
} else {
|
||||
this.polarAreaChartData = [];
|
||||
}
|
||||
// Trigger change detection
|
||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||
console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.polarAreaChartLabels = data.labels;
|
||||
this.polarAreaChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
});
|
||||
// Trigger change detection
|
||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||
console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||
} else {
|
||||
console.warn('Polar chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching polar chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/polar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'polar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For polar charts, we need to extract the data differently
|
||||
// The first dataset's data array contains the values for the polar chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.polarAreaChartLabels = data.chartLabels;
|
||||
if (data.chartData && data.chartData.length > 0) {
|
||||
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
});
|
||||
} else {
|
||||
this.polarAreaChartData = [];
|
||||
}
|
||||
// Trigger change detection
|
||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||
console.log('Updated polar chart with drilldown data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||
} else if (data && data.labels && data.data) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.polarAreaChartLabels = data.labels;
|
||||
this.polarAreaChartData = data.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
});
|
||||
// Trigger change detection
|
||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||
console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.polarAreaChartLabels = [];
|
||||
this.polarAreaChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalPolarAreaChartLabels.length > 0) {
|
||||
this.polarAreaChartLabels = [...this.originalPolarAreaChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalPolarAreaChartData.length > 0) {
|
||||
this.polarAreaChartData = [...this.originalPolarAreaChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.polarAreaChartLabels);
|
||||
console.log('After reset - data:', this.polarAreaChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Polar chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.polarAreaChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on polar slice:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalPolarAreaChartLabels = [...this.polarAreaChartLabels];
|
||||
this.originalPolarAreaChartData = [...this.polarAreaChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
<div style="display: block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="radarChartData"
|
||||
[labels]="radarChartLabels"
|
||||
[type]="radarChartType"
|
||||
(chartHover)="chartHovered($event)"
|
||||
(chartClick)="chartClicked($event)"></canvas>
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +1,36 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-radar-chart',
|
||||
templateUrl: './radar-chart.component.html',
|
||||
styleUrls: ['./radar-chart.component.scss']
|
||||
})
|
||||
export class RadarChartComponent implements OnInit {
|
||||
export class RadarChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
// Radar
|
||||
public radarChartLabels: string[] = [
|
||||
"Eating",
|
||||
@@ -23,17 +48,413 @@ public radarChartData: any = [
|
||||
];
|
||||
public radarChartType: string = "radar";
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalRadarChartLabels: string[] = [];
|
||||
originalRadarChartData: any = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('RadarChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching radar chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/radar?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Radar chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'radar', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received radar chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Radar chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.radarChartLabels = data.chartLabels;
|
||||
// For radar charts, we need to ensure the data is properly formatted
|
||||
// Each dataset should have a data array with numeric values
|
||||
this.radarChartData = data.chartData.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.radarChartData = [...this.radarChartData];
|
||||
console.log('Updated radar chart with data:', { labels: this.radarChartLabels, data: this.radarChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.radarChartLabels = data.labels;
|
||||
this.radarChartData = data.datasets.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.radarChartData = [...this.radarChartData];
|
||||
console.log('Updated radar chart with legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData });
|
||||
} else {
|
||||
console.warn('Radar chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching radar chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for radar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/radar?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'radar', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// Map the API response to the format expected by the chart
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.radarChartLabels = data.chartLabels;
|
||||
// For radar charts, we need to ensure the data is properly formatted
|
||||
// Each dataset should have a data array with numeric values
|
||||
this.radarChartData = data.chartData.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.radarChartData = [...this.radarChartData];
|
||||
console.log('Updated radar chart with drilldown data:', { labels: this.radarChartLabels, data: this.radarChartData });
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.radarChartLabels = data.labels;
|
||||
this.radarChartData = data.datasets.map(dataset => ({
|
||||
...dataset,
|
||||
data: dataset.data ? dataset.data.map(value => {
|
||||
// Convert to number if it's not already
|
||||
return isNaN(Number(value)) ? 0 : Number(value);
|
||||
}) : []
|
||||
}));
|
||||
// Trigger change detection
|
||||
this.radarChartData = [...this.radarChartData];
|
||||
console.log('Updated radar chart with drilldown legacy data format:', { labels: this.radarChartLabels, data: this.radarChartData });
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.radarChartLabels = [];
|
||||
this.radarChartData = [];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalRadarChartLabels.length > 0) {
|
||||
this.radarChartLabels = [...this.originalRadarChartLabels];
|
||||
console.log('Restored original labels');
|
||||
}
|
||||
if (this.originalRadarChartData.length > 0) {
|
||||
this.radarChartData = [...this.originalRadarChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - labels:', this.radarChartLabels);
|
||||
console.log('After reset - data:', this.radarChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Radar chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the label of the clicked element
|
||||
const clickedLabel = this.radarChartLabels[clickedIndex];
|
||||
|
||||
console.log('Clicked on radar point:', { index: clickedIndex, label: clickedLabel });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalRadarChartLabels = [...this.radarChartLabels];
|
||||
this.originalRadarChartData = [...this.radarChartData];
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedLabel: clickedLabel,
|
||||
clickedValue: clickedLabel // Using label as value for now
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
console.log(e);
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,22 @@
|
||||
<div style="display: block">
|
||||
<!-- Drilldown mode indicator -->
|
||||
<div *ngIf="currentDrilldownLevel > 0" style="background-color: #e0e0e0; padding: 5px; margin-bottom: 10px; border-radius: 4px; text-align: center;">
|
||||
<span style="font-weight: bold; color: #333;">Drilldown Level: {{currentDrilldownLevel}}</span>
|
||||
<button (click)="navigateBack()" style="margin-left: 10px; padding: 2px 8px; background-color: #007cba; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Level {{currentDrilldownLevel - 1}}
|
||||
</button>
|
||||
<button (click)="resetToOriginalData()" style="margin-left: 10px; padding: 2px 8px; background-color: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
||||
Back to Main View
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
||||
No data available
|
||||
</div>
|
||||
|
||||
<!-- Chart display -->
|
||||
<div *ngIf="!noDataAvailable">
|
||||
<canvas baseChart
|
||||
[datasets]="scatterChartData"
|
||||
[type]="scatterChartType"
|
||||
@@ -6,3 +24,4 @@
|
||||
(chartClick)="chartClicked($event)">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,44 +1,72 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChartData,ChartDataset } from 'chart.js';
|
||||
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-scatter-chart',
|
||||
templateUrl: './scatter-chart.component.html',
|
||||
styleUrls: ['./scatter-chart.component.scss']
|
||||
})
|
||||
export class ScatterChartComponent implements OnInit {
|
||||
export class ScatterChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
// Drilldown configuration inputs
|
||||
@Input() drilldownEnabled: boolean = false;
|
||||
@Input() drilldownApiUrl: string;
|
||||
@Input() drilldownXAxis: string;
|
||||
@Input() drilldownYAxis: string;
|
||||
@Input() drilldownParameter: string; // Add drilldown parameter input
|
||||
// Multi-layer drilldown configuration inputs
|
||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||
|
||||
constructor() { }
|
||||
constructor(private dashboardService: Dashboard3Service) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// Initialize with default data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('ScatterChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange;
|
||||
// Drilldown configuration changes
|
||||
const drilldownEnabledChanged = changes.drilldownEnabled && !changes.drilldownEnabled.firstChange;
|
||||
const drilldownApiUrlChanged = changes.drilldownApiUrl && !changes.drilldownApiUrl.firstChange;
|
||||
const drilldownXAxisChanged = changes.drilldownXAxis && !changes.drilldownXAxis.firstChange;
|
||||
const drilldownYAxisChanged = changes.drilldownYAxis && !changes.drilldownYAxis.firstChange;
|
||||
const drilldownLayersChanged = changes.drilldownLayers && !changes.drilldownLayers.firstChange;
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged ||
|
||||
drilldownEnabledChanged || drilldownApiUrlChanged || drilldownXAxisChanged || drilldownYAxisChanged ||
|
||||
drilldownLayersChanged) {
|
||||
console.log('X or Y axis or table or connection or drilldown config changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, connection, or drilldown config has changed (and it's not the first change)
|
||||
this.fetchChartData();
|
||||
}
|
||||
}
|
||||
|
||||
public scatterChartLabels: string[] = [ 'Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running' ];
|
||||
|
||||
public scatterChartData: ChartDataset[] = [
|
||||
// {
|
||||
// data: [
|
||||
// { x: 1, y: 1 },
|
||||
// { x: 2, y: 3 },
|
||||
// { x: 3, y: -2 },
|
||||
// { x: 4, y: 4 },
|
||||
// { x: 5, y: -3, r: 20 },
|
||||
// ],
|
||||
// label: 'Series A',
|
||||
// pointRadius: 10,
|
||||
// backgroundColor: 'red',
|
||||
// },
|
||||
// {
|
||||
// data: [
|
||||
// { x: 2, y: 2 },
|
||||
// { x: 3, y: 1 },
|
||||
// { x: 4, y: 3 },
|
||||
// { x: 5, y: 2 },
|
||||
// { x: 6, y: 4, r: 15 },
|
||||
// ],
|
||||
// label: 'Series B',
|
||||
// pointRadius: 8,
|
||||
// backgroundColor: 'green',
|
||||
// },
|
||||
{
|
||||
data: [
|
||||
{ x: 1, y: 1 },
|
||||
@@ -65,10 +93,374 @@ export class ScatterChartComponent implements OnInit {
|
||||
];
|
||||
public scatterChartType: string = 'scatter';
|
||||
|
||||
// Multi-layer drilldown state tracking
|
||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||
currentDrilldownLevel: number = 0; // Current drilldown level (0 = base level)
|
||||
originalScatterChartData: ChartDataset[] = [];
|
||||
|
||||
// No data state
|
||||
noDataAvailable: boolean = false;
|
||||
|
||||
fetchChartData(): void {
|
||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||
this.fetchDrilldownData();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have the necessary data, fetch chart data from the service
|
||||
if (this.table && this.xAxis && this.yAxis) {
|
||||
console.log('Fetching scatter chart data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
|
||||
// Convert yAxis to string if it's an array
|
||||
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/scatter?tableName=${this.table}&xAxis=${this.xAxis}&yAxes=${yAxisString}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Scatter chart data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// For base level, we pass empty parameter and value
|
||||
this.dashboardService.getChartData(this.table, 'scatter', this.xAxis, yAxisString, this.connection, '', '').subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received scatter chart data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Scatter chart API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For scatter charts, we need to transform the data into scatter format
|
||||
// Scatter charts expect data in the format: {x: number, y: number}
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.scatterChartData = this.transformToScatterData(data.chartLabels, data.chartData);
|
||||
console.log('Updated scatter chart with data:', this.scatterChartData);
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.scatterChartData = data.datasets;
|
||||
console.log('Updated scatter chart with legacy data format:', this.scatterChartData);
|
||||
} else {
|
||||
console.warn('Scatter chart received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching scatter chart data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
// Keep default data in case of error
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Missing required data for scatter chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch drilldown data based on current drilldown level
|
||||
fetchDrilldownData(): void {
|
||||
console.log('Fetching drilldown data, current level:', this.currentDrilldownLevel);
|
||||
console.log('Drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Get the current drilldown configuration based on the current level
|
||||
let drilldownConfig;
|
||||
if (this.currentDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = this.currentDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex >= 0 && layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
} else {
|
||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for level', this.currentDrilldownLevel, ':', drilldownConfig);
|
||||
|
||||
// Check if we have valid drilldown configuration
|
||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the parameter value from the drilldown stack
|
||||
let parameterValue = '';
|
||||
if (this.drilldownStack.length > 0) {
|
||||
const lastEntry = this.drilldownStack[this.drilldownStack.length - 1];
|
||||
parameterValue = lastEntry.clickedValue || '';
|
||||
console.log('Parameter value from last click:', parameterValue);
|
||||
}
|
||||
|
||||
// Get the parameter field from drilldown config
|
||||
const parameterField = drilldownConfig.parameter || '';
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
console.log('Fetching drilldown data for level:', this.currentDrilldownLevel, {
|
||||
apiUrl: drilldownConfig.apiUrl,
|
||||
xAxis: drilldownConfig.xAxis,
|
||||
yAxis: drilldownConfig.yAxis,
|
||||
parameterField: parameterField,
|
||||
parameterValue: parameterValue,
|
||||
connection: this.connection
|
||||
});
|
||||
|
||||
// Build the actual API URL with parameter replacement
|
||||
let actualApiUrl = drilldownConfig.apiUrl;
|
||||
console.log('Original API URL:', actualApiUrl);
|
||||
console.log('Parameter value to use:', parameterValue);
|
||||
console.log('Parameter field:', parameterField);
|
||||
|
||||
// Check if the URL contains angle brackets for parameter replacement
|
||||
const hasAngleBrackets = /<[^>]+>/.test(actualApiUrl);
|
||||
|
||||
if (hasAngleBrackets && parameterValue) {
|
||||
// Replace angle brackets placeholder with actual value
|
||||
console.log('Replacing angle brackets with parameter value');
|
||||
const encodedValue = encodeURIComponent(parameterValue);
|
||||
actualApiUrl = actualApiUrl.replace(/<[^>]+>/g, encodedValue);
|
||||
console.log('URL after angle bracket replacement:', actualApiUrl);
|
||||
}
|
||||
|
||||
// Log the URL that will be called
|
||||
const url = `chart/getdashjson/scatter?tableName=${actualApiUrl}&xAxis=${drilldownConfig.xAxis}&yAxes=${drilldownConfig.yAxis}${this.connection ? `&sureId=${this.connection}` : ''}`;
|
||||
console.log('Drilldown data URL:', url);
|
||||
|
||||
// Fetch data from the dashboard service with parameter field and value
|
||||
// Backend handles filtering, we just pass the parameter field and value
|
||||
this.dashboardService.getChartData(actualApiUrl, 'scatter', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue).subscribe(
|
||||
(data: any) => {
|
||||
console.log('Received drilldown data:', data);
|
||||
if (data === null) {
|
||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the actual data structure returned by the API
|
||||
if (data && data.chartLabels && data.chartData) {
|
||||
// For scatter charts, we need to transform the data into scatter format
|
||||
// Scatter charts expect data in the format: {x: number, y: number}
|
||||
this.noDataAvailable = data.chartLabels.length === 0;
|
||||
this.scatterChartData = this.transformToScatterData(data.chartLabels, data.chartData);
|
||||
console.log('Updated scatter chart with drilldown data:', this.scatterChartData);
|
||||
} else if (data && data.labels && data.datasets) {
|
||||
// Handle the original expected format as fallback
|
||||
this.noDataAvailable = data.labels.length === 0;
|
||||
this.scatterChartData = data.datasets;
|
||||
console.log('Updated scatter chart with drilldown legacy data format:', this.scatterChartData);
|
||||
} else {
|
||||
console.warn('Drilldown received data does not have expected structure', data);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('Error fetching drilldown data:', error);
|
||||
this.noDataAvailable = true;
|
||||
this.scatterChartData = [];
|
||||
// Keep current data in case of error
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset to original data (go back to base level)
|
||||
resetToOriginalData(): void {
|
||||
console.log('Resetting to original data');
|
||||
console.log('Current stack before reset:', this.drilldownStack);
|
||||
console.log('Current level before reset:', this.currentDrilldownLevel);
|
||||
|
||||
this.currentDrilldownLevel = 0;
|
||||
this.drilldownStack = [];
|
||||
|
||||
if (this.originalScatterChartData.length > 0) {
|
||||
this.scatterChartData = [...this.originalScatterChartData];
|
||||
console.log('Restored original data');
|
||||
}
|
||||
|
||||
console.log('After reset - data:', this.scatterChartData);
|
||||
|
||||
// Re-fetch original data
|
||||
this.fetchChartData();
|
||||
}
|
||||
|
||||
// Navigate back to previous drilldown level
|
||||
navigateBack(): void {
|
||||
console.log('Navigating back, current stack:', this.drilldownStack);
|
||||
console.log('Current level:', this.currentDrilldownLevel);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Remove the last entry from the stack
|
||||
const removedEntry = this.drilldownStack.pop();
|
||||
console.log('Removed entry from stack:', removedEntry);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = this.drilldownStack.length;
|
||||
console.log('New level after pop:', this.currentDrilldownLevel);
|
||||
console.log('Stack after pop:', this.drilldownStack);
|
||||
|
||||
if (this.drilldownStack.length > 0) {
|
||||
// Fetch data for the previous level
|
||||
console.log('Fetching data for previous level');
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
// Back to base level
|
||||
console.log('Back to base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
} else {
|
||||
// Already at base level, reset to original data
|
||||
console.log('Already at base level, resetting to original data');
|
||||
this.resetToOriginalData();
|
||||
}
|
||||
}
|
||||
|
||||
private transformToScatterData(labels: any[], chartData: any[]): ChartDataset[] {
|
||||
// Transform the API data into scatter chart format
|
||||
const datasets: ChartDataset[] = [];
|
||||
|
||||
// Create a dataset for each data series
|
||||
chartData.forEach((series, index) => {
|
||||
// For scatter charts, we need x and y values
|
||||
// We'll use the labels as x values and the data as y values
|
||||
|
||||
const scatterData = labels.map((label, i) => {
|
||||
const xValue = isNaN(Number(label)) ? i : Number(label);
|
||||
const yValue = series.data && series.data[i] !== undefined ?
|
||||
(isNaN(Number(series.data[i])) ? 0 : Number(series.data[i])) : 0;
|
||||
|
||||
return {
|
||||
x: xValue,
|
||||
y: yValue
|
||||
};
|
||||
});
|
||||
|
||||
datasets.push({
|
||||
data: scatterData,
|
||||
label: series.label || `Series ${index + 1}`,
|
||||
pointRadius: 10,
|
||||
backgroundColor: this.getBackgroundColor(index),
|
||||
});
|
||||
});
|
||||
|
||||
return datasets;
|
||||
}
|
||||
|
||||
private getBackgroundColor(index: number): string {
|
||||
const colors = [
|
||||
'red', 'green', 'blue', 'purple', 'yellow',
|
||||
'brown', 'magenta', 'cyan', 'orange', 'pink'
|
||||
];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
// events
|
||||
public chartClicked(e: any): void {
|
||||
console.log(e);
|
||||
console.log('Scatter chart clicked:', e);
|
||||
|
||||
// If drilldown is enabled and we have a valid click event
|
||||
if (this.drilldownEnabled && e.active && e.active.length > 0) {
|
||||
// Get the index of the clicked element
|
||||
const clickedIndex = e.active[0].index;
|
||||
|
||||
// Get the dataset index
|
||||
const datasetIndex = e.active[0].datasetIndex;
|
||||
|
||||
// Get the data point
|
||||
const dataPoint = this.scatterChartData[datasetIndex].data[clickedIndex];
|
||||
|
||||
console.log('Clicked on scatter point:', { datasetIndex: datasetIndex, index: clickedIndex, dataPoint: dataPoint });
|
||||
|
||||
// If we're not at the base level, store original data
|
||||
if (this.currentDrilldownLevel === 0) {
|
||||
// Store original data before entering drilldown mode
|
||||
this.originalScatterChartData = JSON.parse(JSON.stringify(this.scatterChartData));
|
||||
console.log('Stored original data for drilldown');
|
||||
}
|
||||
|
||||
// Determine the next drilldown level
|
||||
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
|
||||
|
||||
console.log('Next drilldown level will be:', nextDrilldownLevel);
|
||||
|
||||
// Check if there's a drilldown configuration for this level
|
||||
let hasDrilldownConfig = false;
|
||||
let drilldownConfig;
|
||||
|
||||
if (nextDrilldownLevel === 1) {
|
||||
// Base drilldown level
|
||||
drilldownConfig = {
|
||||
apiUrl: this.drilldownApiUrl,
|
||||
xAxis: this.drilldownXAxis,
|
||||
yAxis: this.drilldownYAxis,
|
||||
parameter: this.drilldownParameter
|
||||
};
|
||||
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
|
||||
} else {
|
||||
// Multi-layer drilldown level
|
||||
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
|
||||
if (layerIndex < this.drilldownLayers.length) {
|
||||
drilldownConfig = this.drilldownLayers[layerIndex];
|
||||
hasDrilldownConfig = drilldownConfig.enabled &&
|
||||
!!drilldownConfig.apiUrl &&
|
||||
!!drilldownConfig.xAxis &&
|
||||
!!drilldownConfig.yAxis;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Drilldown config for next level:', drilldownConfig);
|
||||
console.log('Has drilldown config:', hasDrilldownConfig);
|
||||
|
||||
// If there's a drilldown configuration for the next level, proceed
|
||||
if (hasDrilldownConfig) {
|
||||
// For scatter charts, we'll use the x value as the clicked value
|
||||
const clickedValue = dataPoint && (dataPoint as any).x !== undefined ?
|
||||
(dataPoint as any).x.toString() : '';
|
||||
|
||||
// Add this click to the drilldown stack
|
||||
const stackEntry = {
|
||||
level: nextDrilldownLevel,
|
||||
datasetIndex: datasetIndex,
|
||||
clickedIndex: clickedIndex,
|
||||
clickedValue: clickedValue
|
||||
};
|
||||
|
||||
this.drilldownStack.push(stackEntry);
|
||||
|
||||
console.log('Added to drilldown stack:', stackEntry);
|
||||
console.log('Current drilldown stack:', this.drilldownStack);
|
||||
|
||||
// Update the current drilldown level
|
||||
this.currentDrilldownLevel = nextDrilldownLevel;
|
||||
|
||||
console.log('Entering drilldown level:', this.currentDrilldownLevel);
|
||||
|
||||
// Fetch drilldown data for the new level
|
||||
this.fetchDrilldownData();
|
||||
} else {
|
||||
console.log('No drilldown configuration for level:', nextDrilldownLevel);
|
||||
}
|
||||
} else {
|
||||
console.log('Drilldown not enabled or invalid click event');
|
||||
}
|
||||
}
|
||||
|
||||
public chartHovered(e: any): void {
|
||||
|
||||
@@ -1,20 +1,69 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-to-do-chart',
|
||||
templateUrl: './to-do-chart.component.html',
|
||||
styleUrls: ['./to-do-chart.component.scss']
|
||||
})
|
||||
export class ToDoChartComponent implements OnInit {
|
||||
export class ToDoChartComponent implements OnInit, OnChanges {
|
||||
@Input() xAxis: string;
|
||||
@Input() yAxis: string | string[];
|
||||
@Input() table: string;
|
||||
@Input() datastore: string;
|
||||
@Input() charttitle: string;
|
||||
@Input() chartlegend: boolean = true;
|
||||
@Input() showlabel: boolean = true;
|
||||
@Input() chartcolor: boolean;
|
||||
@Input() slices: boolean;
|
||||
@Input() donut: boolean;
|
||||
@Input() charturl: string;
|
||||
@Input() chartparameter: string;
|
||||
@Input() datasource: string;
|
||||
@Input() fieldName: string;
|
||||
@Input() connection: number; // Add connection input
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('ToDoChartComponent input changes:', changes);
|
||||
|
||||
// Check if any of the key properties have changed
|
||||
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
|
||||
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
|
||||
const tableChanged = changes.table && !changes.table.firstChange;
|
||||
const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
|
||||
|
||||
// Respond to input changes
|
||||
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) {
|
||||
console.log('X or Y axis or table or connection changed, fetching new data');
|
||||
// Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
|
||||
this.fetchToDoData();
|
||||
}
|
||||
}
|
||||
|
||||
data: any;
|
||||
todo: string;
|
||||
todoList = ['todo 1'];
|
||||
|
||||
fetchToDoData(): void {
|
||||
// If we have the necessary data, fetch to-do data from the service
|
||||
if (this.table) {
|
||||
console.log('Fetching to-do data for:', { table: this.table });
|
||||
|
||||
// For to-do chart, we might want to fetch data differently
|
||||
// This is a placeholder implementation - you may need to adjust based on your API
|
||||
console.log('To-do chart would fetch data from table:', this.table);
|
||||
|
||||
// In a real implementation, you would connect to your service here
|
||||
// For now, we'll just keep the default to-do list
|
||||
} else {
|
||||
console.log('Missing required data for to-do chart:', { table: this.table });
|
||||
}
|
||||
}
|
||||
|
||||
public addTodo(todo: string) {
|
||||
this.todoList.push(todo);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
.s-info-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.s-info-bar button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.entry-pg {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.button1::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.button1:hover::after {
|
||||
content: "ADD ROWS";
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #dddddd;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section p {
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clr-input {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clr-file {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input[type=text], [type=date], textarea {
|
||||
width: 100%;
|
||||
padding: 15px 15px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.required-field {
|
||||
color: red;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}/*# sourceMappingURL=editsureconnect.component.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["editsureconnect.component.scss","editsureconnect.component.css"],"names":[],"mappings":"AACA;EACE,aAAA;EACA,mBAAA;EACA,8BAAA;ACAF;ADCE;EACE,aAAA;ACCJ;;ADGA;EACE,YAAA;ACAF;;ADGA;EACE,aAAA;ACAF;;ADEA;EACE,mBAAA;ACCF;;ADIA;EACE,yBAHS;EAIT,YAAA;ACDF;;ADIA;EAEE,iBAAA;EACA,aAAA;EACA,eAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EACA,wBAAA;EACA,eAAA;EACA,WAAA;EACA,mBAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EAEA,eAAA;EACA,WAAA;EACA,mBAAA;ACHF;;ADMA;EACE,kBAAA;ACHF;;ADKA;EACE,WAAA;EACA,kBAAA;EAED,oCAAA;EACC,qBAAA;EACA,sBAAA;EACA,kBAAA;EACA,sBAAA;ACHF;;ADMA;EACE,UAAA;ACHF;;ADMA;EACE,WAAA;EACA,gBAAA;EACA,sBAAA;EACA,kBAAA;ACHF","file":"editsureconnect.component.css"}
|
||||
@@ -0,0 +1,164 @@
|
||||
<h4 style="font-weight: 300;display: inline;"><b> Connection</b></h4>
|
||||
<span class="label label-light-blue" style="display: inline;margin-left: 10px;">Edit Mode</span><br>
|
||||
Define A connection to use in a job, that can calls APIs from another App.
|
||||
<!-- <span style="display: inline; float: right;"> <button id="add" class="btn btn-primary" ><clr-icon shape="plus" ></clr-icon>Import</button></span> -->
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- entry form-->
|
||||
<form >
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectName">Connection Name</label>
|
||||
<input id="projectName" type="text" placeholder="Enter Connection Name" name="connection_name" [(ngModel)]="editdata.connection_name"
|
||||
class="clr-input">
|
||||
</div>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Description (optional)</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name="description" placeholder="Enter Description" [(ngModel)]="editdata.description">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- add field here -->
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;"> Default Settings</h4>
|
||||
</div>
|
||||
These configurations default.
|
||||
<hr>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="technologyStack">Type</label>
|
||||
<select selected="null" class="clr-dropdown" name="type" [(ngModel)]="editdata.type">
|
||||
<option value="null">Choose Type</option>
|
||||
<option>No AUTH</option>
|
||||
<option>API KEY</option>
|
||||
<option>Bearer Token</option>
|
||||
<option>Basic Oauth</option>
|
||||
<option>Digest Auth</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Access Token</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name="access_token" [(ngModel)]="editdata.access_token" placeholder="Enter token">
|
||||
</textarea>
|
||||
|
||||
</div></div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectPrefix">Client ID</label>
|
||||
<input type="text" placeholder="Enter Clientid" class="clr-input" name="client_id" [(ngModel)]="editdata.client_id">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectPrefix"> Username</label>
|
||||
<input type="text" placeholder="Enter Database Username" class="clr-input" name="username" [(ngModel)]="editdata.username">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="dbPassword"> Password</label>
|
||||
<input type="text" placeholder="Enter Database Password" class="clr-input" name="password" [(ngModel)]="editdata.password">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<span style="float: right;">
|
||||
<button type="submit" class="btn btn-primary" (click)="onupdate()">Update</button>
|
||||
</span>
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;">Generate Token</h4>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectName">URL</label>
|
||||
<input id="projectName" type="text" placeholder="Enter Connection Name"
|
||||
class="clr-input">
|
||||
</div>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Method</label>
|
||||
<select selected="null" class="clr-dropdown">
|
||||
<option selected>GET</option>
|
||||
<option>POST</option>
|
||||
<option>PUT</option>
|
||||
<option>PATCH</option>
|
||||
<option>DELETE</option>
|
||||
<option>COPY</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class=clr-row>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description"> Token</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name=" remarks" placeholder="Enter token">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectPrefix">URL</label>
|
||||
<input type="text" placeholder="Enter url" class="clr-input">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class=clr-row>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Body</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name=" remarks" placeholder="Enter body">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<span class="center" style="text-align: center;">
|
||||
<button class="btn btn-primary" type="submit">Send</button>
|
||||
<button type="submit" class="btn btn-primary">Use</button>
|
||||
</span>
|
||||
<span style="float: right;">
|
||||
<button type="submit" class="btn btn-primary">TEST</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;">Response</h4>
|
||||
</div>
|
||||
<hr>
|
||||
code
|
||||
<span class="label success" style="background-color: green;">200</span><br>
|
||||
<div class="clr-row" style="padding-top: 10px;">
|
||||
<textarea id="" cols="10" rows="10" name=" remarks" placeholder="Enter Description">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
//@import "../../../../assets/scss/var";
|
||||
.s-info-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-pg {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.button1::after {
|
||||
content: none;
|
||||
}
|
||||
.button1:hover::after {
|
||||
content: "ADD ROWS";
|
||||
}
|
||||
|
||||
$bg-color: #dddddd;
|
||||
|
||||
.section {
|
||||
background-color: $bg-color;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section p {
|
||||
//color: white;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clr-input {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clr-file {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
//padding: 0.6rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
input[type=text],[type=date],textarea {
|
||||
width: 100%;
|
||||
padding: 15px 15px;
|
||||
// margin: 8px 0;
|
||||
background-color:rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.required-field{
|
||||
color: red;
|
||||
|
||||
}
|
||||
select{
|
||||
width: 100%;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditsureconnectComponent } from './editsureconnect.component';
|
||||
|
||||
describe('EditsureconnectComponent', () => {
|
||||
let component: EditsureconnectComponent;
|
||||
let fixture: ComponentFixture<EditsureconnectComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EditsureconnectComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditsureconnectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { SureconnectService } from '../sureconnect.service';
|
||||
@Component({
|
||||
selector: 'app-editsureconnect',
|
||||
templateUrl: './editsureconnect.component.html',
|
||||
styleUrls: ['./editsureconnect.component.scss']
|
||||
})
|
||||
export class EditsureconnectComponent implements OnInit {
|
||||
id: number;
|
||||
editdata: any = {}
|
||||
constructor(private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private sureconnectservice: SureconnectService,) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.id = this.route.snapshot.params["id"];
|
||||
console.log("update with id = ", this.id);
|
||||
this.getById(this.id);
|
||||
}
|
||||
getById(id: any) {
|
||||
this.sureconnectservice.getOne(id).subscribe((data) => {
|
||||
this.editdata = data;
|
||||
console.log(this.editdata);
|
||||
})
|
||||
}
|
||||
onupdate() {
|
||||
this.sureconnectservice.update(this.editdata, this.id).subscribe((data) => {
|
||||
console.log('after edit ', data);
|
||||
// Redirect back to sureconnect page after successful update
|
||||
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.s-info-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.s-info-bar button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.entry-pg {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.button1::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.button1:hover::after {
|
||||
content: "ADD ROWS";
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #dddddd;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section p {
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clr-input {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clr-file {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input[type=text], [type=date], textarea {
|
||||
width: 100%;
|
||||
padding: 15px 15px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.required-field {
|
||||
color: red;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}/*# sourceMappingURL=oauth.component.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["oauth.component.scss","oauth.component.css"],"names":[],"mappings":"AACA;EACE,aAAA;EACA,mBAAA;EACA,8BAAA;ACAF;ADCE;EACE,aAAA;ACCJ;;ADGA;EACE,YAAA;ACAF;;ADGA;EACE,aAAA;ACAF;;ADEA;EACE,mBAAA;ACCF;;ADIA;EACE,yBAHS;EAIT,YAAA;ACDF;;ADIA;EAEE,iBAAA;EACA,aAAA;EACA,eAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EACA,wBAAA;EACA,eAAA;EACA,WAAA;EACA,mBAAA;ACFF;;ADKA;EACE,cAAA;EACA,yBAAA;EACA,sBAAA;EAEA,eAAA;EACA,WAAA;EACA,mBAAA;ACHF;;ADMA;EACE,kBAAA;ACHF;;ADKA;EACE,WAAA;EACA,kBAAA;EAED,oCAAA;EACC,qBAAA;EACA,sBAAA;EACA,kBAAA;EACA,sBAAA;ACHF;;ADMA;EACE,UAAA;ACHF;;ADMA;EACE,WAAA;EACA,gBAAA;EACA,sBAAA;EACA,kBAAA;ACHF","file":"oauth.component.css"}
|
||||
@@ -0,0 +1,185 @@
|
||||
<h4 style="font-weight: 300;display: inline;"><b>New Connection</b></h4>
|
||||
<span class="label label-light-blue" style="display: inline;margin-left: 10px;">Add Mode</span><br>
|
||||
Define A connection to use in a job, that can calls APIs from another App.
|
||||
<!-- <span style="display: inline; float: right;"> <button id="add" class="btn btn-primary" ><clr-icon shape="plus" ></clr-icon>Import</button></span> -->
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- entry form-->
|
||||
<form [formGroup]="entryForm">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectName">Connection Name</label>
|
||||
<input id="projectName" type="text" placeholder="Enter Connection Name"
|
||||
class="clr-input" formControlName="connection_name">
|
||||
</div>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Description (optional)</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name="remarks" placeholder="Enter Description" formControlName="description">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- add field here -->
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;"> Default Settings</h4>
|
||||
</div>
|
||||
These configurations default.
|
||||
<hr>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="technologyStack">Type</label>
|
||||
<select selected="null" class="clr-dropdown" formControlName="type">
|
||||
<option value="null">Choose Type</option>
|
||||
<option>No AUTH</option>
|
||||
<option>API KEY</option>
|
||||
<option>Bearer Token</option>
|
||||
<option>Basic Oauth</option>
|
||||
<option>Digest Auth</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Access Token</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name=" remarks" placeholder="Enter token" formControlName="access_token">
|
||||
</textarea>
|
||||
|
||||
</div></div>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectPrefix">Client ID</label>
|
||||
<input type="text" placeholder="Enter Clientid" class="clr-input" formControlName="client_id">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectPrefix"> Username</label>
|
||||
<input type="text" placeholder="Enter Database Username" class="clr-input" formControlName="username">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="dbPassword"> Password</label>
|
||||
<input type="text" placeholder="Enter Database Password" class="clr-input" formControlName="password">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<span style="float: right;">
|
||||
<button type="submit" class="btn btn-primary" (click)="onSubmit()">SUBMIT</button>
|
||||
</span>
|
||||
</form>
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;">Generate Token</h4>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Request Method</label>
|
||||
<select selected="null" class="clr-dropdown">
|
||||
<option selected>GET</option>
|
||||
<option>POST</option>
|
||||
<option>PUT</option>
|
||||
<option>PATCH</option>
|
||||
<option>DELETE</option>
|
||||
<option>COPY</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectName">URL</label>
|
||||
<input id="projectName" type="text" placeholder="https://example.com"
|
||||
class="clr-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Body</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name=" remarks" placeholder="Enter body" >
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="center" style="text-align: center;">
|
||||
<button class="btn btn-primary" type="submit" (click)="onLogin()">Send</button>
|
||||
<button type="submit" class="btn btn-primary">Use</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;">Test Connection</h4>
|
||||
|
||||
</div>
|
||||
<hr>
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="projectName">URL</label>
|
||||
<input id="projectName" type="text" placeholder="Enter Connection Name"
|
||||
class="clr-input">
|
||||
</div>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Method</label>
|
||||
<select selected="null" class="clr-dropdown">
|
||||
<option selected>GET</option>
|
||||
<option>POST</option>
|
||||
<option>PUT</option>
|
||||
<option>PATCH</option>
|
||||
<option>DELETE</option>
|
||||
<option>COPY</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class=clr-row>
|
||||
<div class="clr-col-md-4 clr-col-sm-12">
|
||||
<label for="description">Body</label>
|
||||
|
||||
<textarea id="" cols="10" rows="2" name=" remarks" placeholder="Enter body">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div style="float: right;">
|
||||
<button type="submit" class="btn btn-primary">TEST</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<h4 style="font-weight: 300;display: inline;">Response</h4>
|
||||
</div>
|
||||
<hr>
|
||||
code
|
||||
<span class="label success" style="background-color: green;color:white;">200</span><br>
|
||||
<div class="clr-row" style="padding-top: 10px;">
|
||||
<textarea id="" cols="10" rows="10" name=" remarks">
|
||||
</textarea>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
//@import "../../../../assets/scss/var";
|
||||
.s-info-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.entry-pg {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.button1::after {
|
||||
content: none;
|
||||
}
|
||||
.button1:hover::after {
|
||||
content: "ADD ROWS";
|
||||
}
|
||||
|
||||
$bg-color: #dddddd;
|
||||
|
||||
.section {
|
||||
background-color: $bg-color;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section p {
|
||||
//color: white;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.clr-input {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clr-file {
|
||||
color: #212529;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
//padding: 0.6rem 0.75rem;
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
input[type=text],[type=date],textarea {
|
||||
width: 100%;
|
||||
padding: 15px 15px;
|
||||
// margin: 8px 0;
|
||||
background-color:rgb(255, 255, 255);
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.required-field{
|
||||
color: red;
|
||||
|
||||
}
|
||||
select{
|
||||
width: 100%;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OauthComponent } from './oauth.component';
|
||||
|
||||
describe('OauthComponent', () => {
|
||||
let component: OauthComponent;
|
||||
let fixture: ComponentFixture<OauthComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ OauthComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OauthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { LoginService } from 'src/app/services/api/login.service';
|
||||
import { SureconnectService } from '../sureconnect.service';
|
||||
@Component({
|
||||
selector: 'app-oauth',
|
||||
templateUrl: './oauth.component.html',
|
||||
styleUrls: ['./oauth.component.scss']
|
||||
})
|
||||
export class OauthComponent implements OnInit {
|
||||
public entryForm: FormGroup;
|
||||
model: any = {};
|
||||
errMsg: string = '';
|
||||
constructor(private sureconnectservice: SureconnectService,
|
||||
private _fb: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private loginService: LoginService,) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.entryForm = this._fb.group({
|
||||
connection_name: [null],
|
||||
description: [null],
|
||||
type: [null],
|
||||
access_token: [null],
|
||||
client_id: [null],
|
||||
username: [null],
|
||||
password: [null],
|
||||
|
||||
});
|
||||
}
|
||||
onSubmit() {
|
||||
this.sureconnectservice.create(this.entryForm.value).subscribe((data) => {
|
||||
console.log(' data after add ', data);
|
||||
// Redirect back to sureconnect page after successful creation
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
})
|
||||
}
|
||||
onLogin() {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
this.loginService.getToken(this.model.email, this.model.password)
|
||||
.subscribe(resp => {
|
||||
if (resp.user === undefined || resp.user.token === undefined || resp.user.token === "INVALID") {
|
||||
this.errMsg = 'Checking Email or password';
|
||||
return;
|
||||
}
|
||||
this.router.navigate([resp.landingPage]);// add , {skipLocationChange: true}
|
||||
},
|
||||
(errResponse: HttpErrorResponse) => {
|
||||
|
||||
switch (errResponse.status) {
|
||||
case 401:
|
||||
this.errMsg = 'Email or password is incorrect!';
|
||||
break;
|
||||
case 404:
|
||||
this.errMsg = 'Service not found';
|
||||
case 408:
|
||||
this.errMsg = 'Request Timedout';
|
||||
case 500:
|
||||
this.errMsg = 'Internal Server Error';
|
||||
default:
|
||||
this.errMsg = 'Server Error';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.delete, .heading {
|
||||
text-align: center;
|
||||
color: red;
|
||||
}/*# sourceMappingURL=sureconnect.component.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["sureconnect.component.scss","sureconnect.component.css"],"names":[],"mappings":"AAAA;EACE,kBAAA;EACA,UAAA;ACCF","file":"sureconnect.component.css"}
|
||||
@@ -0,0 +1,109 @@
|
||||
|
||||
<ol class="breadcrumb breadcrumb-arrow font-trirong">
|
||||
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"> <clr-icon shape="home"></clr-icon></a></li>
|
||||
<li><a href="javascript://"> <clr-icon shape="flag"></clr-icon> SureConnect</a></li>
|
||||
</ol>
|
||||
<br>
|
||||
|
||||
|
||||
<div class="dg-wrapper">
|
||||
<div class="clr-row">
|
||||
<div class="clr-col-8">
|
||||
<h3>All SureConnect </h3>
|
||||
</div>
|
||||
|
||||
<div class="clr-col-4" style="text-align: right;">
|
||||
|
||||
<button id="add" class="btn btn-primary" *ngIf="mcreate == 'true'" (click)="goToAdd()">
|
||||
<clr-icon shape="plus"></clr-icon>ADD
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected" >
|
||||
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>Loading ... </clr-spinner></ng-template>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
|
||||
|
||||
<clr-dg-column [clrDgField]="''" style="max-width: 40px;"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'Name'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Name
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'description'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
Description
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column> <ng-container *clrDgHideableColumn="{hidden: false}"> <clr-icon shape="bars"></clr-icon>
|
||||
Action
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
<clr-dg-row *clrDgItems="let user of alldata" [clrDgItem]="user">
|
||||
<clr-dg-cell style="max-width: 40px;">
|
||||
<span style="cursor: pointer;"><clr-icon shape="edit" (click)="goToEdit(user.id)" class="red is-error" style="color:red;"></clr-icon></span>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell id="word">{{user.connection_name}}</clr-dg-cell>
|
||||
<clr-dg-cell id="word">{{user.description}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span style="cursor: pointer;padding: 10px; "><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span>
|
||||
<clr-signpost>
|
||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span>
|
||||
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||
<h5 style="margin-top: 0">Who Column</h5>
|
||||
<div>Account ID: <code class="clr-code">{{user.accountId}}</code></div>
|
||||
<div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div>
|
||||
<div>Created By: <code class="clr-code">{{user.createdBy}}</code></div>
|
||||
<div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div>
|
||||
<div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div>
|
||||
</clr-signpost-content>
|
||||
</clr-signpost>
|
||||
</clr-dg-cell>
|
||||
|
||||
|
||||
<!-- <clr-dg-action-overflow>
|
||||
<button class="action-item" *ngIf="medit == 'true'" (click)="goToEdit(user.id)">Edit <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
||||
<button class="action-item" *ngIf="mdelete == 'true'" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button>
|
||||
</clr-dg-action-overflow> -->
|
||||
|
||||
<clr-dg-row-detail *clrIfExpanded >
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="td-title">Name: </td>
|
||||
<td class="td-content">{{user.Name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-title"> Description:</td>
|
||||
<td class="td-content">{{user.description}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</clr-dg-row-detail>
|
||||
</clr-dg-row>
|
||||
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">data per page</clr-dg-page-size>
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||
of {{pagination.totalItems}} data
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
|
||||
<clr-modal [(clrModalOpen)]="modaldelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||
|
||||
<div class="modal-body" *ngIf="rowSelected.id">
|
||||
<h1 class="delete">Are You Sure Want to delete?</h1>
|
||||
<h2 class="heading">{{rowSelected.id}}</h2>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="modaldelete = false">Cancel</button>
|
||||
<button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary" >Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.delete,.heading{
|
||||
text-align: center;
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SureconnectComponent } from './sureconnect.component';
|
||||
|
||||
describe('SureconnectComponent', () => {
|
||||
let component: SureconnectComponent;
|
||||
let fixture: ComponentFixture<SureconnectComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SureconnectComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SureconnectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
|
||||
import { SureconnectService } from './sureconnect.service';
|
||||
@Component({
|
||||
selector: 'app-sureconnect',
|
||||
templateUrl: './sureconnect.component.html',
|
||||
styleUrls: ['./sureconnect.component.scss']
|
||||
})
|
||||
export class SureconnectComponent implements OnInit {
|
||||
loading = false;
|
||||
|
||||
selected: any[] = [];
|
||||
rowSelected: any = {};
|
||||
modaldelete = false;
|
||||
modaladd = false;
|
||||
data;
|
||||
alldata;
|
||||
mcreate;
|
||||
medit;
|
||||
mdelete;
|
||||
showdata;
|
||||
error;
|
||||
constructor(private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private toastr: ToastrService,
|
||||
private menuGroupService: MenuGroupService,
|
||||
private sureservice: SureconnectService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.showdata = this.menuGroupService.getdata();
|
||||
console.log(this.showdata);
|
||||
this.mcreate = this.showdata.mcreate;
|
||||
console.log(this.mcreate);
|
||||
this.mdelete = this.showdata.mdelete
|
||||
console.log(this.mdelete);
|
||||
this.medit = this.showdata.medit
|
||||
console.log(this.medit);
|
||||
this.getall();
|
||||
}
|
||||
getall() {
|
||||
this.sureservice.getAll().subscribe((data) => {
|
||||
this.alldata = data;
|
||||
console.log(this.alldata);
|
||||
if (this.alldata.length == 0) {
|
||||
this.error = "No data Available";
|
||||
console.log(this.error)
|
||||
}
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
if (error) {
|
||||
this.error = "Server Error";
|
||||
}
|
||||
})
|
||||
}
|
||||
goToAdd() {
|
||||
this.router.navigate(["../oauth"], { relativeTo: this.route });
|
||||
}
|
||||
goToEdit(id) {
|
||||
this.router.navigate(["../editconnect/" + id], { relativeTo: this.route });
|
||||
}
|
||||
onDelete(row) {
|
||||
this.rowSelected = row;
|
||||
this.modaldelete = true;
|
||||
}
|
||||
delete(id) {
|
||||
this.modaldelete = false;
|
||||
console.log("in delete " + id);
|
||||
this.sureservice.delete(id).subscribe(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
this.ngOnInit();
|
||||
if (data) {
|
||||
this.toastr.success('Deleted successfully');
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log('Error in adding data...', +error);
|
||||
if (error) {
|
||||
this.toastr.error('Not deleted Data Getting Some Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import baseUrl from 'src/app/services/api/helper';
|
||||
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SureconnectService {
|
||||
|
||||
constructor(private _http: HttpClient,
|
||||
private apiRequest: ApiRequestService,) { }
|
||||
public create(data: any){
|
||||
return this._http.post(`${baseUrl}/Sure_Connect`, data);
|
||||
}
|
||||
|
||||
// create card
|
||||
public update(data: any,id:any){
|
||||
return this._http.put(`${baseUrl}/Sure_Connect/${id}`, data);
|
||||
}
|
||||
|
||||
// get all cards
|
||||
public getAll(){
|
||||
return this._http.get(`${baseUrl}/Sure_Connect`);
|
||||
}
|
||||
|
||||
// get one card
|
||||
public getOne(id: any){
|
||||
return this._http.get(`${baseUrl}/Sure_Connect/${id}`);
|
||||
}
|
||||
|
||||
// delete card
|
||||
public delete(id: any){
|
||||
return this._http.delete(`${baseUrl}/Sure_Connect/${id}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user