This commit is contained in:
string 2025-10-15 15:06:01 +05:30
parent b11fda4858
commit 4fe5afae7a
58 changed files with 9526 additions and 990 deletions

View File

@ -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 -->
&nbsp; &nbsp; &nbsp; &nbsp;
<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 -->

View File

@ -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;
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,4 @@
export const Data_lakecardvariable = {
"cardButton": false,
"cardmodeldata": ``
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -16,9 +16,9 @@
<button class="btn btn-primary" (click)="onSchedule()">Schedule</button> -->
</div>
</div>
</div>
<div class="content-container">
<div class="content-container">
<nav class="sidenav" *ngIf="toggle" style="width: 16%;">
<ul class="nav-list" style="list-style-type: none;">
<li *ngFor="let widget of WidgetsMock">
@ -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,37 +59,50 @@
</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>
</div>
<div style="text-align: center;">
</div>
<div style="text-align: center;">
<button class="btn btn-outline" (click)="goBack()">Back</button>
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()" >
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()">
<b>Update</b>
</button>
</div>
</div>
<clr-modal [(clrModalOpen)]="modeledit" [clrModalStaticBackdrop]="true">
<clr-modal [(clrModalOpen)]="modeledit" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Configure Chart</h3>
<div class="modal-body" >
<form [formGroup]="entryForm" class="clr-form-horizontal" >
<div class="modal-body">
<form [formGroup]="entryForm" class="clr-form-horizontal">
<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>
@ -126,10 +142,12 @@
</div>
</div> -->
<div class="clr-row" >
<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%">&nbsp;<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%">&nbsp;<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}">&nbsp;
<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/&lt;country&gt;</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}">&nbsp;
<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/&lt;state&gt;</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>
</clr-modal>

View File

@ -20,7 +20,8 @@ import { GridViewComponent } from '../gadgets/grid-view/grid-view.component';
import { DatastoreService } from 'src/app/services/fnd/datastore.service';
import { AlertsService } from 'src/app/services/fnd/alerts.service';
import { isArray } from 'highcharts';
// import { ChartItem } from '../chartitem';
// Add the SureconnectService import
import { SureconnectService } from '../sureconnect/sureconnect.service';
function isNullArray(arr) {
return !Array.isArray(arr) || arr.length === 0;
@ -36,9 +37,9 @@ function isNullArray(arr) {
export class EditnewdashComponent implements OnInit {
editId:number;
toggle:boolean;
modeledit:boolean = false;
editId: number;
toggle: boolean;
modeledit: boolean = false;
public entryForm: FormGroup;
WidgetsMock: WidgetModel[] = [
@ -90,7 +91,7 @@ export class EditnewdashComponent implements OnInit {
name: 'Grid View',
identifier: 'grid_view'
}
]
]
public options: GridsterConfig;
protected dashboardId: number;
@ -98,7 +99,7 @@ export class EditnewdashComponent implements OnInit {
//dashboardCollection:any;
protected dashboardCollection1: DashboardModel[];
public dashboardArray: DashboardContentModel[];
public dashArr:[];
public dashArr: [];
protected componentCollection = [
{ name: "Line Chart", componentInstance: LineChartComponent },
@ -114,34 +115,49 @@ export class EditnewdashComponent implements OnInit {
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
{ name: "Grid View", componentInstance: GridViewComponent },
];
model:any;
linesdata:any;
id:any;
model: any;
linesdata: any;
id: any;
gadgetsEditdata = {
donut : '',
donut: '',
chartlegend: '',
showlabel : '',
showlabel: '',
charturl: '',
chartparameter : '',
datastore : '',
table:'',
datasource : '',
charttitle:'',
id:'',
fieldName:'',
chartcolor:'',
slices:'',
yAxis:'',
xAxis:''
chartparameter: '',
datastore: '',
table: '',
datasource: '',
charttitle: '',
id: '',
fieldName: '',
chartcolor: '',
slices: '',
yAxis: '',
xAxis: '',
connection: '', // Add connection field
// Drilldown configuration properties (base level)
drilldownEnabled: false,
drilldownApiUrl: '',
// Removed drilldownParameterKey since we're using URL templates
drilldownXAxis: '',
drilldownYAxis: '',
drilldownParameter: '', // Add drilldown parameter property
// Multi-layer drilldown configurations
drilldownLayers: [] as any[]
};
// Add sureconnect data property
sureconnectData: any[] = [];
layerColumnData: { [key: number]: any[] } = {}; // Add layer column data property
};
constructor(private route: ActivatedRoute,
private router : Router,
private router: Router,
private dashboardService: Dashboard3Service,
private toastr:ToastrService,
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,37 +182,61 @@ export class EditnewdashComponent implements OnInit {
minCols: 10,
minRows: 10
};
this.getData();
this.editId = this.route.snapshot.params.id;
console.log(this.editId);
this.dashboardService.getById(this.editId).subscribe((data)=>{
console.log("ngOnInit",data);
this.dashboardService.getById(this.editId).subscribe((data) => {
console.log("ngOnInit", data);
this.linesdata = data;
this.id = data.dashbord1_Line[0].id;
console.log("this.id ",this.id);
console.log("this.id ", this.id);
},
(error: any)=>{
(error: any) => {
}
);
this.entryForm = this._fb.group({
donut : [null],
donut: [null],
chartlegend: [null],
showlabel : [null],
showlabel: [null],
charturl: [null],
chartparameter : [null],
datastore:[null],
table:[null],
chartparameter: [null],
datastore: [null],
table: [null],
fieldName: [null],
datasource : [null],
charttitle:[null],
id:[null],
chartcolor:[null],
slices:[null],
yAxis:[null],
datasource: [null],
charttitle: [null],
id: [null],
chartcolor: [null],
slices: [null],
yAxis: [null],
xAxis: [null],
connection: [null], // Add connection to form group
// Base drilldown configuration form controls
drilldownEnabled: [null],
drilldownApiUrl: [null],
drilldownXAxis: [null],
drilldownYAxis: [null],
drilldownParameter: [null] // Add drilldown parameter to form group
// Note: Dynamic drilldown layers will be handled separately since they're complex objects
});
// Load sureconnect data first, then load dashboard data
this.loadSureconnectData();
}
// Add method to load sureconnect data
loadSureconnectData() {
this.sureconnectService.getAll().subscribe((data: any[]) => {
this.sureconnectData = data;
console.log('Sureconnect data loaded:', this.sureconnectData);
// Now that sureconnect data is loaded, we can safely load dashboard data
this.getData();
}, (error) => {
console.log('Error loading sureconnect data:', error);
// Even if there's an error loading sureconnect data, we still need to load dashboard data
this.getData();
});
}
@ -205,13 +245,13 @@ export class EditnewdashComponent implements OnInit {
}
onDrag(event, identifier) {
console.log("on drag",identifier);
console.log("on drag ",event);
console.log("on drag", identifier);
console.log("on drag ", event);
event.dataTransfer.setData('widgetIdentifier', identifier);
}
datagadgets:any;
dashboardLine:any;
dashboardName:any;
datagadgets: any;
dashboardLine: any;
dashboardName: any;
getData() {
// We get the id in get current router dashboard/:id
this.route.params.subscribe(params => {
@ -224,21 +264,29 @@ export class EditnewdashComponent implements OnInit {
this.datagadgets = dashboard;
this.dashboardLine = dashboard.dashbord1_Line;
//this.dashboardCollection = dashboard.dashbord1_Line.model;
console.log("this.datagadgets",this.datagadgets);
console.log("this.dashboardLine",this.dashboardLine);
this.dashboardCollection =JSON.parse(this.dashboardLine[0].model) ;
console.log("this.datagadgets", this.datagadgets);
console.log("this.dashboardLine", this.dashboardLine);
this.dashboardCollection = JSON.parse(this.dashboardLine[0].model);
//this.dashboardCollection =this.dashboardLine[0].model ;
console.log("this.dasboard ",this.dashboardCollection );
console.log("this.dasboard ", this.dashboardCollection);
console.log(this.dashboardCollection);
// We parse serialized Json to generate components on the fly
this.parseJson(this.dashboardCollection);
// Set default connections for all gadgets if sureconnect data is available
if (this.sureconnectData && this.sureconnectData.length > 0) {
this.dashboardCollection.dashboard.forEach(item => {
if (!item['connection'] || item['connection'] === '') {
item['connection'] = this.sureconnectData[0].id;
}
});
}
// We copy array without reference
this.dashboardArray = this.dashboardCollection.dashboard.slice();
console.log("this.dashboardArray",this.dashboardArray);
console.log("this.dashboardArray", this.dashboardArray);
});
});
}
// Super TOKENIZER 2.0 POWERED BY NATCHOIN
@ -273,15 +321,15 @@ export class EditnewdashComponent implements OnInit {
itemChange() {
this.dashboardCollection.dashboard = this.dashboardArray;
console.log("itemChange this.dashboardCollection.dashboard ",this.dashboardCollection.dashboard);
console.log("itemChange this.dashboardCollection ",this.dashboardCollection);
console.log("itemChange this.dashboardCollection type",typeof this.dashboardCollection);
console.log("itemChange this.dashboardArray ",this.dashboardArray);
console.log("itemChange this.dashboardCollection.dashboard ", this.dashboardCollection.dashboard);
console.log("itemChange this.dashboardCollection ", this.dashboardCollection);
console.log("itemChange this.dashboardCollection type", typeof this.dashboardCollection);
console.log("itemChange this.dashboardArray ", this.dashboardArray);
let tmp = JSON.stringify(this.dashboardCollection);
console.log("temp data",tmp);
console.log("temp data", tmp);
let parsed: DashboardModel = JSON.parse(tmp);
console.log("parsed data",parsed);
console.log("let parsed ",typeof parsed);
console.log("parsed data", parsed);
console.log("let parsed ", typeof parsed);
this.serialize(parsed.dashboard);
console.log("item chnage function ", typeof this.dashboardArray);
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
@ -297,7 +345,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: RadarChartComponent,
name: "Radar Chart"
});
@ -307,7 +355,7 @@ export class EditnewdashComponent implements OnInit {
rows: 7,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: LineChartComponent,
name: "Line Chart"
});
@ -317,7 +365,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: DoughnutChartComponent,
name: "Doughnut Chart"
});
@ -327,7 +375,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: BarChartComponent,
name: "Bar Chart"
});
@ -337,7 +385,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: PieChartComponent,
name: "Pie Chart"
});
@ -347,7 +395,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: PolarChartComponent,
name: "Polar Area Chart"
});
@ -357,7 +405,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: BubbleChartComponent,
name: "Bubble Chart"
});
@ -367,7 +415,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: ScatterChartComponent,
name: "Scatter Chart"
});
@ -377,7 +425,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: DynamicChartComponent,
name: "Dynamic Chart"
});
@ -387,7 +435,7 @@ export class EditnewdashComponent implements OnInit {
rows: 6,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: FinancialChartComponent,
name: "Financial Chart"
});
@ -397,7 +445,7 @@ export class EditnewdashComponent implements OnInit {
rows: 5,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: ToDoChartComponent,
name: "To Do Chart"
});
@ -407,7 +455,7 @@ export class EditnewdashComponent implements OnInit {
rows: 5,
x: 0,
y: 0,
chartid:maxChartId + 1,
chartid: maxChartId + 1,
component: GridViewComponent,
name: "Grid View"
});
@ -425,29 +473,70 @@ export class EditnewdashComponent implements OnInit {
this.options.api.optionsChanged();
}
modelid:number ;
editGadget(item)
{
modelid: number;
editGadget(item) {
this.modeledit = true;
this.modelid = item.chartid;
console.log(this.modelid);
this.gadgetsEditdata = item;
this.gadgetsEditdata.fieldName = item.name;
if(item.showlabel === undefined){ item.showlabel = true; }
if(item.chartcolor === undefined ){ item.chartcolor = true;}
if(item.chartlegend === undefined){ item.chartlegend = true; }
if (item.showlabel === undefined) { item.showlabel = true; }
if (item.chartcolor === undefined) { item.chartcolor = true; }
if (item.chartlegend === undefined) { item.chartlegend = true; }
this.getStores();
if(item.datastore !== undefined || '' || null){
// 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);
const table = item.table;
this.getColumns(datastore,table);
this.getColumns(datastore, table);
console.log(item.yAxis);
if(isArray(item.yAxis)){
if (isArray(item.yAxis)) {
this.selectedyAxis = item.yAxis;
console.log( this.selectedyAxis);
console.log(this.selectedyAxis);
}
}else{
} else {
this.selectedyAxis = [];
}
console.log(item);
@ -455,12 +544,11 @@ export class EditnewdashComponent implements OnInit {
dashbord1_Line = {
//model:JSON.stringify(this.da),
model:''
model: ''
}
UpdateLine()
{
UpdateLine() {
console.log('Add button clicked.......');
console.log(this.dashboardArray);
console.log(this.dashboardCollection);
@ -470,13 +558,14 @@ export class EditnewdashComponent implements OnInit {
//https://www.w3schools.com/js/tryit.asp?filename=tryjson_stringify_function_tostring
let cmp=this.dashboardCollection.dashboard.forEach(dashboard=>{
this.componentCollection.forEach(component=>{
let cmp = this.dashboardCollection.dashboard.forEach(dashboard => {
this.componentCollection.forEach(component => {
if (dashboard.name === component.name) {
dashboard.component = component.name;
} })
})
console.log(cmp);
}
})
})
console.log(cmp);
let tmp = JSON.stringify(this.dashboardCollection);
// var merged = this.dashboardArray.reduce((current, value, index) => {
@ -487,9 +576,9 @@ console.log(cmp);
// }, '');
//console.log(merged);
console.log("temp data",typeof tmp);
console.log("temp data", typeof tmp);
console.log(tmp);
let parsed= JSON.parse(tmp);
let parsed = JSON.parse(tmp);
this.serialize(parsed.dashboard);
this.dashbord1_Line.model = tmp;
@ -498,12 +587,12 @@ console.log(cmp);
// let myJSON = JSON.stringify(obj);
// this.dashbord1_Line.model = myJSON;
console.log("line data in addgadget ",this.dashbord1_Line);
console.log("line data in addgadget type ",typeof this.dashbord1_Line);
console.log("line model data ",this.dashbord1_Line.model);
console.log("line model data type",typeof this.dashbord1_Line.model);
this.dashboardService.UpdateLineData(this.id,this.dashbord1_Line).subscribe(
(data: any)=>{
console.log("line data in addgadget ", this.dashbord1_Line);
console.log("line data in addgadget type ", typeof this.dashbord1_Line);
console.log("line model data ", this.dashbord1_Line.model);
console.log("line model data type", typeof this.dashbord1_Line.model);
this.dashboardService.UpdateLineData(this.id, this.dashbord1_Line).subscribe(
(data: any) => {
console.log('Updation Successful...');
this.ngOnInit();
console.log(data);
@ -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,92 +614,309 @@ 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}
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();
}
goBack(){
/**
* Extract only the relevant chart configuration properties to pass to chart components
* This prevents errors when trying to set properties that don't exist on the components
*/
getChartInputs(item: any): any {
// Only pass properties that are relevant to chart components
const chartInputs = {
xAxis: item.xAxis,
yAxis: item.yAxis,
table: item.table,
datastore: item.datastore,
charttitle: item.charttitle,
chartlegend: item.chartlegend,
showlabel: item.showlabel,
chartcolor: item.chartcolor,
slices: item.slices,
donut: item.donut,
charturl: item.charturl,
chartparameter: item.chartparameter,
datasource: item.datasource,
fieldName: item.name, // Using item.name as fieldName
connection: item['connection'], // Add connection field using bracket notation
// Base drilldown configuration properties
drilldownEnabled: item['drilldownEnabled'],
drilldownApiUrl: item['drilldownApiUrl'],
// Removed drilldownParameterKey since we're using URL templates
drilldownXAxis: item['drilldownXAxis'],
drilldownYAxis: item['drilldownYAxis'],
drilldownParameter: item['drilldownParameter'], // Add drilldown parameter
// Multi-layer drilldown configurations
drilldownLayers: item['drilldownLayers'] || []
};
// Remove undefined properties to avoid passing unnecessary data
Object.keys(chartInputs).forEach(key => {
if (chartInputs[key] === undefined) {
delete chartInputs[key];
}
});
return chartInputs;
}
applyChanges(id) {
console.log('Apply changes for chart ID:', id);
// Check if ID is valid
if (id === null || id === undefined) {
console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid);
id = this.modelid;
}
// Update the form with selected Y-axis values if it's an array
if (!isNullArray(this.selectedyAxis)) {
console.log("get y-axis array", this.selectedyAxis);
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
}
// Get form data
let formdata = this.entryForm.value;
let num = id;
console.log('Form data:', this.entryForm.value);
// Update the dashboard collection with the new configuration
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
if (item.chartid == num) {
// Merge the existing item with the new form data
const updatedItem = { ...item, ...formdata }
// Explicitly ensure drilldown properties are preserved
updatedItem.drilldownEnabled = this.gadgetsEditdata.drilldownEnabled;
updatedItem.drilldownApiUrl = this.gadgetsEditdata.drilldownApiUrl;
updatedItem.drilldownXAxis = this.gadgetsEditdata.drilldownXAxis;
updatedItem.drilldownYAxis = this.gadgetsEditdata.drilldownYAxis;
updatedItem.drilldownParameter = this.gadgetsEditdata.drilldownParameter;
updatedItem.drilldownLayers = this.gadgetsEditdata.drilldownLayers;
console.log('Updated item:', updatedItem);
return updatedItem;
}
return item
});
console.log('Updated dashboard collection:', this.dashboardCollection.dashboard);
// Update the dashboardArray to reflect changes immediately
// Create a new array with new object references to ensure change detection
this.dashboardArray = this.dashboardCollection.dashboard.map(item => ({ ...item }));
// Force gridster to refresh
if (this.options && this.options.api) {
this.options.api.optionsChanged();
}
// Note: We don't close the modal here, allowing the user to make additional changes
// The user can click "Save" when they're done with all changes
}
goBack() {
this.router.navigate(["../../all"], { relativeTo: this.route })
}
onSchedule(){
this.router.navigate(['../../schedule/'+ this.editId],{relativeTo:this.route});
onSchedule() {
this.router.navigate(['../../schedule/' + this.editId], { relativeTo: this.route });
}
///////
storedata;
getStores(){
getStores() {
this.datastoreService.getAll().subscribe((data) => {
console.log(data);
this.storedata = data;
},(error) => {
}, (error) => {
console.log(error);
});
}
selectedStoreId;
storename(val){
storename(val) {
console.log(val);
this.selectedStoreId = val;
this.getTables(this.selectedStoreId);
}
TableData;
getTables(id){
this.alertService.getTablefromstore(id).subscribe(gateway =>{
getTables(id) {
this.alertService.getTablefromstore(id).subscribe(gateway => {
console.log(gateway);
this.TableData = gateway;
},(error)=>{
}, (error) => {
console.log(error);
});
}
tablename(val){
console.log(val);
this.getColumns(this.selectedStoreId,val);
callApi(val) {
console.log(' api value ', val);
this.getColumns(this.selectedStoreId, val);
}
selectedyAxis;
columnData;
getColumns(id,table){
this.alertService.getColumnfromurl(table).subscribe(data =>{
console.log(data);
drilldownColumnData = []; // Add drilldown column data property
getColumns(id, table) {
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
this.alertService.getColumnfromurl(table, connectionId).subscribe(data => {
console.log(' api data ', data);
this.columnData = data;
},(error)=>{
}, (error) => {
console.log(error);
});
}
// Add method to refresh drilldown columns
refreshDrilldownColumns() {
if (this.gadgetsEditdata.drilldownApiUrl) {
const connectionId = this.gadgetsEditdata.connection ? parseInt(this.gadgetsEditdata.connection, 10) : undefined;
this.alertService.getColumnfromurl(this.gadgetsEditdata.drilldownApiUrl, connectionId).subscribe(data => {
console.log('Drilldown column data:', data);
this.drilldownColumnData = data;
}, (error) => {
console.log('Error fetching drilldown columns:', error);
this.drilldownColumnData = [];
});
}
}
// toggleAddToDashboard(item) {
// item.addToDashboard = item.addToDashboard;
// }
// 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 = [];
}
// 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 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] : '';
}
}

View File

@ -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>
```

View File

@ -1,9 +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]="barChartData"
[labels]="barChartLabels"
[type]="barChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</canvas>
</div>
</div>

View File

@ -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);
}
}

View File

@ -1,9 +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]="bubbleChartData"
[type]="bubbleChartType"
[options]="bubbleChartOptions"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</canvas>
</div>
</div>

View File

@ -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 {

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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 {
}
}

View File

@ -1,10 +1,38 @@
<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"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</div>
<button class="btn btn-primary" (click)="randomize()">Update</button>
</div>
<button class="btn btn-primary" (click)="randomize()">Update</button>

View File

@ -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';

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -1,60 +1,27 @@
<div style="display: block;">
<div class="dg-wrapper">
<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>
@ -65,5 +32,5 @@
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
</div>

View File

@ -1,54 +1,178 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ExcelService } from 'src/app/services/excel.service';
import * as moment from 'moment';
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { UsergrpmaintainceService } from 'src/app/services/admin/usergrpmaintaince.service';
import { ToastrService } from 'ngx-toastr';
import { MenuGroupService } from 'src/app/services/admin/menu-group.service';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
@Component({
selector: 'app-grid-view',
templateUrl: './grid-view.component.html',
styleUrls: ['./grid-view.component.scss']
})
export class GridViewComponent implements OnInit {
export class GridViewComponent implements OnInit, OnChanges {
@Input() xAxis: string;
@Input() yAxis: string | string[];
@Input() table: string;
@Input() datastore: string;
@Input() charttitle: string;
@Input() chartlegend: boolean = true;
@Input() showlabel: boolean = true;
@Input() chartcolor: boolean;
@Input() slices: boolean;
@Input() donut: boolean;
@Input() charturl: string;
@Input() chartparameter: string;
@Input() datasource: string;
@Input() fieldName: string;
@Input() connection: number; // Add connection input
loading = false;
public entryForm: FormGroup;
givendata;
givendata: any[] = [];
orders;
error;
modalAdd= false;
modaledit=false;
modaldelete=false;
rowSelected :any= {};
error: string;
modalAdd = false;
modaledit = false;
modaldelete = false;
rowSelected: any = {};
mcreate;
medit;
showdata;
submitted=false;
submitted = false;
dynamicHeaders: any[] = [];
constructor(
private excel: ExcelService,
private toastr:ToastrService,
private _fb: FormBuilder,
private router: Router,
private route: ActivatedRoute,
private menuGroupService: MenuGroupService,
private mainservice:UsergrpmaintainceService,
private mainservice: UsergrpmaintainceService,
private dashboardService: Dashboard3Service,
) { }
ngOnInit(): void {
this.mainservice.getAll().subscribe((data) => {
console.log(data);
this.givendata = data;
if(this.givendata.length==0){
this.error="No data Available";
console.log(this.error)
this.fetchGridData();
}
},(error) => {
ngOnChanges(changes: SimpleChanges): void {
console.log('GridViewComponent input changes:', changes);
// Check if any of the key properties have changed
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
const tableChanged = changes.table && !changes.table.firstChange;
const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
// Respond to input changes
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) {
console.log('X or Y axis or table or connection changed, fetching new data');
// Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
this.fetchGridData();
}
}
// Dynamic headers for the grid
fetchGridData(): void {
// If we have the necessary data, fetch grid data from the service
if (this.table && this.xAxis) {
console.log('Fetching grid data for:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
// Convert yAxis to string if it's an array
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
// Fetch data from the dashboard service, similar to other chart components
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe(
(data: any) => {
console.log('Received grid data:', data);
if (data === null) {
console.warn('Grid API returned null data. Check if the API endpoint is working correctly.');
this.error = "No data Available";
return;
}
// Handle the actual data structure returned by the API
if (data && data.chartData) {
this.givendata = data.chartData;
this.extractDynamicHeaders(data.chartData);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
console.log('Updated grid with data:', this.givendata);
} else if (data && data.data) {
// Handle the original expected format as fallback
this.givendata = data.data;
this.extractDynamicHeaders(data.data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
console.log('Updated grid with legacy data format:', this.givendata);
} else if (Array.isArray(data)) {
// Handle case where data is directly an array
this.givendata = data;
this.extractDynamicHeaders(data);
this.error = this.givendata.length === 0 ? "No data Available" : undefined;
console.log('Updated grid with array data:', this.givendata);
} else {
console.warn('Grid received data does not have expected structure', data);
this.error = "No valid data received";
this.givendata = [];
}
}, (error) => {
console.log('Error fetching grid data:', error);
this.error = "Server Error";
});
} else if (this.table) {
console.log('Missing xAxis, falling back to default data fetching');
// Fallback to default data fetching when only table is provided
// Convert yAxis to string if it's an array
const yAxisString = Array.isArray(this.yAxis) ? this.yAxis.join(',') : this.yAxis;
// Fetch data from the dashboard service, similar to other chart components
this.dashboardService.getChartData(this.table, 'grid', this.xAxis, yAxisString, this.connection).subscribe(
(data: any) => {
// this.mainservice.getAll().subscribe((data: any) => {
console.log('recv data ', data);
this.givendata = Array.isArray(data) ? data : [];
this.extractDynamicHeaders(data);
this.error = this.givendata && this.givendata.length === 0 ? "No data Available" : undefined;
}, (error) => {
console.log(error);
if(error){
this.error="Server Error";
this.error = "Server Error";
});
} else {
console.log('Missing required data for grid:', { table: this.table });
this.error = "Table name is required";
}
}
/**
* Extract dynamic headers from the data
* @param data Array of data objects
*/
private extractDynamicHeaders(data: any): void {
// Ensure data is an array
const dataArray = Array.isArray(data) ? data : [];
if (dataArray && dataArray.length > 0) {
// Get all unique keys from the data objects
const allKeys = new Set<string>();
dataArray.forEach(item => {
if (item && typeof item === 'object') {
Object.keys(item).forEach(key => allKeys.add(key));
}
});
// Convert to array of header objects with key and display name
this.dynamicHeaders = Array.from(allKeys).map(key => ({
key: key,
displayName: this.formatHeader(key)
}));
console.log('Dynamic headers extracted:', this.dynamicHeaders);
} else {
this.dynamicHeaders = [];
}
}
/**
* Format header name for better display
* @param key The key to format
*/
private formatHeader(key: string): string {
// Convert camelCase to Title Case
return key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
}
}

View File

@ -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"
@ -8,5 +26,6 @@
[type]="lineChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
<!-- [color]="lineChartColors"-->

View File

@ -1,21 +1,46 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
@Component({
selector: 'app-line-chart',
templateUrl: './line-chart.component.html',
styleUrls: ['./line-chart.component.scss']
})
export class LineChartComponent implements OnInit {
public lineChartData:Array<any> = [
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'},
{data: [18, 48, 77, 9, 100, 27, 40], label: 'Series C'}
];
public lineChartLabels:Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
public lineChartOptions:any = {
public lineChartLabels: Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
public lineChartOptions: any = {
responsive: true
};
public lineChartColors:Array<any> = [
public lineChartColors: Array<any> = [
{ // grey
backgroundColor: 'rgba(148,159,177,0.2)',
borderColor: 'rgba(148,159,177,1)',
@ -41,11 +66,314 @@ export class LineChartComponent implements OnInit {
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
}
];
public lineChartLegend:boolean = true;
public lineChartType:string = 'line';
public lineChartLegend: boolean = true;
public lineChartType: string = 'line';
public randomize():void {
let _lineChartData:Array<any> = new Array(this.lineChartData.length);
// 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++) {
_lineChartData[i] = {data: new Array(this.lineChartData[i].data.length), label: this.lineChartData[i].label};
for (let j = 0; j < this.lineChartData[i].data.length; j++) {
@ -56,16 +384,91 @@ export class LineChartComponent implements OnInit {
}
// events
public chartClicked(e:any):void {
public chartClicked(e: any): void {
console.log('Line chart clicked:', e);
// If drilldown is enabled and we have a valid click event
if (this.drilldownEnabled && e.active && e.active.length > 0) {
// Get the index of the clicked element
const clickedIndex = e.active[0].index;
// Get the label of the clicked element
const clickedLabel = this.lineChartLabels[clickedIndex];
console.log('Clicked on line point:', { index: clickedIndex, label: clickedLabel });
// If we're not at the base level, store original data
if (this.currentDrilldownLevel === 0) {
// Store original data before entering drilldown mode
this.originalLineChartLabels = [...this.lineChartLabels];
this.originalLineChartData = [...this.lineChartData];
console.log('Stored original data for drilldown');
}
// Determine the next drilldown level
const nextDrilldownLevel = this.currentDrilldownLevel + 1;
console.log('Next drilldown level will be:', nextDrilldownLevel);
// Check if there's a drilldown configuration for this level
let hasDrilldownConfig = false;
let drilldownConfig;
if (nextDrilldownLevel === 1) {
// Base drilldown level
drilldownConfig = {
apiUrl: this.drilldownApiUrl,
xAxis: this.drilldownXAxis,
yAxis: this.drilldownYAxis,
parameter: this.drilldownParameter
};
hasDrilldownConfig = !!this.drilldownApiUrl && !!this.drilldownXAxis && !!this.drilldownYAxis;
} else {
// Multi-layer drilldown level
const layerIndex = nextDrilldownLevel - 2; // -2 because level 1 is base drilldown
if (layerIndex < this.drilldownLayers.length) {
drilldownConfig = this.drilldownLayers[layerIndex];
hasDrilldownConfig = drilldownConfig.enabled &&
!!drilldownConfig.apiUrl &&
!!drilldownConfig.xAxis &&
!!drilldownConfig.yAxis;
}
}
console.log('Drilldown config for next level:', drilldownConfig);
console.log('Has drilldown config:', hasDrilldownConfig);
// If there's a drilldown configuration for the next level, proceed
if (hasDrilldownConfig) {
// Add this click to the drilldown stack
const stackEntry = {
level: nextDrilldownLevel,
clickedIndex: clickedIndex,
clickedLabel: clickedLabel,
clickedValue: clickedLabel // Using label as value for now
};
this.drilldownStack.push(stackEntry);
console.log('Added to drilldown stack:', stackEntry);
console.log('Current drilldown stack:', this.drilldownStack);
// Update the current drilldown level
this.currentDrilldownLevel = nextDrilldownLevel;
console.log('Entering drilldown level:', this.currentDrilldownLevel);
// Fetch drilldown data for the new level
this.fetchDrilldownData();
} else {
console.log('No drilldown configuration for level:', nextDrilldownLevel);
}
} else {
console.log('Drilldown not enabled or invalid click event');
}
}
public chartHovered(e: any): void {
console.log(e);
}
public chartHovered(e:any):void {
console.log(e);
}
constructor() { }
ngOnInit(): void {
}
}

View File

@ -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>
</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>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -1,13 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Dashboard3Service } from 'src/app/services/builder/dashboard3.service';
@Component({
selector: 'app-radar-chart',
templateUrl: './radar-chart.component.html',
styleUrls: ['./radar-chart.component.scss']
})
export class RadarChartComponent implements OnInit {
// Radar
public radarChartLabels: string[] = [
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",
"Drinking",
"Sleeping",
@ -15,25 +40,421 @@ public radarChartLabels: string[] = [
"Coding",
"Cycling",
"Running"
];
];
public radarChartData: any = [
public radarChartData: any = [
{ data: [65, 59, 90, 81, 56, 55, 40], label: "Series A" },
{ data: [28, 48, 40, 19, 96, 27, 100], label: "Series B" }
];
public radarChartType: string = "radar";
];
public radarChartType: string = "radar";
// events
public chartClicked(e: any): void {
console.log(e);
}
// 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 = [];
public chartHovered(e: any): void {
console.log(e);
}
constructor() { }
// 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('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);
}
}

View File

@ -1,8 +1,27 @@
<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"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</div>
</div>

View File

@ -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 {

View File

@ -1,27 +1,76 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-to-do-chart',
templateUrl: './to-do-chart.component.html',
styleUrls: ['./to-do-chart.component.scss']
})
export class ToDoChartComponent implements OnInit {
export class ToDoChartComponent implements OnInit, OnChanges {
@Input() xAxis: string;
@Input() yAxis: string | string[];
@Input() table: string;
@Input() datastore: string;
@Input() charttitle: string;
@Input() chartlegend: boolean = true;
@Input() showlabel: boolean = true;
@Input() chartcolor: boolean;
@Input() slices: boolean;
@Input() donut: boolean;
@Input() charturl: string;
@Input() chartparameter: string;
@Input() datasource: string;
@Input() fieldName: string;
@Input() connection: number; // Add connection input
constructor() { }
ngOnInit(): void {
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ToDoChartComponent input changes:', changes);
// Check if any of the key properties have changed
const xAxisChanged = changes.xAxis && !changes.xAxis.firstChange;
const yAxisChanged = changes.yAxis && !changes.yAxis.firstChange;
const tableChanged = changes.table && !changes.table.firstChange;
const connectionChanged = changes.connection && !changes.connection.firstChange; // Add connection change detection
// Respond to input changes
if (xAxisChanged || yAxisChanged || tableChanged || connectionChanged) {
console.log('X or Y axis or table or connection changed, fetching new data');
// Only fetch data if xAxis, yAxis, table, or connection has changed (and it's not the first change)
this.fetchToDoData();
}
}
data: any;
todo: string;
todoList = ['todo 1'];
fetchToDoData(): void {
// If we have the necessary data, fetch to-do data from the service
if (this.table) {
console.log('Fetching to-do data for:', { table: this.table });
// For to-do chart, we might want to fetch data differently
// This is a placeholder implementation - you may need to adjust based on your API
console.log('To-do chart would fetch data from table:', this.table);
// In a real implementation, you would connect to your service here
// For now, we'll just keep the default to-do list
} else {
console.log('Missing required data for to-do chart:', { table: this.table });
}
}
public addTodo(todo: string) {
this.todoList.push(todo);
}
}
public removeTodo(todoIx: number) {
public removeTodo(todoIx: number) {
if (this.todoList.length) {
this.todoList.splice(todoIx, 1);
}
}
}
}

View File

@ -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 */

View File

@ -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"}

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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 });
})
}
}

View File

@ -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 */

View File

@ -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"}

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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';
}
}
);
}
}

View File

@ -0,0 +1,4 @@
.delete, .heading {
text-align: center;
color: red;
}/*# sourceMappingURL=sureconnect.component.css.map */

View File

@ -0,0 +1 @@
{"version":3,"sources":["sureconnect.component.scss","sureconnect.component.css"],"names":[],"mappings":"AAAA;EACE,kBAAA;EACA,UAAA;ACCF","file":"sureconnect.component.css"}

View File

@ -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>

View File

@ -0,0 +1,4 @@
.delete,.heading{
text-align: center;
color: red;
}

View File

@ -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();
});
});

View File

@ -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');
}
});
}
}

View File

@ -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}`);
}
}