Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
285aeca0f8 | ||
|
|
5a89e7bc96 | ||
|
|
d8ad061ad7 | ||
|
|
c1da21b300 | ||
|
|
ef2dba3b92 | ||
|
|
cf0af07104 | ||
|
|
874d5dfed2 | ||
|
|
91af36866e | ||
|
|
534bc307a7 | ||
|
|
53424f0a7c | ||
|
|
49b5300887 | ||
|
|
c5d730ae22 | ||
|
|
40438fcc1b | ||
|
|
71be18b21f | ||
|
|
1263805d61 | ||
|
|
0e6e4899e5 | ||
|
|
29e50253af | ||
|
|
68b793ea60 | ||
|
|
5f0594c93c | ||
|
|
e7ee226b66 | ||
|
|
ff2f5a1c34 | ||
|
|
fc36794b22 | ||
|
|
75ccca8caf | ||
|
|
c4dfb0283c | ||
|
|
57acdb0300 | ||
|
|
aee28f604f | ||
|
|
8375e53fdf | ||
|
|
db7a9c727d | ||
|
|
6e91af551e | ||
|
|
de5479cc07 | ||
|
|
807058e40d | ||
|
|
c6022b0e22 | ||
|
|
9aed6e0d43 | ||
|
|
64b664e625 | ||
|
|
49f1a2fbf2 | ||
|
|
6bfe890cd9 | ||
|
|
3e70f85644 | ||
|
|
482805b5cf | ||
|
|
ffda17e6b1 | ||
|
|
7396843bc6 | ||
|
|
4f75ecb3e0 | ||
|
|
7c1a487114 | ||
|
|
47e9fb92e3 | ||
|
|
7f735dcada | ||
|
|
bd315f42a3 | ||
|
|
e8c1f46430 | ||
|
|
fa96ca81bd | ||
|
|
c384f44c0c | ||
|
|
50df914ca9 | ||
|
|
e0bd888c45 | ||
|
|
02b82fcaf8 | ||
|
|
557afc348f | ||
|
|
1dec787062 | ||
|
|
8853cf75cf | ||
|
|
ad57f11f8a |
@@ -4,5 +4,6 @@ public report_name:string;
|
|||||||
public description: string;
|
public description: string;
|
||||||
public report_tags: string;
|
public report_tags: string;
|
||||||
public servicename: string;
|
public servicename: string;
|
||||||
|
// Add SureConnect reference
|
||||||
|
public sureConnectId: number | null;
|
||||||
}
|
}
|
||||||
@@ -23,8 +23,22 @@ export interface DashboardContentModel {
|
|||||||
component?: any;
|
component?: any;
|
||||||
name: string;
|
name: string;
|
||||||
type?:string;
|
type?:string;
|
||||||
// Common properties
|
// Chart properties
|
||||||
|
xAxis?: string;
|
||||||
|
yAxis?: string | string[];
|
||||||
|
chartType?: string;
|
||||||
|
charttitle?: string;
|
||||||
|
chartlegend?: boolean;
|
||||||
|
showlabel?: boolean;
|
||||||
|
chartcolor?: boolean;
|
||||||
|
slices?: boolean;
|
||||||
|
donut?: boolean;
|
||||||
|
charturl?: string;
|
||||||
|
chartparameter?: string;
|
||||||
|
datastore?: string;
|
||||||
table?: string;
|
table?: string;
|
||||||
|
datasource?: string;
|
||||||
|
fieldName?: string;
|
||||||
connection?: string;
|
connection?: string;
|
||||||
baseFilters?: any[];
|
baseFilters?: any[];
|
||||||
// Common filter properties
|
// Common filter properties
|
||||||
@@ -37,6 +51,11 @@ export interface DashboardContentModel {
|
|||||||
drilldownParameter?: string;
|
drilldownParameter?: string;
|
||||||
drilldownFilters?: any[];
|
drilldownFilters?: any[];
|
||||||
drilldownLayers?: any[];
|
drilldownLayers?: any[];
|
||||||
|
// Compact filter properties
|
||||||
|
filterKey?: string;
|
||||||
|
filterType?: string;
|
||||||
|
filterLabel?: string;
|
||||||
|
filterOptions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardModel {
|
export interface DashboardModel {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>gaurav</title>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>this is h1</h1>
|
||||||
|
<h2>this is h1</h2>
|
||||||
|
<h3>this is h1</h3>
|
||||||
|
<h4>this is h1</h4>
|
||||||
|
<p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ipsa fuga, asperiores mollitia iste vitae repellendus adipisci atque eum corrupti ad placeat unde voluptatum quia perferendis neque expedita, sequi iure quo. Ut error adipisci ex cum sint, suscipit, voluptatem repellat nemo dolorum unde dolores quasi aut. A earum quo mollitia voluptatibus!</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -71,13 +71,19 @@
|
|||||||
Calculated Fields
|
Calculated Fields
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
<!-- Webhook Status Column -->
|
||||||
|
<clr-dg-column>
|
||||||
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
Webhook Status
|
||||||
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
|
|
||||||
<!-- who column -->
|
<!-- who column -->
|
||||||
<clr-dg-column>
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-icon shape="bars"></clr-icon> Action
|
<clr-icon shape="bars"></clr-icon> Action
|
||||||
</ng-container></clr-dg-column>
|
</ng-container>
|
||||||
|
</clr-dg-column>
|
||||||
<!-- end -->
|
<!-- end -->
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user">
|
<clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user">
|
||||||
@@ -104,7 +110,7 @@
|
|||||||
<span
|
<span
|
||||||
style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;"
|
style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;"
|
||||||
title="{{user.url_endpoint}}" (click)="showFullUrl(user.url_endpoint)">
|
title="{{user.url_endpoint}}" (click)="showFullUrl(user.url_endpoint)">
|
||||||
{{user.url | slice:0:30}}{{user.url_endpoint.length > 30 ? '...' : ''}}
|
{{user.url | slice:0:30}}{{user?.url_endpoint?.length > 30 ? '...' : ''}}
|
||||||
</span>
|
</span>
|
||||||
<button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url_endpoint)" title="Copy URL">
|
<button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url_endpoint)" title="Copy URL">
|
||||||
<clr-icon shape="copy-to-clipboard"></clr-icon>
|
<clr-icon shape="copy-to-clipboard"></clr-icon>
|
||||||
@@ -138,11 +144,14 @@
|
|||||||
style="cursor: pointer; align-items: center;"><clr-icon shape="details"
|
style="cursor: pointer; align-items: center;"><clr-icon shape="details"
|
||||||
*ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell>
|
*ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell>
|
||||||
|
|
||||||
|
<!-- Webhook Status Cell -->
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span *ngIf="user.webhook_url && user.webhook_url.trim() !== ''" class="webhook-enabled">Enabled</span>
|
||||||
|
<span *ngIf="!user.webhook_url || user.webhook_url.trim() === ''" class="webhook-disabled">Disabled</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
|
||||||
<!-- who column -->
|
<!-- who column -->
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<div style="display: flex; align-items: center; gap: 1px; flex-wrap: nowrap;">
|
|
||||||
<clr-signpost>
|
<clr-signpost>
|
||||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
||||||
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
||||||
@@ -157,20 +166,57 @@
|
|||||||
</clr-signpost>
|
</clr-signpost>
|
||||||
|
|
||||||
<!-- New JSON Update button -->
|
<!-- New JSON Update button -->
|
||||||
<button class="btn btn-icon btn-sm" (click)="updateJson(user.id)" title="Update JSON">
|
<button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON">
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Calculated Field button -->
|
<!-- Calculated Field button -->
|
||||||
<button class="btn btn-icon btn-sm" (click)="fetchAvailableKeys(user, false)" title="Create Calculated Field">
|
<button class="btn btn-icon" (click)="fetchAvailableKeys(user)" title="Create Calculated Field">
|
||||||
<clr-icon shape="calculator"></clr-icon>
|
<clr-icon shape="calculator"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Group By button -->
|
<!-- Group By button -->
|
||||||
<button class="btn btn-icon btn-sm" (click)="openGroupByModal(user)" title="Group By Configuration">
|
<button class="btn btn-icon" (click)="openGroupByModal(user)" title="Group By Configuration">
|
||||||
<clr-icon shape="group"></clr-icon>
|
<clr-icon shape="group"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
<!-- Blending Keys button (only shown for blending type with non-empty blending_lakeids) -->
|
||||||
|
<button class="btn btn-icon"
|
||||||
|
(click)="fetchBlendingKeys(user)"
|
||||||
|
title="Configure Blending Query"
|
||||||
|
*ngIf="canShowBlendingAction(user)">
|
||||||
|
<clr-icon shape="code"></clr-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Field Mapping button -->
|
||||||
|
<button class="btn btn-icon"
|
||||||
|
(click)="openFieldMappingModal(user)"
|
||||||
|
title="Field Mapping"
|
||||||
|
*ngIf="user.url && user.sure_connect_id">
|
||||||
|
<clr-icon shape="map"></clr-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Scheduler button -->
|
||||||
|
<button class="btn btn-icon"
|
||||||
|
(click)="openSchedulerModal(user)"
|
||||||
|
title="Scheduler">
|
||||||
|
<clr-icon shape="clock"></clr-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Webhook toggle button -->
|
||||||
|
<button class="btn btn-icon"
|
||||||
|
(click)="toggleWebhook(user)"
|
||||||
|
title="Toggle Webhook">
|
||||||
|
<clr-icon shape="network-globe"></clr-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Copy Webhook URL button -->
|
||||||
|
<button class="btn btn-icon"
|
||||||
|
(click)="copyWebhookUrl(user.webhook_url)"
|
||||||
|
title="Copy Webhook URL"
|
||||||
|
*ngIf="user.webhook_url && user.webhook_url.trim() !== ''">
|
||||||
|
<clr-icon shape="copy-to-clipboard"></clr-icon>
|
||||||
|
</button>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
|
||||||
<!-- who colmn -->
|
<!-- who colmn -->
|
||||||
@@ -434,8 +480,32 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add ref_datalake_id dropdown -->
|
<!-- Data Lake Type -->
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
|
<label>Data Lake Type</label>
|
||||||
|
<select class="clr-input" [(ngModel)]="rowSelected.datalake_type" name="datalake_type">
|
||||||
|
<option *ngFor="let type of dataLakeTypes" [value]="type.value">{{ type.label }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Blending Data Lakes (only show when blending type is selected) -->
|
||||||
|
<div class="clr-col-sm-12" *ngIf="rowSelected.datalake_type === 'blending'">
|
||||||
|
<label>Blending Data Lakes</label>
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<div *ngFor="let dataLake of dataLakeList" class="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[value]="dataLake.id"
|
||||||
|
(change)="onEditBlendingLakeCheckboxChange($event, dataLake.id)"
|
||||||
|
[checked]="isEditBlendingLakeSelected(dataLake.id)">
|
||||||
|
<label>{{ dataLake.name }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="clr-subtext">Select data lakes to blend</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reference Data Lake (only show when normal type is selected) -->
|
||||||
|
<div class="clr-col-sm-12" *ngIf="rowSelected.datalake_type === 'normal'">
|
||||||
<label>Reference Data Lake</label>
|
<label>Reference Data Lake</label>
|
||||||
<select class="clr-input" [(ngModel)]="rowSelected.ref_datalake_id" name="ref_datalake_id">
|
<select class="clr-input" [(ngModel)]="rowSelected.ref_datalake_id" name="ref_datalake_id">
|
||||||
<option value="">Select Data Lake</option>
|
<option value="">Select Data Lake</option>
|
||||||
@@ -563,8 +633,32 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add ref_datalake_id dropdown -->
|
<!-- Data Lake Type -->
|
||||||
<div class="clr-col-sm-12">
|
<div class="clr-col-sm-12">
|
||||||
|
<label>Data Lake Type</label>
|
||||||
|
<select formControlName="datalake_type">
|
||||||
|
<option *ngFor="let type of dataLakeTypes" [value]="type.value">{{ type.label }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Blending Data Lakes (only show when blending type is selected) -->
|
||||||
|
<div class="clr-col-sm-12" *ngIf="entryForm.get('datalake_type')?.value === 'blending'">
|
||||||
|
<label>Blending Data Lakes</label>
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<div *ngFor="let dataLake of dataLakeList" class="checkbox-item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[value]="dataLake.id"
|
||||||
|
(change)="onBlendingLakeCheckboxChange($event, dataLake.id)"
|
||||||
|
[checked]="isBlendingLakeSelected(dataLake.id)">
|
||||||
|
<label>{{ dataLake.name }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="clr-subtext">Select data lakes to blend</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reference Data Lake (only show when normal type is selected) -->
|
||||||
|
<div class="clr-col-sm-12" *ngIf="entryForm.get('datalake_type')?.value === 'normal'">
|
||||||
<label>Reference Data Lake</label>
|
<label>Reference Data Lake</label>
|
||||||
<select formControlName="ref_datalake_id">
|
<select formControlName="ref_datalake_id">
|
||||||
<option value="">Select Data Lake</option>
|
<option value="">Select Data Lake</option>
|
||||||
@@ -916,4 +1010,200 @@
|
|||||||
</div>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
<!-- htmlpopup -->
|
<!-- Blending Keys Panel -->
|
||||||
|
<div class="blending-keys-panel" *ngIf="showBlendingKeys">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>Blending Configuration</h3>
|
||||||
|
<button class="btn btn-icon" (click)="closeBlendingKeys()" title="Close">
|
||||||
|
<clr-icon shape="close"></clr-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-content">
|
||||||
|
<!-- Display keys for each blending lake -->
|
||||||
|
<div class="keys-container" *ngFor="let keyData of blendingKeysData">
|
||||||
|
<h4>Data Lake {{ keyData.lakeId }} - Table: {{ keyData.tableName }}</h4>
|
||||||
|
<div class="headers-container">
|
||||||
|
<span class="header-tag" *ngFor="let header of keyData.headers">{{ header }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SQL Query Editor -->
|
||||||
|
<div class="sql-editor-container">
|
||||||
|
<h4>SQL Query Builder</h4>
|
||||||
|
<textarea class="clr-textarea sql-textarea"
|
||||||
|
[(ngModel)]="sqlQueryText"
|
||||||
|
placeholder="Enter your SQL query here using the available headers..."
|
||||||
|
rows="6"></textarea>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateSqlQuery()">Update SQL Query</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Blending Configuration Modal -->
|
||||||
|
<clr-modal [(clrModalOpen)]="showBlendingKeys" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true">
|
||||||
|
<h3 class="modal-title">Blending Configuration</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="blending-config-container">
|
||||||
|
<!-- Display keys for each blending lake -->
|
||||||
|
<div class="keys-section" *ngIf="blendingKeysData && blendingKeysData.length > 0">
|
||||||
|
<h4 class="section-title">
|
||||||
|
<clr-icon shape="data-field" class="section-icon"></clr-icon>
|
||||||
|
Available Data Sources
|
||||||
|
</h4>
|
||||||
|
<div class="keys-container">
|
||||||
|
<div class="key-card" *ngFor="let keyData of blendingKeysData">
|
||||||
|
<div class="key-header">
|
||||||
|
<h5>Data Lake {{ keyData.lakeId }} - Table: {{ keyData.tableName }}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="headers-container">
|
||||||
|
<span class="header-tag" *ngFor="let header of keyData.headers"
|
||||||
|
(click)="insertHeaderIntoQuery(header)">
|
||||||
|
{{ header }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SQL Query Editor -->
|
||||||
|
<div class="sql-editor-section">
|
||||||
|
<h4 class="section-title">
|
||||||
|
<clr-icon shape="code" class="section-icon"></clr-icon>
|
||||||
|
SQL Query Builder
|
||||||
|
</h4>
|
||||||
|
<div class="sql-editor-container">
|
||||||
|
<textarea class="clr-textarea sql-textarea"
|
||||||
|
[(ngModel)]="sqlQueryText"
|
||||||
|
placeholder="Enter your SQL query here using the available headers. Click on header names above to insert them into your query."
|
||||||
|
rows="8"></textarea>
|
||||||
|
<div class="editor-hint">
|
||||||
|
<small class="clr-subtext">Tip: Click on header names above to automatically insert them into your query</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline" (click)="closeBlendingKeys()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" (click)="updateSqlQuery()">Update SQL Query</button>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
||||||
|
|
||||||
|
<!-- Field Mapping Modal -->
|
||||||
|
<clr-modal [(clrModalOpen)]="showFieldMappingModal" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
|
||||||
|
<h3 class="modal-title">Field Mapping Configuration</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="field-mapping-container">
|
||||||
|
<div class="instructions">
|
||||||
|
<p>Map original field names to new names. Leave mapping blank to exclude field from mapping.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mapping-table-container" *ngIf="fieldMappingData && fieldMappingData.length > 0">
|
||||||
|
<table class="table mapping-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="50%">Original Field Name</th>
|
||||||
|
<th width="50%">Mapped Field Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let field of fieldMappingData; let i = index">
|
||||||
|
<td class="original-field">
|
||||||
|
<span class="field-tag">{{ field.original }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text"
|
||||||
|
class="clr-input mapping-input"
|
||||||
|
[(ngModel)]="field.mapped"
|
||||||
|
placeholder="Enter new field name"
|
||||||
|
(ngModelChange)="updateFieldMapping(i, $event)">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="no-data-message" *ngIf="fieldMappingData && fieldMappingData.length === 0">
|
||||||
|
<p>No fields available for mapping. Please check the data source configuration.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline" (click)="closeFieldMappingModal()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" (click)="updateFieldMappings()" [disabled]="!fieldMappingData || fieldMappingData.length === 0">Update Field Mappings</button>
|
||||||
|
</div>
|
||||||
|
</clr-modal>
|
||||||
|
|
||||||
|
<!-- Scheduler Modal -->
|
||||||
|
<clr-modal [(clrModalOpen)]="showSchedulerModal" [clrModalSize]="'md'" [clrModalStaticBackdrop]="true">
|
||||||
|
<h3 class="modal-title">Job Scheduler</h3>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="scheduler-container">
|
||||||
|
<div class="data-lake-info">
|
||||||
|
<h4>Data Lake: {{ selectedSchedulerItem?.name }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Information -->
|
||||||
|
<div class="job-info-section" *ngIf="schedulerJob">
|
||||||
|
<div class="job-header">
|
||||||
|
<h5>Job Information</h5>
|
||||||
|
<span class="badge" [ngClass]="getStatusBadgeClass(schedulerJob.status)">
|
||||||
|
{{ schedulerJob.status }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="job-details">
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Job Name:</label>
|
||||||
|
<span>{{ schedulerJob.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Job Type:</label>
|
||||||
|
<span>{{ schedulerJob.jobType }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Description:</label>
|
||||||
|
<span>{{ schedulerJob.description }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Actions -->
|
||||||
|
<div class="job-actions">
|
||||||
|
<button class="btn btn-warning" (click)="pauseJob()" *ngIf="schedulerJob.status === 'RUNNING'">
|
||||||
|
<clr-icon shape="pause"></clr-icon> Pause Job
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" (click)="resumeJob()" *ngIf="schedulerJob.status === 'PAUSED'">
|
||||||
|
<clr-icon shape="play"></clr-icon> Resume Job
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" (click)="stopJob()" *ngIf="schedulerJob.status !== 'STOPPED'">
|
||||||
|
<clr-icon shape="stop"></clr-icon> Stop Job
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" (click)="restartJob()" *ngIf="schedulerJob.status === 'STOPPED'">
|
||||||
|
<clr-icon shape="play"></clr-icon> Restart Job
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No Job Found -->
|
||||||
|
<div class="no-job-section" *ngIf="schedulerJob === null">
|
||||||
|
<div class="no-job-message">
|
||||||
|
<p>No scheduled job found for this Data Lake.</p>
|
||||||
|
<p>Would you like to create a new job?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="create-job-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createJob()">
|
||||||
|
<clr-icon shape="plus"></clr-icon> Create Job
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline" (click)="closeSchedulerModal()">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</clr-modal>
|
||||||
|
|||||||
@@ -384,3 +384,494 @@
|
|||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Data Lake Type Styles */
|
||||||
|
.clr-subtext {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-multiple] {
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox Styles for Blending Data Lakes */
|
||||||
|
.checkbox-container {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item input[type="checkbox"] {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blending Keys Panel Styles */
|
||||||
|
.blending-keys-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: white;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
box-shadow: 0 -2px 4px rgba(0,0,0,0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keys-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keys-container h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headers-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tag {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-editor-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-textarea {
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blending Configuration Modal Styles */
|
||||||
|
.blending-config-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
color: #0072a0;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keys-section {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-header h5 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headers-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
margin: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: monospace;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tag:hover {
|
||||||
|
background-color: #bbdefb;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-editor-section {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-editor-container {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-textarea {
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-hint {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Field Mapping Modal Styles */
|
||||||
|
.field-mapping-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions p {
|
||||||
|
margin: 0;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-table th {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-field {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-tag {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border: 1px solid #bdbdbd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #0072a0;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 114, 160, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scheduler Modal Styles */
|
||||||
|
.scheduler-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-lake-info h4 {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info-section {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-header h5 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background-color: #ff9800;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-light {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-details {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row label {
|
||||||
|
font-weight: 500;
|
||||||
|
width: 120px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row span {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-actions .btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-job-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-job-message p {
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-job-message p:first-child {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-job-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-job-actions .btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Webhook Status Styles */
|
||||||
|
.webhook-enabled {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-disabled {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Webhook Status Styles */
|
||||||
|
.webhook-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-status.enabled {
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-status.disabled {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Webhook Modal Styles */
|
||||||
|
.webhook-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-container .data-lake-info h4 {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-form {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-info {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-info p {
|
||||||
|
margin: 0;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { AlertService } from 'src/app/services/alert.service';
|
import { AlertService } from 'src/app/services/alert.service';
|
||||||
import { Data_lakeservice } from './Data_lake.service';
|
import { Data_lakeservice } from './Data_lake.service';
|
||||||
|
import { SchedulerService } from 'src/app/services/scheduler.service';
|
||||||
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators, ValidationErrors } from '@angular/forms';
|
import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators, ValidationErrors } from '@angular/forms';
|
||||||
import { ExtensionService } from 'src/app/services/fnd/extension.service';
|
import { ExtensionService } from 'src/app/services/fnd/extension.service';
|
||||||
import { DashboardContentModel2 } from 'src/app/models/builder/dashboard';
|
import { DashboardContentModel2 } from 'src/app/models/builder/dashboard';
|
||||||
@@ -10,6 +11,7 @@ import { UserInfoService } from 'src/app/services/user-info.service';
|
|||||||
import { SureconnectService } from '../sureconnect/sureconnect.service';
|
import { SureconnectService } from '../sureconnect/sureconnect.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { DataLakeSchedulerService } from './dataLakescheduler.service';
|
||||||
declare var JsBarcode: any;
|
declare var JsBarcode: any;
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-Data_lake',
|
selector: 'app-Data_lake',
|
||||||
@@ -66,6 +68,12 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
dataLakeList: any[] = [];
|
dataLakeList: any[] = [];
|
||||||
selectedDataLake: any = null;
|
selectedDataLake: any = null;
|
||||||
|
|
||||||
|
// New properties for data lake type
|
||||||
|
dataLakeTypes = [
|
||||||
|
{ value: 'normal', label: 'Normal' },
|
||||||
|
{ value: 'blending', label: 'Blending' }
|
||||||
|
];
|
||||||
|
|
||||||
// Calculated field properties
|
// Calculated field properties
|
||||||
calculatedFieldModalOpen = false;
|
calculatedFieldModalOpen = false;
|
||||||
availableKeys: string[] = [];
|
availableKeys: string[] = [];
|
||||||
@@ -99,10 +107,36 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
|
|
||||||
selectedAggregationFields: { field: string; operation: string }[] = [];
|
selectedAggregationFields: { field: string; operation: string }[] = [];
|
||||||
|
|
||||||
|
// New property to store selected blending lake IDs as string
|
||||||
|
selectedBlendingLakeIds: string[] = [];
|
||||||
|
editSelectedBlendingLakeIds: string[] = [];
|
||||||
|
|
||||||
|
// New properties for blending functionality
|
||||||
|
showBlendingKeys = false;
|
||||||
|
blendingKeysData: any[] = [];
|
||||||
|
sqlQueryText = '';
|
||||||
|
selectedBlendingItem: any = null;
|
||||||
|
|
||||||
|
// New properties for field mapping
|
||||||
|
showFieldMappingModal = false;
|
||||||
|
fieldMappingData: any[] = [];
|
||||||
|
fieldMappings: { [key: string]: string } = {};
|
||||||
|
selectedMappingItem: any = null;
|
||||||
|
|
||||||
|
// New properties for scheduler functionality
|
||||||
|
showSchedulerModal = false;
|
||||||
|
schedulerJob: any = null;
|
||||||
|
selectedSchedulerItem: any = null;
|
||||||
|
|
||||||
|
// New properties for webhook functionality
|
||||||
|
showWebhookModal = false;
|
||||||
|
selectedWebhookItem: any = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private extensionService: ExtensionService,
|
private extensionService: ExtensionService,
|
||||||
private userInfoService: UserInfoService,
|
private userInfoService: UserInfoService,
|
||||||
private mainService: Data_lakeservice,
|
private mainService: Data_lakeservice,
|
||||||
|
private schedulerService: DataLakeSchedulerService,
|
||||||
private alertService: AlertService,
|
private alertService: AlertService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
@@ -124,23 +158,19 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
this.getDataLakeList(); // Fetch Data Lake list for reference dropdown
|
this.getDataLakeList(); // Fetch Data Lake list for reference dropdown
|
||||||
this.entryForm = this._fb.group({
|
this.entryForm = this._fb.group({
|
||||||
name: [null],
|
name: [null],
|
||||||
|
|
||||||
url: [null],
|
url: [null],
|
||||||
|
|
||||||
schedule: [null],
|
schedule: [null],
|
||||||
|
|
||||||
cron_job: [null],
|
cron_job: [null],
|
||||||
|
|
||||||
json: [null],
|
json: [null],
|
||||||
|
|
||||||
url_endpoint: [null],
|
url_endpoint: [null],
|
||||||
|
|
||||||
batch_volume: [null],
|
batch_volume: [null],
|
||||||
|
sure_connect_id: [null],
|
||||||
|
ref_datalake_id: [null],
|
||||||
|
datalake_type: ['normal'], // Default to normal
|
||||||
|
blending_lakeids: [[]], // Array for multiple selections
|
||||||
|
webhook_url: [null] // Add webhook_url field
|
||||||
|
});
|
||||||
|
|
||||||
sure_connect_id: [null], // Add SureConnect field
|
|
||||||
ref_datalake_id: [null], // Add Data Lake reference field
|
|
||||||
|
|
||||||
}); // component_button200
|
|
||||||
// form code start
|
// form code start
|
||||||
this.extensionService.getJsonObjectsByFormCodeList(this.formcode).subscribe(data => {
|
this.extensionService.getJsonObjectsByFormCodeList(this.formcode).subscribe(data => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
@@ -240,8 +270,7 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
// Fetch Data Lake list for reference dropdown
|
// Fetch Data Lake list for reference dropdown
|
||||||
getDataLakeList() {
|
getDataLakeList() {
|
||||||
this.mainService.getAll().subscribe((data: any[]) => {
|
this.mainService.getAll().subscribe((data: any[]) => {
|
||||||
// Filter out the current item to avoid self-reference
|
this.dataLakeList = data;
|
||||||
this.dataLakeList = data.filter(item => item.id !== (this.rowSelected?.id || 0));
|
|
||||||
console.log('Data Lake List:', this.dataLakeList);
|
console.log('Data Lake List:', this.dataLakeList);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.log('Error fetching Data Lake list:', error);
|
console.log('Error fetching Data Lake list:', error);
|
||||||
@@ -686,8 +715,16 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
this.editCronExpression = row.cron_job || '';
|
this.editCronExpression = row.cron_job || '';
|
||||||
// Set the selected SureConnect for edit form
|
// Set the selected SureConnect for edit form
|
||||||
this.selectedSureConnect = row.sure_connect_id || null;
|
this.selectedSureConnect = row.sure_connect_id || null;
|
||||||
// Set the selected Data Lake for edit form
|
|
||||||
this.selectedDataLake = row.ref_datalake_id || null;
|
// Initialize blending lake IDs for edit form from string
|
||||||
|
if (row.blending_lakeids && typeof row.blending_lakeids === 'string') {
|
||||||
|
this.editSelectedBlendingLakeIds = row.blending_lakeids.split(',').filter(id => id.trim() !== '');
|
||||||
|
} else if (row.blending_lakeids && Array.isArray(row.blending_lakeids)) {
|
||||||
|
this.editSelectedBlendingLakeIds = row.blending_lakeids.map(id => id.toString());
|
||||||
|
} else {
|
||||||
|
this.editSelectedBlendingLakeIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Use setTimeout to ensure the component has time to initialize
|
// Use setTimeout to ensure the component has time to initialize
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.modalEdit = true;
|
this.modalEdit = true;
|
||||||
@@ -838,18 +875,22 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
goToAdd(row) {
|
goToAdd(row) {
|
||||||
this.modalAdd = true;
|
this.modalAdd = false;
|
||||||
this.addCronExpression = '';
|
this.addCronExpression = '';
|
||||||
this.selectedSureConnect = null; // Reset SureConnect selection
|
this.selectedSureConnect = null; // Reset SureConnect selection
|
||||||
this.selectedDataLake = null; // Reset Data Lake selection
|
|
||||||
this.submitted = false;
|
this.submitted = false;
|
||||||
|
// Reset blending lake IDs for add form
|
||||||
|
this.selectedBlendingLakeIds = [];
|
||||||
|
|
||||||
// Reset the form control for cron_job and sure_connect_id
|
// Reset the form control for cron_job and sure_connect_id
|
||||||
if (this.entryForm) {
|
if (this.entryForm) {
|
||||||
if (this.entryForm.get('cron_job')) {
|
if (this.entryForm.get('cron_job')) {
|
||||||
this.entryForm.get('cron_job')?.setValue('');
|
this.entryForm.get('cron_job')?.setValue('');
|
||||||
}
|
}
|
||||||
|
// Reset blending_lakeids to empty string
|
||||||
|
this.entryForm.get('blending_lakeids')?.setValue('');
|
||||||
}
|
}
|
||||||
|
this.modalAdd = true;
|
||||||
}
|
}
|
||||||
submitted = false;
|
submitted = false;
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
@@ -1290,4 +1331,433 @@ export class Data_lakeComponent implements OnInit {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to check if blending action should be available
|
||||||
|
canShowBlendingAction(item: any): boolean {
|
||||||
|
return item.datalake_type === 'blending' &&
|
||||||
|
item.blending_lakeids &&
|
||||||
|
item.blending_lakeids.trim() !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to insert header into SQL query at cursor position
|
||||||
|
insertHeaderIntoQuery(header: string) {
|
||||||
|
const textarea = document.querySelector('.sql-textarea') as HTMLTextAreaElement;
|
||||||
|
if (textarea) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const before = text.substring(0, start);
|
||||||
|
const after = text.substring(end, text.length);
|
||||||
|
|
||||||
|
// Insert the header at cursor position
|
||||||
|
this.sqlQueryText = before + header + after;
|
||||||
|
|
||||||
|
// Set cursor position after the inserted text
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.selectionStart = start + header.length;
|
||||||
|
textarea.selectionEnd = start + header.length;
|
||||||
|
textarea.focus();
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
// Fallback: append to end of text
|
||||||
|
this.sqlQueryText += header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to fetch blending keys
|
||||||
|
fetchBlendingKeys(item: any) {
|
||||||
|
this.selectedBlendingItem = item;
|
||||||
|
this.blendingKeysData = [];
|
||||||
|
this.showBlendingKeys = true;
|
||||||
|
|
||||||
|
// Initialize SQL query text from existing data
|
||||||
|
if (item.sqlquery_json) {
|
||||||
|
this.sqlQueryText = item.sqlquery_json;
|
||||||
|
} else {
|
||||||
|
this.sqlQueryText = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse blending_lakeids string to array
|
||||||
|
const lakeIds = item.blending_lakeids.split(',').map(id => id.trim()).filter(id => id !== '');
|
||||||
|
|
||||||
|
// Fetch keys for each lake ID
|
||||||
|
lakeIds.forEach(lakeId => {
|
||||||
|
this.mainService.fetchBlendingKeys(Number(lakeId)).subscribe(
|
||||||
|
(data: any) => {
|
||||||
|
this.blendingKeysData.push({
|
||||||
|
lakeId: lakeId,
|
||||||
|
tableName: data.tableName,
|
||||||
|
headers: data.headers
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching keys for lake ID:', lakeId, error);
|
||||||
|
this.toastr.error(`Failed to fetch keys for Data Lake ${lakeId}`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update SQL query
|
||||||
|
updateSqlQuery() {
|
||||||
|
if (!this.selectedBlendingItem) {
|
||||||
|
this.toastr.error('No blending item selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the SQL query JSON field
|
||||||
|
this.selectedBlendingItem.sqlquery_json = this.sqlQueryText;
|
||||||
|
|
||||||
|
// Call update service
|
||||||
|
this.mainService.update(this.selectedBlendingItem.id, this.selectedBlendingItem).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastr.success('SQL query updated successfully');
|
||||||
|
// Update the local data
|
||||||
|
const index = this.product.findIndex(p => p.id === this.selectedBlendingItem.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.product[index] = {...this.selectedBlendingItem};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error updating SQL query:', error);
|
||||||
|
this.toastr.error('Failed to update SQL query');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to close blending keys panel
|
||||||
|
closeBlendingKeys() {
|
||||||
|
this.showBlendingKeys = false;
|
||||||
|
this.blendingKeysData = [];
|
||||||
|
this.sqlQueryText = '';
|
||||||
|
this.selectedBlendingItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to handle checkbox change for blending lakes (ADD form)
|
||||||
|
onBlendingLakeCheckboxChange(event: any, lakeId: number) {
|
||||||
|
const lakeIdStr = lakeId.toString();
|
||||||
|
if (event.target.checked) {
|
||||||
|
// Add to selected blending lake IDs
|
||||||
|
if (!this.selectedBlendingLakeIds.includes(lakeIdStr)) {
|
||||||
|
this.selectedBlendingLakeIds.push(lakeIdStr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove from selected blending lake IDs
|
||||||
|
const index = this.selectedBlendingLakeIds.indexOf(lakeIdStr);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedBlendingLakeIds.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the form control value as a comma-separated string
|
||||||
|
this.entryForm.get('blending_lakeids')?.setValue(this.selectedBlendingLakeIds.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to check if a blending lake is selected (ADD form)
|
||||||
|
isBlendingLakeSelected(lakeId: number): boolean {
|
||||||
|
return this.selectedBlendingLakeIds.includes(lakeId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to handle checkbox change for blending lakes (EDIT form)
|
||||||
|
onEditBlendingLakeCheckboxChange(event: any, lakeId: number) {
|
||||||
|
const lakeIdStr = lakeId.toString();
|
||||||
|
if (event.target.checked) {
|
||||||
|
// Add to selected blending lake IDs
|
||||||
|
if (!this.editSelectedBlendingLakeIds.includes(lakeIdStr)) {
|
||||||
|
this.editSelectedBlendingLakeIds.push(lakeIdStr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove from selected blending lake IDs
|
||||||
|
const index = this.editSelectedBlendingLakeIds.indexOf(lakeIdStr);
|
||||||
|
if (index > -1) {
|
||||||
|
this.editSelectedBlendingLakeIds.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the rowSelected value as a comma-separated string
|
||||||
|
this.rowSelected.blending_lakeids = this.editSelectedBlendingLakeIds.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to check if a blending lake is selected (EDIT form)
|
||||||
|
isEditBlendingLakeSelected(lakeId: number): boolean {
|
||||||
|
return this.editSelectedBlendingLakeIds.includes(lakeId.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to open field mapping modal
|
||||||
|
openFieldMappingModal(item: any) {
|
||||||
|
this.selectedMappingItem = item;
|
||||||
|
this.fieldMappingData = [];
|
||||||
|
this.fieldMappings = {};
|
||||||
|
this.showFieldMappingModal = true;
|
||||||
|
|
||||||
|
// Fetch available keys using the same API as calculated fields
|
||||||
|
this.mainService.fetchAvailableKeys(item.url, item.sure_connect_id).subscribe(
|
||||||
|
(keys: string[]) => {
|
||||||
|
// Initialize field mappings with empty values
|
||||||
|
this.fieldMappingData = keys.map(key => ({
|
||||||
|
original: key,
|
||||||
|
mapped: ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
// If there's existing mapping data, populate it
|
||||||
|
if (item.mapping_json) {
|
||||||
|
try {
|
||||||
|
const existingMappings = JSON.parse(item.mapping_json);
|
||||||
|
this.fieldMappingData.forEach(field => {
|
||||||
|
if (existingMappings[field.original]) {
|
||||||
|
field.mapped = existingMappings[field.original];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing existing mapping JSON:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error fetching keys for field mapping:', error);
|
||||||
|
this.toastr.error('Failed to fetch available keys for field mapping');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update field mappings
|
||||||
|
updateFieldMappings() {
|
||||||
|
if (!this.selectedMappingItem) {
|
||||||
|
this.toastr.error('No item selected for mapping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create mapping object
|
||||||
|
const mappings: { [key: string]: string } = {};
|
||||||
|
this.fieldMappingData.forEach(field => {
|
||||||
|
if (field.mapped && field.mapped.trim() !== '') {
|
||||||
|
mappings[field.original] = field.mapped.trim();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to JSON string
|
||||||
|
const mappingJson = JSON.stringify(mappings);
|
||||||
|
|
||||||
|
// Update the mapping JSON field
|
||||||
|
this.selectedMappingItem.mapping_json = mappingJson;
|
||||||
|
|
||||||
|
// Call update service
|
||||||
|
this.mainService.update(this.selectedMappingItem.id, this.selectedMappingItem).subscribe(
|
||||||
|
(response) => {
|
||||||
|
this.toastr.success('Field mappings updated successfully');
|
||||||
|
// Update the local data
|
||||||
|
const index = this.product.findIndex(p => p.id === this.selectedMappingItem.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.product[index] = {...this.selectedMappingItem};
|
||||||
|
}
|
||||||
|
this.closeFieldMappingModal();
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error updating field mappings:', error);
|
||||||
|
this.toastr.error('Failed to update field mappings');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to close field mapping modal
|
||||||
|
closeFieldMappingModal() {
|
||||||
|
this.showFieldMappingModal = false;
|
||||||
|
this.fieldMappingData = [];
|
||||||
|
this.fieldMappings = {};
|
||||||
|
this.selectedMappingItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update individual field mapping
|
||||||
|
updateFieldMapping(index: number, value: string) {
|
||||||
|
if (index >= 0 && index < this.fieldMappingData.length) {
|
||||||
|
this.fieldMappingData[index].mapped = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Method to open scheduler modal
|
||||||
|
openSchedulerModal(item: any) {
|
||||||
|
this.selectedSchedulerItem = item;
|
||||||
|
this.schedulerJob = null;
|
||||||
|
this.showSchedulerModal = true;
|
||||||
|
|
||||||
|
// Fetch job by lake ID
|
||||||
|
this.schedulerService.getJobByLakeId(item.id).subscribe(
|
||||||
|
(job: any) => {
|
||||||
|
this.schedulerJob = job;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// If job not found, it's expected - we'll show create option
|
||||||
|
if (error.status === 404) {
|
||||||
|
this.schedulerJob = null;
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching scheduler job:', error);
|
||||||
|
this.toastr.error('Failed to fetch scheduler job information');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to create a new job
|
||||||
|
createJob() {
|
||||||
|
if (!this.selectedSchedulerItem) {
|
||||||
|
this.toastr.error('No data lake selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobData = {
|
||||||
|
name: this.selectedSchedulerItem.name,
|
||||||
|
status: 'RUNNING',
|
||||||
|
description: `Scheduled job for ${this.selectedSchedulerItem.name}`,
|
||||||
|
jobType: 'DATALAKE',
|
||||||
|
lakeid: this.selectedSchedulerItem.id
|
||||||
|
};
|
||||||
|
|
||||||
|
this.schedulerService.createJob(jobData).subscribe(
|
||||||
|
(job: any) => {
|
||||||
|
this.schedulerJob = job;
|
||||||
|
this.toastr.success('Job created successfully');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error creating job:', error);
|
||||||
|
this.toastr.error('Failed to create job');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to pause a job
|
||||||
|
pauseJob() {
|
||||||
|
if (!this.schedulerJob || !this.schedulerJob.id) {
|
||||||
|
this.toastr.error('No job selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schedulerService.pauseJob(this.schedulerJob.id).subscribe(
|
||||||
|
(response: any) => {
|
||||||
|
this.schedulerJob.status = 'PAUSED';
|
||||||
|
this.toastr.success('Job paused successfully');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error pausing job:', error);
|
||||||
|
this.toastr.error('Failed to pause job');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to resume a job
|
||||||
|
resumeJob() {
|
||||||
|
if (!this.schedulerJob || !this.schedulerJob.id) {
|
||||||
|
this.toastr.error('No job selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schedulerService.resumeJob(this.schedulerJob.id).subscribe(
|
||||||
|
(response: any) => {
|
||||||
|
this.schedulerJob.status = 'RUNNING';
|
||||||
|
this.toastr.success('Job resumed successfully');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error resuming job:', error);
|
||||||
|
this.toastr.error('Failed to resume job');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to stop a job
|
||||||
|
stopJob() {
|
||||||
|
if (!this.schedulerJob || !this.schedulerJob.id) {
|
||||||
|
this.toastr.error('No job selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schedulerService.stopJob(this.schedulerJob.id).subscribe(
|
||||||
|
(response: any) => {
|
||||||
|
this.schedulerJob.status = 'STOPPED';
|
||||||
|
this.toastr.success('Job stopped successfully');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error stopping job:', error);
|
||||||
|
this.toastr.error('Failed to stop job');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to restart a stopped job
|
||||||
|
restartJob() {
|
||||||
|
if (!this.schedulerJob || !this.schedulerJob.id) {
|
||||||
|
this.toastr.error('No job selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To restart a job, we need to create a new job with the same parameters
|
||||||
|
const jobData = {
|
||||||
|
name: this.schedulerJob.name,
|
||||||
|
status: 'RUNNING',
|
||||||
|
description: this.schedulerJob.description,
|
||||||
|
jobType: this.schedulerJob.jobType,
|
||||||
|
lakeid: this.schedulerJob.lakeid
|
||||||
|
};
|
||||||
|
|
||||||
|
this.schedulerService.createJob(jobData).subscribe(
|
||||||
|
(job: any) => {
|
||||||
|
this.schedulerJob = job;
|
||||||
|
this.toastr.success('Job restarted successfully');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error restarting job:', error);
|
||||||
|
this.toastr.error('Failed to restart job');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to close scheduler modal
|
||||||
|
closeSchedulerModal() {
|
||||||
|
this.showSchedulerModal = false;
|
||||||
|
this.schedulerJob = null;
|
||||||
|
this.selectedSchedulerItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to get status badge class
|
||||||
|
getStatusBadgeClass(status: string): string {
|
||||||
|
switch (status) {
|
||||||
|
case 'RUNNING':
|
||||||
|
return 'badge-success';
|
||||||
|
case 'PAUSED':
|
||||||
|
return 'badge-warning';
|
||||||
|
case 'STOPPED':
|
||||||
|
return 'badge-danger';
|
||||||
|
default:
|
||||||
|
return 'badge-light';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to toggle webhook for a data lake item
|
||||||
|
toggleWebhook(item: any) {
|
||||||
|
// Call the enableWebhook service method
|
||||||
|
this.mainService.enableWebhook(item.id).subscribe(
|
||||||
|
(updatedItem: any) => {
|
||||||
|
// Update the local data with the response from the server
|
||||||
|
const index = this.product.findIndex(p => p.id === item.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.product[index] = {...updatedItem};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message based on the new webhook status
|
||||||
|
if (updatedItem.webhook_url && updatedItem.webhook_url.trim() !== '') {
|
||||||
|
this.toastr.success('Webhook enabled successfully');
|
||||||
|
} else {
|
||||||
|
this.toastr.success('Webhook disabled successfully');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Error toggling webhook:', error);
|
||||||
|
this.toastr.error('Failed to toggle webhook');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to copy webhook URL to clipboard
|
||||||
|
copyWebhookUrl(webhookUrl: string) {
|
||||||
|
this.copyToClipboard(webhookUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ export class Data_lakeservice{
|
|||||||
return this.apiRequest.get(apiUrl);
|
return this.apiRequest.get(apiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to fetch blending keys for a specific lake ID
|
||||||
|
fetchBlendingKeys(lakeId: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/keys/${lakeId}`;
|
||||||
|
return this.apiRequest.get(_http);
|
||||||
|
}
|
||||||
|
|
||||||
// Method to update calculated fields for a data lake item
|
// Method to update calculated fields for a data lake item
|
||||||
updateCalculatedFields(id: number, calculatedFieldJson: string, isCalculatedField: boolean): Observable<any> {
|
updateCalculatedFields(id: number, calculatedFieldJson: string, isCalculatedField: boolean): Observable<any> {
|
||||||
const _http = this.baseURL + "/" + id;
|
const _http = this.baseURL + "/" + id;
|
||||||
@@ -51,5 +57,11 @@ export class Data_lakeservice{
|
|||||||
};
|
};
|
||||||
return this.apiRequest.put(_http, data);
|
return this.apiRequest.put(_http, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to enable webhook for a data lake item
|
||||||
|
enableWebhook(id: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/webhook/${id}`;
|
||||||
|
return this.apiRequest.get(_http);
|
||||||
|
}
|
||||||
// updateaction
|
// updateaction
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
<div class="cron-job-builder">
|
<div class="cron-job-builder">
|
||||||
<h4>Cron Job Builder</h4>
|
<h4>Cron Job Builder</h4>
|
||||||
<div class="clr-row">
|
<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>Second</label>
|
||||||
|
<select class="form-control" [(ngModel)]="second" (change)="onFieldChange()">
|
||||||
|
<option *ngFor="let option of secondOptions" [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="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Minute</label>
|
<label>Minute</label>
|
||||||
@@ -28,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-6 clr-col-12">
|
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Month</label>
|
<label>Month</label>
|
||||||
<select class="form-control" [(ngModel)]="month" (change)="onFieldChange()">
|
<select class="form-control" [(ngModel)]="month" (change)="onFieldChange()">
|
||||||
@@ -37,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clr-col-lg-3 clr-col-md-6 clr-col-sm-12 clr-col-12">
|
<div class="clr-col-lg-2 clr-col-md-4 clr-col-sm-6 clr-col-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Day of Week</label>
|
<label>Day of Week</label>
|
||||||
<select class="form-control" [(ngModel)]="dayOfWeek" (change)="onFieldChange()">
|
<select class="form-control" [(ngModel)]="dayOfWeek" (change)="onFieldChange()">
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
@Input() instanceId: string = ''; // Unique identifier for each instance
|
@Input() instanceId: string = ''; // Unique identifier for each instance
|
||||||
@Output() cronExpressionChange = new EventEmitter<string>();
|
@Output() cronExpressionChange = new EventEmitter<string>();
|
||||||
|
|
||||||
// Cron job fields
|
// Cron job fields (now starting with seconds)
|
||||||
|
second: string = '*';
|
||||||
minute: string = '*';
|
minute: string = '*';
|
||||||
hour: string = '*';
|
hour: string = '*';
|
||||||
dayOfMonth: string = '*';
|
dayOfMonth: string = '*';
|
||||||
@@ -21,6 +22,7 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
cronDescription: string = '';
|
cronDescription: string = '';
|
||||||
|
|
||||||
// Options for selectors
|
// Options for selectors
|
||||||
|
secondOptions: any[] = [];
|
||||||
minuteOptions: any[] = [];
|
minuteOptions: any[] = [];
|
||||||
hourOptions: any[] = [];
|
hourOptions: any[] = [];
|
||||||
dayOfMonthOptions: any[] = [];
|
dayOfMonthOptions: any[] = [];
|
||||||
@@ -29,9 +31,10 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// Initialize options for each instance
|
// Initialize options for each instance
|
||||||
this.minuteOptions = this.generateOptions(0, 59);
|
this.secondOptions = this.generateOptions(0, 59, 'second');
|
||||||
this.hourOptions = this.generateOptions(0, 23);
|
this.minuteOptions = this.generateOptions(0, 59, 'minute');
|
||||||
this.dayOfMonthOptions = this.generateOptions(1, 31);
|
this.hourOptions = this.generateOptions(0, 23, 'hour');
|
||||||
|
this.dayOfMonthOptions = this.generateOptions(1, 31, 'day');
|
||||||
this.monthOptions = [
|
this.monthOptions = [
|
||||||
{ value: '*', label: 'Every month' },
|
{ value: '*', label: 'Every month' },
|
||||||
{ value: '1', label: 'January' },
|
{ value: '1', label: 'January' },
|
||||||
@@ -73,8 +76,8 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateOptions(start: number, end: number): any[] {
|
generateOptions(start: number, end: number, type: string): any[] {
|
||||||
const options = [{ value: '*', label: `Every (${start === 0 ? '0' : start}-${end})` }];
|
const options = [{ value: '*', label: `Every ${type} (${start === 0 ? '0' : start}-${end})` }];
|
||||||
for (let i = start; i <= end; i++) {
|
for (let i = start; i <= end; i++) {
|
||||||
options.push({ value: i.toString(), label: i.toString() });
|
options.push({ value: i.toString(), label: i.toString() });
|
||||||
}
|
}
|
||||||
@@ -83,7 +86,16 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
parseCronExpression(expression: string) {
|
parseCronExpression(expression: string) {
|
||||||
const parts = expression.split(' ');
|
const parts = expression.split(' ');
|
||||||
if (parts.length >= 5) {
|
if (parts.length >= 6) { // Now expecting 6 parts with seconds
|
||||||
|
this.second = parts[0];
|
||||||
|
this.minute = parts[1];
|
||||||
|
this.hour = parts[2];
|
||||||
|
this.dayOfMonth = parts[3];
|
||||||
|
this.month = parts[4];
|
||||||
|
this.dayOfWeek = parts[5];
|
||||||
|
} else if (parts.length >= 5) { // For backward compatibility with 5-part cron expressions
|
||||||
|
// Default seconds to 0 if not provided
|
||||||
|
this.second = '0';
|
||||||
this.minute = parts[0];
|
this.minute = parts[0];
|
||||||
this.hour = parts[1];
|
this.hour = parts[1];
|
||||||
this.dayOfMonth = parts[2];
|
this.dayOfMonth = parts[2];
|
||||||
@@ -95,19 +107,33 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
generateDescription() {
|
generateDescription() {
|
||||||
let description = 'Runs ';
|
let description = 'Runs ';
|
||||||
|
|
||||||
|
// Second description
|
||||||
|
if (this.second === '*') {
|
||||||
|
description += 'every second';
|
||||||
|
} else if (this.second.includes('/')) {
|
||||||
|
const interval = this.second.split('/')[1];
|
||||||
|
description += `every ${interval} seconds`;
|
||||||
|
} else {
|
||||||
|
description += `at second ${this.second}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Minute description
|
// Minute description
|
||||||
if (this.minute === '*') {
|
if (this.minute === '*') {
|
||||||
description += 'every minute';
|
if (this.second === '*') {
|
||||||
|
description += '';
|
||||||
|
} else {
|
||||||
|
description += ' of every minute';
|
||||||
|
}
|
||||||
} else if (this.minute.includes('/')) {
|
} else if (this.minute.includes('/')) {
|
||||||
const interval = this.minute.split('/')[1];
|
const interval = this.minute.split('/')[1];
|
||||||
description += `every ${interval} minutes`;
|
description += ` of every ${interval} minutes`;
|
||||||
} else {
|
} else {
|
||||||
description += ` at minute ${this.minute}`;
|
description += ` at minute ${this.minute}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hour description
|
// Hour description
|
||||||
if (this.hour === '*') {
|
if (this.hour === '*') {
|
||||||
if (this.minute === '*') {
|
if (this.minute === '*' && this.second === '*') {
|
||||||
description += '';
|
description += '';
|
||||||
} else {
|
} else {
|
||||||
description += ' past every hour';
|
description += ' past every hour';
|
||||||
@@ -146,21 +172,23 @@ export class CronJobBuilderComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special case for simple patterns
|
// Special case for simple patterns
|
||||||
if (this.minute === '0' && this.hour === '0' && this.dayOfMonth === '1' && this.month === '*' && this.dayOfWeek === '*') {
|
if (this.second === '0' && this.minute === '0' && this.hour === '0' && this.dayOfMonth === '1' && this.month === '*' && this.dayOfWeek === '*') {
|
||||||
description = 'Runs at midnight on the first day of every month';
|
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 === '*') {
|
} else if (this.second === '0' && this.minute === '0' && this.hour === '0' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||||
description = 'Runs at midnight every day';
|
description = 'Runs at midnight every day';
|
||||||
} else if (this.minute === '0' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
} else if (this.second === '0' && this.minute === '0' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||||
description = 'Runs at the top of every hour';
|
description = 'Runs at the top of every hour';
|
||||||
} else if (this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
} else if (this.second === '0' && this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||||
description = 'Runs every minute';
|
description = 'Runs at the top of every minute';
|
||||||
|
} else if (this.second === '*' && this.minute === '*' && this.hour === '*' && this.dayOfMonth === '*' && this.month === '*' && this.dayOfWeek === '*') {
|
||||||
|
description = 'Runs every second';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cronDescription = description;
|
this.cronDescription = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCronExpression() {
|
buildCronExpression() {
|
||||||
const expression = `${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`;
|
const expression = `${this.second} ${this.minute} ${this.hour} ${this.dayOfMonth} ${this.month} ${this.dayOfWeek}`;
|
||||||
this.cronExpressionChange.emit(expression);
|
this.cronExpressionChange.emit(expression);
|
||||||
this.generateDescription(); // Update description when expression changes
|
this.generateDescription(); // Update description when expression changes
|
||||||
return expression;
|
return expression;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { HttpClient } from "@angular/common/http";
|
||||||
|
import { ApiRequestService } from "src/app/services/api/api-request.service";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DataLakeSchedulerService {
|
||||||
|
private baseURL = "scheduler";
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private apiRequest: ApiRequestService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Get job by lake ID
|
||||||
|
getJobByLakeId(lakeId: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/lake/${lakeId}`;
|
||||||
|
return this.apiRequest.get(_http);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new job
|
||||||
|
createJob(jobData: any): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/create`;
|
||||||
|
return this.apiRequest.post(_http, jobData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause a job
|
||||||
|
pauseJob(jobId: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/pause/${jobId}`;
|
||||||
|
return this.apiRequest.post(_http, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume a job
|
||||||
|
resumeJob(jobId: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/resume/${jobId}`;
|
||||||
|
return this.apiRequest.post(_http, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop a job
|
||||||
|
stopJob(jobId: number): Observable<any> {
|
||||||
|
const _http = `${this.baseURL}/stop/${jobId}`;
|
||||||
|
return this.apiRequest.delete(_http);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<div class="dg-wrapper">
|
<div class="dg-wrapper">
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="clr-col-8">
|
<div class="clr-col-8">x
|
||||||
<h3>{{ 'Dashboard_builder' | translate }}</h3>
|
<h3>{{ 'Dashboard_builder' | translate }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-4" style="text-align: right;">
|
<div class="clr-col-4" style="text-align: right;">
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
<button id="add" class="btn btn-primary" (click)="gotorunner()">
|
<button id="add" class="btn btn-primary" (click)="gotorunner()">
|
||||||
<clr-icon shape="grid-view"></clr-icon>{{ 'Dashboard_runner' | translate }}
|
<clr-icon shape="grid-view"></clr-icon>{{ 'Dashboard_runner' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Add Chart Config button -->
|
||||||
|
<button class="btn btn-primary" (click)="openChartConfig()">
|
||||||
|
<clr-icon shape="cog"></clr-icon> Chart Config
|
||||||
|
</button>
|
||||||
<button class="btn btn-outline" (click)="onExport()">
|
<button class="btn btn-outline" (click)="onExport()">
|
||||||
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
|
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
|
||||||
</button>
|
</button>
|
||||||
@@ -27,8 +31,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ...... </clr-spinner></ng-template>
|
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{ 'Loading' | translate }} ......
|
||||||
<div *ngIf="error;else loadingSpinner">{{error}}</div></clr-dg-placeholder>
|
</clr-spinner></ng-template>
|
||||||
|
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
||||||
|
</clr-dg-placeholder>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="''">
|
<clr-dg-column [clrDgField]="''">
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
@@ -70,7 +76,8 @@
|
|||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of data?.slice()?.reverse();" [clrDgItem]="user">
|
<clr-dg-row *clrDgItems="let user of data?.slice()?.reverse();" [clrDgItem]="user">
|
||||||
<clr-dg-cell><span class="label label-light-blue" style="display: inline;margin-left: 10px; cursor: pointer;" (click)="goToEdit(user.id)"> {{ 'SET_UP' | translate }}</span></clr-dg-cell>
|
<clr-dg-cell><span class="label label-light-blue" style="display: inline;margin-left: 10px; cursor: pointer;"
|
||||||
|
(click)="goToEdit(user.id)"> {{ 'SET_UP' | translate }}</span></clr-dg-cell>
|
||||||
<clr-dg-cell>{{user.dashboard_name}}</clr-dg-cell>
|
<clr-dg-cell>{{user.dashboard_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{user.description}}</clr-dg-cell>
|
<clr-dg-cell>{{user.description}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{user.secuirity_profile}}</clr-dg-cell>
|
<clr-dg-cell>{{user.secuirity_profile}}</clr-dg-cell>
|
||||||
@@ -80,13 +87,16 @@
|
|||||||
<clr-dg-cell>{{user.testing}}</clr-dg-cell> -->
|
<clr-dg-cell>{{user.testing}}</clr-dg-cell> -->
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<!-- <span style="cursor: pointer; padding: 10px; "><clr-icon shape="form" (click)="goToEdit(user.id)" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span> -->
|
<!-- <span style="cursor: pointer; padding: 10px; "><clr-icon shape="form" (click)="goToEdit(user.id)" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span> -->
|
||||||
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true" class="tooltip tooltip-sm tooltip-top-left">
|
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true"
|
||||||
<span style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span>
|
class="tooltip tooltip-sm tooltip-top-left">
|
||||||
|
<span style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error"
|
||||||
|
style="color: red;"></clr-icon></span>
|
||||||
<span class="tooltip-content">{{ 'Delete' | translate }}</span>
|
<span class="tooltip-content">{{ 'Delete' | translate }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<clr-signpost>
|
<clr-signpost>
|
||||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span>
|
<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>
|
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||||
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
|
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
|
||||||
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
|
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
|
||||||
@@ -99,7 +109,8 @@
|
|||||||
|
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
<clr-dg-action-overflow>
|
<clr-dg-action-overflow>
|
||||||
<button class="action-item" (click)="goToEditData(user.id)">{{ 'Edit' | translate }} <clr-icon shape="edit" class="is-error"></clr-icon></button>
|
<button class="action-item" (click)="goToEditData(user.id)">{{ 'Edit' | translate }} <clr-icon shape="edit"
|
||||||
|
class="is-error"></clr-icon></button>
|
||||||
<!-- <button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> -->
|
<!-- <button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> -->
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export class AllnewdashComponent implements OnInit {
|
|||||||
projectname;
|
projectname;
|
||||||
projectId;
|
projectId;
|
||||||
error;
|
error;
|
||||||
|
chartConfigManagerOpen = false;
|
||||||
constructor(
|
constructor(
|
||||||
private router : Router,
|
private router : Router,
|
||||||
private route: ActivatedRoute,private dashboardService : DashboardService,
|
private route: ActivatedRoute,private dashboardService : DashboardService,
|
||||||
@@ -121,4 +122,9 @@ export class AllnewdashComponent implements OnInit {
|
|||||||
// this.router.navigate(['../editdashn'],{relativeTo:this.route});
|
// this.router.navigate(['../editdashn'],{relativeTo:this.route});
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// Add method to open chart configuration manager
|
||||||
|
openChartConfig(): void {
|
||||||
|
this.router.navigate(['../chart-types'],{relativeTo:this.route});
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,630 @@
|
|||||||
|
<div class="chart-config-manager">
|
||||||
|
<h2>Chart Configuration Manager</h2>
|
||||||
|
|
||||||
|
<!-- Chart Types Section -->
|
||||||
|
<clr-tabs>
|
||||||
|
<clr-tab>
|
||||||
|
<button clrTabLink>Chart Types</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Chart Types</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddChartTypeForm = true">
|
||||||
|
Add Chart Type
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Chart Type Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddChartTypeForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newChartType.name" name="chartTypeName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newChartType.displayName" name="chartTypeDisplayName" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newChartType.description" name="chartTypeDescription"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newChartType.isActive" name="chartTypeIsActive" />
|
||||||
|
<label>Active</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createChartType()" [disabled]="!newChartType.name">Save</button>
|
||||||
|
<button class="btn" (click)="showAddChartTypeForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Types Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Description</clr-dg-column>
|
||||||
|
<clr-dg-column>Status</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let chartType of chartTypes" [clrDgItem]="chartType">
|
||||||
|
<clr-dg-cell>{{chartType.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.displayName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.description}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="chartType.isActive" [class.label-danger]="!chartType.isActive">
|
||||||
|
{{chartType.isActive ? 'Active' : 'Inactive'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectChartTypeForEdit(chartType)">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartType(chartType.id)">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="onChartTypeSelect(chartType)">
|
||||||
|
<cds-icon shape="eye"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTypes.length}} chart types
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Chart Type Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedChartType">
|
||||||
|
<h4>Edit Chart Type</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedChartType.name" name="editChartTypeName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedChartType.displayName" name="editChartTypeDisplayName" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedChartType.description" name="editChartTypeDescription"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedChartType.isActive" name="editChartTypeIsActive" />
|
||||||
|
<label>Active</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateChartType()" [disabled]="!selectedChartType.name">Update</button>
|
||||||
|
<button class="btn" (click)="selectedChartType = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- UI Components Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>UI Components</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>UI Components for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddUiComponentForm = true">
|
||||||
|
Add UI Component
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add UI Component Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddUiComponentForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.componentName" name="componentName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.componentType" name="componentType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.displayLabel" name="displayLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Placeholder</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.placeholder" name="placeholder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="newUiComponent.sortOrder" name="sortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newUiComponent.isRequired" name="isRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createUiComponent()" [disabled]="!newUiComponent.componentName">Save</button>
|
||||||
|
<button class="btn" (click)="showAddUiComponentForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- UI Components Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Component Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Component Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Placeholder</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Sort Order</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let uiComponent of uiComponents" [clrDgItem]="uiComponent">
|
||||||
|
<clr-dg-cell>{{uiComponent.componentName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.componentType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.displayLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.placeholder}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="uiComponent.isRequired" [class.label-danger]="!uiComponent.isRequired">
|
||||||
|
{{uiComponent.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.sortOrder}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectUiComponentForEdit(uiComponent)">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteUiComponent(uiComponent.id)">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{uiComponents.length}} UI components
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit UI Component Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedUiComponent">
|
||||||
|
<h4>Edit UI Component</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.componentName" name="editComponentName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.componentType" name="editComponentType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.displayLabel" name="editDisplayLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Placeholder</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.placeholder" name="editPlaceholder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="selectedUiComponent.sortOrder" name="editSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedUiComponent.isRequired" name="editIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateUiComponent()" [disabled]="!selectedUiComponent.componentName">Update</button>
|
||||||
|
<button class="btn" (click)="selectedUiComponent = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Component Properties Section -->
|
||||||
|
<clr-tab *ngIf="selectedUiComponent">
|
||||||
|
<button clrTabLink>Component Properties</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Properties for {{selectedUiComponent?.componentName}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddComponentPropertyForm = true">
|
||||||
|
Add Property
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddComponentPropertyForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyName" name="propertyName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Value</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyValue" name="propertyValue" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyType" name="propertyType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createComponentProperty()" [disabled]="!newComponentProperty.propertyName">Save</button>
|
||||||
|
<button class="btn" (click)="showAddComponentPropertyForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Properties Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Property Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Value</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let property of componentProperties" [clrDgItem]="property">
|
||||||
|
<clr-dg-cell>{{property.propertyName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyValue}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectComponentPropertyForEdit(property)">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteComponentProperty(property.id)">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{componentProperties.length}} properties
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedComponentProperty">
|
||||||
|
<h4>Edit Component Property</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyName" name="editPropertyName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Value</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyValue" name="editPropertyValue" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyType" name="editPropertyType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateComponentProperty()" [disabled]="!selectedComponentProperty.propertyName">Update</button>
|
||||||
|
<button class="btn" (click)="selectedComponentProperty = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Chart Templates Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>Chart Templates</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Templates for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddChartTemplateForm = true">
|
||||||
|
Add Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Chart Template Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddChartTemplateForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Template Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newChartTemplate.templateName" name="templateName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>HTML Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newChartTemplate.templateHtml" name="templateHtml" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>CSS Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newChartTemplate.templateCss" name="templateCss" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Default</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newChartTemplate.isDefault" name="isDefault" />
|
||||||
|
<label>Default Template</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createChartTemplate()" [disabled]="!newChartTemplate.templateName">Save</button>
|
||||||
|
<button class="btn" (click)="showAddChartTemplateForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Templates Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Template Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Is Default</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let template of chartTemplates" [clrDgItem]="template">
|
||||||
|
<clr-dg-cell>{{template.templateName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="template.isDefault" [class.label-danger]="!template.isDefault">
|
||||||
|
{{template.isDefault ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectChartTemplateForEdit(template)">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartTemplate(template.id)">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTemplates.length}} templates
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Chart Template Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedChartTemplate">
|
||||||
|
<h4>Edit Chart Template</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Template Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedChartTemplate.templateName" name="editTemplateName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>HTML Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedChartTemplate.templateHtml" name="editTemplateHtml" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>CSS Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedChartTemplate.templateCss" name="editTemplateCss" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Default</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedChartTemplate.isDefault" name="editIsDefault" />
|
||||||
|
<label>Default Template</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateChartTemplate()" [disabled]="!selectedChartTemplate.templateName">Update</button>
|
||||||
|
<button class="btn" (click)="selectedChartTemplate = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Dynamic Fields Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>Dynamic Fields</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Dynamic Fields for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddDynamicFieldForm = true">
|
||||||
|
Add Field
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Dynamic Field Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddDynamicFieldForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldName" name="fieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldLabel" name="fieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldType" name="fieldType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newDynamicField.fieldOptions" name="fieldOptions"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="newDynamicField.sortOrder" name="fieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.isRequired" name="fieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.showInUi" name="fieldShowInUi" />
|
||||||
|
<label>Show in UI</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createDynamicField()" [disabled]="!newDynamicField.fieldName">Save</button>
|
||||||
|
<button class="btn" (click)="showAddDynamicFieldForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dynamic Fields Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Field Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Show in UI</clr-dg-column>
|
||||||
|
<clr-dg-column>Sort Order</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let field of dynamicFields" [clrDgItem]="field">
|
||||||
|
<clr-dg-cell>{{field.fieldName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.fieldLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.fieldType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="field.isRequired" [class.label-danger]="!field.isRequired">
|
||||||
|
{{field.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="field.showInUi" [class.label-danger]="!field.showInUi">
|
||||||
|
{{field.showInUi ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.sortOrder}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectDynamicFieldForEdit(field)">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteDynamicField(field.id)">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{dynamicFields.length}} fields
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Dynamic Field Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedDynamicField">
|
||||||
|
<h4>Edit Dynamic Field</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldName" name="editFieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldLabel" name="editFieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldType" name="editFieldType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedDynamicField.fieldOptions" name="editFieldOptions"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="selectedDynamicField.sortOrder" name="editFieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.isRequired" name="editFieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.showInUi" name="editFieldShowInUi" />
|
||||||
|
<label>Show in UI</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateDynamicField()" [disabled]="!selectedDynamicField.fieldName">Update</button>
|
||||||
|
<button class="btn" (click)="selectedDynamicField = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
</clr-tabs>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.chart-config-manager {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-success {
|
||||||
|
background-color: #318700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-danger {
|
||||||
|
background-color: #e62200;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,658 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
|
||||||
|
interface ChartType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UiComponent {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
componentName: string;
|
||||||
|
componentType: string;
|
||||||
|
displayLabel: string;
|
||||||
|
placeholder: string;
|
||||||
|
isRequired: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentProperty {
|
||||||
|
id: number;
|
||||||
|
component: UiComponent;
|
||||||
|
propertyName: string;
|
||||||
|
propertyValue: string;
|
||||||
|
propertyType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChartTemplate {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
templateName: string;
|
||||||
|
templateHtml: string;
|
||||||
|
templateCss: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicField {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
fieldName: string;
|
||||||
|
fieldLabel: string;
|
||||||
|
fieldType: string;
|
||||||
|
fieldOptions: string;
|
||||||
|
isRequired: boolean;
|
||||||
|
showInUi: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-config-manager',
|
||||||
|
templateUrl: './chart-config-manager.component.html',
|
||||||
|
styleUrls: ['./chart-config-manager.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartConfigManagerComponent implements OnInit {
|
||||||
|
// Chart Types
|
||||||
|
chartTypes: ChartType[] = [];
|
||||||
|
selectedChartType: ChartType | null = null;
|
||||||
|
newChartType: Partial<ChartType> = {};
|
||||||
|
showAddChartTypeForm = false;
|
||||||
|
chartTypeLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// UI Components
|
||||||
|
uiComponents: UiComponent[] = [];
|
||||||
|
selectedUiComponent: UiComponent | null = null;
|
||||||
|
newUiComponent: Partial<UiComponent> = {};
|
||||||
|
showAddUiComponentForm = false;
|
||||||
|
uiComponentLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Component Properties
|
||||||
|
componentProperties: ComponentProperty[] = [];
|
||||||
|
selectedComponentProperty: ComponentProperty | null = null;
|
||||||
|
newComponentProperty: Partial<ComponentProperty> = {};
|
||||||
|
showAddComponentPropertyForm = false;
|
||||||
|
componentPropertyLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Chart Templates
|
||||||
|
chartTemplates: ChartTemplate[] = [];
|
||||||
|
selectedChartTemplate: ChartTemplate | null = null;
|
||||||
|
newChartTemplate: Partial<ChartTemplate> = {};
|
||||||
|
showAddChartTemplateForm = false;
|
||||||
|
chartTemplateLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Dynamic Fields
|
||||||
|
dynamicFields: DynamicField[] = [];
|
||||||
|
selectedDynamicField: DynamicField | null = null;
|
||||||
|
newDynamicField: Partial<DynamicField> = {};
|
||||||
|
showAddDynamicFieldForm = false;
|
||||||
|
dynamicFieldLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// API base URL
|
||||||
|
private apiUrl = environment.apiUrl || 'http://localhost:8080/api';
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadChartTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart Type Methods
|
||||||
|
loadChartTypes(): void {
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.get<ChartType[]>(`${this.apiUrl}/chart-types`).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTypes = data;
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart types:', error);
|
||||||
|
this.showError('Error loading chart types: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartType(): void {
|
||||||
|
if (!this.newChartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.post<ChartType>(`${this.apiUrl}/chart-types`, this.newChartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTypes.push(data);
|
||||||
|
this.newChartType = {};
|
||||||
|
this.showAddChartTypeForm = false;
|
||||||
|
this.showSuccess('Chart type created successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart type:', error);
|
||||||
|
this.showError('Error creating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartType(): void {
|
||||||
|
if (!this.selectedChartType || !this.selectedChartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.put<ChartType>(`${this.apiUrl}/chart-types/${this.selectedChartType.id}`, this.selectedChartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.chartTypes.findIndex(ct => ct.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.chartTypes[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.showSuccess('Chart type updated successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart type:', error);
|
||||||
|
this.showError('Error updating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartType(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart type?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.delete(`${this.apiUrl}/chart-types/${id}`).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTypes = this.chartTypes.filter(ct => ct.id !== id);
|
||||||
|
// Also clear related data if the deleted chart type was selected
|
||||||
|
if (this.selectedChartType && this.selectedChartType.id === id) {
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.uiComponents = [];
|
||||||
|
this.chartTemplates = [];
|
||||||
|
this.dynamicFields = [];
|
||||||
|
}
|
||||||
|
this.showSuccess('Chart type deleted successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart type:', error);
|
||||||
|
this.showError('Error deleting chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChartTypeForEdit(chartType: ChartType): void {
|
||||||
|
this.selectedChartType = { ...chartType };
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Component Methods
|
||||||
|
loadUiComponents(chartTypeId: number): void {
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.get<UiComponent[]>(`${this.apiUrl}/ui-components/chart-type/${chartTypeId}`).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents = data;
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading UI components:', error);
|
||||||
|
this.showError('Error loading UI components: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createUiComponent(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
const uiComponentData = {
|
||||||
|
...this.newUiComponent,
|
||||||
|
chartType: { id: this.selectedChartType.id }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post<UiComponent>(`${this.apiUrl}/ui-components`, uiComponentData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents.push(data);
|
||||||
|
this.newUiComponent = {};
|
||||||
|
this.showAddUiComponentForm = false;
|
||||||
|
this.showSuccess('UI component created successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating UI component:', error);
|
||||||
|
this.showError('Error creating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUiComponent(): void {
|
||||||
|
if (!this.selectedUiComponent || !this.selectedUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.put<UiComponent>(`${this.apiUrl}/ui-components/${this.selectedUiComponent.id}`, this.selectedUiComponent).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.uiComponents.findIndex(uc => uc.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.uiComponents[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
this.showSuccess('UI component updated successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating UI component:', error);
|
||||||
|
this.showError('Error updating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUiComponent(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this UI component?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.delete(`${this.apiUrl}/ui-components/${id}`).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.uiComponents = this.uiComponents.filter(uc => uc.id !== id);
|
||||||
|
if (this.selectedUiComponent && this.selectedUiComponent.id === id) {
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
}
|
||||||
|
this.showSuccess('UI component deleted successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting UI component:', error);
|
||||||
|
this.showError('Error deleting UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUiComponentForEdit(uiComponent: UiComponent): void {
|
||||||
|
this.selectedUiComponent = { ...uiComponent };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component Property Methods
|
||||||
|
loadComponentProperties(componentId: number): void {
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.get<ComponentProperty[]>(`${this.apiUrl}/component-properties/component/${componentId}`).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties = data;
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading component properties:', error);
|
||||||
|
this.showError('Error loading component properties: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createComponentProperty(): void {
|
||||||
|
if (!this.selectedUiComponent) {
|
||||||
|
this.showError('Please select a UI component first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
const componentPropertyData = {
|
||||||
|
...this.newComponentProperty,
|
||||||
|
component: { id: this.selectedUiComponent.id }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post<ComponentProperty>(`${this.apiUrl}/component-properties`, componentPropertyData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties.push(data);
|
||||||
|
this.newComponentProperty = {};
|
||||||
|
this.showAddComponentPropertyForm = false;
|
||||||
|
this.showSuccess('Component property created successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating component property:', error);
|
||||||
|
this.showError('Error creating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateComponentProperty(): void {
|
||||||
|
if (!this.selectedComponentProperty || !this.selectedComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.put<ComponentProperty>(`${this.apiUrl}/component-properties/${this.selectedComponentProperty.id}`, this.selectedComponentProperty).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.componentProperties.findIndex(cp => cp.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.componentProperties[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
this.showSuccess('Component property updated successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating component property:', error);
|
||||||
|
this.showError('Error updating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteComponentProperty(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this component property?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.delete(`${this.apiUrl}/component-properties/${id}`).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.componentProperties = this.componentProperties.filter(cp => cp.id !== id);
|
||||||
|
if (this.selectedComponentProperty && this.selectedComponentProperty.id === id) {
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
}
|
||||||
|
this.showSuccess('Component property deleted successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting component property:', error);
|
||||||
|
this.showError('Error deleting component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectComponentPropertyForEdit(componentProperty: ComponentProperty): void {
|
||||||
|
this.selectedComponentProperty = { ...componentProperty };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart Template Methods
|
||||||
|
loadChartTemplates(chartTypeId: number): void {
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.get<ChartTemplate[]>(`${this.apiUrl}/chart-templates/chart-type/${chartTypeId}`).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates = data;
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart templates:', error);
|
||||||
|
this.showError('Error loading chart templates: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartTemplate(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
const chartTemplateData = {
|
||||||
|
...this.newChartTemplate,
|
||||||
|
chartType: { id: this.selectedChartType.id }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post<ChartTemplate>(`${this.apiUrl}/chart-templates`, chartTemplateData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates.push(data);
|
||||||
|
this.newChartTemplate = {};
|
||||||
|
this.showAddChartTemplateForm = false;
|
||||||
|
this.showSuccess('Chart template created successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart template:', error);
|
||||||
|
this.showError('Error creating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartTemplate(): void {
|
||||||
|
if (!this.selectedChartTemplate || !this.selectedChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.put<ChartTemplate>(`${this.apiUrl}/chart-templates/${this.selectedChartTemplate.id}`, this.selectedChartTemplate).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.chartTemplates.findIndex(ct => ct.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.chartTemplates[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
this.showSuccess('Chart template updated successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart template:', error);
|
||||||
|
this.showError('Error updating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartTemplate(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart template?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.delete(`${this.apiUrl}/chart-templates/${id}`).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTemplates = this.chartTemplates.filter(ct => ct.id !== id);
|
||||||
|
if (this.selectedChartTemplate && this.selectedChartTemplate.id === id) {
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
}
|
||||||
|
this.showSuccess('Chart template deleted successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart template:', error);
|
||||||
|
this.showError('Error deleting chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChartTemplateForEdit(chartTemplate: ChartTemplate): void {
|
||||||
|
this.selectedChartTemplate = { ...chartTemplate };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic Field Methods
|
||||||
|
loadDynamicFields(chartTypeId: number): void {
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.get<DynamicField[]>(`${this.apiUrl}/dynamic-fields/chart-type/${chartTypeId}`).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields = data;
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading dynamic fields:', error);
|
||||||
|
this.showError('Error loading dynamic fields: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createDynamicField(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
const dynamicFieldData = {
|
||||||
|
...this.newDynamicField,
|
||||||
|
chartType: { id: this.selectedChartType.id }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post<DynamicField>(`${this.apiUrl}/dynamic-fields`, dynamicFieldData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields.push(data);
|
||||||
|
this.newDynamicField = {};
|
||||||
|
this.showAddDynamicFieldForm = false;
|
||||||
|
this.showSuccess('Dynamic field created successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating dynamic field:', error);
|
||||||
|
this.showError('Error creating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDynamicField(): void {
|
||||||
|
if (!this.selectedDynamicField || !this.selectedDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.put<DynamicField>(`${this.apiUrl}/dynamic-fields/${this.selectedDynamicField.id}`, this.selectedDynamicField).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.dynamicFields.findIndex(df => df.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.dynamicFields[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
this.showSuccess('Dynamic field updated successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating dynamic field:', error);
|
||||||
|
this.showError('Error updating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDynamicField(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this dynamic field?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.http.delete(`${this.apiUrl}/dynamic-fields/${id}`).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.dynamicFields = this.dynamicFields.filter(df => df.id !== id);
|
||||||
|
if (this.selectedDynamicField && this.selectedDynamicField.id === id) {
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
}
|
||||||
|
this.showSuccess('Dynamic field deleted successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting dynamic field:', error);
|
||||||
|
this.showError('Error deleting dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDynamicFieldForEdit(dynamicField: DynamicField): void {
|
||||||
|
this.selectedDynamicField = { ...dynamicField };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
onChartTypeSelect(chartType: ChartType): void {
|
||||||
|
this.selectedChartType = chartType;
|
||||||
|
this.loadUiComponents(chartType.id);
|
||||||
|
this.loadChartTemplates(chartType.id);
|
||||||
|
this.loadDynamicFields(chartType.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForms(): void {
|
||||||
|
this.newChartType = {};
|
||||||
|
this.newUiComponent = {};
|
||||||
|
this.newComponentProperty = {};
|
||||||
|
this.newChartTemplate = {};
|
||||||
|
this.newDynamicField = {};
|
||||||
|
this.showAddChartTypeForm = false;
|
||||||
|
this.showAddUiComponentForm = false;
|
||||||
|
this.showAddComponentPropertyForm = false;
|
||||||
|
this.showAddChartTemplateForm = false;
|
||||||
|
this.showAddDynamicFieldForm = false;
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,495 @@
|
|||||||
|
<div class="chart-config-manager">
|
||||||
|
<h2>Chart Configuration Manager</h2>
|
||||||
|
|
||||||
|
<!-- Test message to verify component is loading -->
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">Chart Config Manager Component Loaded Successfully</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Types Section -->
|
||||||
|
<clr-tab>
|
||||||
|
<button clrTabLink>Chart Types</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Chart Types</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddChartTypeForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Chart Type
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Chart Type Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddChartTypeForm">
|
||||||
|
<app-chart-type-form
|
||||||
|
[chartType]="newChartType"
|
||||||
|
(save)="createChartType()"
|
||||||
|
(cancel)="showAddChartTypeForm = false">
|
||||||
|
</app-chart-type-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Types Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="chartTypeLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Description</clr-dg-column>
|
||||||
|
<clr-dg-column>Status</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let chartType of chartTypes" [clrDgItem]="chartType">
|
||||||
|
<clr-dg-cell>{{chartType.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.displayName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.description}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="chartType.isActive" [class.label-danger]="!chartType.isActive">
|
||||||
|
{{chartType.isActive ? 'Active' : 'Inactive'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectChartTypeForEdit(chartType)" title="Edit" [disabled]="chartTypeLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="chartTypeLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="chartTypeLoadingState !== ClrLoadingState.LOADING" shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartType(chartType.id)" title="Delete" [disabled]="chartTypeLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="chartTypeLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="chartTypeLoadingState !== ClrLoadingState.LOADING" shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="onChartTypeSelect(chartType)" title="View Details" [disabled]="chartTypeLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="chartTypeLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="chartTypeLoadingState !== ClrLoadingState.LOADING" shape="eye"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTypes.length}} chart type(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Chart Type Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedChartType">
|
||||||
|
<h4>Edit Chart Type</h4>
|
||||||
|
<app-chart-type-form
|
||||||
|
[chartType]="selectedChartType"
|
||||||
|
[isEdit]="true"
|
||||||
|
(save)="updateChartType()"
|
||||||
|
(cancel)="selectedChartType = null">
|
||||||
|
</app-chart-type-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- UI Components Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>UI Components</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>UI Components for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddUiComponentForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add UI Component
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add UI Component Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddUiComponentForm">
|
||||||
|
<app-ui-component-form
|
||||||
|
[uiComponent]="newUiComponent"
|
||||||
|
(save)="createUiComponent()"
|
||||||
|
(cancel)="showAddUiComponentForm = false">
|
||||||
|
</app-ui-component-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- UI Components Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="uiComponentLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Component Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Component Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let uiComponent of uiComponents" [clrDgItem]="uiComponent">
|
||||||
|
<clr-dg-cell>{{uiComponent.componentName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.componentType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.displayLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="uiComponent.isRequired" [class.label-danger]="!uiComponent.isRequired">
|
||||||
|
{{uiComponent.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell class="action-cell">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectUiComponentForEdit(uiComponent)" title="Edit" [disabled]="uiComponentLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="uiComponentLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="uiComponentLoadingState !== ClrLoadingState.LOADING" shape="pencil" aria-label="Edit"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteUiComponent(uiComponent.id)" title="Delete" [disabled]="uiComponentLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="uiComponentLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="uiComponentLoadingState !== ClrLoadingState.LOADING" shape="trash" aria-label="Delete"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="onUiComponentSelect(uiComponent)" title="View Properties" [disabled]="uiComponentLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="uiComponentLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="uiComponentLoadingState !== ClrLoadingState.LOADING" shape="eye" aria-label="View Properties"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{uiComponents.length}} UI component(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit UI Component Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedUiComponent">
|
||||||
|
<h4>Edit UI Component</h4>
|
||||||
|
<app-ui-component-form
|
||||||
|
[uiComponent]="selectedUiComponent"
|
||||||
|
[isEdit]="true"
|
||||||
|
(save)="updateUiComponent()"
|
||||||
|
(cancel)="selectedUiComponent = null">
|
||||||
|
</app-ui-component-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Component Properties Section -->
|
||||||
|
<clr-tab *ngIf="selectedUiComponent">
|
||||||
|
<button clrTabLink>Component Properties</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Properties for {{selectedUiComponent?.componentName}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddComponentPropertyForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Property
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddComponentPropertyForm">
|
||||||
|
<app-component-property-form
|
||||||
|
[componentProperty]="newComponentProperty"
|
||||||
|
(save)="createComponentProperty()"
|
||||||
|
(cancel)="showAddComponentPropertyForm = false">
|
||||||
|
</app-component-property-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Properties Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Property Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Value</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let property of componentProperties" [clrDgItem]="property">
|
||||||
|
<clr-dg-cell>{{property.propertyName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyValue}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectComponentPropertyForEdit(property)" title="Edit" [disabled]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="componentPropertyLoadingState !== ClrLoadingState.LOADING" shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteComponentProperty(property.id)" title="Delete" [disabled]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="componentPropertyLoadingState !== ClrLoadingState.LOADING" shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{componentProperties.length}} propertie(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedComponentProperty">
|
||||||
|
<h4>Edit Component Property</h4>
|
||||||
|
<app-component-property-form
|
||||||
|
[componentProperty]="selectedComponentProperty"
|
||||||
|
[isEdit]="true"
|
||||||
|
(save)="updateComponentProperty()"
|
||||||
|
(cancel)="selectedComponentProperty = null">
|
||||||
|
</app-component-property-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Chart Templates Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>Chart Templates</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Templates for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddChartTemplateForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Chart Template Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddChartTemplateForm">
|
||||||
|
<app-chart-template-form
|
||||||
|
[chartTemplate]="newChartTemplate"
|
||||||
|
(save)="createChartTemplate()"
|
||||||
|
(cancel)="showAddChartTemplateForm = false">
|
||||||
|
</app-chart-template-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Templates Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="chartTemplateLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Template Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Is Default</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let template of chartTemplates" [clrDgItem]="template">
|
||||||
|
<clr-dg-cell>{{template.templateName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="template.isDefault" [class.label-danger]="!template.isDefault">
|
||||||
|
{{template.isDefault ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{template.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{template.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectChartTemplateForEdit(template)" title="Edit" [disabled]="chartTemplateLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="chartTemplateLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="chartTemplateLoadingState !== ClrLoadingState.LOADING" shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartTemplate(template.id)" title="Delete" [disabled]="chartTemplateLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="chartTemplateLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="chartTemplateLoadingState !== ClrLoadingState.LOADING" shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTemplates.length}} template(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Chart Template Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedChartTemplate">
|
||||||
|
<h4>Edit Chart Template</h4>
|
||||||
|
<app-chart-template-form
|
||||||
|
[chartTemplate]="selectedChartTemplate"
|
||||||
|
[isEdit]="true"
|
||||||
|
(save)="updateChartTemplate()"
|
||||||
|
(cancel)="selectedChartTemplate = null">
|
||||||
|
</app-chart-template-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
|
||||||
|
<!-- Dynamic Fields Section -->
|
||||||
|
<clr-tab *ngIf="selectedChartType">
|
||||||
|
<button clrTabLink>Dynamic Fields</button>
|
||||||
|
<clr-tab-content>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Dynamic Fields for {{selectedChartType?.name}}</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="showAddDynamicFieldForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Field
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Dynamic Field Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddDynamicFieldForm">
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldName" name="fieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldLabel" name="fieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldType" name="fieldType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newDynamicField.fieldOptions" name="fieldOptions"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="newDynamicField.sortOrder" name="fieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.isRequired" name="fieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.showInUi" name="fieldShowInUi" />
|
||||||
|
<label>Show in UI</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="createDynamicField()" [disabled]="!newDynamicField.fieldName || dynamicFieldLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="dynamicFieldLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button class="btn" (click)="showAddDynamicFieldForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dynamic Fields Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="dynamicFieldLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Field Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Show in UI</clr-dg-column>
|
||||||
|
<clr-dg-column>Sort Order</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let field of dynamicFields" [clrDgItem]="field">
|
||||||
|
<clr-dg-cell>{{field.fieldName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.fieldLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.fieldType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="field.isRequired" [class.label-danger]="!field.isRequired">
|
||||||
|
{{field.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="field.showInUi" [class.label-danger]="!field.showInUi">
|
||||||
|
{{field.showInUi ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.sortOrder}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{field.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectDynamicFieldForEdit(field)" title="Edit">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteDynamicField(field.id)" title="Delete">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{dynamicFields.length}} field(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Dynamic Field Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedDynamicField">
|
||||||
|
<h4>Edit Dynamic Field</h4>
|
||||||
|
<form clrForm>
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldName" name="editFieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldLabel" name="editFieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldType" name="editFieldType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedDynamicField.fieldOptions" name="editFieldOptions"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="selectedDynamicField.sortOrder" name="editFieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.isRequired" name="editFieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.showInUi" name="editFieldShowInUi" />
|
||||||
|
<label>Show in UI</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" (click)="updateDynamicField()" [disabled]="!selectedDynamicField.fieldName || dynamicFieldLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="dynamicFieldLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button class="btn" (click)="selectedDynamicField = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-tab-content>
|
||||||
|
</clr-tab>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
.chart-config-manager {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-success {
|
||||||
|
background-color: #318700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-danger {
|
||||||
|
background-color: #e62200;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-tab-content {
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-cell {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .btn-icon {
|
||||||
|
min-width: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure icons are visible
|
||||||
|
cds-icon {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-spinner {
|
||||||
|
margin: 0;
|
||||||
|
}cds-icon {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-spinner {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartTypeService } from '../chart-type-manager/chart-type.service';
|
||||||
|
import { UiComponentService } from './ui-component.service';
|
||||||
|
import { ComponentPropertyService } from './component-property.service';
|
||||||
|
import { ChartTemplateService } from './chart-template.service';
|
||||||
|
import { DynamicFieldService } from './dynamic-field.service';
|
||||||
|
|
||||||
|
export interface ChartType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UiComponent {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
componentName: string;
|
||||||
|
componentType: string;
|
||||||
|
displayLabel: string;
|
||||||
|
placeholder: string;
|
||||||
|
isRequired: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComponentProperty {
|
||||||
|
id: number;
|
||||||
|
component: UiComponent;
|
||||||
|
propertyName: string;
|
||||||
|
propertyValue: string;
|
||||||
|
propertyType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChartTemplate {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
templateName: string;
|
||||||
|
templateHtml: string;
|
||||||
|
templateCss: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DynamicField {
|
||||||
|
id: number;
|
||||||
|
chartType: ChartType;
|
||||||
|
fieldName: string;
|
||||||
|
fieldLabel: string;
|
||||||
|
fieldType: string;
|
||||||
|
fieldOptions: string;
|
||||||
|
isRequired: boolean;
|
||||||
|
showInUi: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-config-manager',
|
||||||
|
templateUrl: './chart-config-manager.component.html',
|
||||||
|
styleUrls: ['./chart-config-manager.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartConfigManagerComponent implements OnInit {
|
||||||
|
// Chart Types
|
||||||
|
chartTypes: ChartType[] = [];
|
||||||
|
selectedChartType: ChartType | null = null;
|
||||||
|
newChartType: Partial<ChartType> = {};
|
||||||
|
showAddChartTypeForm = false;
|
||||||
|
chartTypeLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// UI Components
|
||||||
|
uiComponents: UiComponent[] = [];
|
||||||
|
selectedUiComponent: UiComponent | null = null;
|
||||||
|
newUiComponent: Partial<UiComponent> = {};
|
||||||
|
showAddUiComponentForm = false;
|
||||||
|
uiComponentLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Component Properties
|
||||||
|
componentProperties: ComponentProperty[] = [];
|
||||||
|
selectedComponentProperty: ComponentProperty | null = null;
|
||||||
|
newComponentProperty: Partial<ComponentProperty> = {};
|
||||||
|
showAddComponentPropertyForm = false;
|
||||||
|
componentPropertyLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Chart Templates
|
||||||
|
chartTemplates: ChartTemplate[] = [];
|
||||||
|
selectedChartTemplate: ChartTemplate | null = null;
|
||||||
|
newChartTemplate: Partial<ChartTemplate> = {};
|
||||||
|
showAddChartTemplateForm = false;
|
||||||
|
chartTemplateLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Dynamic Fields
|
||||||
|
dynamicFields: DynamicField[] = [];
|
||||||
|
selectedDynamicField: DynamicField | null = null;
|
||||||
|
newDynamicField: Partial<DynamicField> = {};
|
||||||
|
showAddDynamicFieldForm = false;
|
||||||
|
dynamicFieldLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private uiComponentService: UiComponentService,
|
||||||
|
private componentPropertyService: ComponentPropertyService,
|
||||||
|
private chartTemplateService: ChartTemplateService,
|
||||||
|
private dynamicFieldService: DynamicFieldService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
console.log('ChartConfigManagerComponent initialized');
|
||||||
|
this.loadChartTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart Type Methods
|
||||||
|
loadChartTypes(): void {
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
console.log('Loading chart types...');
|
||||||
|
this.chartTypeService.getAllChartTypes().subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
console.log('Chart types loaded:', data);
|
||||||
|
this.chartTypes = data;
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart types:', error);
|
||||||
|
this.showError('Error loading chart types: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartType(): void {
|
||||||
|
if (!this.newChartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.createChartType(this.newChartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTypes.push(data);
|
||||||
|
this.newChartType = {};
|
||||||
|
this.showAddChartTypeForm = false;
|
||||||
|
this.showSuccess('Chart type created successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart type:', error);
|
||||||
|
this.showError('Error creating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartType(): void {
|
||||||
|
if (!this.selectedChartType || !this.selectedChartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.updateChartType(this.selectedChartType.id, this.selectedChartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.chartTypes.findIndex(ct => ct.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.chartTypes[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.showSuccess('Chart type updated successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart type:', error);
|
||||||
|
this.showError('Error updating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartType(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart type? This will also delete all related UI components, templates, and fields.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.deleteChartType(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTypes = this.chartTypes.filter(ct => ct.id !== id);
|
||||||
|
// Clear related data if the deleted chart type was selected
|
||||||
|
if (this.selectedChartType && this.selectedChartType.id === id) {
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.uiComponents = [];
|
||||||
|
this.chartTemplates = [];
|
||||||
|
this.dynamicFields = [];
|
||||||
|
}
|
||||||
|
this.showSuccess('Chart type deleted successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart type:', error);
|
||||||
|
this.showError('Error deleting chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChartTypeForEdit(chartType: ChartType): void {
|
||||||
|
this.selectedChartType = { ...chartType };
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Component Methods
|
||||||
|
loadUiComponents(chartTypeId: number): void {
|
||||||
|
if (!chartTypeId) {
|
||||||
|
this.uiComponents = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.getUiComponentsByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents = data;
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading UI components:', error);
|
||||||
|
this.showError('Error loading UI components: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createUiComponent(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Create a complete chartType object with only the ID
|
||||||
|
const chartTypeWithId = {
|
||||||
|
id: this.selectedChartType.id,
|
||||||
|
name: '',
|
||||||
|
displayName: '',
|
||||||
|
description: '',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const uiComponentData = {
|
||||||
|
...this.newUiComponent,
|
||||||
|
chartType: chartTypeWithId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.uiComponentService.createUiComponent(uiComponentData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents.push(data);
|
||||||
|
this.newUiComponent = {};
|
||||||
|
this.showAddUiComponentForm = false;
|
||||||
|
this.showSuccess('UI component created successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating UI component:', error);
|
||||||
|
this.showError('Error creating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUiComponent(): void {
|
||||||
|
if (!this.selectedUiComponent || !this.selectedUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.updateUiComponent(this.selectedUiComponent.id, this.selectedUiComponent).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.uiComponents.findIndex(uc => uc.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.uiComponents[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
this.showSuccess('UI component updated successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating UI component:', error);
|
||||||
|
this.showError('Error updating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUiComponent(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this UI component?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.deleteUiComponent(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.uiComponents = this.uiComponents.filter(uc => uc.id !== id);
|
||||||
|
this.showSuccess('UI component deleted successfully');
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting UI component:', error);
|
||||||
|
this.showError('Error deleting UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.uiComponentLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUiComponentForEdit(uiComponent: UiComponent): void {
|
||||||
|
this.selectedUiComponent = { ...uiComponent };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component Property Methods
|
||||||
|
loadComponentProperties(componentId: number): void {
|
||||||
|
if (!componentId) {
|
||||||
|
this.componentProperties = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.getComponentPropertiesByComponent(componentId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties = data;
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading component properties:', error);
|
||||||
|
this.showError('Error loading component properties: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createComponentProperty(): void {
|
||||||
|
if (!this.selectedUiComponent) {
|
||||||
|
this.showError('Please select a UI component first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Create a complete component object with only the ID
|
||||||
|
const componentWithId = {
|
||||||
|
id: this.selectedUiComponent.id
|
||||||
|
} as UiComponent;
|
||||||
|
|
||||||
|
const componentPropertyData = {
|
||||||
|
...this.newComponentProperty,
|
||||||
|
component: componentWithId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.componentPropertyService.createComponentProperty(componentPropertyData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties.push(data);
|
||||||
|
this.newComponentProperty = {};
|
||||||
|
this.showAddComponentPropertyForm = false;
|
||||||
|
this.showSuccess('Component property created successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating component property:', error);
|
||||||
|
this.showError('Error creating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateComponentProperty(): void {
|
||||||
|
if (!this.selectedComponentProperty || !this.selectedComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.updateComponentProperty(this.selectedComponentProperty.id, this.selectedComponentProperty).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.componentProperties.findIndex(cp => cp.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.componentProperties[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
this.showSuccess('Component property updated successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating component property:', error);
|
||||||
|
this.showError('Error updating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteComponentProperty(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this component property?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.deleteComponentProperty(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.componentProperties = this.componentProperties.filter(cp => cp.id !== id);
|
||||||
|
this.showSuccess('Component property deleted successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting component property:', error);
|
||||||
|
this.showError('Error deleting component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectComponentPropertyForEdit(componentProperty: ComponentProperty): void {
|
||||||
|
this.selectedComponentProperty = { ...componentProperty };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart Template Methods
|
||||||
|
loadChartTemplates(chartTypeId: number): void {
|
||||||
|
if (!chartTypeId) {
|
||||||
|
this.chartTemplates = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.getChartTemplatesByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates = data;
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart templates:', error);
|
||||||
|
this.showError('Error loading chart templates: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartTemplate(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Remove the chartType from the template data since we're passing it as a parameter
|
||||||
|
const chartTemplateData = { ...this.newChartTemplate };
|
||||||
|
// Remove chartType property if it exists
|
||||||
|
delete (chartTemplateData as any).chartType;
|
||||||
|
|
||||||
|
this.chartTemplateService.createChartTemplate(chartTemplateData, this.selectedChartType.id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates.push(data);
|
||||||
|
this.newChartTemplate = {};
|
||||||
|
this.showAddChartTemplateForm = false;
|
||||||
|
this.showSuccess('Chart template created successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart template:', error);
|
||||||
|
this.showError('Error creating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartTemplate(): void {
|
||||||
|
if (!this.selectedChartTemplate || !this.selectedChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.updateChartTemplate(this.selectedChartTemplate.id, this.selectedChartTemplate).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.chartTemplates.findIndex(ct => ct.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.chartTemplates[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
this.showSuccess('Chart template updated successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart template:', error);
|
||||||
|
this.showError('Error updating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartTemplate(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart template?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.deleteChartTemplate(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTemplates = this.chartTemplates.filter(ct => ct.id !== id);
|
||||||
|
this.showSuccess('Chart template deleted successfully');
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart template:', error);
|
||||||
|
this.showError('Error deleting chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTemplateLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChartTemplateForEdit(chartTemplate: ChartTemplate): void {
|
||||||
|
this.selectedChartTemplate = { ...chartTemplate };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic Field Methods
|
||||||
|
loadDynamicFields(chartTypeId: number): void {
|
||||||
|
if (!chartTypeId) {
|
||||||
|
this.dynamicFields = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.getDynamicFieldsByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields = data;
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading dynamic fields:', error);
|
||||||
|
this.showError('Error loading dynamic fields: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createDynamicField(): void {
|
||||||
|
if (!this.selectedChartType) {
|
||||||
|
this.showError('Please select a chart type first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Remove the chartType from the dynamic field data since we're passing it as a parameter
|
||||||
|
const dynamicFieldData = { ...this.newDynamicField };
|
||||||
|
// Remove chartType property if it exists
|
||||||
|
delete (dynamicFieldData as any).chartType;
|
||||||
|
|
||||||
|
this.dynamicFieldService.createDynamicField(dynamicFieldData, this.selectedChartType.id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields.push(data);
|
||||||
|
this.newDynamicField = {};
|
||||||
|
this.showAddDynamicFieldForm = false;
|
||||||
|
this.showSuccess('Dynamic field created successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating dynamic field:', error);
|
||||||
|
this.showError('Error creating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDynamicField(): void {
|
||||||
|
if (!this.selectedDynamicField || !this.selectedDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.updateDynamicField(this.selectedDynamicField.id, this.selectedDynamicField).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.dynamicFields.findIndex(df => df.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.dynamicFields[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
this.showSuccess('Dynamic field updated successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating dynamic field:', error);
|
||||||
|
this.showError('Error updating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDynamicField(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this dynamic field?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.deleteDynamicField(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.dynamicFields = this.dynamicFields.filter(df => df.id !== id);
|
||||||
|
this.showSuccess('Dynamic field deleted successfully');
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting dynamic field:', error);
|
||||||
|
this.showError('Error deleting dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.dynamicFieldLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDynamicFieldForEdit(dynamicField: DynamicField): void {
|
||||||
|
this.selectedDynamicField = { ...dynamicField };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
onChartTypeSelect(chartType: ChartType): void {
|
||||||
|
this.selectedChartType = chartType;
|
||||||
|
this.loadUiComponents(chartType.id);
|
||||||
|
this.loadChartTemplates(chartType.id);
|
||||||
|
this.loadDynamicFields(chartType.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUiComponentSelect(uiComponent: UiComponent): void {
|
||||||
|
this.selectedUiComponent = uiComponent;
|
||||||
|
this.loadComponentProperties(uiComponent.id);
|
||||||
|
// Scroll to the Component Properties tab
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.querySelector('clr-tab[ng-reflect-ng-if="true"] button[clr-tab-link]:nth-child(3)');
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForms(): void {
|
||||||
|
this.newChartType = {};
|
||||||
|
this.newUiComponent = {};
|
||||||
|
this.newComponentProperty = {};
|
||||||
|
this.newChartTemplate = {};
|
||||||
|
this.newDynamicField = {};
|
||||||
|
this.showAddChartTypeForm = false;
|
||||||
|
this.showAddUiComponentForm = false;
|
||||||
|
this.showAddComponentPropertyForm = false;
|
||||||
|
this.showAddChartTemplateForm = false;
|
||||||
|
this.showAddDynamicFieldForm = false;
|
||||||
|
this.selectedChartType = null;
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { ChartTemplate } from './chart-config-manager.component';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ChartTemplateService {
|
||||||
|
private chartTemplatesUrl = 'api/chart-templates';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
// Get all chart templates for a chart type
|
||||||
|
getChartTemplatesByChartType(chartTypeId: number): Observable<ChartTemplate[]> {
|
||||||
|
const url = `${this.chartTemplatesUrl}/chart-type/${chartTypeId}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get chart template by ID
|
||||||
|
getChartTemplateById(id: number): Observable<ChartTemplate> {
|
||||||
|
const url = `${this.chartTemplatesUrl}/${id}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new chart template with optional chart type ID as parameter
|
||||||
|
createChartTemplate(chartTemplate: Partial<ChartTemplate>, chartTypeId?: number): Observable<ChartTemplate> {
|
||||||
|
let url = this.chartTemplatesUrl;
|
||||||
|
if (chartTypeId) {
|
||||||
|
url = `${this.chartTemplatesUrl}?chartTypeId=${chartTypeId}`;
|
||||||
|
}
|
||||||
|
return this.apiRequest.post(url, chartTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chart template
|
||||||
|
updateChartTemplate(id: number, chartTemplate: ChartTemplate): Observable<ChartTemplate> {
|
||||||
|
const url = `${this.chartTemplatesUrl}/${id}`;
|
||||||
|
return this.apiRequest.put(url, chartTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete chart template
|
||||||
|
deleteChartTemplate(id: number): Observable<void> {
|
||||||
|
const url = `${this.chartTemplatesUrl}/${id}`;
|
||||||
|
return this.apiRequest.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { ComponentProperty } from './chart-config-manager.component';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ComponentPropertyService {
|
||||||
|
private componentPropertiesUrl = 'api/component-properties';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
// Get all component properties for a component
|
||||||
|
getComponentPropertiesByComponent(componentId: number): Observable<ComponentProperty[]> {
|
||||||
|
const url = `${this.componentPropertiesUrl}/component/${componentId}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get component property by ID
|
||||||
|
getComponentPropertyById(id: number): Observable<ComponentProperty> {
|
||||||
|
const url = `${this.componentPropertiesUrl}/${id}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new component property
|
||||||
|
createComponentProperty(componentProperty: Partial<ComponentProperty>): Observable<ComponentProperty> {
|
||||||
|
return this.apiRequest.post(this.componentPropertiesUrl, componentProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update component property
|
||||||
|
updateComponentProperty(id: number, componentProperty: ComponentProperty): Observable<ComponentProperty> {
|
||||||
|
const url = `${this.componentPropertiesUrl}/${id}`;
|
||||||
|
return this.apiRequest.put(url, componentProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete component property
|
||||||
|
deleteComponentProperty(id: number): Observable<void> {
|
||||||
|
const url = `${this.componentPropertiesUrl}/${id}`;
|
||||||
|
return this.apiRequest.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable, forkJoin } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { ChartType, UiComponent, ComponentProperty, ChartTemplate, DynamicField } from './chart-config-manager.component';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DynamicChartLoaderService {
|
||||||
|
private chartTypesUrl = 'api/chart-types';
|
||||||
|
private uiComponentsUrl = 'api/ui-components';
|
||||||
|
private componentPropertiesUrl = 'api/component-properties';
|
||||||
|
private chartTemplatesUrl = 'api/chart-templates';
|
||||||
|
private dynamicFieldsUrl = 'api/dynamic-fields';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all chart configurations dynamically
|
||||||
|
* This method fetches all chart types and their associated components, templates, and fields
|
||||||
|
*/
|
||||||
|
loadAllChartConfigurations(): Observable<any> {
|
||||||
|
console.log('Loading all chart configurations dynamically');
|
||||||
|
|
||||||
|
// Load all chart types first
|
||||||
|
return this.apiRequest.get(this.chartTypesUrl).pipe(
|
||||||
|
map(chartTypes => {
|
||||||
|
console.log('Loaded chart types:', chartTypes);
|
||||||
|
return chartTypes;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load complete configuration for a specific chart type
|
||||||
|
* This includes UI components, templates, and dynamic fields
|
||||||
|
*/
|
||||||
|
loadChartConfiguration(chartTypeId: number): Observable<{
|
||||||
|
chartType: ChartType,
|
||||||
|
uiComponents: UiComponent[],
|
||||||
|
templates: ChartTemplate[],
|
||||||
|
dynamicFields: DynamicField[]
|
||||||
|
}> {
|
||||||
|
console.log(`Loading complete configuration for chart type ${chartTypeId}`);
|
||||||
|
|
||||||
|
// Load all related data in parallel
|
||||||
|
return forkJoin({
|
||||||
|
chartType: this.apiRequest.get(`${this.chartTypesUrl}/${chartTypeId}`),
|
||||||
|
uiComponents: this.apiRequest.get(`${this.uiComponentsUrl}/chart-type/${chartTypeId}`),
|
||||||
|
templates: this.apiRequest.get(`${this.chartTemplatesUrl}/chart-type/${chartTypeId}`),
|
||||||
|
dynamicFields: this.apiRequest.get(`${this.dynamicFieldsUrl}/chart-type/${chartTypeId}`)
|
||||||
|
}).pipe(
|
||||||
|
map(result => {
|
||||||
|
console.log(`Loaded complete configuration for chart type ${chartTypeId}:`, result);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load chart template for a specific chart type
|
||||||
|
* This is used to render the chart UI dynamically
|
||||||
|
*/
|
||||||
|
loadChartTemplate(chartTypeId: number): Observable<ChartTemplate[]> {
|
||||||
|
console.log(`Loading chart templates for chart type ${chartTypeId}`);
|
||||||
|
return this.apiRequest.get(`${this.chartTemplatesUrl}/chart-type/${chartTypeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load UI components for a specific chart type
|
||||||
|
* These define what configuration fields are needed for the chart
|
||||||
|
*/
|
||||||
|
loadUiComponents(chartTypeId: number): Observable<UiComponent[]> {
|
||||||
|
console.log(`Loading UI components for chart type ${chartTypeId}`);
|
||||||
|
return this.apiRequest.get(`${this.uiComponentsUrl}/chart-type/${chartTypeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load dynamic fields for a specific chart type
|
||||||
|
* These define additional dynamic fields that can be used in the chart
|
||||||
|
*/
|
||||||
|
loadDynamicFields(chartTypeId: number): Observable<DynamicField[]> {
|
||||||
|
console.log(`Loading dynamic fields for chart type ${chartTypeId}`);
|
||||||
|
return this.apiRequest.get(`${this.dynamicFieldsUrl}/chart-type/${chartTypeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get chart type by name
|
||||||
|
* This is useful for finding a chart type by its name rather than ID
|
||||||
|
*/
|
||||||
|
// getChartTypeByName(name: string): Observable<ChartType | null> {
|
||||||
|
// console.log(`Finding chart type by name: ${name}`);
|
||||||
|
// return this.apiRequest.get(`${this.chartTypesUrl}/byname?chartName=${name}`).pipe(
|
||||||
|
// map((chartTypes: ChartType[]) => {
|
||||||
|
// console.log('Available chart types:', chartTypes);
|
||||||
|
// const chartType = chartTypes.find(ct => ct.name === name);
|
||||||
|
// console.log(`Found chart type for name ${name}:`, chartType);
|
||||||
|
// return chartType || null;
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
getChartTypeByName(name: string): Observable<any> {
|
||||||
|
console.log(`Finding chart type by name: ${name}`);
|
||||||
|
return this.apiRequest.get(`${this.chartTypesUrl}/byname?chartName=${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all active chart types
|
||||||
|
* This is used to populate the chart selection in the dashboard editor
|
||||||
|
*/
|
||||||
|
loadActiveChartTypes(): Observable<ChartType[]> {
|
||||||
|
console.log('Loading active chart types');
|
||||||
|
return this.apiRequest.get(`${this.chartTypesUrl}`).pipe(
|
||||||
|
map((chartTypes: ChartType[]) => {
|
||||||
|
const activeChartTypes = chartTypes.filter(ct => ct.isActive);
|
||||||
|
console.log('Loaded active chart types:', activeChartTypes);
|
||||||
|
return activeChartTypes;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { DynamicField } from './chart-config-manager.component';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DynamicFieldService {
|
||||||
|
private dynamicFieldsUrl = 'api/dynamic-fields';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
// Get all dynamic fields for a chart type
|
||||||
|
getDynamicFieldsByChartType(chartTypeId: number): Observable<DynamicField[]> {
|
||||||
|
const url = `${this.dynamicFieldsUrl}/chart-type/${chartTypeId}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dynamic field by ID
|
||||||
|
getDynamicFieldById(id: number): Observable<DynamicField> {
|
||||||
|
const url = `${this.dynamicFieldsUrl}/${id}`;
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new dynamic field with optional chart type ID as parameter
|
||||||
|
createDynamicField(dynamicField: Partial<DynamicField>, chartTypeId?: number): Observable<DynamicField> {
|
||||||
|
let url = this.dynamicFieldsUrl;
|
||||||
|
if (chartTypeId) {
|
||||||
|
url = `${this.dynamicFieldsUrl}?chartTypeId=${chartTypeId}`;
|
||||||
|
}
|
||||||
|
return this.apiRequest.post(url, dynamicField);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dynamic field
|
||||||
|
updateDynamicField(id: number, dynamicField: DynamicField): Observable<DynamicField> {
|
||||||
|
const url = `${this.dynamicFieldsUrl}/${id}`;
|
||||||
|
return this.apiRequest.put(url, dynamicField);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete dynamic field
|
||||||
|
deleteDynamicField(id: number): Observable<void> {
|
||||||
|
const url = `${this.dynamicFieldsUrl}/${id}`;
|
||||||
|
return this.apiRequest.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="chart-template-form">
|
||||||
|
<form clrForm (ngSubmit)="onSubmit()" #chartTemplateForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Template Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartTemplate.templateName" name="templateName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>HTML Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="chartTemplate.templateHtml" name="templateHtml" rows="5" placeholder="<div class='chart-container'>...</div>"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>CSS Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="chartTemplate.templateCss" name="templateCss" rows="5" placeholder=".chart-container { ... }"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Default</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="chartTemplate.isDefault" name="isDefault" />
|
||||||
|
<label>Default Template</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!chartTemplate.templateName">
|
||||||
|
{{ isEdit ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.chart-template-form {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { ChartTemplate } from '../chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-template-form',
|
||||||
|
templateUrl: './chart-template-form.component.html',
|
||||||
|
styleUrls: ['./chart-template-form.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTemplateFormComponent {
|
||||||
|
@Input() chartTemplate: Partial<ChartTemplate> = {};
|
||||||
|
@Input() isEdit = false;
|
||||||
|
@Output() save = new EventEmitter<Partial<ChartTemplate>>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
this.save.emit(this.chartTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.cancel.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="chart-type-form">
|
||||||
|
<form clrForm (ngSubmit)="onSubmit()" #chartTypeForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.name" name="chartTypeName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.displayName" name="chartTypeDisplayName" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="chartType.description" name="chartTypeDescription" rows="3"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="chartType.isActive" name="chartTypeIsActive" />
|
||||||
|
<label>Active</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!chartType.name">
|
||||||
|
{{ isEdit ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.chart-type-form {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { ChartType } from '../chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-form',
|
||||||
|
templateUrl: './chart-type-form.component.html',
|
||||||
|
styleUrls: ['./chart-type-form.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTypeFormComponent {
|
||||||
|
@Input() chartType: Partial<ChartType> = {};
|
||||||
|
@Input() isEdit = false;
|
||||||
|
@Output() save = new EventEmitter<Partial<ChartType>>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
this.save.emit(this.chartType);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.cancel.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="component-property-form">
|
||||||
|
<form clrForm (ngSubmit)="onSubmit()" #componentPropertyForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="componentProperty.propertyName" name="propertyName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Value</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="componentProperty.propertyValue" name="propertyValue" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-select-container>
|
||||||
|
<label>Property Type</label>
|
||||||
|
<select clrSelect [(ngModel)]="componentProperty.propertyType" name="propertyType">
|
||||||
|
<option value="">Select a type</option>
|
||||||
|
<option *ngFor="let type of propertyTypes" [value]="type">{{ type | titlecase }}</option>
|
||||||
|
</select>
|
||||||
|
</clr-select-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!componentProperty.propertyName">
|
||||||
|
{{ isEdit ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.component-property-form {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { ComponentProperty } from '../chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-component-property-form',
|
||||||
|
templateUrl: './component-property-form.component.html',
|
||||||
|
styleUrls: ['./component-property-form.component.scss']
|
||||||
|
})
|
||||||
|
export class ComponentPropertyFormComponent {
|
||||||
|
@Input() componentProperty: Partial<ComponentProperty> = {};
|
||||||
|
@Input() isEdit = false;
|
||||||
|
@Output() save = new EventEmitter<Partial<ComponentProperty>>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
propertyTypes = [
|
||||||
|
'string',
|
||||||
|
'number',
|
||||||
|
'boolean',
|
||||||
|
'array',
|
||||||
|
'object',
|
||||||
|
'function'
|
||||||
|
];
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
this.save.emit(this.componentProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.cancel.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<div class="dynamic-field-form">
|
||||||
|
<form clrForm (ngSubmit)="onSubmit()" #dynamicFieldForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="dynamicField.fieldName" name="fieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="dynamicField.fieldLabel" name="fieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-select-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<select clrSelect [(ngModel)]="dynamicField.fieldType" name="fieldType">
|
||||||
|
<option value="">Select a type</option>
|
||||||
|
<option *ngFor="let type of fieldTypes" [value]="type">{{ type | titlecase }}</option>
|
||||||
|
</select>
|
||||||
|
</clr-select-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options (JSON format)</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="dynamicField.fieldOptions" name="fieldOptions" rows="3" placeholder='{"option1": "Option 1", "option2": "Option 2"}'></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="dynamicField.sortOrder" name="fieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="dynamicField.isRequired" name="fieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="dynamicField.showInUi" name="fieldShowInUi" />
|
||||||
|
<label>Show in UI</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!dynamicField.fieldName">
|
||||||
|
{{ isEdit ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.dynamic-field-form {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { DynamicField } from '../chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dynamic-field-form',
|
||||||
|
templateUrl: './dynamic-field-form.component.html',
|
||||||
|
styleUrls: ['./dynamic-field-form.component.scss']
|
||||||
|
})
|
||||||
|
export class DynamicFieldFormComponent {
|
||||||
|
@Input() dynamicField: Partial<DynamicField> = {};
|
||||||
|
@Input() isEdit = false;
|
||||||
|
@Output() save = new EventEmitter<Partial<DynamicField>>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
fieldTypes = [
|
||||||
|
'text',
|
||||||
|
'number',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
'date',
|
||||||
|
'select',
|
||||||
|
'checkbox',
|
||||||
|
'radio',
|
||||||
|
'textarea'
|
||||||
|
];
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
this.save.emit(this.dynamicField);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.cancel.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="ui-component-form">
|
||||||
|
<form clrForm (ngSubmit)="onSubmit()" #uiComponentForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="uiComponent.componentName" name="componentName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-select-container>
|
||||||
|
<label>Component Type</label>
|
||||||
|
<select clrSelect [(ngModel)]="uiComponent.componentType" name="componentType">
|
||||||
|
<option value="">Select a type</option>
|
||||||
|
<option *ngFor="let type of componentTypes" [value]="type">{{ type | titlecase }}</option>
|
||||||
|
</select>
|
||||||
|
</clr-select-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="uiComponent.displayLabel" name="displayLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Placeholder</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="uiComponent.placeholder" name="placeholder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="uiComponent.sortOrder" name="sortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="uiComponent.isRequired" name="isRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!uiComponent.componentName">
|
||||||
|
{{ isEdit ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.ui-component-form {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { UiComponent } from '../chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ui-component-form',
|
||||||
|
templateUrl: './ui-component-form.component.html',
|
||||||
|
styleUrls: ['./ui-component-form.component.scss']
|
||||||
|
})
|
||||||
|
export class UiComponentFormComponent {
|
||||||
|
@Input() uiComponent: Partial<UiComponent> = {};
|
||||||
|
@Input() isEdit = false;
|
||||||
|
@Output() save = new EventEmitter<Partial<UiComponent>>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
componentTypes = [
|
||||||
|
'input',
|
||||||
|
'select',
|
||||||
|
'checkbox',
|
||||||
|
'radio',
|
||||||
|
'textarea',
|
||||||
|
'datepicker',
|
||||||
|
'slider',
|
||||||
|
'toggle'
|
||||||
|
];
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
this.save.emit(this.uiComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.cancel.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
import { UiComponent } from './chart-config-manager.component';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UiComponentService {
|
||||||
|
private uiComponentsUrl = 'api/ui-components';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
// Get all UI components for a chart type
|
||||||
|
getUiComponentsByChartType(chartTypeId: number): Observable<UiComponent[]> {
|
||||||
|
const url = `${this.uiComponentsUrl}/chart-type/${chartTypeId}`;
|
||||||
|
console.log(`Fetching UI components for chart type ${chartTypeId} from ${url}`);
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get UI component by ID
|
||||||
|
getUiComponentById(id: number): Observable<UiComponent> {
|
||||||
|
const url = `${this.uiComponentsUrl}/${id}`;
|
||||||
|
console.log(`Fetching UI component ${id} from ${url}`);
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new UI component
|
||||||
|
createUiComponent(uiComponent: Partial<UiComponent>): Observable<UiComponent> {
|
||||||
|
console.log('Creating UI component:', uiComponent);
|
||||||
|
return this.apiRequest.post(this.uiComponentsUrl, uiComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI component
|
||||||
|
updateUiComponent(id: number, uiComponent: UiComponent): Observable<UiComponent> {
|
||||||
|
const url = `${this.uiComponentsUrl}/${id}`;
|
||||||
|
console.log(`Updating UI component ${id}:`, uiComponent);
|
||||||
|
return this.apiRequest.put(url, uiComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete UI component
|
||||||
|
deleteUiComponent(id: number): Observable<void> {
|
||||||
|
const url = `${this.uiComponentsUrl}/${id}`;
|
||||||
|
console.log(`Deleting UI component ${id}`);
|
||||||
|
return this.apiRequest.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<div class="add-chart-type-page">
|
||||||
|
<div class="header">
|
||||||
|
<h2>Add New Chart Type</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="createChartType()" #addChartTypeForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.name" name="chartTypeName" required />
|
||||||
|
<clr-control-helper>Enter a unique name for the chart type (e.g., "line-chart", "bar-chart")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.displayName" name="chartTypeDisplayName" />
|
||||||
|
<clr-control-helper>This is the user-friendly name shown in the UI</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="chartType.description" name="chartTypeDescription" rows="3"></textarea>
|
||||||
|
<clr-control-helper>Provide a detailed description of this chart type and when to use it</clr-control-helper>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="chartType.isActive" name="chartTypeIsActive" />
|
||||||
|
<label>Active</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Deactivate chart types that should not be available for selection</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!chartType.name || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Create Chart Type
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h4>About Chart Types</h4>
|
||||||
|
<p>Chart types define the different visualization options available in the dashboard builder. Each chart type can have:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Associated UI components that define the configuration form</li>
|
||||||
|
<li>Templates that define how the chart is rendered</li>
|
||||||
|
<li>Dynamic fields that capture specific configuration parameters</li>
|
||||||
|
</ul>
|
||||||
|
<p>After creating a chart type, you can configure its components, templates, and fields in the chart configuration manager.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
.add-chart-type-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-left: 4px solid #0079b8;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-chart-type',
|
||||||
|
templateUrl: './add-chart-type.component.html',
|
||||||
|
styleUrls: ['./add-chart-type.component.scss']
|
||||||
|
})
|
||||||
|
export class AddChartTypeComponent {
|
||||||
|
chartType: Partial<ChartType> = {
|
||||||
|
isActive: true
|
||||||
|
};
|
||||||
|
loadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartType(): void {
|
||||||
|
if (!this.chartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.createChartType(this.chartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.showSuccess('Chart type created successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
// Redirect to chart types list after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart type:', error);
|
||||||
|
this.showError('Error creating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
<div class="chart-type-fields-page">
|
||||||
|
<div class="header">
|
||||||
|
<h2>
|
||||||
|
<button class="btn btn-link back-button" (click)="goBack()">
|
||||||
|
<cds-icon shape="arrow" direction="left"></cds-icon>
|
||||||
|
</button>
|
||||||
|
Dynamic Fields for {{ chartType?.name || 'Chart Type' }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Dynamic Field Form -->
|
||||||
|
<div class="card" *ngIf="showAddForm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Add New Dynamic Field</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="createDynamicField()" #addDynamicFieldForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldName" name="fieldName" required />
|
||||||
|
<clr-control-helper>Enter a unique name for the dynamic field (e.g., "chart-title", "x-axis-label")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldLabel" name="fieldLabel" />
|
||||||
|
<clr-control-helper>User-friendly label shown in the configuration form</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newDynamicField.fieldType" name="fieldType" />
|
||||||
|
<clr-control-helper>Type of the field (e.g., "string", "number", "boolean")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newDynamicField.fieldOptions" name="fieldOptions" rows="3"></textarea>
|
||||||
|
<clr-control-helper>JSON options for the field (for select fields, etc.)</clr-control-helper>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="newDynamicField.sortOrder" name="fieldSortOrder" />
|
||||||
|
<clr-control-helper>Order in which fields appear in the form</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.isRequired" name="fieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Mark as required if this field must be filled</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newDynamicField.showInUi" name="fieldShowInUi" />
|
||||||
|
<label>Visible</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Uncheck to hide this field in the configuration form</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!newDynamicField.fieldName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Create Dynamic Field
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="showAddForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dynamic Fields Grid -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<h3>Dynamic Fields</h3>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col" style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" (click)="showAddForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Dynamic Field
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Field Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Field Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Visible</clr-dg-column>
|
||||||
|
<clr-dg-column>Sort Order</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let dynamicField of dynamicFields" [clrDgItem]="dynamicField">
|
||||||
|
<clr-dg-cell>{{dynamicField.fieldName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{dynamicField.fieldLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{dynamicField.fieldType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="dynamicField.isRequired" [class.label-danger]="!dynamicField.isRequired">
|
||||||
|
{{dynamicField.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="dynamicField.showInUi" [class.label-danger]="!dynamicField.showInUi">
|
||||||
|
{{dynamicField.showInUi ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{dynamicField.sortOrder}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectDynamicFieldForEdit(dynamicField)" title="Edit">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteDynamicField(dynamicField.id)" title="Delete">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{dynamicFields.length}} dynamic field(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Dynamic Field Form -->
|
||||||
|
<div class="card" *ngIf="selectedDynamicField">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Edit Dynamic Field</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="updateDynamicField()" #editDynamicFieldForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldName" name="editFieldName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldLabel" name="editFieldLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Field Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedDynamicField.fieldType" name="editFieldType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Field Options</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedDynamicField.fieldOptions" name="editFieldOptions" rows="3"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="selectedDynamicField.sortOrder" name="editFieldSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.isRequired" name="editFieldIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Show in UI</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedDynamicField.showInUi" name="editFieldShowInUi" />
|
||||||
|
<label>Visible</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!selectedDynamicField.fieldName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update Dynamic Field
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="selectedDynamicField = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h4>About Dynamic Fields</h4>
|
||||||
|
<p>Dynamic fields define the configurable parameters for a chart type. Each field represents:</p>
|
||||||
|
<ul>
|
||||||
|
<li>A configuration option that can be set when creating or editing a chart</li>
|
||||||
|
<li>Metadata like type, label, and validation rules</li>
|
||||||
|
<li>Visibility and requirement settings</li>
|
||||||
|
</ul>
|
||||||
|
<p>Dynamic fields allow you to create flexible chart configurations that can be customized per instance.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
.chart-type-fields-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
padding: 0;
|
||||||
|
cds-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.label-success {
|
||||||
|
background-color: #3d9970;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.label-danger {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
clr-dg-cell {
|
||||||
|
&:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-left: 4px solid #0079b8;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
.clr-row {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.clr-col {
|
||||||
|
text-align: left !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
import { DynamicField } from '../chart-config/chart-config-manager.component';
|
||||||
|
import { DynamicFieldService } from '../chart-config/dynamic-field.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-fields',
|
||||||
|
templateUrl: './chart-type-fields.component.html',
|
||||||
|
styleUrls: ['./chart-type-fields.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTypeFieldsComponent implements OnInit {
|
||||||
|
chartType: ChartType | null = null;
|
||||||
|
dynamicFields: DynamicField[] = [];
|
||||||
|
newDynamicField: Partial<DynamicField> = {
|
||||||
|
isRequired: false,
|
||||||
|
showInUi: true
|
||||||
|
};
|
||||||
|
selectedDynamicField: DynamicField | null = null;
|
||||||
|
showAddForm = false;
|
||||||
|
|
||||||
|
loadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private dynamicFieldService: DynamicFieldService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const chartTypeId = Number(this.route.snapshot.paramMap.get('id'));
|
||||||
|
if (chartTypeId) {
|
||||||
|
this.loadChartType(chartTypeId);
|
||||||
|
this.loadDynamicFields(chartTypeId);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid chart type ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartType(id: number): void {
|
||||||
|
this.chartTypeService.getChartTypeById(id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartType = data;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart type:', error);
|
||||||
|
this.showError('Error loading chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDynamicFields(chartTypeId: number): void {
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.getDynamicFieldsByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields = data;
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading dynamic fields:', error);
|
||||||
|
this.showError('Error loading dynamic fields: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createDynamicField(): void {
|
||||||
|
if (!this.chartType || !this.newDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a copy without the chartType property
|
||||||
|
const fieldData: Partial<DynamicField> = {
|
||||||
|
fieldName: this.newDynamicField.fieldName,
|
||||||
|
fieldLabel: this.newDynamicField.fieldLabel,
|
||||||
|
fieldType: this.newDynamicField.fieldType,
|
||||||
|
fieldOptions: this.newDynamicField.fieldOptions,
|
||||||
|
isRequired: this.newDynamicField.isRequired,
|
||||||
|
showInUi: this.newDynamicField.showInUi,
|
||||||
|
sortOrder: this.newDynamicField.sortOrder
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.createDynamicField(fieldData, this.chartType.id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.dynamicFields.push(data);
|
||||||
|
this.newDynamicField = { isRequired: false, showInUi: true };
|
||||||
|
this.showAddForm = false;
|
||||||
|
this.showSuccess('Dynamic field created successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating dynamic field:', error);
|
||||||
|
this.showError('Error creating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDynamicField(): void {
|
||||||
|
if (!this.selectedDynamicField || !this.selectedDynamicField.fieldName) {
|
||||||
|
this.showError('Field name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Updating dynamic field:', this.selectedDynamicField);
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.updateDynamicField(this.selectedDynamicField.id, this.selectedDynamicField).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.dynamicFields.findIndex(df => df.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.dynamicFields[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedDynamicField = null;
|
||||||
|
this.showSuccess('Dynamic field updated successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating dynamic field:', error);
|
||||||
|
this.showError('Error updating dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDynamicField(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this dynamic field?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.dynamicFieldService.deleteDynamicField(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.dynamicFields = this.dynamicFields.filter(df => df.id !== id);
|
||||||
|
this.showSuccess('Dynamic field deleted successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting dynamic field:', error);
|
||||||
|
this.showError('Error deleting dynamic field: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDynamicFieldForEdit(dynamicField: DynamicField): void {
|
||||||
|
this.selectedDynamicField = { ...dynamicField };
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
if (this.chartType) {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types/edit', this.chartType.id]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<div class="chart-type-manager">
|
||||||
|
<h2>Chart Type Management</h2>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Types Grid -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<h3>Chart Types</h3>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col" style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" [routerLink]="['/cns-portal/dashboardbuilder/chart-types/add']">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Chart Type
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="chartTypeLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Description</clr-dg-column>
|
||||||
|
<clr-dg-column>Status</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let chartType of chartTypes" [clrDgItem]="chartType">
|
||||||
|
<clr-dg-cell>{{chartType.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.displayName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.description}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="chartType.isActive" [class.label-danger]="!chartType.isActive">
|
||||||
|
{{chartType.isActive ? 'Active' : 'Inactive'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.createdAt ? (chartType.createdAt | date:'short') : 'N/A'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartType.updatedAt ? (chartType.updatedAt | date:'short') : 'N/A'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" [routerLink]="['/cns-portal/dashboardbuilder/chart-types/edit', chartType.id]" title="Edit">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartType(chartType.id)" title="Delete">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTypes.length}} chart type(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
.chart-type-manager {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.label-success {
|
||||||
|
background-color: #3d9970;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.label-danger {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
clr-dg-cell {
|
||||||
|
&:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
.clr-row {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.clr-col {
|
||||||
|
text-align: left !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-manager',
|
||||||
|
templateUrl: './chart-type-manager.component.html',
|
||||||
|
styleUrls: ['./chart-type-manager.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTypeManagerComponent implements OnInit {
|
||||||
|
chartTypes: ChartType[] = [];
|
||||||
|
chartTypeLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
constructor(private chartTypeService: ChartTypeService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadChartTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart Type Methods
|
||||||
|
loadChartTypes(): void {
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.getAllChartTypes().subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
// Process the data to ensure dates are properly formatted
|
||||||
|
this.chartTypes = data.map(chartType => ({
|
||||||
|
...chartType,
|
||||||
|
createdAt: this.formatDate(chartType.createdAt),
|
||||||
|
updatedAt: this.formatDate(chartType.updatedAt)
|
||||||
|
}));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart types:', error);
|
||||||
|
this.showError('Error loading chart types: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format date to handle both string and object formats
|
||||||
|
private formatDate(date: any): string {
|
||||||
|
if (!date) return '';
|
||||||
|
|
||||||
|
// If it's already a string, return as is
|
||||||
|
if (typeof date === 'string') {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's an object, try to convert to string
|
||||||
|
if (typeof date === 'object') {
|
||||||
|
// Handle various date object formats
|
||||||
|
if (date instanceof Date) {
|
||||||
|
return date.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ISO string within object
|
||||||
|
if (date.date) {
|
||||||
|
return date.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other object formats
|
||||||
|
try {
|
||||||
|
return new Date(date).toISOString();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartType(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart type?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.deleteChartType(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTypes = this.chartTypes.filter(ct => ct.id !== id);
|
||||||
|
this.showSuccess('Chart type deleted successfully');
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart type:', error);
|
||||||
|
this.showError('Error deleting chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.chartTypeLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-page',
|
||||||
|
template: `
|
||||||
|
<div class="chart-type-page">
|
||||||
|
<app-chart-type-manager></app-chart-type-manager>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [`
|
||||||
|
.chart-type-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.chart-type-page {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class ChartTypePageComponent { }
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<div class="chart-type-templates-page">
|
||||||
|
<div class="header">
|
||||||
|
<h2>
|
||||||
|
<button class="btn btn-link back-button" (click)="goBack()">
|
||||||
|
<cds-icon shape="arrow" direction="left"></cds-icon>
|
||||||
|
</button>
|
||||||
|
Chart Templates for {{ chartType?.name || 'Chart Type' }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Chart Template Form -->
|
||||||
|
<div class="card" *ngIf="showAddForm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Add New Chart Template</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="createChartTemplate()" #addChartTemplateForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Template Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newChartTemplate.templateName" name="templateName" required />
|
||||||
|
<clr-control-helper>Enter a unique name for the chart template</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>HTML Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newChartTemplate.templateHtml" name="templateHtml" rows="5"></textarea>
|
||||||
|
<clr-control-helper>HTML structure for rendering the chart</clr-control-helper>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>CSS Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="newChartTemplate.templateCss" name="templateCss" rows="5"></textarea>
|
||||||
|
<clr-control-helper>CSS styling for the chart template</clr-control-helper>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Default</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newChartTemplate.isDefault" name="isDefault" />
|
||||||
|
<label>Default Template</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Mark as default template for this chart type</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!newChartTemplate.templateName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Create Chart Template
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="showAddForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Templates Grid -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<h3>Chart Templates</h3>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col" style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" (click)="showAddForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Chart Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Template Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Default</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let chartTemplate of chartTemplates" [clrDgItem]="chartTemplate">
|
||||||
|
<clr-dg-cell>{{chartTemplate.templateName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="chartTemplate.isDefault" [class.label-danger]="!chartTemplate.isDefault">
|
||||||
|
{{chartTemplate.isDefault ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartTemplate.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{chartTemplate.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectChartTemplateForEdit(chartTemplate)" title="Edit">
|
||||||
|
<cds-icon shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteChartTemplate(chartTemplate.id)" title="Delete">
|
||||||
|
<cds-icon shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{chartTemplates.length}} chart template(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Chart Template Form -->
|
||||||
|
<div class="card" *ngIf="selectedChartTemplate">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Edit Chart Template</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="updateChartTemplate()" #editChartTemplateForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Template Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedChartTemplate.templateName" name="editTemplateName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>HTML Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedChartTemplate.templateHtml" name="editTemplateHtml" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>CSS Template</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="selectedChartTemplate.templateCss" name="editTemplateCss" rows="5"></textarea>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Default</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedChartTemplate.isDefault" name="editIsDefault" />
|
||||||
|
<label>Default Template</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!selectedChartTemplate.templateName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update Chart Template
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="selectedChartTemplate = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h4>About Chart Templates</h4>
|
||||||
|
<p>Chart templates define how a chart of this type is rendered. Each template includes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>HTML structure that defines the chart layout</li>
|
||||||
|
<li>CSS styling that controls the appearance</li>
|
||||||
|
<li>A default flag to indicate the primary template</li>
|
||||||
|
</ul>
|
||||||
|
<p>Templates allow you to have multiple visual representations for the same chart type.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
.chart-type-templates-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
padding: 0;
|
||||||
|
cds-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.label-success {
|
||||||
|
background-color: #3d9970;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.label-danger {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
clr-dg-cell {
|
||||||
|
&:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-left: 4px solid #0079b8;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
.clr-row {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.clr-col {
|
||||||
|
text-align: left !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
import { ChartTemplateService } from '../chart-config/chart-template.service';
|
||||||
|
import { ChartTemplate } from '../chart-config/chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-templates',
|
||||||
|
templateUrl: './chart-type-templates.component.html',
|
||||||
|
styleUrls: ['./chart-type-templates.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTypeTemplatesComponent implements OnInit {
|
||||||
|
chartType: ChartType | null = null;
|
||||||
|
chartTemplates: ChartTemplate[] = [];
|
||||||
|
newChartTemplate: Partial<ChartTemplate> = {
|
||||||
|
isDefault: false
|
||||||
|
};
|
||||||
|
selectedChartTemplate: ChartTemplate | null = null;
|
||||||
|
showAddForm = false;
|
||||||
|
|
||||||
|
loadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private chartTemplateService: ChartTemplateService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const chartTypeId = Number(this.route.snapshot.paramMap.get('id'));
|
||||||
|
if (chartTypeId) {
|
||||||
|
this.loadChartType(chartTypeId);
|
||||||
|
this.loadChartTemplates(chartTypeId);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid chart type ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartType(id: number): void {
|
||||||
|
this.chartTypeService.getChartTypeById(id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartType = data;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart type:', error);
|
||||||
|
this.showError('Error loading chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartTemplates(chartTypeId: number): void {
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.getChartTemplatesByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates = data;
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart templates:', error);
|
||||||
|
this.showError('Error loading chart templates: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChartTemplate(): void {
|
||||||
|
if (!this.chartType || !this.newChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a copy without the chartType property
|
||||||
|
const templateData: Partial<ChartTemplate> = {
|
||||||
|
templateName: this.newChartTemplate.templateName,
|
||||||
|
templateHtml: this.newChartTemplate.templateHtml,
|
||||||
|
templateCss: this.newChartTemplate.templateCss,
|
||||||
|
isDefault: this.newChartTemplate.isDefault
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.createChartTemplate(templateData, this.chartType.id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartTemplates.push(data);
|
||||||
|
this.newChartTemplate = { isDefault: false };
|
||||||
|
this.showAddForm = false;
|
||||||
|
this.showSuccess('Chart template created successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating chart template:', error);
|
||||||
|
this.showError('Error creating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartTemplate(): void {
|
||||||
|
if (!this.selectedChartTemplate || !this.selectedChartTemplate.templateName) {
|
||||||
|
this.showError('Template name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.updateChartTemplate(this.selectedChartTemplate.id, this.selectedChartTemplate).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.chartTemplates.findIndex(ct => ct.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.chartTemplates[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedChartTemplate = null;
|
||||||
|
this.showSuccess('Chart template updated successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart template:', error);
|
||||||
|
this.showError('Error updating chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteChartTemplate(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this chart template?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTemplateService.deleteChartTemplate(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.chartTemplates = this.chartTemplates.filter(ct => ct.id !== id);
|
||||||
|
this.showSuccess('Chart template deleted successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart template:', error);
|
||||||
|
this.showError('Error deleting chart template: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChartTemplateForEdit(chartTemplate: ChartTemplate): void {
|
||||||
|
this.selectedChartTemplate = { ...chartTemplate };
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
if (this.chartType) {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types/edit', this.chartType.id]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
<div class="chart-type-ui-components-page">
|
||||||
|
<div class="header">
|
||||||
|
<h2>
|
||||||
|
<button class="btn btn-link back-button" (click)="goBack()">
|
||||||
|
<cds-icon shape="arrow" direction="left"></cds-icon>
|
||||||
|
</button>
|
||||||
|
UI Components for {{ chartType?.name || 'Chart Type' }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add UI Component Form -->
|
||||||
|
<div class="card" *ngIf="showAddForm">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Add New UI Component</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="createUiComponent()" #addUiComponentForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.componentName" name="componentName" required />
|
||||||
|
<clr-control-helper>Enter a unique name for the UI component (e.g., "title-config", "axis-config")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.componentType" name="componentType" />
|
||||||
|
<clr-control-helper>Type of the component (e.g., "input", "select", "checkbox")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.displayLabel" name="displayLabel" />
|
||||||
|
<clr-control-helper>User-friendly label shown in the configuration form</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Placeholder</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newUiComponent.placeholder" name="placeholder" />
|
||||||
|
<clr-control-helper>Placeholder text for input fields</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="newUiComponent.sortOrder" name="sortOrder" />
|
||||||
|
<clr-control-helper>Order in which components appear in the form</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="newUiComponent.isRequired" name="isRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Mark as required if this component must be filled</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!newUiComponent.componentName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Create UI Component
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="showAddForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- UI Components Grid -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<h3>UI Components</h3>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col" style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" (click)="showAddForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add UI Component
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Component Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Component Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Display Label</clr-dg-column>
|
||||||
|
<clr-dg-column>Required</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let uiComponent of uiComponents" [clrDgItem]="uiComponent">
|
||||||
|
<clr-dg-cell>{{uiComponent.componentName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.componentType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{uiComponent.displayLabel}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span class="label" [class.label-success]="uiComponent.isRequired" [class.label-danger]="!uiComponent.isRequired">
|
||||||
|
{{uiComponent.isRequired ? 'Yes' : 'No'}}
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell class="action-cell">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectUiComponentForEdit(uiComponent)" title="Edit" [disabled]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="loadingState !== ClrLoadingState.LOADING" shape="pencil" aria-label="Edit"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteUiComponent(uiComponent.id)" title="Delete" [disabled]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="loadingState !== ClrLoadingState.LOADING" shape="trash" aria-label="Delete"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon btn-primary" (click)="onViewComponentProperties(uiComponent)" title="View Properties" [disabled]="loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="loadingState !== ClrLoadingState.LOADING" shape="eye" aria-label="View Properties"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{uiComponents.length}} UI component(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit UI Component Form -->
|
||||||
|
<div class="card" *ngIf="selectedUiComponent && !showAddComponentPropertyForm && !selectedComponentProperty">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Edit UI Component</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="updateUiComponent()" #editUiComponentForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.componentName" name="editComponentName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Component Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.componentType" name="editComponentType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Label</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.displayLabel" name="editDisplayLabel" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Placeholder</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedUiComponent.placeholder" name="editPlaceholder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Sort Order</label>
|
||||||
|
<input clrInput type="number" [(ngModel)]="selectedUiComponent.sortOrder" name="editSortOrder" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Required</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="selectedUiComponent.isRequired" name="editIsRequired" />
|
||||||
|
<label>Required</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!selectedUiComponent.componentName || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update UI Component
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="selectedUiComponent = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Properties Section -->
|
||||||
|
<div class="card" *ngIf="selectedUiComponent">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col">
|
||||||
|
<h3>Properties for {{selectedUiComponent?.componentName}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="clr-col" style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" (click)="showAddComponentPropertyForm = true">
|
||||||
|
<cds-icon shape="plus"></cds-icon> Add Property
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="showAddComponentPropertyForm">
|
||||||
|
<form clrForm (ngSubmit)="createComponentProperty()" #addComponentPropertyForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyName" name="propertyName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Value</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyValue" name="propertyValue" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="newComponentProperty.propertyType" name="propertyType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!newComponentProperty.propertyName || componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Create Property
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="showAddComponentPropertyForm = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Properties Table -->
|
||||||
|
<div class="card-block">
|
||||||
|
<clr-datagrid [clrDgLoading]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-dg-column>Property Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Value</clr-dg-column>
|
||||||
|
<clr-dg-column>Property Type</clr-dg-column>
|
||||||
|
<clr-dg-column>Created At</clr-dg-column>
|
||||||
|
<clr-dg-column>Updated At</clr-dg-column>
|
||||||
|
<clr-dg-column>Actions</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let property of componentProperties" [clrDgItem]="property">
|
||||||
|
<clr-dg-cell>{{property.propertyName}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyValue}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.propertyType}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.createdAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{property.updatedAt | date:'short'}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="selectComponentPropertyForEdit(property)" title="Edit" [disabled]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="componentPropertyLoadingState !== ClrLoadingState.LOADING" shape="pencil"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-icon" (click)="deleteComponentProperty(property.id)" title="Delete" [disabled]="componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
<cds-icon *ngIf="componentPropertyLoadingState !== ClrLoadingState.LOADING" shape="trash"></cds-icon>
|
||||||
|
</button>
|
||||||
|
</clr-dg-cell>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
{{componentProperties.length}} propertie(s)
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Component Property Form -->
|
||||||
|
<div class="card-block" *ngIf="selectedComponentProperty">
|
||||||
|
<h4>Edit Component Property</h4>
|
||||||
|
<form clrForm (ngSubmit)="updateComponentProperty()" #editComponentPropertyForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyName" name="editPropertyName" required />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Value</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyValue" name="editPropertyValue" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Property Type</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="selectedComponentProperty.propertyType" name="editPropertyType" />
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!selectedComponentProperty.propertyName || componentPropertyLoadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="componentPropertyLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update Property
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="selectedComponentProperty = null">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h4>About UI Components</h4>
|
||||||
|
<p>UI components define the configuration form elements for a chart type. Each component represents:</p>
|
||||||
|
<ul>
|
||||||
|
<li>A form field that appears when configuring a chart of this type</li>
|
||||||
|
<li>Metadata like label, placeholder, and validation rules</li>
|
||||||
|
<li>An order in which they appear in the configuration form</li>
|
||||||
|
</ul>
|
||||||
|
<p>After creating UI components, you can define their properties using the "View Properties" button.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
.chart-type-ui-components-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
padding: 0;
|
||||||
|
cds-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.label-success {
|
||||||
|
background-color: #3d9970;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.label-danger {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-datagrid {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
clr-dg-cell {
|
||||||
|
&:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-left: 4px solid #0079b8;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component Properties Section Styles
|
||||||
|
.action-cell {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons .btn-icon {
|
||||||
|
min-width: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure icons are visible
|
||||||
|
cds-icon {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
clr-spinner {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
.clr-row {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.clr-col {
|
||||||
|
text-align: left !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
import { UiComponentService } from '../chart-config/ui-component.service';
|
||||||
|
import { ComponentPropertyService } from '../chart-config/component-property.service';
|
||||||
|
import { UiComponent, ComponentProperty } from '../chart-config/chart-config-manager.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-type-ui-components',
|
||||||
|
templateUrl: './chart-type-ui-components.component.html',
|
||||||
|
styleUrls: ['./chart-type-ui-components.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartTypeUiComponentsComponent implements OnInit {
|
||||||
|
chartType: ChartType | null = null;
|
||||||
|
uiComponents: UiComponent[] = [];
|
||||||
|
newUiComponent: Partial<UiComponent> = {
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
selectedUiComponent: UiComponent | null = null;
|
||||||
|
showAddForm = false;
|
||||||
|
|
||||||
|
// Component Properties
|
||||||
|
componentProperties: ComponentProperty[] = [];
|
||||||
|
selectedComponentProperty: ComponentProperty | null = null;
|
||||||
|
newComponentProperty: Partial<ComponentProperty> = {};
|
||||||
|
showAddComponentPropertyForm = false;
|
||||||
|
componentPropertyLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
|
||||||
|
loadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private uiComponentService: UiComponentService,
|
||||||
|
private componentPropertyService: ComponentPropertyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const chartTypeId = Number(this.route.snapshot.paramMap.get('id'));
|
||||||
|
if (chartTypeId) {
|
||||||
|
this.loadChartType(chartTypeId);
|
||||||
|
this.loadUiComponents(chartTypeId);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid chart type ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartType(id: number): void {
|
||||||
|
this.chartTypeService.getChartTypeById(id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.chartType = data;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart type:', error);
|
||||||
|
this.showError('Error loading chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUiComponents(chartTypeId: number): void {
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.getUiComponentsByChartType(chartTypeId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents = data;
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading UI components:', error);
|
||||||
|
this.showError('Error loading UI components: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createUiComponent(): void {
|
||||||
|
if (!this.chartType || !this.newUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Create a complete chartType object with only the ID (following the pattern in chart-config-manager)
|
||||||
|
const chartTypeWithId = {
|
||||||
|
id: this.chartType.id,
|
||||||
|
name: '',
|
||||||
|
displayName: '',
|
||||||
|
description: '',
|
||||||
|
isActive: true,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentData = {
|
||||||
|
...this.newUiComponent,
|
||||||
|
chartType: chartTypeWithId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.uiComponentService.createUiComponent(componentData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.uiComponents.push(data);
|
||||||
|
this.newUiComponent = { isRequired: false };
|
||||||
|
this.showAddForm = false;
|
||||||
|
this.showSuccess('UI component created successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating UI component:', error);
|
||||||
|
this.showError('Error creating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUiComponent(): void {
|
||||||
|
if (!this.selectedUiComponent || !this.selectedUiComponent.componentName) {
|
||||||
|
this.showError('Component name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.updateUiComponent(this.selectedUiComponent.id, this.selectedUiComponent).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.uiComponents.findIndex(uc => uc.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.uiComponents[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedUiComponent = null;
|
||||||
|
this.showSuccess('UI component updated successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating UI component:', error);
|
||||||
|
this.showError('Error updating UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUiComponent(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this UI component?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.uiComponentService.deleteUiComponent(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.uiComponents = this.uiComponents.filter(uc => uc.id !== id);
|
||||||
|
this.showSuccess('UI component deleted successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting UI component:', error);
|
||||||
|
this.showError('Error deleting UI component: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUiComponentForEdit(uiComponent: UiComponent): void {
|
||||||
|
this.selectedUiComponent = { ...uiComponent };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component Property Methods
|
||||||
|
loadComponentProperties(componentId: number): void {
|
||||||
|
if (!componentId) {
|
||||||
|
this.componentProperties = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.getComponentPropertiesByComponent(componentId).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties = data;
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading component properties:', error);
|
||||||
|
this.showError('Error loading component properties: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createComponentProperty(): void {
|
||||||
|
if (!this.selectedUiComponent) {
|
||||||
|
this.showError('Please select a UI component first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.newComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
|
||||||
|
// Create a complete component object with only the ID
|
||||||
|
const componentWithId = {
|
||||||
|
id: this.selectedUiComponent.id
|
||||||
|
} as UiComponent;
|
||||||
|
|
||||||
|
const componentPropertyData = {
|
||||||
|
...this.newComponentProperty,
|
||||||
|
component: componentWithId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.componentPropertyService.createComponentProperty(componentPropertyData).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.componentProperties.push(data);
|
||||||
|
this.newComponentProperty = {};
|
||||||
|
this.showAddComponentPropertyForm = false;
|
||||||
|
this.showSuccess('Component property created successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error creating component property:', error);
|
||||||
|
this.showError('Error creating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateComponentProperty(): void {
|
||||||
|
if (!this.selectedComponentProperty || !this.selectedComponentProperty.propertyName) {
|
||||||
|
this.showError('Property name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.updateComponentProperty(this.selectedComponentProperty.id, this.selectedComponentProperty).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
const index = this.componentProperties.findIndex(cp => cp.id === data.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.componentProperties[index] = data;
|
||||||
|
}
|
||||||
|
this.selectedComponentProperty = null;
|
||||||
|
this.showSuccess('Component property updated successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating component property:', error);
|
||||||
|
this.showError('Error updating component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteComponentProperty(id: number): void {
|
||||||
|
if (!confirm('Are you sure you want to delete this component property?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.LOADING;
|
||||||
|
this.componentPropertyService.deleteComponentProperty(id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.componentProperties = this.componentProperties.filter(cp => cp.id !== id);
|
||||||
|
this.showSuccess('Component property deleted successfully');
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting component property:', error);
|
||||||
|
this.showError('Error deleting component property: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.componentPropertyLoadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectComponentPropertyForEdit(componentProperty: ComponentProperty): void {
|
||||||
|
this.selectedComponentProperty = { ...componentProperty };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to view properties for a UI component
|
||||||
|
onViewComponentProperties(uiComponent: UiComponent): void {
|
||||||
|
this.selectedUiComponent = uiComponent;
|
||||||
|
this.loadComponentProperties(uiComponent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
if (this.chartType) {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types/edit', this.chartType.id]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiRequestService } from 'src/app/services/api/api-request.service';
|
||||||
|
|
||||||
|
export interface ChartType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ChartTypeService {
|
||||||
|
private chartTypesUrl = 'api/chart-types';
|
||||||
|
|
||||||
|
constructor(private apiRequest: ApiRequestService) { }
|
||||||
|
|
||||||
|
// Get all chart types
|
||||||
|
getAllChartTypes(): Observable<ChartType[]> {
|
||||||
|
console.log('Fetching all chart types from', this.chartTypesUrl);
|
||||||
|
return this.apiRequest.get(this.chartTypesUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get chart type by ID
|
||||||
|
getChartTypeById(id: number): Observable<ChartType> {
|
||||||
|
const url = `${this.chartTypesUrl}/${id}`;
|
||||||
|
console.log(`Fetching chart type ${id} from ${url}`);
|
||||||
|
return this.apiRequest.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new chart type
|
||||||
|
createChartType(chartType: Partial<ChartType>): Observable<ChartType> {
|
||||||
|
console.log('Creating chart type:', chartType);
|
||||||
|
return this.apiRequest.post(this.chartTypesUrl, chartType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chart type
|
||||||
|
updateChartType(id: number, chartType: ChartType): Observable<ChartType> {
|
||||||
|
const url = `${this.chartTypesUrl}/${id}`;
|
||||||
|
console.log(`Updating chart type ${id}:`, chartType);
|
||||||
|
return this.apiRequest.put(url, chartType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete chart type
|
||||||
|
deleteChartType(id: number): Observable<void> {
|
||||||
|
const url = `${this.chartTypesUrl}/${id}`;
|
||||||
|
console.log(`Deleting chart type ${id}`);
|
||||||
|
return this.apiRequest.delete(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<div class="edit-chart-type-page">
|
||||||
|
<div class="header">
|
||||||
|
<h2>Edit Chart Type</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div *ngIf="loadingState === ClrLoadingState.LOADING" class="loading-spinner">
|
||||||
|
<clr-spinner clrMedium></clr-spinner>
|
||||||
|
<span>Loading chart type...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error and Success Messages -->
|
||||||
|
<div class="alert alert-danger" *ngIf="errorMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="successMessage">
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="successMessage = null">
|
||||||
|
<cds-icon shape="close"></cds-icon>
|
||||||
|
</button>
|
||||||
|
<div class="alert-item">
|
||||||
|
<span class="alert-text">{{ successMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Type Form -->
|
||||||
|
<div class="card" *ngIf="chartType && loadingState !== ClrLoadingState.LOADING">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="chart-type-header">
|
||||||
|
<h3>{{ chartType.name }}</h3>
|
||||||
|
<span class="label" [class.label-success]="chartType.isActive" [class.label-danger]="!chartType.isActive">
|
||||||
|
{{ chartType.isActive ? 'Active' : 'Inactive' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<form clrForm (ngSubmit)="updateChartType()" #editChartTypeForm="ngForm">
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Name <span class="required">*</span></label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.name" name="chartTypeName" required />
|
||||||
|
<clr-control-helper>Enter a unique name for the chart type (e.g., "line-chart", "bar-chart")</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-input-container>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input clrInput type="text" [(ngModel)]="chartType.displayName" name="chartTypeDisplayName" />
|
||||||
|
<clr-control-helper>This is the user-friendly name shown in the UI</clr-control-helper>
|
||||||
|
</clr-input-container>
|
||||||
|
|
||||||
|
<clr-textarea-container>
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea clrTextarea [(ngModel)]="chartType.description" name="chartTypeDescription" rows="3"></textarea>
|
||||||
|
<clr-control-helper>Provide a detailed description of this chart type and when to use it</clr-control-helper>
|
||||||
|
</clr-textarea-container>
|
||||||
|
|
||||||
|
<clr-checkbox-container>
|
||||||
|
<label>Is Active</label>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox [(ngModel)]="chartType.isActive" name="chartTypeIsActive" />
|
||||||
|
<label>Active</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<clr-control-helper>Deactivate chart types that should not be available for selection</clr-control-helper>
|
||||||
|
</clr-checkbox-container>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" [disabled]="!chartType.name || loadingState === ClrLoadingState.LOADING">
|
||||||
|
<clr-spinner *ngIf="loadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
|
||||||
|
Update Chart Type
|
||||||
|
</button>
|
||||||
|
<button class="btn" type="button" (click)="onCancel()">Cancel</button>
|
||||||
|
<button class="btn btn-danger" type="button" (click)="onDelete()" style="margin-left: auto;">
|
||||||
|
<cds-icon shape="trash"></cds-icon> Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Related Entities Management -->
|
||||||
|
<div class="card" *ngIf="chartType && loadingState !== ClrLoadingState.LOADING">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Related Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<div class="related-entities-grid">
|
||||||
|
<div class="entity-card">
|
||||||
|
<div class="entity-header">
|
||||||
|
<cds-icon shape="view-list" size="24"></cds-icon>
|
||||||
|
<h4>UI Components</h4>
|
||||||
|
</div>
|
||||||
|
<p>Manage the UI components that define the configuration form for this chart type.</p>
|
||||||
|
<button class="btn btn-sm btn-link" [routerLink]="['/cns-portal/dashboardbuilder/chart-types', chartType.id, 'ui-components']">
|
||||||
|
Manage UI Components
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="entity-card">
|
||||||
|
<div class="entity-header">
|
||||||
|
<cds-icon shape="template" size="24"></cds-icon>
|
||||||
|
<h4>Chart Templates</h4>
|
||||||
|
</div>
|
||||||
|
<p>Manage the templates that define how this chart type is rendered.</p>
|
||||||
|
<button class="btn btn-sm btn-link" [routerLink]="['/cns-portal/dashboardbuilder/chart-types', chartType.id, 'templates']">
|
||||||
|
Manage Templates
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="entity-card">
|
||||||
|
<div class="entity-header">
|
||||||
|
<cds-icon shape="form" size="24"></cds-icon>
|
||||||
|
<h4>Dynamic Fields</h4>
|
||||||
|
</div>
|
||||||
|
<p>Manage the dynamic fields that capture specific configuration parameters.</p>
|
||||||
|
<button class="btn btn-sm btn-link" [routerLink]="['/cns-portal/dashboardbuilder/chart-types', chartType.id, 'fields']">
|
||||||
|
Manage Fields
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Type Details -->
|
||||||
|
<div class="card" *ngIf="chartType && loadingState !== ClrLoadingState.LOADING">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Chart Type Details</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-block">
|
||||||
|
<div class="details-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>ID:</label>
|
||||||
|
<span>{{ chartType.id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Created At:</label>
|
||||||
|
<span>{{ chartType.createdAt ? (chartType.createdAt | date:'medium') : 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<label>Updated At:</label>
|
||||||
|
<span>{{ chartType.updatedAt ? (chartType.updatedAt | date:'medium') : 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h4>About Chart Types</h4>
|
||||||
|
<p>Chart types define the different visualization options available in the dashboard builder. Each chart type can have:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Associated UI components that define the configuration form</li>
|
||||||
|
<li>Templates that define how the chart is rendered</li>
|
||||||
|
<li>Dynamic fields that capture specific configuration parameters</li>
|
||||||
|
</ul>
|
||||||
|
<p>After creating a chart type, you can configure its components, templates, and fields using the management links above.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
.edit-chart-type-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
h2 {
|
||||||
|
color: #0079b8;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
.chart-type-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-entities-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.entity-card {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
|
||||||
|
.entity-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
cds-icon {
|
||||||
|
color: #0079b8;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border-left: 4px solid #0079b8;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #0079b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-type-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-entities-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ClrLoadingState } from '@clr/angular';
|
||||||
|
import { ChartType, ChartTypeService } from './chart-type.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-chart-type',
|
||||||
|
templateUrl: './edit-chart-type.component.html',
|
||||||
|
styleUrls: ['./edit-chart-type.component.scss']
|
||||||
|
})
|
||||||
|
export class EditChartTypeComponent implements OnInit {
|
||||||
|
chartType: ChartType | null = null;
|
||||||
|
loadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
successMessage: string | null = null;
|
||||||
|
|
||||||
|
// Make ClrLoadingState available to template
|
||||||
|
readonly ClrLoadingState = ClrLoadingState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartTypeService: ChartTypeService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const id = Number(this.route.snapshot.paramMap.get('id'));
|
||||||
|
if (id) {
|
||||||
|
this.loadChartType(id);
|
||||||
|
} else {
|
||||||
|
this.showError('Invalid chart type ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
private showError(message: string): void {
|
||||||
|
this.errorMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.errorMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
private showSuccess(message: string): void {
|
||||||
|
this.successMessage = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartType(id: number): void {
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.getChartTypeById(id).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
// Process the data to ensure dates are properly formatted
|
||||||
|
this.chartType = {
|
||||||
|
...data,
|
||||||
|
createdAt: this.formatDate(data.createdAt),
|
||||||
|
updatedAt: this.formatDate(data.updatedAt)
|
||||||
|
};
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart type:', error);
|
||||||
|
this.showError('Error loading chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format date to handle both string and object formats
|
||||||
|
private formatDate(date: any): string {
|
||||||
|
if (!date) return '';
|
||||||
|
|
||||||
|
// If it's already a string, return as is
|
||||||
|
if (typeof date === 'string') {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's an object, try to convert to string
|
||||||
|
if (typeof date === 'object') {
|
||||||
|
// Handle various date object formats
|
||||||
|
if (date instanceof Date) {
|
||||||
|
return date.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ISO string within object
|
||||||
|
if (date.date) {
|
||||||
|
return date.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other object formats
|
||||||
|
try {
|
||||||
|
return new Date(date).toISOString();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateChartType(): void {
|
||||||
|
if (!this.chartType || !this.chartType.name) {
|
||||||
|
this.showError('Chart type name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.updateChartType(this.chartType.id, this.chartType).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
// Process the data to ensure dates are properly formatted
|
||||||
|
this.chartType = {
|
||||||
|
...data,
|
||||||
|
createdAt: this.formatDate(data.createdAt),
|
||||||
|
updatedAt: this.formatDate(data.updatedAt)
|
||||||
|
};
|
||||||
|
this.showSuccess('Chart type updated successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
// Redirect to chart types list after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error updating chart type:', error);
|
||||||
|
this.showError('Error updating chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelete(): void {
|
||||||
|
if (!this.chartType) return;
|
||||||
|
|
||||||
|
if (confirm('Are you sure you want to delete this chart type? This action cannot be undone.')) {
|
||||||
|
this.loadingState = ClrLoadingState.LOADING;
|
||||||
|
this.chartTypeService.deleteChartType(this.chartType.id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.showSuccess('Chart type deleted successfully');
|
||||||
|
this.loadingState = ClrLoadingState.SUCCESS;
|
||||||
|
// Redirect to chart types list after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.router.navigate(['/cns-portal/dashboardbuilder/chart-types']);
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error deleting chart type:', error);
|
||||||
|
this.showError('Error deleting chart type: ' + (error.error?.message || error.message || 'Unknown error'));
|
||||||
|
this.loadingState = ClrLoadingState.ERROR;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,7 +79,12 @@
|
|||||||
|
|
||||||
<!-- Multi-Select Filter -->
|
<!-- Multi-Select Filter -->
|
||||||
<div class="filter-control" *ngIf="filterType === 'multiselect'">
|
<div class="filter-control" *ngIf="filterType === 'multiselect'">
|
||||||
<div class="compact-multiselect-checkboxes" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
|
<div class="compact-multiselect-display" (click)="toggleMultiselectDropdown()" style="padding: 5px; border: 1px solid #ddd; cursor: pointer; background-color: #f8f8f8;">
|
||||||
|
<span *ngIf="filterValue && filterValue.length > 0">{{ filterValue.length }} selected</span>
|
||||||
|
<span *ngIf="!filterValue || filterValue.length === 0">{{ filterLabel || filterKey || 'Select options' }}</span>
|
||||||
|
<clr-icon shape="caret down" style="float: right; margin-top: 3px;"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<div class="compact-multiselect-dropdown" *ngIf="showMultiselectDropdown" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-top: none; padding: 10px; background-color: white; position: absolute; z-index: 1000; width: calc(100% - 2px); box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||||
<div *ngFor="let option of filterOptions" class="clr-checkbox-wrapper" style="margin-bottom: 5px;">
|
<div *ngFor="let option of filterOptions" class="clr-checkbox-wrapper" style="margin-bottom: 5px;">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
[id]="'multiselect-' + option"
|
[id]="'multiselect-' + option"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
|
||||||
import { FilterService, Filter } from './filter.service';
|
import { FilterService, Filter } from './filter.service';
|
||||||
import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import { AlertsService } from 'src/app/services/fnd/alerts.service';
|
|||||||
templateUrl: './compact-filter.component.html',
|
templateUrl: './compact-filter.component.html',
|
||||||
styleUrls: ['./compact-filter.component.scss']
|
styleUrls: ['./compact-filter.component.scss']
|
||||||
})
|
})
|
||||||
export class CompactFilterComponent implements OnInit, OnChanges {
|
export class CompactFilterComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() filterKey: string = '';
|
@Input() filterKey: string = '';
|
||||||
@Input() filterType: string = 'text';
|
@Input() filterType: string = 'text';
|
||||||
@Input() filterOptions: string[] = [];
|
@Input() filterOptions: string[] = [];
|
||||||
@@ -23,6 +23,9 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
availableKeys: string[] = [];
|
availableKeys: string[] = [];
|
||||||
availableValues: string[] = [];
|
availableValues: string[] = [];
|
||||||
|
|
||||||
|
// Multiselect dropdown state
|
||||||
|
showMultiselectDropdown: boolean = false;
|
||||||
|
|
||||||
// Configuration properties
|
// Configuration properties
|
||||||
isConfigMode: boolean = false;
|
isConfigMode: boolean = false;
|
||||||
configFilterKey: string = '';
|
configFilterKey: string = '';
|
||||||
@@ -73,6 +76,24 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
// If filterKey changes, clear the previous filter value and remove old filter from service
|
||||||
|
if (changes.filterKey) {
|
||||||
|
// Clear the previous filter value
|
||||||
|
this.filterValue = '';
|
||||||
|
|
||||||
|
// Clear filter options
|
||||||
|
this.filterOptions = [];
|
||||||
|
|
||||||
|
// Clear available values
|
||||||
|
this.availableValues = [];
|
||||||
|
|
||||||
|
// If we had a previous selected filter, clear its value in the service
|
||||||
|
if (this.selectedFilter && changes.filterKey.previousValue) {
|
||||||
|
const oldFilterId = changes.filterKey.previousValue;
|
||||||
|
this.filterService.updateFilterValue(oldFilterId, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If filterKey or filterType changes, re-register the filter
|
// If filterKey or filterType changes, re-register the filter
|
||||||
if (changes.filterKey || changes.filterType) {
|
if (changes.filterKey || changes.filterType) {
|
||||||
// Load available values for the current filter key if it's a dropdown or multiselect
|
// Load available values for the current filter key if it's a dropdown or multiselect
|
||||||
@@ -201,6 +222,14 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
this.onFilterValueChange(dateRange);
|
this.onFilterValueChange(dateRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
// Component cleanup - remove this filter from the filter service
|
||||||
|
if (this.selectedFilter) {
|
||||||
|
// Use the proper removeFilter method which handles both filter definition and state
|
||||||
|
this.filterService.removeFilter(this.selectedFilter.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load available keys from API
|
// Load available keys from API
|
||||||
loadAvailableKeys(): void {
|
loadAvailableKeys(): void {
|
||||||
if (this.apiUrl) {
|
if (this.apiUrl) {
|
||||||
@@ -278,6 +307,9 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
this.apiUrl = config.apiUrl;
|
this.apiUrl = config.apiUrl;
|
||||||
this.connectionId = config.connectionId;
|
this.connectionId = config.connectionId;
|
||||||
|
|
||||||
|
// Clear filter value when changing configuration
|
||||||
|
this.filterValue = '';
|
||||||
|
|
||||||
// Load available keys if API URL is provided
|
// Load available keys if API URL is provided
|
||||||
if (this.apiUrl) {
|
if (this.apiUrl) {
|
||||||
this.loadAvailableKeys();
|
this.loadAvailableKeys();
|
||||||
@@ -304,11 +336,23 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
// Handle filter key change in configuration
|
// Handle filter key change in configuration
|
||||||
onFilterKeyChange(key: string): void {
|
onFilterKeyChange(key: string): void {
|
||||||
|
// Clear the previous filter value when changing keys
|
||||||
|
this.filterValue = '';
|
||||||
|
|
||||||
|
// Clear filter options until new values are loaded
|
||||||
|
this.filterOptions = [];
|
||||||
|
|
||||||
this.configFilterKey = key;
|
this.configFilterKey = key;
|
||||||
|
|
||||||
// Load available values for the selected key if it's a dropdown or multiselect
|
// Load available values for the selected key if it's a dropdown or multiselect
|
||||||
if ((this.configFilterType === 'dropdown' || this.configFilterType === 'multiselect') && key) {
|
if ((this.configFilterType === 'dropdown' || this.configFilterType === 'multiselect') && key) {
|
||||||
this.loadAvailableValues(key);
|
this.loadAvailableValues(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the filter service value for the previous filter key
|
||||||
|
if (this.selectedFilter) {
|
||||||
|
this.filterService.updateFilterValue(this.selectedFilter.id, '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle API URL change in configuration
|
// Handle API URL change in configuration
|
||||||
@@ -375,4 +419,23 @@ export class CompactFilterComponent implements OnInit, OnChanges {
|
|||||||
// Emit the change event
|
// Emit the change event
|
||||||
this.onFilterValueChange(this.filterValue);
|
this.onFilterValueChange(this.filterValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add method to toggle multiselect dropdown visibility
|
||||||
|
toggleMultiselectDropdown(): void {
|
||||||
|
this.showMultiselectDropdown = !this.showMultiselectDropdown;
|
||||||
|
|
||||||
|
// Add document click handler to close dropdown when clicking outside
|
||||||
|
if (this.showMultiselectDropdown) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (!target.closest('.compact-multiselect-display') && !target.closest('.compact-multiselect-dropdown')) {
|
||||||
|
this.showMultiselectDropdown = false;
|
||||||
|
document.removeEventListener('click', handleClick);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', handleClick);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="chart-config-modal">
|
||||||
|
<h3>Chart Configuration Manager</h3>
|
||||||
|
<app-chart-config-manager></app-chart-config-manager>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.chart-config-modal {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-config-modal',
|
||||||
|
templateUrl: './chart-config-modal.component.html',
|
||||||
|
styleUrls: ['./chart-config-modal.component.scss']
|
||||||
|
})
|
||||||
|
export class ChartConfigModalComponent {
|
||||||
|
// This component will be used to display the chart configuration manager in a modal
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
</ol> -->
|
</ol> -->
|
||||||
|
|
||||||
<div style="display: inline;">
|
<div style="display: inline;">
|
||||||
<button class="btn componentbtn" (click)="toggleMenu()"><clr-icon shape="plus"></clr-icon>component</button>
|
<button class="btn componentbtn" (click)="toggleMenu()" *ngIf="!fromRunner"><clr-icon shape="plus"></clr-icon>component</button>
|
||||||
<button class="btn btn-primary" (click)="openCommonFilterModal()" style="margin-left: 10px;">
|
<button class="btn btn-primary" (click)="openCommonFilterModal()" style="margin-left: 10px;" *ngIf="!fromRunner">
|
||||||
<clr-icon shape="filter"></clr-icon> Common Filter
|
<clr-icon shape="filter"></clr-icon> Common Filter
|
||||||
</button>
|
</button>
|
||||||
<div style="display: inline;">
|
<div style="display: inline;">
|
||||||
@@ -17,12 +17,15 @@
|
|||||||
<div style="display: inline; float: right;">
|
<div style="display: inline; float: right;">
|
||||||
<!-- <button class="btn btn-primary">Build</button>
|
<!-- <button class="btn btn-primary">Build</button>
|
||||||
<button class="btn btn-primary" (click)="onSchedule()">Schedule</button> -->
|
<button class="btn btn-primary" (click)="onSchedule()">Schedule</button> -->
|
||||||
|
<button class="btn btn-success" (click)="testDynamicChartCreation()" style="margin-left: 10px;" *ngIf="!fromRunner">
|
||||||
|
<clr-icon shape="test"></clr-icon> Test Dynamic Chart
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<nav class="sidenav" *ngIf="toggle" style="width: 16%;">
|
<nav class="sidenav" *ngIf="toggle && !fromRunner" style="width: 16%;">
|
||||||
<ul class="nav-list" style="list-style-type: none;">
|
<ul class="nav-list" style="list-style-type: none;">
|
||||||
<li *ngFor="let widget of WidgetsMock">
|
<li *ngFor="let widget of WidgetsMock">
|
||||||
|
|
||||||
@@ -42,14 +45,14 @@
|
|||||||
<gridster [options]="options" (drop)="onDrop($event)" style="background-color: transparent;">
|
<gridster [options]="options" (drop)="onDrop($event)" style="background-color: transparent;">
|
||||||
<gridster-item [item]="item" *ngFor="let item of dashboardArray">
|
<gridster-item [item]="item" *ngFor="let item of dashboardArray">
|
||||||
<!-- <ng-container *ngIf="addToDashboard && item.addToDashboard"> -->
|
<!-- <ng-container *ngIf="addToDashboard && item.addToDashboard"> -->
|
||||||
<button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)">
|
<button class="btn btn-icon btn-danger" style="margin-left: 10px; margin-top: 10px;" (click)="removeItem(item)" *ngIf="!fromRunner">
|
||||||
<clr-icon shape="trash"></clr-icon>
|
<clr-icon shape="trash"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-icon drag-handler" style="margin-left: 10px; margin-top: 10px;">
|
<button class="btn btn-icon drag-handler" style="margin-left: 10px; margin-top: 10px;" *ngIf="!fromRunner">
|
||||||
<clr-icon shape="drag-handle"></clr-icon>
|
<clr-icon shape="drag-handle"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-icon" style="margin-top: 10px; float: right;">
|
<button class="btn btn-icon" style="margin-top: 10px; float: right;" *ngIf="!fromRunner">
|
||||||
<input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch"
|
<input type="checkbox" clrToggle [(ngModel)]="item.addToDashboard" name="addToDashboardSwitch"
|
||||||
(change)="toggleAddToDashboard(item)" />
|
(change)="toggleAddToDashboard(item)" />
|
||||||
</button>
|
</button>
|
||||||
@@ -57,7 +60,7 @@
|
|||||||
<!-- <label for="workflow_name">Add to Dasboard</label>
|
<!-- <label for="workflow_name">Add to Dasboard</label>
|
||||||
<input class="btn btn-icon" style="margin-top: 10px;float: right;" type="checkbox" clrToggle value="billable" name="billable" />
|
<input class="btn btn-icon" style="margin-top: 10px;float: right;" type="checkbox" clrToggle value="billable" name="billable" />
|
||||||
-->
|
-->
|
||||||
<button class="btn btn-icon" style="margin-top: 10px;float: right;" (click)="editGadget(item)">
|
<button class="btn btn-icon" style="margin-top: 10px;float: right;" (click)="editGadget(item)" *ngIf="!fromRunner">
|
||||||
<clr-icon shape="pencil"></clr-icon>
|
<clr-icon shape="pencil"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -72,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<button class="btn btn-outline" (click)="goBack()">Back</button>
|
<button class="btn btn-outline" (click)="goBack()">Back</button>
|
||||||
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()">
|
<button type="submit" class="btn btn-primary btn-adddata " (click)="UpdateLine()" *ngIf="!fromRunner">
|
||||||
<b>Update</b>
|
<b>Update</b>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ import { SureconnectService } from '../sureconnect/sureconnect.service';
|
|||||||
import { CommonFilterComponent } from '../common-filter/common-filter.component';
|
import { CommonFilterComponent } from '../common-filter/common-filter.component';
|
||||||
// Add the CompactFilterComponent import
|
// Add the CompactFilterComponent import
|
||||||
import { CompactFilterComponent } from '../common-filter';
|
import { CompactFilterComponent } from '../common-filter';
|
||||||
|
// Add the FilterService import
|
||||||
|
import { FilterService } from '../common-filter/filter.service';
|
||||||
|
// Add the UnifiedChartComponent import
|
||||||
|
import { UnifiedChartComponent } from '../gadgets/unified-chart';
|
||||||
|
// Add the DynamicChartLoaderService import
|
||||||
|
import { DynamicChartLoaderService } from '../chart-config/dynamic-chart-loader.service';
|
||||||
|
|
||||||
function isNullArray(arr) {
|
function isNullArray(arr) {
|
||||||
return !Array.isArray(arr) || arr.length === 0;
|
return !Array.isArray(arr) || arr.length === 0;
|
||||||
@@ -54,6 +60,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
// Add availableKeys property for compact filter
|
// Add availableKeys property for compact filter
|
||||||
availableKeys: string[] = [];
|
availableKeys: string[] = [];
|
||||||
|
|
||||||
|
// Initialize with default widgets and update dynamically
|
||||||
WidgetsMock: WidgetModel[] = [
|
WidgetsMock: WidgetModel[] = [
|
||||||
{
|
{
|
||||||
name: 'Common Filter',
|
name: 'Common Filter',
|
||||||
@@ -71,26 +78,26 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
name: 'Line Chart',
|
name: 'Line Chart',
|
||||||
identifier: 'line_chart'
|
identifier: 'line_chart'
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
name: 'Bar Chart',
|
// name: 'Bar Chart',
|
||||||
identifier: 'bar_chart'
|
// identifier: 'bar_chart'
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Pie Chart',
|
// name: 'Pie Chart',
|
||||||
identifier: 'pie_chart'
|
// identifier: 'pie_chart'
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Polar Area Chart',
|
// name: 'Polar Area Chart',
|
||||||
identifier: 'polar_area_chart'
|
// identifier: 'polar_area_chart'
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Bubble Chart',
|
// name: 'Bubble Chart',
|
||||||
identifier: 'bubble_chart'
|
// identifier: 'bubble_chart'
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Scatter Chart',
|
// name: 'Scatter Chart',
|
||||||
identifier: 'scatter_chart'
|
// identifier: 'scatter_chart'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: 'Dynamic Chart',
|
name: 'Dynamic Chart',
|
||||||
identifier: 'dynamic_chart'
|
identifier: 'dynamic_chart'
|
||||||
@@ -110,6 +117,10 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
name: 'Compact Filter',
|
name: 'Compact Filter',
|
||||||
identifier: 'compact_filter'
|
identifier: 'compact_filter'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Unified Chart',
|
||||||
|
identifier: 'unified_chart'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -123,19 +134,20 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
|
|
||||||
protected componentCollection = [
|
protected componentCollection = [
|
||||||
{ name: "Common Filter", componentInstance: CommonFilterComponent },
|
{ name: "Common Filter", componentInstance: CommonFilterComponent },
|
||||||
{ name: "Line Chart", componentInstance: LineChartComponent },
|
{ name: "Line Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Doughnut Chart", componentInstance: DoughnutChartComponent },
|
{ name: "Doughnut Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Radar Chart", componentInstance: RadarChartComponent },
|
{ name: "Radar Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Bar Chart", componentInstance: BarChartComponent },
|
{ name: "Bar Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Pie Chart", componentInstance: PieChartComponent },
|
{ name: "Pie Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Polar Area Chart", componentInstance: PolarChartComponent },
|
{ name: "Polar Area Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Bubble Chart", componentInstance: BubbleChartComponent },
|
{ name: "Bubble Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Scatter Chart", componentInstance: ScatterChartComponent },
|
{ name: "Scatter Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Dynamic Chart", componentInstance: DynamicChartComponent },
|
{ name: "Dynamic Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "Financial Chart", componentInstance: FinancialChartComponent },
|
{ name: "Financial Chart", componentInstance: UnifiedChartComponent },
|
||||||
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
|
{ name: "To Do Chart", componentInstance: ToDoChartComponent },
|
||||||
{ name: "Grid View", componentInstance: GridViewComponent },
|
{ name: "Grid View", componentInstance: GridViewComponent },
|
||||||
{ name: "Compact Filter", componentInstance: CompactFilterComponent }, // Add this line
|
{ name: "Compact Filter", componentInstance: CompactFilterComponent },
|
||||||
|
{ name: "Unified Chart", componentInstance: UnifiedChartComponent },
|
||||||
];
|
];
|
||||||
model: any;
|
model: any;
|
||||||
linesdata: any;
|
linesdata: any;
|
||||||
@@ -168,6 +180,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
yAxis: '',
|
yAxis: '',
|
||||||
xAxis: '',
|
xAxis: '',
|
||||||
connection: '', // Add connection field
|
connection: '', // Add connection field
|
||||||
|
chartType: '', // Add chartType field
|
||||||
// Drilldown configuration properties (base level)
|
// Drilldown configuration properties (base level)
|
||||||
drilldownEnabled: false,
|
drilldownEnabled: false,
|
||||||
drilldownApiUrl: '',
|
drilldownApiUrl: '',
|
||||||
@@ -196,6 +209,9 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
// Add drilldown column data property
|
// Add drilldown column data property
|
||||||
drilldownColumnData = []; // Add drilldown column data property
|
drilldownColumnData = []; // Add drilldown column data property
|
||||||
|
|
||||||
|
// Add chart types property for dynamic chart selection
|
||||||
|
chartTypes: any[] = [];
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private dashboardService: Dashboard3Service,
|
private dashboardService: Dashboard3Service,
|
||||||
@@ -203,9 +219,16 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
private _fb: FormBuilder,
|
private _fb: FormBuilder,
|
||||||
private datastoreService: DatastoreService,
|
private datastoreService: DatastoreService,
|
||||||
private alertService: AlertsService,
|
private alertService: AlertsService,
|
||||||
private sureconnectService: SureconnectService) { } // Add SureconnectService to constructor
|
private sureconnectService: SureconnectService,
|
||||||
|
private filterService: FilterService,
|
||||||
|
private dynamicChartLoader: DynamicChartLoaderService) { } // Add SureconnectService, FilterService, and DynamicChartLoaderService to constructor
|
||||||
|
|
||||||
|
// Add property to track if coming from dashboard runner
|
||||||
|
fromRunner: boolean = false;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// Reset the filter service when the component is initialized
|
||||||
|
this.filterService.resetFilters();
|
||||||
|
|
||||||
// Grid options
|
// Grid options
|
||||||
this.options = {
|
this.options = {
|
||||||
@@ -231,6 +254,13 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
itemResizeCallback: this.itemResize.bind(this)
|
itemResizeCallback: this.itemResize.bind(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if coming from dashboard runner
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['fromRunner'] === 'true') {
|
||||||
|
this.fromRunner = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.editId = this.route.snapshot.params.id;
|
this.editId = this.route.snapshot.params.id;
|
||||||
console.log(this.editId);
|
console.log(this.editId);
|
||||||
this.dashboardService.getById(this.editId).subscribe((data) => {
|
this.dashboardService.getById(this.editId).subscribe((data) => {
|
||||||
@@ -276,6 +306,9 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
apiUrl: ['']
|
apiUrl: ['']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load chart types for dynamic chart selection
|
||||||
|
this.loadChartTypesForSelection();
|
||||||
|
|
||||||
// Load sureconnect data first, then load dashboard data
|
// Load sureconnect data first, then load dashboard data
|
||||||
this.loadSureconnectData();
|
this.loadSureconnectData();
|
||||||
|
|
||||||
@@ -283,6 +316,37 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
this.loadCommonFilterData();
|
this.loadCommonFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add method to load all chart types for dynamic selection
|
||||||
|
loadChartTypesForSelection() {
|
||||||
|
console.log('Loading chart types for selection');
|
||||||
|
this.dynamicChartLoader.loadActiveChartTypes().subscribe({
|
||||||
|
next: (chartTypes) => {
|
||||||
|
console.log('Loaded chart types for selection:', chartTypes);
|
||||||
|
this.chartTypes = chartTypes;
|
||||||
|
|
||||||
|
// Convert each chart type to a WidgetModel
|
||||||
|
const newWidgets = chartTypes.map(ct => ({
|
||||||
|
name: ct.displayName || ct.name,
|
||||||
|
// identifier: ct.name.toLowerCase().replace(/\s+/g, '_')
|
||||||
|
identifier: `${ct.name.toLowerCase().replace(/\s+/g, '_')}_chart`
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Filter out duplicates by identifier
|
||||||
|
const existingIds = new Set(this.WidgetsMock.map(w => w.identifier));
|
||||||
|
const uniqueNewWidgets = newWidgets.filter(w => !existingIds.has(w.identifier));
|
||||||
|
|
||||||
|
// Append unique new widgets to WidgetsMock
|
||||||
|
this.WidgetsMock = [...this.WidgetsMock, ...uniqueNewWidgets];
|
||||||
|
|
||||||
|
console.log('Updated WidgetsMock:', this.WidgetsMock);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart types for selection:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add method to load sureconnect data
|
// Add method to load sureconnect data
|
||||||
loadSureconnectData() {
|
loadSureconnectData() {
|
||||||
this.sureconnectService.getAll().subscribe((data: any[]) => {
|
this.sureconnectService.getAll().subscribe((data: any[]) => {
|
||||||
@@ -317,6 +381,9 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
dashboardLine: any;
|
dashboardLine: any;
|
||||||
dashboardName: any;
|
dashboardName: any;
|
||||||
getData() {
|
getData() {
|
||||||
|
// Reset the filter service when switching between dashboard records
|
||||||
|
this.filterService.resetFilters();
|
||||||
|
|
||||||
// We get the id in get current router dashboard/:id
|
// We get the id in get current router dashboard/:id
|
||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
// + is used to cast string to int
|
// + is used to cast string to int
|
||||||
@@ -367,6 +434,27 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map chart names to unified chart types
|
||||||
|
const chartTypeMap = {
|
||||||
|
'Radar Chart': 'radar',
|
||||||
|
'Line Chart': 'line',
|
||||||
|
'Doughnut Chart': 'doughnut',
|
||||||
|
'Bar Chart': 'bar',
|
||||||
|
'Pie Chart': 'pie',
|
||||||
|
'Polar Area Chart': 'polar',
|
||||||
|
'Bubble Chart': 'bubble',
|
||||||
|
'Scatter Chart': 'scatter',
|
||||||
|
'Dynamic Chart': 'line',
|
||||||
|
'Financial Chart': 'line'
|
||||||
|
};
|
||||||
|
|
||||||
|
// If this is a chart, set the chartType property
|
||||||
|
if (chartTypeMap.hasOwnProperty(dashboard.name)) {
|
||||||
|
dashboard.chartType = chartTypeMap[dashboard.name];
|
||||||
|
// Keep the original name instead of changing it to "Unified Chart"
|
||||||
|
// dashboard.name = "Unified Chart";
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure compact filter configuration properties are properly initialized
|
// Ensure compact filter configuration properties are properly initialized
|
||||||
if (dashboard.component === 'Compact Filter' || dashboard.name === 'Compact Filter') {
|
if (dashboard.component === 'Compact Filter' || dashboard.name === 'Compact Filter') {
|
||||||
// Make sure all compact filter properties exist
|
// Make sure all compact filter properties exist
|
||||||
@@ -391,6 +479,30 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map unified chart types back to chart names for serialization
|
||||||
|
const chartNameMap = {
|
||||||
|
'radar': 'Radar Chart',
|
||||||
|
'line': 'Line Chart',
|
||||||
|
'doughnut': 'Doughnut Chart',
|
||||||
|
'bar': 'Bar Chart',
|
||||||
|
'pie': 'Pie Chart',
|
||||||
|
'polar': 'Polar Area Chart',
|
||||||
|
'bubble': 'Bubble Chart',
|
||||||
|
'scatter': 'Scatter Chart'
|
||||||
|
// Removed hardcoded heatmap entry to make it fully dynamic
|
||||||
|
};
|
||||||
|
|
||||||
|
// If this is a unified chart, set the name back to the appropriate chart name
|
||||||
|
if (dashboard.name === 'Unified Chart' && dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType)) {
|
||||||
|
dashboard.name = chartNameMap[dashboard.chartType];
|
||||||
|
}
|
||||||
|
// Also handle the case where the chart already has the correct name
|
||||||
|
else if (dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType) &&
|
||||||
|
dashboard.name === chartNameMap[dashboard.chartType]) {
|
||||||
|
// The name is already correct, no need to change it
|
||||||
|
dashboard.component = "Unified Chart";
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure compact filter configuration properties are preserved
|
// Ensure compact filter configuration properties are preserved
|
||||||
if (dashboard.name === 'Compact Filter') {
|
if (dashboard.name === 'Compact Filter') {
|
||||||
// Make sure all compact filter properties exist
|
// Make sure all compact filter properties exist
|
||||||
@@ -402,6 +514,7 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add method to get available fields for a filter dropdown (excluding already selected fields)
|
// Add method to get available fields for a filter dropdown (excluding already selected fields)
|
||||||
getAvailableFields(filters: any[], currentIndex: number, allFields: string[]): string[] {
|
getAvailableFields(filters: any[], currentIndex: number, allFields: string[]): string[] {
|
||||||
if (!filters || !allFields) {
|
if (!filters || !allFields) {
|
||||||
@@ -433,110 +546,44 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
|
//this._ds.updateDashboard(this.dashboardId, parsed).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(ev) {
|
onDrop = (ev) => {
|
||||||
|
console.log("on drop event ", ev);
|
||||||
const componentType = ev.dataTransfer.getData("widgetIdentifier");
|
const componentType = ev.dataTransfer.getData("widgetIdentifier");
|
||||||
let maxChartId = this.dashboardArray?.reduce((maxId, item) => Math.max(maxId, item.chartid), 0);
|
// Safely calculate maxChartId, handling cases where chartid might be NaN or missing
|
||||||
|
console.log("on drop ", componentType);
|
||||||
|
let maxChartId = 0;
|
||||||
|
if (this.dashboardArray && this.dashboardArray.length > 0) {
|
||||||
|
const validChartIds = this.dashboardArray
|
||||||
|
.map(item => item.chartid)
|
||||||
|
.filter(chartid => typeof chartid === 'number' && !isNaN(chartid));
|
||||||
|
|
||||||
|
if (validChartIds.length > 0) {
|
||||||
|
maxChartId = Math.max(...validChartIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
switch (componentType) {
|
switch (componentType) {
|
||||||
|
// Handle all chart types by converting them to unified charts
|
||||||
case "radar_chart":
|
case "radar_chart":
|
||||||
return this.dashboardArray.push({
|
// Use dynamic chart creation for all chart types
|
||||||
cols: 5,
|
return this.createDynamicChart('radar', maxChartId);
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: RadarChartComponent,
|
|
||||||
name: "Radar Chart"
|
|
||||||
});
|
|
||||||
case "line_chart":
|
case "line_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('line', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 7,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: LineChartComponent,
|
|
||||||
name: "Line Chart"
|
|
||||||
});
|
|
||||||
case "doughnut_chart":
|
case "doughnut_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('doughnut', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: DoughnutChartComponent,
|
|
||||||
name: "Doughnut Chart"
|
|
||||||
});
|
|
||||||
case "bar_chart":
|
case "bar_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('bar', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: BarChartComponent,
|
|
||||||
name: "Bar Chart"
|
|
||||||
});
|
|
||||||
case "pie_chart":
|
case "pie_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('pie', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: PieChartComponent,
|
|
||||||
name: "Pie Chart"
|
|
||||||
});
|
|
||||||
case "polar_area_chart":
|
case "polar_area_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('polar', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: PolarChartComponent,
|
|
||||||
name: "Polar Area Chart"
|
|
||||||
});
|
|
||||||
case "bubble_chart":
|
case "bubble_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('bubble', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: BubbleChartComponent,
|
|
||||||
name: "Bubble Chart"
|
|
||||||
});
|
|
||||||
case "scatter_chart":
|
case "scatter_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('scatter', maxChartId);
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: ScatterChartComponent,
|
|
||||||
name: "Scatter Chart"
|
|
||||||
});
|
|
||||||
case "dynamic_chart":
|
case "dynamic_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('line', maxChartId); // Default to line for dynamic chart
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: DynamicChartComponent,
|
|
||||||
name: "Dynamic Chart"
|
|
||||||
});
|
|
||||||
case "financial_chart":
|
case "financial_chart":
|
||||||
return this.dashboardArray.push({
|
return this.createDynamicChart('line', maxChartId); // Default to line for financial chart
|
||||||
cols: 5,
|
|
||||||
rows: 6,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
chartid: maxChartId + 1,
|
|
||||||
component: FinancialChartComponent,
|
|
||||||
name: "Financial Chart"
|
|
||||||
});
|
|
||||||
case "to_do_chart":
|
case "to_do_chart":
|
||||||
return this.dashboardArray.push({
|
return this.dashboardArray.push({
|
||||||
cols: 5,
|
cols: 5,
|
||||||
@@ -582,6 +629,17 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
component: GridViewComponent,
|
component: GridViewComponent,
|
||||||
name: "Grid View"
|
name: "Grid View"
|
||||||
});
|
});
|
||||||
|
case "unified_chart":
|
||||||
|
return this.createDynamicChart('bar', maxChartId); // Default to bar for unified chart
|
||||||
|
default:
|
||||||
|
// Handle any other chart types dynamically
|
||||||
|
// Extract chart type name from identifier (e.g., "heatmap_chart" -> "heatmap")
|
||||||
|
const chartTypeName = componentType.replace('_chart', '');
|
||||||
|
console.log('Creating dynamic chart of type:', chartTypeName);
|
||||||
|
console.log('Display name for chart:', this.getChartDisplayName(chartTypeName));
|
||||||
|
|
||||||
|
// Use dynamic chart creation for all chart types
|
||||||
|
return this.createDynamicChart(chartTypeName, maxChartId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeItem(item) {
|
removeItem(item) {
|
||||||
@@ -599,6 +657,12 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
modelid: number;
|
modelid: number;
|
||||||
// Update the editGadget method to initialize filter properties
|
// Update the editGadget method to initialize filter properties
|
||||||
editGadget(item) {
|
editGadget(item) {
|
||||||
|
// If coming from dashboard runner, skip showing the config modal
|
||||||
|
if (this.fromRunner) {
|
||||||
|
console.log('Coming from dashboard runner, skipping config modal');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.modeledit = true;
|
this.modeledit = true;
|
||||||
this.modelid = item.chartid;
|
this.modelid = item.chartid;
|
||||||
console.log(this.modelid);
|
console.log(this.modelid);
|
||||||
@@ -628,6 +692,10 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
if (item['filterOptions'] === undefined) {
|
if (item['filterOptions'] === undefined) {
|
||||||
this.gadgetsEditdata['filterOptions'] = [];
|
this.gadgetsEditdata['filterOptions'] = [];
|
||||||
}
|
}
|
||||||
|
// Initialize chartType property if not present (for unified chart)
|
||||||
|
if (item['chartType'] === undefined) {
|
||||||
|
this.gadgetsEditdata['chartType'] = 'bar';
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize filterOptionsString for compact filter
|
// Initialize filterOptionsString for compact filter
|
||||||
if (item.name === 'Compact Filter') {
|
if (item.name === 'Compact Filter') {
|
||||||
@@ -734,15 +802,34 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
this.refreshBaseDrilldownColumns();
|
this.refreshBaseDrilldownColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.datastore !== undefined || '' || null) {
|
// Check if we have either datastore or table to fetch columns
|
||||||
|
if ((item.datastore !== undefined && item.datastore !== '' && item.datastore !== null) ||
|
||||||
|
(item.table !== undefined && item.table !== '' && item.table !== null)) {
|
||||||
const datastore = item.datastore;
|
const datastore = item.datastore;
|
||||||
this.getTables(datastore);
|
|
||||||
const table = item.table;
|
const table = item.table;
|
||||||
|
|
||||||
|
// Fetch tables if datastore is available
|
||||||
|
if (datastore) {
|
||||||
|
this.getTables(datastore);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch columns if table is available
|
||||||
|
if (table) {
|
||||||
this.getColumns(datastore, table);
|
this.getColumns(datastore, table);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(item.yAxis);
|
console.log(item.yAxis);
|
||||||
|
// Set selectedyAxis regardless of whether it's an array or string
|
||||||
|
if (item.yAxis !== undefined && item.yAxis !== '' && item.yAxis !== null) {
|
||||||
if (isArray(item.yAxis)) {
|
if (isArray(item.yAxis)) {
|
||||||
this.selectedyAxis = item.yAxis;
|
this.selectedyAxis = item.yAxis;
|
||||||
|
} else {
|
||||||
|
// For single yAxis values, convert to array
|
||||||
|
this.selectedyAxis = [item.yAxis];
|
||||||
|
}
|
||||||
console.log(this.selectedyAxis);
|
console.log(this.selectedyAxis);
|
||||||
|
} else {
|
||||||
|
this.selectedyAxis = [];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.selectedyAxis = [];
|
this.selectedyAxis = [];
|
||||||
@@ -816,12 +903,29 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
// Update the onSubmit method to properly save filter data
|
// Update the onSubmit method to properly save filter data
|
||||||
onSubmit(id) {
|
onSubmit(id) {
|
||||||
console.log(id);
|
console.log(id);
|
||||||
if (!isNullArray(this.selectedyAxis)) {
|
|
||||||
console.log("get y-axis array", this.selectedyAxis);
|
// Check if ID is valid, including handling NaN
|
||||||
|
if (id === null || id === undefined || isNaN(id)) {
|
||||||
|
console.warn('Chart ID is null, undefined, or NaN, using modelid instead:', this.modelid);
|
||||||
|
id = this.modelid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have a valid numeric ID
|
||||||
|
const numId = typeof id === 'number' ? id : parseInt(id, 10);
|
||||||
|
if (isNaN(numId)) {
|
||||||
|
console.error('Unable to determine valid chart ID, aborting onSubmit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle both array and string yAxis values
|
||||||
|
if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
|
||||||
|
((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
|
||||||
|
(typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
|
||||||
|
console.log("get y-axis", this.selectedyAxis);
|
||||||
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
||||||
}
|
}
|
||||||
let formdata = this.entryForm.value;
|
let formdata = this.entryForm.value;
|
||||||
let num = id;
|
let num = numId;
|
||||||
console.log(this.entryForm.value);
|
console.log(this.entryForm.value);
|
||||||
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
this.dashboardCollection.dashboard = this.dashboardCollection.dashboard.map(item => {
|
||||||
if (item.chartid == num) {
|
if (item.chartid == num) {
|
||||||
@@ -860,6 +964,11 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
xyz.connection = this.gadgetsEditdata.connection || undefined;
|
xyz.connection = this.gadgetsEditdata.connection || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For unified chart, preserve chart configuration properties
|
||||||
|
if (item.name === 'Unified Chart') {
|
||||||
|
xyz.chartType = this.gadgetsEditdata.chartType || 'bar';
|
||||||
|
}
|
||||||
|
|
||||||
console.log(xyz);
|
console.log(xyz);
|
||||||
return xyz;
|
return xyz;
|
||||||
}
|
}
|
||||||
@@ -943,6 +1052,51 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
return commonFilterInputs;
|
return commonFilterInputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For UnifiedChartComponent, pass chart properties with chartType
|
||||||
|
// Check if the component is UnifiedChartComponent dynamically
|
||||||
|
if (item.component === UnifiedChartComponent ||
|
||||||
|
(item.component && item.component.name === 'UnifiedChartComponent') ||
|
||||||
|
item.name === 'Unified Chart') {
|
||||||
|
const unifiedChartInputs = {
|
||||||
|
chartType: item.chartType || 'bar',
|
||||||
|
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
|
||||||
|
baseFilters: item['baseFilters'] || [], // Add base filters
|
||||||
|
drilldownFilters: item['drilldownFilters'] || [], // Add drilldown filters
|
||||||
|
// Multi-layer drilldown configurations
|
||||||
|
drilldownLayers: item['drilldownLayers'] || []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove undefined properties to avoid passing unnecessary data
|
||||||
|
Object.keys(unifiedChartInputs).forEach(key => {
|
||||||
|
if (unifiedChartInputs[key] === undefined) {
|
||||||
|
delete unifiedChartInputs[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return unifiedChartInputs;
|
||||||
|
}
|
||||||
|
|
||||||
// For GridViewComponent, pass chart properties with drilldown support
|
// For GridViewComponent, pass chart properties with drilldown support
|
||||||
if (item.component && item.component.name === 'GridViewComponent') {
|
if (item.component && item.component.name === 'GridViewComponent') {
|
||||||
const gridInputs = {
|
const gridInputs = {
|
||||||
@@ -1028,15 +1182,25 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
applyChanges(id) {
|
applyChanges(id) {
|
||||||
console.log('Apply changes for chart ID:', id);
|
console.log('Apply changes for chart ID:', id);
|
||||||
|
|
||||||
// Check if ID is valid
|
// Check if ID is valid, including handling NaN
|
||||||
if (id === null || id === undefined) {
|
if (id === null || id === undefined || isNaN(id)) {
|
||||||
console.warn('Chart ID is null or undefined, using modelid instead:', this.modelid);
|
console.warn('Chart ID is null, undefined, or NaN, using modelid instead:', this.modelid);
|
||||||
id = this.modelid;
|
id = this.modelid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the form with selected Y-axis values if it's an array
|
// Ensure we have a valid numeric ID
|
||||||
if (!isNullArray(this.selectedyAxis)) {
|
const numId = typeof id === 'number' ? id : parseInt(id, 10);
|
||||||
console.log("get y-axis array", this.selectedyAxis);
|
if (isNaN(numId)) {
|
||||||
|
console.error('Unable to determine valid chart ID, aborting applyChanges');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the form with selected Y-axis values
|
||||||
|
// Handle both array and string yAxis values
|
||||||
|
if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
|
||||||
|
((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
|
||||||
|
(typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
|
||||||
|
console.log("get y-axis", this.selectedyAxis);
|
||||||
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
this.entryForm.patchValue({ yAxis: this.selectedyAxis });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1127,6 +1291,9 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
|
|
||||||
// Note: We don't close the modal here, allowing the user to make additional changes
|
// 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
|
// The user can click "Save" when they're done with all changes
|
||||||
|
|
||||||
|
// Reset the filter service to ensure clean state
|
||||||
|
this.filterService.resetFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
@@ -1784,4 +1951,266 @@ export class EditnewdashComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add method to apply dynamic template to a chart
|
||||||
|
applyDynamicTemplate(chartItem: any, template: any) {
|
||||||
|
console.log('Applying dynamic template to chart:', chartItem, template);
|
||||||
|
|
||||||
|
// Apply HTML template
|
||||||
|
if (template.templateHtml) {
|
||||||
|
// In a real implementation, you would dynamically render the HTML template
|
||||||
|
// For now, we'll just log it
|
||||||
|
console.log('HTML Template:', template.templateHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply CSS styles
|
||||||
|
if (template.templateCss) {
|
||||||
|
// In a real implementation, you would dynamically apply the CSS styles
|
||||||
|
// For now, we'll just log it
|
||||||
|
console.log('CSS Template:', template.templateCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the chart item with template applied
|
||||||
|
return {
|
||||||
|
...chartItem,
|
||||||
|
template: template
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to test dynamic chart creation
|
||||||
|
testDynamicChartCreation() {
|
||||||
|
console.log('Testing dynamic chart creation');
|
||||||
|
|
||||||
|
// Show a success message to the user
|
||||||
|
alert('Dynamic chart test started. Check the browser console for detailed output.');
|
||||||
|
|
||||||
|
// Load all chart types
|
||||||
|
this.dynamicChartLoader.loadAllChartConfigurations().subscribe({
|
||||||
|
next: (chartTypes) => {
|
||||||
|
console.log('Loaded chart types:', chartTypes);
|
||||||
|
|
||||||
|
// Find bar chart type
|
||||||
|
const barChartType = chartTypes.find((ct: any) => ct.name === 'bar');
|
||||||
|
if (barChartType) {
|
||||||
|
console.log('Found bar chart type:', barChartType);
|
||||||
|
|
||||||
|
// Load configuration for bar chart
|
||||||
|
this.dynamicChartLoader.loadChartConfiguration(barChartType.id).subscribe({
|
||||||
|
next: (config) => {
|
||||||
|
console.log('Loaded bar chart configuration:', config);
|
||||||
|
|
||||||
|
// Create a test chart item
|
||||||
|
const chartItem = {
|
||||||
|
cols: 5,
|
||||||
|
rows: 6,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
chartid: 100,
|
||||||
|
component: UnifiedChartComponent,
|
||||||
|
name: 'Test Dynamic Bar Chart',
|
||||||
|
chartType: 'bar',
|
||||||
|
xAxis: '',
|
||||||
|
yAxis: '',
|
||||||
|
table: '',
|
||||||
|
connection: undefined,
|
||||||
|
// Add dynamic fields from configuration
|
||||||
|
dynamicFields: config.dynamicFields || []
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Created test chart item:', chartItem);
|
||||||
|
|
||||||
|
// If we have templates, apply the default one
|
||||||
|
if (config.templates && config.templates.length > 0) {
|
||||||
|
const defaultTemplate = config.templates.find((t: any) => t.isDefault) || config.templates[0];
|
||||||
|
if (defaultTemplate) {
|
||||||
|
console.log('Applying default template:', defaultTemplate);
|
||||||
|
const chartWithTemplate = this.applyDynamicTemplate(chartItem, defaultTemplate);
|
||||||
|
console.log('Chart with template:', chartWithTemplate);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
alert('Dynamic chart test completed successfully! Check console for details.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show success message even without templates
|
||||||
|
alert('Dynamic chart test completed successfully! No templates found. Check console for details.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading bar chart configuration:', error);
|
||||||
|
alert('Error loading bar chart configuration. Check console for details.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('Bar chart type not found');
|
||||||
|
alert('Bar chart type not found in the database.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading chart types:', error);
|
||||||
|
alert('Error loading chart types. Check console for details.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add method to load dynamic chart configuration
|
||||||
|
loadDynamicChartConfiguration(chartTypeId: number) {
|
||||||
|
console.log(`Loading dynamic chart configuration for chart type ${chartTypeId}`);
|
||||||
|
this.dynamicChartLoader.loadChartConfiguration(chartTypeId).subscribe({
|
||||||
|
next: (config) => {
|
||||||
|
console.log('Loaded dynamic chart configuration:', config);
|
||||||
|
// Here you would apply the configuration to the UI
|
||||||
|
// For example, populate form fields, set up templates, etc.
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading dynamic chart configuration:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add method to create a dynamic chart with configuration from database
|
||||||
|
createDynamicChart = (chartTypeName: string, maxChartId: number) => {
|
||||||
|
console.log(`Creating dynamic chart of type: ${chartTypeName}`);
|
||||||
|
|
||||||
|
// First, get the chart type by name
|
||||||
|
this.dynamicChartLoader.getChartTypeByName(chartTypeName).subscribe({
|
||||||
|
next: (chartType) => {
|
||||||
|
if (chartType) {
|
||||||
|
console.log(`Found chart type:`, chartType);
|
||||||
|
|
||||||
|
// Load the complete configuration for this chart type
|
||||||
|
this.dynamicChartLoader.loadChartConfiguration(chartType.id).subscribe({
|
||||||
|
next: (config) => {
|
||||||
|
console.log(`Loaded configuration for ${chartTypeName}:`, config);
|
||||||
|
|
||||||
|
// Create the chart item with dynamic configuration
|
||||||
|
const chartItem = {
|
||||||
|
cols: 5,
|
||||||
|
rows: 6,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
chartid: maxChartId + 1,
|
||||||
|
component: UnifiedChartComponent,
|
||||||
|
name: chartType.displayName || chartTypeName,
|
||||||
|
chartType: chartType.name,
|
||||||
|
xAxis: '',
|
||||||
|
yAxis: '',
|
||||||
|
table: '',
|
||||||
|
connection: undefined,
|
||||||
|
// Add any dynamic fields from the configuration
|
||||||
|
dynamicFields: config.dynamicFields || []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add UI components as configuration properties
|
||||||
|
if (config.uiComponents && config.uiComponents.length > 0) {
|
||||||
|
config.uiComponents.forEach(component => {
|
||||||
|
chartItem[component.componentName] = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dashboardArray.push(chartItem);
|
||||||
|
console.log(`Created dynamic chart:`, chartItem);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(`Error loading configuration for ${chartTypeName}:`, error);
|
||||||
|
// Fallback to default configuration
|
||||||
|
this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Chart type ${chartTypeName} not found, using default configuration`);
|
||||||
|
this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading configuration for chart type:', error);
|
||||||
|
// Fallback to default configuration
|
||||||
|
this.createDefaultChart(chartTypeName, this.getChartDisplayName(chartTypeName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback method to create default chart configuration
|
||||||
|
createDefaultChart = (chartTypeName: string, chartDisplayName: string) => {
|
||||||
|
console.log(`Creating default chart for ${chartTypeName}`);
|
||||||
|
|
||||||
|
// Map chart type names to chart types - making it fully dynamic
|
||||||
|
const chartTypeMap = {
|
||||||
|
'bar': 'bar',
|
||||||
|
'line': 'line',
|
||||||
|
'pie': 'pie',
|
||||||
|
'doughnut': 'doughnut',
|
||||||
|
'radar': 'radar',
|
||||||
|
'polar': 'polar',
|
||||||
|
'bubble': 'bubble',
|
||||||
|
'scatter': 'scatter'
|
||||||
|
// Removed hardcoded heatmap entry to make it fully dynamic
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the chart type from the name - default to bubble for unknown chart types
|
||||||
|
const chartType = chartTypeMap[chartTypeName.toLowerCase()] || 'bubble';
|
||||||
|
|
||||||
|
// Safely calculate maxChartId, handling cases where chartid might be NaN or missing
|
||||||
|
let maxChartId = 0;
|
||||||
|
if (this.dashboardArray && this.dashboardArray.length > 0) {
|
||||||
|
const validChartIds = this.dashboardArray
|
||||||
|
.map(item => item.chartid)
|
||||||
|
.filter(chartid => typeof chartid === 'number' && !isNaN(chartid));
|
||||||
|
|
||||||
|
if (validChartIds.length > 0) {
|
||||||
|
maxChartId = Math.max(...validChartIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartItem = {
|
||||||
|
cols: 5,
|
||||||
|
rows: 6,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
chartid: maxChartId + 1,
|
||||||
|
component: UnifiedChartComponent,
|
||||||
|
name: chartDisplayName,
|
||||||
|
chartType: chartType,
|
||||||
|
xAxis: '',
|
||||||
|
yAxis: '',
|
||||||
|
table: '',
|
||||||
|
connection: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dashboardArray.push(chartItem);
|
||||||
|
console.log('Created default chart:', chartItem);
|
||||||
|
|
||||||
|
// Update the dashboard collection
|
||||||
|
this.dashboardCollection.dashboard = this.dashboardArray.slice();
|
||||||
|
|
||||||
|
// Force gridster to refresh
|
||||||
|
if (this.options && this.options.api) {
|
||||||
|
this.options.api.optionsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get display name for chart type - making it fully dynamic
|
||||||
|
getChartDisplayName = (chartTypeName: string): string => {
|
||||||
|
const displayNameMap = {
|
||||||
|
'bar': 'Bar Chart',
|
||||||
|
'line': 'Line Chart',
|
||||||
|
'pie': 'Pie Chart',
|
||||||
|
'doughnut': 'Doughnut Chart',
|
||||||
|
'radar': 'Radar Chart',
|
||||||
|
'polar': 'Polar Area Chart',
|
||||||
|
'bubble': 'Bubble Chart',
|
||||||
|
'scatter': 'Scatter Chart'
|
||||||
|
// Removed hardcoded heatmap entry to make it fully dynamic
|
||||||
|
};
|
||||||
|
|
||||||
|
// For unknown chart types, create a display name by capitalizing the first letter and adding ' Chart'
|
||||||
|
const displayName = displayNameMap[chartTypeName.toLowerCase()];
|
||||||
|
if (displayName) {
|
||||||
|
return displayName;
|
||||||
|
} else {
|
||||||
|
// Capitalize first letter and add ' Chart'
|
||||||
|
return chartTypeName.charAt(0).toUpperCase() + chartTypeName.slice(1) + ' Chart';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,19 +279,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart container -->
|
||||||
|
<div style="position: relative; height: calc(100% - 80px); width: 100%; padding: 0 10px 30px 10px;">
|
||||||
|
<!-- Loading indicator -->
|
||||||
|
<div *ngIf="!dataLoaded" style="text-align: center; padding: 20px; color: #666; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; width: 100%;">
|
||||||
|
Loading data...
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- No data message -->
|
<!-- No data message -->
|
||||||
<div *ngIf="noDataAvailable" style="text-align: center; padding: 20px; color: #666; font-style: italic;">
|
<div *ngIf="dataLoaded && (noDataAvailable || !isChartDataValid())" style="text-align: center; padding: 20px; color: #666; font-style: italic; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; width: 100%;">
|
||||||
No data available
|
No data available
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chart display -->
|
<!-- Chart display - Always render the canvas but conditionally show/hide with CSS -->
|
||||||
<div *ngIf="!noDataAvailable" style="position: relative; height: calc(100% - 50px);">
|
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="bubbleChartData"
|
[datasets]="bubbleChartData"
|
||||||
[type]="bubbleChartType"
|
[type]="bubbleChartType"
|
||||||
[options]="bubbleChartOptions"
|
[options]="bubbleChartOptions"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)"
|
||||||
|
[style.visibility]="dataLoaded && !noDataAvailable && isChartDataValid() ? 'visible' : 'hidden'"
|
||||||
|
[style.position]="'absolute'"
|
||||||
|
[style.top]="'0'"
|
||||||
|
[style.left]="'0'"
|
||||||
|
[style.height]="'100%'"
|
||||||
|
[style.width]="'100%'"
|
||||||
|
[style.padding]="'0 10px 20px 10px'">
|
||||||
</canvas>
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,55 +37,77 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
@Input() drilldownLayers: any[] = []; // Array of drilldown layer configurations
|
||||||
|
|
||||||
public bubbleChartOptions: ChartConfiguration['options'] = {
|
public bubbleChartOptions: ChartConfiguration['options'] = {
|
||||||
// scales: {
|
responsive: true,
|
||||||
// x: {
|
maintainAspectRatio: false,
|
||||||
// min: 0,
|
scales: {
|
||||||
// max: 30,
|
x: {
|
||||||
// ticks: {}
|
beginAtZero: true,
|
||||||
// },
|
title: {
|
||||||
// y: {
|
display: true,
|
||||||
// min: 0,
|
text: 'X Axis'
|
||||||
// max: 30,
|
},
|
||||||
// ticks: {}
|
ticks: {
|
||||||
// },
|
autoSkip: false,
|
||||||
// plugins: {
|
maxRotation: 45,
|
||||||
// title: {
|
minRotation: 45
|
||||||
// display: true,
|
}
|
||||||
// text: 'Bubble Chart'
|
},
|
||||||
// }
|
y: {
|
||||||
// }
|
beginAtZero: true,
|
||||||
// }
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Y Axis'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
mode: 'point',
|
||||||
|
intersect: false,
|
||||||
|
callbacks: {
|
||||||
|
label: function(context: any) {
|
||||||
|
const point: any = context.raw;
|
||||||
|
if (point && point.hasOwnProperty('y') && point.hasOwnProperty('r')) {
|
||||||
|
const yValue = parseFloat(point.y);
|
||||||
|
const rValue = parseFloat(point.r);
|
||||||
|
if (!isNaN(yValue) && !isNaN(rValue)) {
|
||||||
|
return `Value: ${yValue.toFixed(2)}, Size: ${rValue.toFixed(1)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context.dataset.label || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
},
|
||||||
|
// Enable individual point styling
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
hoverRadius: 12,
|
||||||
|
hoverBorderWidth: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Add padding to ensure x-axis labels are visible
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
top: 10,
|
||||||
|
bottom: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public bubbleChartType: string = 'bubble';
|
public bubbleChartType: string = 'bubble';
|
||||||
public bubbleChartData: ChartDataset[] = [
|
public bubbleChartData: ChartDataset[] = [];
|
||||||
{
|
|
||||||
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: 'rgba(255, 0, 0, 0.6)', // Red
|
|
||||||
borderColor: 'blue',
|
|
||||||
hoverBackgroundColor: 'purple',
|
|
||||||
hoverBorderColor: 'red',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{ x: 5, y: 15, r: 12 },
|
|
||||||
{ x: 20, y: 7, r: 8 },
|
|
||||||
{ x: 12, y: 18, r: 15 },
|
|
||||||
{ x: 8, y: 6, r: 10 },
|
|
||||||
],
|
|
||||||
label: 'Investment Bonds',
|
|
||||||
backgroundColor: 'rgba(0, 255, 0, 0.6)', // Green
|
|
||||||
borderColor: 'green',
|
|
||||||
hoverBackgroundColor: 'yellow',
|
|
||||||
hoverBorderColor: 'blue',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Multi-layer drilldown state tracking
|
// Multi-layer drilldown state tracking
|
||||||
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
drilldownStack: any[] = []; // Stack to track drilldown navigation history
|
||||||
@@ -94,6 +116,7 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
// No data state
|
// No data state
|
||||||
noDataAvailable: boolean = false;
|
noDataAvailable: boolean = false;
|
||||||
|
dataLoaded: boolean = false; // Track if data has been loaded
|
||||||
|
|
||||||
// Flag to prevent infinite loops
|
// Flag to prevent infinite loops
|
||||||
private isFetchingData: boolean = false;
|
private isFetchingData: boolean = false;
|
||||||
@@ -465,45 +488,250 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
this.fetchChartData();
|
this.fetchChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to replace alpha value in RGBA color string
|
||||||
|
private replaceAlpha(color: string, newAlpha: number): string {
|
||||||
|
// Match rgba(r, g, b, a) format and replace alpha value
|
||||||
|
return color.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/, `rgba($1, $2, $3, ${newAlpha})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to generate different colors for bubbles
|
||||||
|
private generateBubbleColor(index: number, value: number, total: number): string {
|
||||||
|
// Generate colors based on index or value
|
||||||
|
// Using HSL color model for better color distribution
|
||||||
|
const hue = (index * 137.508) % 360; // Golden angle approximation for good distribution
|
||||||
|
const saturation = 80 + (index % 20); // High saturation for vibrant colors
|
||||||
|
const lightness = 40 + (index % 30); // Vary lightness for contrast
|
||||||
|
|
||||||
|
// Convert HSL to RGB
|
||||||
|
const h = hue / 360;
|
||||||
|
const s = saturation / 100;
|
||||||
|
const l = lightness / 100;
|
||||||
|
|
||||||
|
const rgb = this.hslToRgb(h, s, l);
|
||||||
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert HSL to RGB
|
||||||
|
private hslToRgb(h: number, s: number, l: number): { r: number, g: number, b: number } {
|
||||||
|
let r, g, b;
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p: number, q: number, t: number) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1/2) return q;
|
||||||
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1/3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1/3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: Math.round(r * 255),
|
||||||
|
g: Math.round(g * 255),
|
||||||
|
b: Math.round(b * 255)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to calculate logarithmic scaling
|
||||||
|
private logScale(value: number, min: number, max: number, minRadius: number, maxRadius: number): number {
|
||||||
|
if (min === max) return (minRadius + maxRadius) / 2;
|
||||||
|
|
||||||
|
// Normalize value to 0-1 range
|
||||||
|
const normalized = (value - min) / (max - min);
|
||||||
|
|
||||||
|
// Apply logarithmic scaling (base 10)
|
||||||
|
// Add 1 to avoid log(0) and scale to 1-10 range
|
||||||
|
const logValue = Math.log10(normalized * 9 + 1);
|
||||||
|
|
||||||
|
// Scale to desired radius range
|
||||||
|
return minRadius + (logValue / Math.log10(10)) * (maxRadius - minRadius);
|
||||||
|
}
|
||||||
|
|
||||||
// Transform data to bubble chart format
|
// Transform data to bubble chart format
|
||||||
private transformToBubbleData(labels: any[], data: any[]): ChartDataset[] {
|
private transformToBubbleData(labels: any[], data: any[]): ChartDataset[] {
|
||||||
// For bubble charts, we need to transform the data into bubble format
|
// 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}
|
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||||
console.log('Transforming data to bubble format:', { labels, data });
|
console.log('Transforming data to bubble format:', { labels, data });
|
||||||
|
|
||||||
|
// Handle null/undefined data
|
||||||
|
if (!labels || !data) {
|
||||||
|
console.log('Labels or data is null/undefined, returning empty dataset');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
// If we have the expected bubble data format, return it as is
|
// If we have the expected bubble data format, return it as is
|
||||||
if (data && data.length > 0 && data[0].data && data[0].data.length > 0 &&
|
if (data && data.length > 0 && data[0].data && data[0].data.length > 0 &&
|
||||||
typeof data[0].data[0] === 'object' && data[0].data[0].hasOwnProperty('x') &&
|
typeof data[0].data[0] === 'object' && data[0].data[0].hasOwnProperty('x') &&
|
||||||
data[0].data[0].hasOwnProperty('y') && data[0].data[0].hasOwnProperty('r')) {
|
data[0].data[0].hasOwnProperty('y') && data[0].data[0].hasOwnProperty('r')) {
|
||||||
|
console.log('Data is already in bubble format, returning as is');
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, create a default bubble dataset
|
// Transform the data properly for bubble chart
|
||||||
|
// Assuming labels are x-values and data[0].data are y-values
|
||||||
|
if (labels && data && data.length > 0 && data[0].data) {
|
||||||
|
console.log('Transforming regular data to bubble format');
|
||||||
|
const yValues = data[0].data;
|
||||||
|
const label = data[0].label || 'Dataset 1';
|
||||||
|
|
||||||
|
// Handle case where yValues might not be an array
|
||||||
|
if (!Array.isArray(yValues)) {
|
||||||
|
console.log('yValues is not an array, returning empty dataset');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('yValues type:', typeof yValues);
|
||||||
|
console.log('yValues length:', yValues.length);
|
||||||
|
console.log('First few yValues:', yValues.slice(0, 5));
|
||||||
|
|
||||||
|
// Find min and max values for scaling
|
||||||
|
let minValue = Infinity;
|
||||||
|
let maxValue = -Infinity;
|
||||||
|
const validYValues = [];
|
||||||
|
|
||||||
|
// First pass: collect valid values and find min/max
|
||||||
|
for (let i = 0; i < yValues.length; i++) {
|
||||||
|
let y;
|
||||||
|
if (typeof yValues[i] === 'string') {
|
||||||
|
y = parseFloat(yValues[i]);
|
||||||
|
} else {
|
||||||
|
y = yValues[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(y)) {
|
||||||
|
validYValues.push(y);
|
||||||
|
minValue = Math.min(minValue, y);
|
||||||
|
maxValue = Math.max(maxValue, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Value range:', { minValue, maxValue });
|
||||||
|
|
||||||
|
// Adjust radius range based on number of data points
|
||||||
|
// For fewer points, we can use larger bubbles; for more points, smaller bubbles to prevent overlap
|
||||||
|
const dataPointCount = Math.min(labels.length, yValues.length);
|
||||||
|
let minRadius = 3;
|
||||||
|
let maxRadius = 30;
|
||||||
|
|
||||||
|
// Adjust radius range based on data point count
|
||||||
|
if (dataPointCount > 50) {
|
||||||
|
minRadius = 2;
|
||||||
|
maxRadius = 20;
|
||||||
|
} else if (dataPointCount > 20) {
|
||||||
|
minRadius = 3;
|
||||||
|
maxRadius = 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Radius range:', { minRadius, maxRadius, dataPointCount });
|
||||||
|
|
||||||
|
// Create bubble points from labels (x) and data (y)
|
||||||
|
const bubblePoints = [];
|
||||||
|
const bubbleColors = [];
|
||||||
|
const minLength = Math.min(labels.length, yValues.length);
|
||||||
|
|
||||||
|
console.log('Processing data points:', { labels, yValues, minLength });
|
||||||
|
|
||||||
|
for (let i = 0; i < minLength; i++) {
|
||||||
|
// Log each point for debugging
|
||||||
|
console.log(`Processing point ${i}: label=${labels[i]}, yValue=${yValues[i]}, type=${typeof yValues[i]}`);
|
||||||
|
|
||||||
|
// Convert y to number if it's a string
|
||||||
|
let y;
|
||||||
|
if (typeof yValues[i] === 'string') {
|
||||||
|
y = parseFloat(yValues[i]);
|
||||||
|
console.log(`Converted string yValue to number: ${yValues[i]} -> ${y}`);
|
||||||
|
} else {
|
||||||
|
y = yValues[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle NaN values
|
||||||
|
if (isNaN(y)) {
|
||||||
|
console.log(`Skipping point ${i} due to NaN y value: ${yValues[i]}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate radius based on the y-value with logarithmic scaling
|
||||||
|
const r = this.logScale(y, minValue, maxValue, minRadius, maxRadius);
|
||||||
|
|
||||||
|
console.log(`Value: ${y}, Radius: ${r}`);
|
||||||
|
|
||||||
|
// For x-value, we'll use the index position since labels are strings
|
||||||
|
const x = i;
|
||||||
|
|
||||||
|
// Generate a unique color for this bubble
|
||||||
|
const backgroundColor = this.generateBubbleColor(i, y, minLength);
|
||||||
|
|
||||||
|
// Store the color for the dataset
|
||||||
|
bubbleColors.push(backgroundColor);
|
||||||
|
|
||||||
|
// Add the point
|
||||||
|
const point = {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r
|
||||||
|
};
|
||||||
|
console.log(`Adding point ${i}:`, point);
|
||||||
|
bubblePoints.push(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Generated bubble points:', bubblePoints);
|
||||||
|
console.log('Generated bubble points count:', bubblePoints.length);
|
||||||
|
console.log('Generated bubble colors count:', bubbleColors.length);
|
||||||
|
|
||||||
|
// If we have no valid points, return empty array
|
||||||
|
if (bubblePoints.length === 0) {
|
||||||
|
console.log('No valid bubble points generated, returning empty dataset');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a single dataset with all bubble points
|
||||||
const bubbleDatasets: ChartDataset[] = [
|
const bubbleDatasets: ChartDataset[] = [
|
||||||
{
|
{
|
||||||
data: [
|
data: bubblePoints,
|
||||||
{ x: 10, y: 10, r: 10 },
|
label: label,
|
||||||
{ x: 15, y: 5, r: 15 },
|
backgroundColor: bubbleColors,
|
||||||
{ x: 26, y: 12, r: 23 },
|
borderColor: bubbleColors.map(color => this.replaceAlpha(color, 1)), // Slightly more opaque border
|
||||||
{ x: 7, y: 8, r: 8 },
|
hoverBackgroundColor: bubbleColors.map(color => this.replaceAlpha(color, 0.9)),
|
||||||
],
|
hoverBorderColor: 'rgba(255, 255, 255, 1)',
|
||||||
label: 'Dataset 1',
|
borderWidth: 2,
|
||||||
backgroundColor: 'rgba(255, 0, 0, 0.6)',
|
pointHoverRadius: 10,
|
||||||
borderColor: 'blue',
|
|
||||||
hoverBackgroundColor: 'purple',
|
|
||||||
hoverBorderColor: 'red',
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
console.log('Transformed bubble data:', bubbleDatasets);
|
||||||
return bubbleDatasets;
|
return bubbleDatasets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Could not transform data, returning empty dataset');
|
||||||
|
// Return empty dataset instead of default data
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
fetchChartData(): void {
|
fetchChartData(): void {
|
||||||
// Set flag to prevent recursive calls
|
// Set flag to prevent recursive calls
|
||||||
this.isFetchingData = true;
|
this.isFetchingData = true;
|
||||||
|
this.dataLoaded = false; // Mark data as not loaded yet
|
||||||
|
this.noDataAvailable = false; // Reset no data flag
|
||||||
|
|
||||||
|
console.log('Starting fetchChartData, current state:', {
|
||||||
|
table: this.table,
|
||||||
|
xAxis: this.xAxis,
|
||||||
|
yAxis: this.yAxis,
|
||||||
|
connection: this.connection
|
||||||
|
});
|
||||||
|
|
||||||
// If we're in drilldown mode, fetch the appropriate drilldown data
|
// If we're in drilldown mode, fetch the appropriate drilldown data
|
||||||
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
if (this.currentDrilldownLevel > 0 && this.drilldownStack.length > 0) {
|
||||||
|
console.log('Fetching drilldown data');
|
||||||
this.fetchDrilldownData();
|
this.fetchDrilldownData();
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
@@ -583,32 +811,82 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
|
this.dashboardService.getChartData(this.table, 'bubble', this.xAxis, yAxisString, this.connection, '', '', filterParams).subscribe(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
console.log('Received bubble chart data:', data);
|
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 = [];
|
|
||||||
// Reset flag after fetching
|
|
||||||
this.isFetchingData = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the actual data structure returned by the API
|
// Reset chart data to empty first
|
||||||
if (data && data.chartLabels && data.chartData) {
|
this.bubbleChartData = [];
|
||||||
|
|
||||||
|
if (data === null || data === undefined) {
|
||||||
|
console.warn('Bubble chart API returned null/undefined data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
} else if (data && data.chartLabels && data.chartData) {
|
||||||
// For bubble charts, we need to transform the data into bubble format
|
// 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}
|
// Bubble charts expect data in the format: {x: number, y: number, r: number}
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
console.log('Processing chartLabels and chartData format');
|
||||||
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
const transformedData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||||
|
console.log('Transformed data:', transformedData);
|
||||||
|
|
||||||
|
// Check if we have valid data
|
||||||
|
let hasValidData = false;
|
||||||
|
if (transformedData && transformedData.length > 0) {
|
||||||
|
for (const dataset of transformedData) {
|
||||||
|
if (dataset.data && dataset.data.length > 0) {
|
||||||
|
hasValidData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidData) {
|
||||||
|
// Create a new array reference to trigger change detection
|
||||||
|
this.bubbleChartData = [...transformedData];
|
||||||
|
this.noDataAvailable = false;
|
||||||
console.log('Updated bubble chart with data:', this.bubbleChartData);
|
console.log('Updated bubble chart with data:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.log('No valid data after transformation');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
}
|
||||||
} else if (data && data.labels && data.datasets) {
|
} else if (data && data.labels && data.datasets) {
|
||||||
// Handle the original expected format as fallback
|
// Handle the original expected format as fallback
|
||||||
this.noDataAvailable = data.labels.length === 0;
|
console.log('Processing labels and datasets format');
|
||||||
this.bubbleChartData = data.datasets;
|
// Check if we have valid data
|
||||||
|
let hasValidData = false;
|
||||||
|
if (data.datasets && data.datasets.length > 0) {
|
||||||
|
for (const dataset of data.datasets) {
|
||||||
|
if (dataset.data && dataset.data.length > 0) {
|
||||||
|
hasValidData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidData) {
|
||||||
|
// Create a new array reference to trigger change detection
|
||||||
|
this.bubbleChartData = [...data.datasets];
|
||||||
|
this.noDataAvailable = false;
|
||||||
console.log('Updated bubble chart with legacy data format:', this.bubbleChartData);
|
console.log('Updated bubble chart with legacy data format:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.log('No valid data in legacy format');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Bubble chart received data does not have expected structure', data);
|
console.warn('Bubble chart received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dataLoaded = true; // Mark data as loaded
|
||||||
|
|
||||||
|
console.log('Final state after data fetch:', {
|
||||||
|
noDataAvailable: this.noDataAvailable,
|
||||||
|
dataLoaded: this.dataLoaded,
|
||||||
|
bubbleChartDataLength: this.bubbleChartData.length,
|
||||||
|
isChartDataValid: this.isChartDataValid()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger change detection with a small delay to ensure proper rendering
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
},
|
},
|
||||||
@@ -616,15 +894,24 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
console.error('Error fetching bubble chart data:', error);
|
console.error('Error fetching bubble chart data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
|
this.dataLoaded = true;
|
||||||
|
// Trigger change detection
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
// Keep default data in case of error
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
console.log('Missing required data for bubble chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
|
this.dataLoaded = true;
|
||||||
|
// Trigger change detection
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
}
|
}
|
||||||
@@ -654,6 +941,10 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
|
this.dataLoaded = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,6 +956,10 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
|
this.dataLoaded = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,35 +1053,84 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue, filterParams).subscribe(
|
this.dashboardService.getChartData(actualApiUrl, 'bubble', drilldownConfig.xAxis, drilldownConfig.yAxis, this.connection, parameterField, parameterValue, filterParams).subscribe(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
console.log('Received drilldown data:', data);
|
console.log('Received drilldown data:', data);
|
||||||
if (data === null) {
|
|
||||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
// Reset chart data to empty first
|
||||||
this.noDataAvailable = true;
|
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
return;
|
|
||||||
|
if (data === null || data === undefined) {
|
||||||
|
console.warn('Drilldown API returned null/undefined data. Check if the API endpoint is working correctly.');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
} else if (data && data.chartLabels && data.chartData) {
|
||||||
|
// For bubble charts, we need to transform the data into bubble format
|
||||||
|
const transformedData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
||||||
|
|
||||||
|
// Check if we have valid data
|
||||||
|
let hasValidData = false;
|
||||||
|
if (transformedData && transformedData.length > 0) {
|
||||||
|
for (const dataset of transformedData) {
|
||||||
|
if (dataset.data && dataset.data.length > 0) {
|
||||||
|
hasValidData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the actual data structure returned by the API
|
if (hasValidData) {
|
||||||
if (data && data.chartLabels && data.chartData) {
|
this.bubbleChartData = transformedData;
|
||||||
// For bubble charts, we need to transform the data into bubble format
|
this.noDataAvailable = false;
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
|
||||||
this.bubbleChartData = this.transformToBubbleData(data.chartLabels, data.chartData);
|
|
||||||
console.log('Updated bubble chart with drilldown data:', this.bubbleChartData);
|
console.log('Updated bubble chart with drilldown data:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.log('No valid data after transformation in drilldown');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
}
|
||||||
} else if (data && data.labels && data.datasets) {
|
} else if (data && data.labels && data.datasets) {
|
||||||
// Handle the original expected format as fallback
|
// Handle the original expected format as fallback
|
||||||
this.noDataAvailable = data.labels.length === 0;
|
// Check if we have valid data
|
||||||
|
let hasValidData = false;
|
||||||
|
if (data.datasets && data.datasets.length > 0) {
|
||||||
|
for (const dataset of data.datasets) {
|
||||||
|
if (dataset.data && dataset.data.length > 0) {
|
||||||
|
hasValidData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidData) {
|
||||||
this.bubbleChartData = data.datasets;
|
this.bubbleChartData = data.datasets;
|
||||||
|
this.noDataAvailable = false;
|
||||||
console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData);
|
console.log('Updated bubble chart with drilldown legacy data format:', this.bubbleChartData);
|
||||||
|
} else {
|
||||||
|
console.log('No valid data in legacy format in drilldown');
|
||||||
|
this.noDataAvailable = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Drilldown received data does not have expected structure', data);
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dataLoaded = true; // Mark data as loaded
|
||||||
|
|
||||||
|
console.log('Final state after drilldown data fetch:', {
|
||||||
|
noDataAvailable: this.noDataAvailable,
|
||||||
|
dataLoaded: this.dataLoaded,
|
||||||
|
bubbleChartDataLength: this.bubbleChartData.length,
|
||||||
|
isChartDataValid: this.isChartDataValid()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger change detection
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching drilldown data:', error);
|
console.error('Error fetching drilldown data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.bubbleChartData = [];
|
this.bubbleChartData = [];
|
||||||
// Keep current data in case of error
|
this.dataLoaded = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.forceChartUpdate();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -933,4 +1277,52 @@ export class BubbleChartComponent implements OnInit, OnChanges {
|
|||||||
public chartHovered(e: any): void {
|
public chartHovered(e: any): void {
|
||||||
console.log('Bubble chart hovered:', e);
|
console.log('Bubble chart hovered:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to check if chart data is valid
|
||||||
|
public isChartDataValid(): boolean {
|
||||||
|
console.log('Checking if chart data is valid:', this.bubbleChartData);
|
||||||
|
if (!this.bubbleChartData || this.bubbleChartData.length === 0) {
|
||||||
|
console.log('Chart data is null or empty');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any dataset has data
|
||||||
|
for (const dataset of this.bubbleChartData) {
|
||||||
|
console.log('Checking dataset:', dataset);
|
||||||
|
if (dataset.data && dataset.data.length > 0) {
|
||||||
|
console.log('Dataset has data, length:', dataset.data.length);
|
||||||
|
// For bubble charts, check if data points have x, y, r properties
|
||||||
|
for (const point of dataset.data) {
|
||||||
|
console.log('Checking point:', point);
|
||||||
|
if (typeof point === 'object' && point.hasOwnProperty('x') && point.hasOwnProperty('y') && point.hasOwnProperty('r')) {
|
||||||
|
// Valid bubble point
|
||||||
|
console.log('Found valid bubble point');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('No valid chart data found');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to force chart update
|
||||||
|
private forceChartUpdate(): void {
|
||||||
|
console.log('Forcing chart update');
|
||||||
|
console.log('Current bubbleChartData:', this.bubbleChartData);
|
||||||
|
console.log('Current bubbleChartData length:', this.bubbleChartData ? this.bubbleChartData.length : 0);
|
||||||
|
if (this.bubbleChartData && this.bubbleChartData.length > 0) {
|
||||||
|
console.log('First dataset data length:', this.bubbleChartData[0].data ? this.bubbleChartData[0].data.length : 0);
|
||||||
|
}
|
||||||
|
// Create a new reference to trigger change detection
|
||||||
|
if (this.bubbleChartData) {
|
||||||
|
this.bubbleChartData = [...this.bubbleChartData];
|
||||||
|
}
|
||||||
|
// Also update noDataAvailable to trigger UI changes
|
||||||
|
this.noDataAvailable = this.noDataAvailable;
|
||||||
|
console.log('Chart update forced, noDataAvailable:', this.noDataAvailable);
|
||||||
|
console.log('Chart update forced, bubbleChartData length:', this.bubbleChartData ? this.bubbleChartData.length : 0);
|
||||||
|
console.log('Chart update forced, isChartDataValid:', this.isChartDataValid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -284,13 +284,12 @@
|
|||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<div class="chart-content" [class.loading]="isLoading">
|
<div class="chart-content" [class.loading]="isLoading">
|
||||||
<!-- Show no data message -->
|
<!-- Show no data message -->
|
||||||
<div class="no-data-message" *ngIf="noDataAvailable">
|
<div class="no-data-message" *ngIf="noDataAvailable && doughnutChartLabels.length === 0">
|
||||||
<p>No chart data available</p>
|
<p>No chart data available</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Show chart when data is available -->
|
<!-- Show chart when data is available -->
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
*ngIf="!noDataAvailable && doughnutChartLabels.length > 0 && doughnutChartData.length > 0"
|
|
||||||
[datasets]="doughnutChartData"
|
[datasets]="doughnutChartData"
|
||||||
[labels]="doughnutChartLabels"
|
[labels]="doughnutChartLabels"
|
||||||
[type]="doughnutChartType"
|
[type]="doughnutChartType"
|
||||||
@@ -306,7 +305,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-legend" *ngIf="!noDataAvailable && showlabel && doughnutChartLabels && doughnutChartLabels.length > 0">
|
<div class="chart-legend" *ngIf="showlabel && doughnutChartLabels && doughnutChartLabels.length > 0">
|
||||||
<div class="legend-item" *ngFor="let label of doughnutChartLabels; let i = index">
|
<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-color" [style.background-color]="getLegendColor(i)"></span>
|
||||||
<span class="legend-label">{{ label }}</span>
|
<span class="legend-label">{{ label }}</span>
|
||||||
|
|||||||
@@ -148,8 +148,11 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
|
|
||||||
// Validate initial data
|
// Validate initial data
|
||||||
this.validateChartData();
|
this.validateChartData();
|
||||||
|
// Only fetch data if we have the required inputs, otherwise show default data
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
this.fetchChartData();
|
this.fetchChartData();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and sanitize chart data
|
* Validate and sanitize chart data
|
||||||
@@ -237,6 +240,12 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
console.log('Chart configuration changed, fetching new data');
|
console.log('Chart configuration changed, fetching new data');
|
||||||
this.fetchChartData();
|
this.fetchChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have the required inputs and haven't fetched data yet, fetch it
|
||||||
|
if ((xAxisChanged || yAxisChanged || tableChanged) && this.table && this.xAxis && this.yAxis && !this.isFetchingData) {
|
||||||
|
console.log('Required inputs available, fetching data');
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked() {
|
ngAfterViewChecked() {
|
||||||
@@ -674,11 +683,31 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.doughnutChartLabels = data.chartLabels;
|
this.doughnutChartLabels = data.chartLabels;
|
||||||
|
|
||||||
|
// Handle different data structures
|
||||||
|
let chartDataValues;
|
||||||
|
if (Array.isArray(data.chartData)) {
|
||||||
|
// If chartData is already an array of values
|
||||||
|
if (data.chartData.length > 0 && typeof data.chartData[0] !== 'object') {
|
||||||
|
chartDataValues = data.chartData;
|
||||||
|
}
|
||||||
|
// If chartData is an array with one object containing the data
|
||||||
|
else if (data.chartData.length > 0 && data.chartData[0].data) {
|
||||||
|
chartDataValues = data.chartData[0].data;
|
||||||
|
}
|
||||||
|
// Default case
|
||||||
|
else {
|
||||||
|
chartDataValues = data.chartData;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chartDataValues = [data.chartData];
|
||||||
|
}
|
||||||
|
|
||||||
this.doughnutChartData = [
|
this.doughnutChartData = [
|
||||||
{
|
{
|
||||||
data: data.chartData,
|
data: chartDataValues,
|
||||||
backgroundColor: this.chartColors.slice(0, data.chartData.length),
|
backgroundColor: this.chartColors.slice(0, chartDataValues.length),
|
||||||
hoverBackgroundColor: this.chartColors.slice(0, data.chartData.length)
|
hoverBackgroundColor: this.chartColors.slice(0, chartDataValues.length)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
console.log('Updated doughnut chart with data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
@@ -691,12 +720,21 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Received data does not have expected structure', data);
|
console.warn('Received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.doughnutChartLabels = [];
|
// Keep default data instead of empty arrays
|
||||||
|
this.doughnutChartLabels = ["Category A", "Category B", "Category C"];
|
||||||
this.doughnutChartData = [
|
this.doughnutChartData = [
|
||||||
{
|
{
|
||||||
data: [],
|
data: [30, 50, 20],
|
||||||
backgroundColor: [],
|
backgroundColor: [
|
||||||
hoverBackgroundColor: []
|
'#FF6384',
|
||||||
|
'#36A2EB',
|
||||||
|
'#FFCE56'
|
||||||
|
],
|
||||||
|
hoverBackgroundColor: [
|
||||||
|
'#FF6384',
|
||||||
|
'#36A2EB',
|
||||||
|
'#FFCE56'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -707,12 +745,26 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching doughnut chart data:', error);
|
console.error('Error fetching doughnut chart data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.doughnutChartLabels = [];
|
// Keep default data in case of error
|
||||||
this.doughnutChartData = [];
|
this.doughnutChartLabels = ["Category A", "Category B", "Category C"];
|
||||||
|
this.doughnutChartData = [
|
||||||
|
{
|
||||||
|
data: [30, 50, 20],
|
||||||
|
backgroundColor: [
|
||||||
|
'#FF6384',
|
||||||
|
'#36A2EB',
|
||||||
|
'#FFCE56'
|
||||||
|
],
|
||||||
|
hoverBackgroundColor: [
|
||||||
|
'#FF6384',
|
||||||
|
'#36A2EB',
|
||||||
|
'#FFCE56'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
// Reset flags after fetching
|
// Reset flags after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
// Keep default data in case of error
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -869,11 +921,31 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.doughnutChartLabels = data.chartLabels;
|
this.doughnutChartLabels = data.chartLabels;
|
||||||
|
|
||||||
|
// Handle different data structures
|
||||||
|
let chartDataValues;
|
||||||
|
if (Array.isArray(data.chartData)) {
|
||||||
|
// If chartData is already an array of values
|
||||||
|
if (data.chartData.length > 0 && typeof data.chartData[0] !== 'object') {
|
||||||
|
chartDataValues = data.chartData;
|
||||||
|
}
|
||||||
|
// If chartData is an array with one object containing the data
|
||||||
|
else if (data.chartData.length > 0 && data.chartData[0].data) {
|
||||||
|
chartDataValues = data.chartData[0].data;
|
||||||
|
}
|
||||||
|
// Default case
|
||||||
|
else {
|
||||||
|
chartDataValues = data.chartData;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chartDataValues = [data.chartData];
|
||||||
|
}
|
||||||
|
|
||||||
this.doughnutChartData = [
|
this.doughnutChartData = [
|
||||||
{
|
{
|
||||||
data: data.chartData,
|
data: chartDataValues,
|
||||||
backgroundColor: this.chartColors.slice(0, data.chartData.length),
|
backgroundColor: this.chartColors.slice(0, chartDataValues.length),
|
||||||
hoverBackgroundColor: this.chartColors.slice(0, data.chartData.length)
|
hoverBackgroundColor: this.chartColors.slice(0, chartDataValues.length)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
console.log('Updated doughnut chart with drilldown data:', { labels: this.doughnutChartLabels, data: this.doughnutChartData });
|
||||||
@@ -890,14 +962,7 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Drilldown received data does not have expected structure', data);
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.doughnutChartLabels = [];
|
// Keep current data instead of empty arrays
|
||||||
this.doughnutChartData = [
|
|
||||||
{
|
|
||||||
data: [],
|
|
||||||
backgroundColor: [],
|
|
||||||
hoverBackgroundColor: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Set loading state to false
|
// Set loading state to false
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
@@ -905,11 +970,9 @@ export class DoughnutChartComponent implements OnInit, OnChanges, AfterViewCheck
|
|||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching drilldown data:', error);
|
console.error('Error fetching drilldown data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.doughnutChartLabels = [];
|
// Keep current data in case of error
|
||||||
this.doughnutChartData = [];
|
|
||||||
// Set loading state to false
|
// Set loading state to false
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
// Keep current data in case of error
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -281,7 +281,7 @@
|
|||||||
|
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<!-- Show loading indicator -->
|
<!-- Show loading indicator -->
|
||||||
<div class="loading-indicator" *ngIf="pieChartLabels.length === 0 && pieChartData.length === 0 && !noDataAvailable">
|
<div class="loading-indicator" *ngIf="pieChartLabels.length === 0 && pieChartData.length === 0 && !noDataAvailable && !isFetchingData">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Loading chart data...</p>
|
<p>Loading chart data...</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,15 +291,15 @@
|
|||||||
<p>No chart data available</p>
|
<p>No chart data available</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Show chart when data is available -->
|
<!-- Show chart when data is available or show default data -->
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
*ngIf="pieChartLabels.length > 0 && pieChartData.length > 0"
|
[datasets]="pieChartDatasets"
|
||||||
[data]="pieChartData"
|
|
||||||
[labels]="pieChartLabels"
|
[labels]="pieChartLabels"
|
||||||
[type]="pieChartType"
|
[type]="pieChartType"
|
||||||
[options]="pieChartOptions"
|
[options]="pieChartOptions"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)"
|
||||||
|
[style.display]="shouldShowChart() ? 'block' : 'none'">
|
||||||
</canvas>
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-legend" *ngIf="showlabel && pieChartLabels && pieChartLabels.length > 0">
|
<div class="chart-legend" *ngIf="showlabel && pieChartLabels && pieChartLabels.length > 0">
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
|
|
||||||
public pieChartLabels: string[] = ['Category A', 'Category B', 'Category C'];
|
public pieChartLabels: string[] = ['Category A', 'Category B', 'Category C'];
|
||||||
public pieChartData: number[] = [30, 50, 20];
|
public pieChartData: number[] = [30, 50, 20];
|
||||||
|
public pieChartDatasets: any[] = [
|
||||||
|
{
|
||||||
|
data: [30, 50, 20],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
public pieChartType: string = 'pie';
|
public pieChartType: string = 'pie';
|
||||||
public pieChartOptions: any = {
|
public pieChartOptions: any = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@@ -96,7 +102,7 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
noDataAvailable: boolean = false;
|
noDataAvailable: boolean = false;
|
||||||
|
|
||||||
// Flag to prevent infinite loops
|
// Flag to prevent infinite loops
|
||||||
private isFetchingData: boolean = false;
|
isFetchingData: boolean = false;
|
||||||
|
|
||||||
// Subscriptions to unsubscribe on destroy
|
// Subscriptions to unsubscribe on destroy
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
@@ -132,8 +138,18 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
console.log('PieChartComponent initialized with default data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
console.log('PieChartComponent initialized with default data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||||
// Validate initial data
|
// Validate initial data
|
||||||
this.validateChartData();
|
this.validateChartData();
|
||||||
|
// Initialize datasets with default data
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// Only fetch data if we have the required inputs, otherwise show default data
|
||||||
|
if (this.table && this.xAxis && this.yAxis) {
|
||||||
this.fetchChartData();
|
this.fetchChartData();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
console.log('PieChartComponent input changes:', changes);
|
console.log('PieChartComponent input changes:', changes);
|
||||||
@@ -165,6 +181,12 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
console.log('Chart configuration changed, fetching new data');
|
console.log('Chart configuration changed, fetching new data');
|
||||||
this.fetchChartData();
|
this.fetchChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have the required inputs and haven't fetched data yet, fetch it
|
||||||
|
if ((xAxisChanged || yAxisChanged || tableChanged) && this.table && this.xAxis && this.yAxis && !this.isFetchingData) {
|
||||||
|
console.log('Required inputs available, fetching data');
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -576,8 +598,6 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
if (data === null) {
|
if (data === null) {
|
||||||
console.warn('API returned null data. Check if the API endpoint is working correctly.');
|
console.warn('API returned null data. Check if the API endpoint is working correctly.');
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
return;
|
return;
|
||||||
@@ -588,9 +608,24 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.pieChartLabels = data.chartLabels;
|
this.pieChartLabels = data.chartLabels;
|
||||||
this.pieChartData = data.chartData;
|
|
||||||
|
// Extract the actual data values from the chartData array
|
||||||
|
// chartData is an array with one object containing the data
|
||||||
|
if (data.chartData.length > 0 && data.chartData[0].data) {
|
||||||
|
this.pieChartData = data.chartData[0].data;
|
||||||
|
} else {
|
||||||
|
this.pieChartData = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
|
this.pieChartLabels = [...this.pieChartLabels];
|
||||||
this.pieChartData = [...this.pieChartData];
|
this.pieChartData = [...this.pieChartData];
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
console.log('Updated pie chart with data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
console.log('Updated pie chart with data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||||
} else if (data && data.labels && data.datasets) {
|
} else if (data && data.labels && data.datasets) {
|
||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
@@ -598,13 +633,29 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
this.pieChartLabels = data.labels;
|
this.pieChartLabels = data.labels;
|
||||||
this.pieChartData = data.datasets[0]?.data || [];
|
this.pieChartData = data.datasets[0]?.data || [];
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
|
this.pieChartLabels = [...this.pieChartLabels];
|
||||||
this.pieChartData = [...this.pieChartData];
|
this.pieChartData = [...this.pieChartData];
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
console.log('Updated pie chart with legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
console.log('Updated pie chart with legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||||
} else {
|
} else {
|
||||||
console.warn('Received data does not have expected structure', data);
|
console.warn('Received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
// Keep default data if no data is available
|
||||||
this.pieChartData = [];
|
if (this.pieChartLabels.length === 0 && this.pieChartData.length === 0) {
|
||||||
|
this.pieChartLabels = ['Category A', 'Category B', 'Category C'];
|
||||||
|
this.pieChartData = [30, 50, 20];
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
@@ -612,18 +663,13 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching pie chart data:', error);
|
console.error('Error fetching pie chart data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
// Keep default data in case of error
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('Missing required data for chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
console.log('Missing required data for chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
}
|
}
|
||||||
@@ -652,8 +698,6 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
} else {
|
} else {
|
||||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,8 +708,6 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
if (!drilldownConfig || !drilldownConfig.apiUrl || !drilldownConfig.xAxis || !drilldownConfig.yAxis) {
|
||||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,8 +804,6 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
if (data === null) {
|
if (data === null) {
|
||||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,9 +812,24 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.pieChartLabels = data.chartLabels;
|
this.pieChartLabels = data.chartLabels;
|
||||||
this.pieChartData = data.chartData;
|
|
||||||
|
// Extract the actual data values from the chartData array
|
||||||
|
// chartData is an array with one object containing the data
|
||||||
|
if (data.chartData.length > 0 && data.chartData[0].data) {
|
||||||
|
this.pieChartData = data.chartData[0].data;
|
||||||
|
} else {
|
||||||
|
this.pieChartData = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
|
this.pieChartLabels = [...this.pieChartLabels];
|
||||||
this.pieChartData = [...this.pieChartData];
|
this.pieChartData = [...this.pieChartData];
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
console.log('Updated pie chart with drilldown data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
console.log('Updated pie chart with drilldown data:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||||
} else if (data && data.labels && data.datasets) {
|
} else if (data && data.labels && data.datasets) {
|
||||||
// Backend has already filtered the data, just display it
|
// Backend has already filtered the data, just display it
|
||||||
@@ -782,21 +837,24 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
this.pieChartLabels = data.labels;
|
this.pieChartLabels = data.labels;
|
||||||
this.pieChartData = data.datasets[0]?.data || [];
|
this.pieChartData = data.datasets[0]?.data || [];
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
|
this.pieChartLabels = [...this.pieChartLabels];
|
||||||
this.pieChartData = [...this.pieChartData];
|
this.pieChartData = [...this.pieChartData];
|
||||||
|
this.pieChartDatasets = [
|
||||||
|
{
|
||||||
|
data: this.pieChartData,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
console.log('Updated pie chart with drilldown legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
console.log('Updated pie chart with drilldown legacy data format:', { labels: this.pieChartLabels, data: this.pieChartData });
|
||||||
} else {
|
} else {
|
||||||
console.warn('Drilldown received data does not have expected structure', data);
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
// Keep current data if no data is available
|
||||||
this.pieChartData = [];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching drilldown data:', error);
|
console.error('Error fetching drilldown data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.pieChartLabels = [];
|
|
||||||
this.pieChartData = [];
|
|
||||||
// Keep current data in case of error
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -885,6 +943,26 @@ export class PieChartComponent implements OnInit, OnChanges, AfterViewChecked, O
|
|||||||
return this.chartColors[index % this.chartColors.length];
|
return this.chartColors[index % this.chartColors.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to determine if chart should be displayed
|
||||||
|
shouldShowChart(): boolean {
|
||||||
|
// Show chart if we have data
|
||||||
|
if (this.pieChartLabels.length > 0 && this.pieChartData.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show chart if we're still fetching data
|
||||||
|
if (this.isFetchingData) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show chart if we have default data
|
||||||
|
if (this.pieChartLabels.length > 0 && this.originalPieChartLabels.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
public chartClicked(e: any): void {
|
public chartClicked(e: any): void {
|
||||||
console.log('Pie chart clicked:', e);
|
console.log('Pie chart clicked:', e);
|
||||||
|
|||||||
@@ -279,10 +279,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="position: relative; height: calc(100% - 50px);">
|
<div style="position: relative; height: calc(100% - 80px); padding: 0 10px 30px 10px;">
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
[datasets]="polarAreaChartData"
|
[datasets]="polarAreaChartData"
|
||||||
[labels]="polarAreaChartLabels"
|
[labels]="polarAreaChartLabels"
|
||||||
|
[options]="polarAreaChartOptions"
|
||||||
[type]="polarAreaChartType"
|
[type]="polarAreaChartType"
|
||||||
(chartHover)="chartHovered($event)"
|
(chartHover)="chartHovered($event)"
|
||||||
(chartClick)="chartClicked($event)">
|
(chartClick)="chartClicked($event)">
|
||||||
|
|||||||
@@ -90,6 +90,32 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
{ data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'}
|
{ data: [ 300, 500, 100, 40, 120 ], label: 'Series 1'}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public polarAreaChartOptions: any = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
top: 10,
|
||||||
|
bottom: 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
r: {
|
||||||
|
ticks: {
|
||||||
|
backdropColor: 'rgba(0, 0, 0, 0)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public polarAreaChartType: string = 'polarArea';
|
public polarAreaChartType: string = 'polarArea';
|
||||||
|
|
||||||
// Multi-layer drilldown state tracking
|
// Multi-layer drilldown state tracking
|
||||||
@@ -511,7 +537,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.');
|
console.warn('Polar chart API returned null data. Check if the API endpoint is working correctly.');
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
return;
|
return;
|
||||||
@@ -524,32 +555,54 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.polarAreaChartLabels = data.chartLabels;
|
this.polarAreaChartLabels = data.chartLabels;
|
||||||
if (data.chartData && data.chartData.length > 0) {
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
// Convert the data to the expected format for polar area charts
|
||||||
|
const chartValues = data.chartData[0].data.map(value => {
|
||||||
// Convert to number if it's not already
|
// Convert to number if it's not already
|
||||||
return isNaN(Number(value)) ? 0 : Number(value);
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
});
|
});
|
||||||
} else {
|
// Assign data in the correct format (array of objects with data property)
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: chartValues,
|
||||||
|
label: data.chartData[0].label || 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
// Trigger change detection
|
|
||||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
|
||||||
console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
console.log('Updated polar chart with data:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
} else if (data && data.labels && data.data) {
|
} else if (data && data.labels && data.data) {
|
||||||
// Handle the original expected format as fallback
|
// Handle the original expected format as fallback
|
||||||
this.noDataAvailable = data.labels.length === 0;
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
this.polarAreaChartLabels = data.labels;
|
this.polarAreaChartLabels = data.labels;
|
||||||
this.polarAreaChartData = data.data.map(value => {
|
// Convert the data to the expected format for polar area charts
|
||||||
|
const chartValues = data.data.map(value => {
|
||||||
// Convert to number if it's not already
|
// Convert to number if it's not already
|
||||||
return isNaN(Number(value)) ? 0 : Number(value);
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
});
|
});
|
||||||
// Trigger change detection
|
// Assign data in the correct format (array of objects with data property)
|
||||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: chartValues,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
console.log('Updated polar chart with legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
} else {
|
} else {
|
||||||
console.warn('Polar chart received data does not have expected structure', data);
|
console.warn('Polar chart received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
@@ -558,7 +611,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.error('Error fetching polar chart data:', error);
|
console.error('Error fetching polar chart data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
// Keep default data in case of error
|
// Keep default data in case of error
|
||||||
@@ -568,7 +626,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
console.log('Missing required data for polar chart:', { table: this.table, xAxis: this.xAxis, yAxis: this.yAxis, connection: this.connection });
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
// Reset flag after fetching
|
// Reset flag after fetching
|
||||||
this.isFetchingData = false;
|
this.isFetchingData = false;
|
||||||
}
|
}
|
||||||
@@ -598,7 +661,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Invalid drilldown layer index:', layerIndex);
|
console.warn('Invalid drilldown layer index:', layerIndex);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +678,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
console.warn('Missing drilldown configuration for level:', this.currentDrilldownLevel);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +781,12 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
console.warn('Drilldown API returned null data. Check if the API endpoint is working correctly.');
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,12 +796,25 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
this.noDataAvailable = data.chartLabels.length === 0;
|
this.noDataAvailable = data.chartLabels.length === 0;
|
||||||
this.polarAreaChartLabels = data.chartLabels;
|
this.polarAreaChartLabels = data.chartLabels;
|
||||||
if (data.chartData && data.chartData.length > 0) {
|
if (data.chartData && data.chartData.length > 0) {
|
||||||
this.polarAreaChartData = data.chartData[0].data.map(value => {
|
// Convert the data to the expected format for polar area charts
|
||||||
|
const chartValues = data.chartData[0].data.map(value => {
|
||||||
// Convert to number if it's not already
|
// Convert to number if it's not already
|
||||||
return isNaN(Number(value)) ? 0 : Number(value);
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
});
|
});
|
||||||
|
// Assign data in the correct format (array of objects with data property)
|
||||||
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: chartValues,
|
||||||
|
label: data.chartData[0].label || 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
@@ -732,10 +823,18 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
// Handle the original expected format as fallback
|
// Handle the original expected format as fallback
|
||||||
this.noDataAvailable = data.labels.length === 0;
|
this.noDataAvailable = data.labels.length === 0;
|
||||||
this.polarAreaChartLabels = data.labels;
|
this.polarAreaChartLabels = data.labels;
|
||||||
this.polarAreaChartData = data.data.map(value => {
|
// Convert the data to the expected format for polar area charts
|
||||||
|
const chartValues = data.data.map(value => {
|
||||||
// Convert to number if it's not already
|
// Convert to number if it's not already
|
||||||
return isNaN(Number(value)) ? 0 : Number(value);
|
return isNaN(Number(value)) ? 0 : Number(value);
|
||||||
});
|
});
|
||||||
|
// Assign data in the correct format (array of objects with data property)
|
||||||
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: chartValues,
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
// Trigger change detection
|
// Trigger change detection
|
||||||
this.polarAreaChartData = [...this.polarAreaChartData];
|
this.polarAreaChartData = [...this.polarAreaChartData];
|
||||||
console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
console.log('Updated polar chart with drilldown legacy data format:', { labels: this.polarAreaChartLabels, data: this.polarAreaChartData });
|
||||||
@@ -743,14 +842,24 @@ export class PolarChartComponent implements OnInit, OnChanges {
|
|||||||
console.warn('Drilldown received data does not have expected structure', data);
|
console.warn('Drilldown received data does not have expected structure', data);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Error fetching drilldown data:', error);
|
console.error('Error fetching drilldown data:', error);
|
||||||
this.noDataAvailable = true;
|
this.noDataAvailable = true;
|
||||||
this.polarAreaChartLabels = [];
|
this.polarAreaChartLabels = [];
|
||||||
this.polarAreaChartData = [];
|
this.polarAreaChartData = [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}
|
||||||
|
];
|
||||||
// Keep current data in case of error
|
// Keep current data in case of error
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './unified-chart.component';
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
<div class="chart-container" *ngIf="!noDataAvailable && !isLoading">
|
||||||
|
<!-- Back button for drilldown navigation -->
|
||||||
|
<div class="drilldown-back" *ngIf="currentDrilldownLevel > 0">
|
||||||
|
<button class="btn btn-sm btn-secondary" (click)="navigateBack()">
|
||||||
|
<clr-icon shape="arrow" dir="left"></clr-icon>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<span class="drilldown-level">Level: {{ currentDrilldownLevel }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart title -->
|
||||||
|
<div class="chart-title" *ngIf="charttitle">
|
||||||
|
<h4>{{ charttitle }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter toggle icon -->
|
||||||
|
<div class="filter-toggle-icon" *ngIf="hasFilters()" (click)="toggleFilters()">
|
||||||
|
<clr-icon shape="filter" size="24"
|
||||||
|
[style.color]="showFilters ? '#007cba' : '#666'"
|
||||||
|
title="Toggle Filters">
|
||||||
|
</clr-icon>
|
||||||
|
<span>
|
||||||
|
{{ showFilters ? 'Hide Filters' : 'Show Filters' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Render different chart types based on chartType input -->
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<!-- Dynamic Chart Container - uses extracted dynamic options and styles but static template -->
|
||||||
|
<div *ngIf="dynamicTemplate || chartType" class="chart-canvas-container">
|
||||||
|
<canvas baseChart
|
||||||
|
[datasets]="chartData"
|
||||||
|
[labels]="chartLabels"
|
||||||
|
[options]="chartOptions"
|
||||||
|
[legend]="chartLegend"
|
||||||
|
[chartType]="chartType || 'bar'"
|
||||||
|
(chartClick)="chartClicked($event)"
|
||||||
|
(chartHover)="chartHovered($event)">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fallback for when no chart type is specified -->
|
||||||
|
<div *ngIf="!dynamicTemplate && !chartType" class="chart-canvas-container">
|
||||||
|
<canvas baseChart
|
||||||
|
[datasets]="chartData"
|
||||||
|
[labels]="chartLabels"
|
||||||
|
[options]="chartOptions"
|
||||||
|
[legend]="chartLegend"
|
||||||
|
[chartType]="'bar'"
|
||||||
|
(chartClick)="chartClicked($event)"
|
||||||
|
(chartHover)="chartHovered($event)">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Collapsible Base Filters -->
|
||||||
|
<div class="filters-section" *ngIf="baseFilters && baseFilters.length > 0 && showFilters">
|
||||||
|
<h5>Filters</h5>
|
||||||
|
<div class="filters-container">
|
||||||
|
<div class="filter-item" *ngFor="let filter of baseFilters; let i = index">
|
||||||
|
<!-- Text Filter -->
|
||||||
|
<div *ngIf="!filter.type || filter.type === 'text'" class="filter-text">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<input type="text" [(ngModel)]="filter.value" (ngModelChange)="onBaseFilterChange(filter)"
|
||||||
|
class="form-control" placeholder="Enter {{ filter.field || 'value' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dropdown Filter -->
|
||||||
|
<div *ngIf="filter.type === 'dropdown'" class="filter-dropdown">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<select [(ngModel)]="filter.value" (ngModelChange)="onBaseFilterChange(filter)" class="form-control">
|
||||||
|
<option value="">Select {{ filter.field || 'value' }}</option>
|
||||||
|
<option *ngFor="let option of getFilterOptions(filter)" [value]="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Multiselect Filter -->
|
||||||
|
<div *ngIf="filter.type === 'multiselect'" class="filter-multiselect">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="multiselect-container">
|
||||||
|
<div class="multiselect-display" (click)="toggleMultiselect(filter, 'base')">
|
||||||
|
<span *ngIf="!filter.value || (Array.isArray(filter.value) && filter.value.length === 0)">Select {{ filter.field || 'options' }}</span>
|
||||||
|
<span *ngIf="filter.value && !Array.isArray(filter.value)">
|
||||||
|
{{ filter.value }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="filter.value && Array.isArray(filter.value) && filter.value.length > 0">
|
||||||
|
{{ filter.value.length }} selected
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'base')">
|
||||||
|
<div class="multiselect-option" *ngFor="let option of getFilterOptions(filter)">
|
||||||
|
<input type="checkbox"
|
||||||
|
[checked]="isOptionSelected(filter, option)"
|
||||||
|
(change)="onMultiSelectChange(filter, option, $event)"
|
||||||
|
id="base-{{ filter.field }}-{{ option }}">
|
||||||
|
<label [for]="'base-' + filter.field + '-' + option">{{ option }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date Range Filter -->
|
||||||
|
<div *ngIf="filter.type === 'date-range'" class="filter-date-range">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="date-range-inputs">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.start" (ngModelChange)="onDateRangeInputChange(filter, 'start', $event)"
|
||||||
|
class="form-control" placeholder="Start Date">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.end" (ngModelChange)="onDateRangeInputChange(filter, 'end', $event)"
|
||||||
|
class="form-control" placeholder="End Date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toggle Filter -->
|
||||||
|
<div *ngIf="filter.type === 'toggle'" class="filter-toggle">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="toggle-switch">
|
||||||
|
<input type="checkbox" [(ngModel)]="filter.value" (ngModelChange)="onToggleChange(filter, $event.target.checked)"
|
||||||
|
id="toggle-{{ filter.field }}">
|
||||||
|
<label [for]="'toggle-' + filter.field" class="toggle-label">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Drilldown Filters -->
|
||||||
|
<div class="filters-section" *ngIf="drilldownFilters && drilldownFilters.length > 0 && currentDrilldownLevel > 0 && showFilters">
|
||||||
|
<h5>Drilldown Filters</h5>
|
||||||
|
<div class="filters-container">
|
||||||
|
<div class="filter-item" *ngFor="let filter of drilldownFilters; let i = index">
|
||||||
|
<!-- Text Filter -->
|
||||||
|
<div *ngIf="!filter.type || filter.type === 'text'" class="filter-text">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<input type="text" [(ngModel)]="filter.value" (ngModelChange)="onDrilldownFilterChange(filter)"
|
||||||
|
class="form-control" placeholder="Enter {{ filter.field || 'value' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dropdown Filter -->
|
||||||
|
<div *ngIf="filter.type === 'dropdown'" class="filter-dropdown">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<select [(ngModel)]="filter.value" (ngModelChange)="onDrilldownFilterChange(filter)" class="form-control">
|
||||||
|
<option value="">Select {{ filter.field || 'value' }}</option>
|
||||||
|
<option *ngFor="let option of getFilterOptions(filter)" [value]="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Multiselect Filter -->
|
||||||
|
<div *ngIf="filter.type === 'multiselect'" class="filter-multiselect">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="multiselect-container">
|
||||||
|
<div class="multiselect-display" (click)="toggleMultiselect(filter, 'drilldown')">
|
||||||
|
<span *ngIf="!filter.value || (Array.isArray(filter.value) && filter.value.length === 0)">Select {{ filter.field || 'options' }}</span>
|
||||||
|
<span *ngIf="filter.value && !Array.isArray(filter.value)">
|
||||||
|
{{ filter.value }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="filter.value && Array.isArray(filter.value) && filter.value.length > 0">
|
||||||
|
{{ filter.value.length }} selected
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'drilldown')">
|
||||||
|
<div class="multiselect-option" *ngFor="let option of getFilterOptions(filter)">
|
||||||
|
<input type="checkbox"
|
||||||
|
[checked]="isOptionSelected(filter, option)"
|
||||||
|
(change)="onMultiSelectChange(filter, option, $event)"
|
||||||
|
id="drilldown-{{ filter.field }}-{{ option }}">
|
||||||
|
<label [for]="'drilldown-' + filter.field + '-' + option">{{ option }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date Range Filter -->
|
||||||
|
<div *ngIf="filter.type === 'date-range'" class="filter-date-range">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="date-range-inputs">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.start" (ngModelChange)="onDateRangeInputChange(filter, 'start', $event)"
|
||||||
|
class="form-control" placeholder="Start Date">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.end" (ngModelChange)="onDateRangeInputChange(filter, 'end', $event)"
|
||||||
|
class="form-control" placeholder="End Date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toggle Filter -->
|
||||||
|
<div *ngIf="filter.type === 'toggle'" class="filter-toggle">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="toggle-switch">
|
||||||
|
<input type="checkbox" [(ngModel)]="filter.value" (ngModelChange)="onToggleChange(filter, $event.target.checked)"
|
||||||
|
id="drilldown-toggle-{{ filter.field }}">
|
||||||
|
<label [for]="'drilldown-toggle-' + filter.field" class="toggle-label">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Layer Filters -->
|
||||||
|
<div class="filters-section" *ngIf="hasActiveLayerFilters() && showFilters">
|
||||||
|
<h5>Layer Filters</h5>
|
||||||
|
<div class="filters-container">
|
||||||
|
<div class="filter-item" *ngFor="let filter of getActiveLayerFilters(); let i = index">
|
||||||
|
<!-- Text Filter -->
|
||||||
|
<div *ngIf="!filter.type || filter.type === 'text'" class="filter-text">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<input type="text" [(ngModel)]="filter.value" (ngModelChange)="onLayerFilterChange(filter)"
|
||||||
|
class="form-control" placeholder="Enter {{ filter.field || 'value' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dropdown Filter -->
|
||||||
|
<div *ngIf="filter.type === 'dropdown'" class="filter-dropdown">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<select [(ngModel)]="filter.value" (ngModelChange)="onLayerFilterChange(filter)" class="form-control">
|
||||||
|
<option value="">Select {{ filter.field || 'value' }}</option>
|
||||||
|
<option *ngFor="let option of getFilterOptions(filter)" [value]="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Multiselect Filter -->
|
||||||
|
<div *ngIf="filter.type === 'multiselect'" class="filter-multiselect">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="multiselect-container">
|
||||||
|
<div class="multiselect-display" (click)="toggleMultiselect(filter, 'layer')">
|
||||||
|
<span *ngIf="!filter.value || (Array.isArray(filter.value) && filter.value.length === 0)">Select {{ filter.field || 'options' }}</span>
|
||||||
|
<span *ngIf="filter.value && !Array.isArray(filter.value)">
|
||||||
|
{{ filter.value }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="filter.value && Array.isArray(filter.value) && filter.value.length > 0">
|
||||||
|
{{ filter.value.length }} selected
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="multiselect-dropdown" *ngIf="isMultiselectOpen(filter, 'layer')">
|
||||||
|
<div class="multiselect-option" *ngFor="let option of getFilterOptions(filter)">
|
||||||
|
<input type="checkbox"
|
||||||
|
[checked]="isOptionSelected(filter, option)"
|
||||||
|
(change)="onMultiSelectChange(filter, option, $event)"
|
||||||
|
id="layer-{{ filter.field }}-{{ option }}">
|
||||||
|
<label [for]="'layer-' + filter.field + '-' + option">{{ option }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date Range Filter -->
|
||||||
|
<div *ngIf="filter.type === 'date-range'" class="filter-date-range">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="date-range-inputs">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.start" (ngModelChange)="onDateRangeInputChange(filter, 'start', $event)"
|
||||||
|
class="form-control" placeholder="Start Date">
|
||||||
|
<input type="date" [(ngModel)]="filter.value.end" (ngModelChange)="onDateRangeInputChange(filter, 'end', $event)"
|
||||||
|
class="form-control" placeholder="End Date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toggle Filter -->
|
||||||
|
<div *ngIf="filter.type === 'toggle'" class="filter-toggle">
|
||||||
|
<label>{{ filter.field || 'Filter ' + (i + 1) }}</label>
|
||||||
|
<div class="toggle-switch">
|
||||||
|
<input type="checkbox" [(ngModel)]="filter.value" (ngModelChange)="onToggleChange(filter, $event.target.checked)"
|
||||||
|
id="layer-toggle-{{ filter.field }}">
|
||||||
|
<label [for]="'layer-toggle-' + filter.field" class="toggle-label">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Clear Filters Button -->
|
||||||
|
<div class="clear-filters" *ngIf="hasActiveFilters() && showFilters">
|
||||||
|
<button class="btn btn-sm btn-outline" (click)="clearAllFilters()">
|
||||||
|
Clear All Filters
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- No Data Available Message -->
|
||||||
|
<div class="no-data-message" *ngIf="noDataAvailable && !isLoading">
|
||||||
|
<p>No data available for the selected filters.</p>
|
||||||
|
<button class="btn btn-sm btn-primary" (click)="fetchChartData()">Retry</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Indicator -->
|
||||||
|
<div class="loading-indicator" *ngIf="isLoading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Loading chart data...</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drilldown-back {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.drilldown-level {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
min-height: 400px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggle-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
clr-icon {
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #007cba !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover span {
|
||||||
|
color: #007cba;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
flex: 1 1 200px;
|
||||||
|
min-width: 150px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-text,
|
||||||
|
.filter-dropdown,
|
||||||
|
.filter-date-range {
|
||||||
|
.form-control {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-multiselect {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.multiselect-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-display {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1000;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect-option {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-label {
|
||||||
|
background-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-label .toggle-slider {
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-filters {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 300px;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 300px;
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.filters-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UnifiedChartComponent } from './unified-chart.component';
|
||||||
|
|
||||||
|
describe('UnifiedChartComponent', () => {
|
||||||
|
let component: UnifiedChartComponent;
|
||||||
|
let fixture: ComponentFixture<UnifiedChartComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [UnifiedChartComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(UnifiedChartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -51,8 +51,7 @@ export class DashrunnerallComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getdashboard()
|
getdashboard() {
|
||||||
{
|
|
||||||
this.dashboardService.getAllDash().subscribe((data) => {
|
this.dashboardService.getAllDash().subscribe((data) => {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.rows = this.data;
|
this.rows = this.data;
|
||||||
@@ -62,17 +61,23 @@ export class DashrunnerallComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openModal()
|
openModal() {
|
||||||
{
|
|
||||||
this.addModall = true;
|
this.addModall = true;
|
||||||
}
|
}
|
||||||
gotoadd()
|
gotoadd() {
|
||||||
{
|
|
||||||
this.router.navigate(['../../dashboardbuilder'], { relativeTo: this.route });
|
this.router.navigate(['../../dashboardbuilder'], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
goToEdit(id:number)
|
// for runner line navigation
|
||||||
{
|
// goToEditData(id: number){
|
||||||
this.router.navigate(['../dashrunner/'+id],{relativeTo:this.route});
|
// this.router.navigate(['../editdata/'+id],{relativeTo:this.route});
|
||||||
|
// }
|
||||||
|
goToEdit(id: number) {
|
||||||
|
// Navigate to editnewdash component instead of dashrunnerline
|
||||||
|
// Pass a query parameter to indicate this is from dashboard runner
|
||||||
|
this.router.navigate(['../../dashboardbuilder/editdashn/' + id], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: { fromRunner: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goToEditData(id: number) {
|
goToEditData(id: number) {
|
||||||
@@ -96,8 +101,7 @@ export class DashrunnerallComponent implements OnInit {
|
|||||||
console.log(this.rowSelected);
|
console.log(this.rowSelected);
|
||||||
this.modalDelete = true;
|
this.modalDelete = true;
|
||||||
}
|
}
|
||||||
delete(id)
|
delete(id) {
|
||||||
{
|
|
||||||
this.modalDelete = false;
|
this.modalDelete = false;
|
||||||
console.log("in delete " + id);
|
console.log("in delete " + id);
|
||||||
this.dashboardService.deleteField(id).subscribe((data) => {
|
this.dashboardService.deleteField(id).subscribe((data) => {
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export class ReportbuildqueryComponent implements OnInit {
|
|||||||
name;
|
name;
|
||||||
databaseName;
|
databaseName;
|
||||||
databasename(val) {
|
databasename(val) {
|
||||||
console.log(val);
|
console.log('connection ', val);
|
||||||
this.databaseName = val.name;
|
this.databaseName = val.name;
|
||||||
this.selecteddatabase = val.conn_string;
|
this.selecteddatabase = val.conn_string;
|
||||||
console.log(this.selecteddatabase);
|
console.log(this.selecteddatabase);
|
||||||
|
|||||||
@@ -23,6 +23,18 @@
|
|||||||
<label for="workflow_name">{{'ACTIVE'| translate}}</label>
|
<label for="workflow_name">{{'ACTIVE'| translate}}</label>
|
||||||
<input type="checkbox" formControlName="active" clrToggle value="billable" name="billable" />
|
<input type="checkbox" formControlName="active" clrToggle value="billable" name="billable" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SureConnect Dropdown -->
|
||||||
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
|
<label for="sureConnectId">SureConnect Connection</label>
|
||||||
|
<select formControlName="sureConnectId" class="clr-select">
|
||||||
|
<option value="">-- Select SureConnect --</option>
|
||||||
|
<option *ngFor="let conn of sureconnectData" [value]="conn.id">
|
||||||
|
{{conn.connection_name || conn.id}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<div class="clr-col-md-4 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="url">Get URL</label>
|
<label for="url">Get URL</label>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { FormBuilder, FormGroup } from '@angular/forms';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ReportBuilderService } from 'src/app/services/api/report-builder.service';
|
import { ReportBuilderService } from 'src/app/services/api/report-builder.service';
|
||||||
|
import { SureconnectService } from '../../dashboardnew/sureconnect/sureconnect.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-report-build2add',
|
selector: 'app-report-build2add',
|
||||||
templateUrl: './report-build2add.component.html',
|
templateUrl: './report-build2add.component.html',
|
||||||
@@ -11,8 +13,12 @@ import { ReportBuilderService } from 'src/app/services/api/report-builder.servic
|
|||||||
})
|
})
|
||||||
export class ReportBuild2addComponent implements OnInit {
|
export class ReportBuild2addComponent implements OnInit {
|
||||||
public entryForm: FormGroup;
|
public entryForm: FormGroup;
|
||||||
|
// Add sureconnect data property
|
||||||
|
sureconnectData: any[] = [];
|
||||||
|
|
||||||
constructor(private _fb: FormBuilder, private router: Router,private toastr: ToastrService,
|
constructor(private _fb: FormBuilder, private router: Router,private toastr: ToastrService,
|
||||||
private route: ActivatedRoute,private reportBuilderService: ReportBuilderService) { }
|
private route: ActivatedRoute,private reportBuilderService: ReportBuilderService,
|
||||||
|
private sureconnectService: SureconnectService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.entryForm = this._fb.group({
|
this.entryForm = this._fb.group({
|
||||||
@@ -20,9 +26,23 @@ export class ReportBuild2addComponent implements OnInit {
|
|||||||
description:[null],
|
description:[null],
|
||||||
active:[null],
|
active:[null],
|
||||||
isSql:[false],
|
isSql:[false],
|
||||||
|
// Add sureConnectId field to the form
|
||||||
|
sureConnectId: [null],
|
||||||
Rpt_builder2_lines: this._fb.array([this.initLinesFormReport()]),
|
Rpt_builder2_lines: this._fb.array([this.initLinesFormReport()]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load sureconnect 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);
|
||||||
|
}, (error) => {
|
||||||
|
console.log('Error loading sureconnect data:', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initLinesFormReport() {
|
initLinesFormReport() {
|
||||||
|
|||||||
@@ -22,37 +22,54 @@
|
|||||||
|
|
||||||
<clr-datagrid [clrDgLoading]="loading">
|
<clr-datagrid [clrDgLoading]="loading">
|
||||||
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{'LOADING' | translate}}</clr-spinner></ng-template>
|
<clr-dg-placeholder><ng-template #loadingSpinner><clr-spinner>{{'LOADING' | translate}}</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]="''"><ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="''"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{'GO_TO' | translate}}
|
{{'GO_TO' | translate}}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'name'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'name'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{'REPORT_NAME' | translate}}
|
{{'REPORT_NAME' | translate}}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
<clr-dg-column [clrDgField]="'description'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'description'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{'REPORT_DESCRIPTION' | translate}}
|
{{'REPORT_DESCRIPTION' | translate}}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-column [clrDgField]="''"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
Sureconnect
|
||||||
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'active'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'active'"><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
{{'ACTIVE' | translate}}
|
{{'ACTIVE' | translate}}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<clr-dg-column><ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column><ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
<clr-icon shape="bars"></clr-icon>{{'ACTION' | translate}}
|
<clr-icon shape="bars"></clr-icon>{{'ACTION' | translate}}
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row *clrDgItems="let user of gridData?.slice()?.reverse();" [clrDgItem]="user">
|
<clr-dg-row *clrDgItems="let user of gridData?.slice()?.reverse();" [clrDgItem]="user">
|
||||||
<clr-dg-cell><span class="label label-light-blue" style="display: inline;margin-left: 10px; cursor: pointer;" (click)="goToLines(user)"> {{'SET_UP' | translate}}</span></clr-dg-cell>
|
<clr-dg-cell><span class="label label-light-blue" style="display: inline;margin-left: 10px; cursor: pointer;"
|
||||||
|
(click)="goToLines(user)"> {{'SET_UP' | translate}}</span></clr-dg-cell>
|
||||||
<clr-dg-cell id="word">{{user.reportName}}</clr-dg-cell>
|
<clr-dg-cell id="word">{{user.reportName}}</clr-dg-cell>
|
||||||
<clr-dg-cell id="word">{{user.description}}</clr-dg-cell>
|
<clr-dg-cell id="word">{{user.description}}</clr-dg-cell>
|
||||||
|
|
||||||
|
<clr-dg-cell id="word">{{user.sureconnect_name}}</clr-dg-cell>
|
||||||
<clr-dg-cell id="word">{{user.active}}</clr-dg-cell>
|
<clr-dg-cell id="word">{{user.active}}</clr-dg-cell>
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true" class="tooltip tooltip-sm tooltip-top-left">
|
<a href="javascript:void(0)" style="padding-right: 10px;" role="tooltip" aria-haspopup="true"
|
||||||
<span style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error" style="color: red;"></clr-icon></span>
|
class="tooltip tooltip-sm tooltip-top-left">
|
||||||
|
<span style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error"
|
||||||
|
style="color: red;"></clr-icon></span>
|
||||||
<span class="tooltip-content"> {{'DELETE' | translate}}</span>
|
<span class="tooltip-content"> {{'DELETE' | translate}}</span>
|
||||||
</a>
|
</a>
|
||||||
<clr-signpost>
|
<clr-signpost>
|
||||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" style="color: rgb(0, 130, 236);"></clr-icon></span>
|
<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>
|
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
|
||||||
<h5 style="margin-top: 0">{{'WHO_COLUMN' | translate}}</h5>
|
<h5 style="margin-top: 0">{{'WHO_COLUMN' | translate}}</h5>
|
||||||
<div>{{'ACCOUNT_ID' | translate}}: <code class="clr-code">{{user.accountId}}</code></div>
|
<div>{{'ACCOUNT_ID' | translate}}: <code class="clr-code">{{user.accountId}}</code></div>
|
||||||
|
|||||||
@@ -26,20 +26,35 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
<div class="clr-col-md-4 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="url">Get URL</label>
|
<label for="url">Get URL</label>
|
||||||
<div> <input type="text" class="clr-input" formControlName="url" name="url" [(ngModel)]="nodeEditProperties.url" placeholder="Enter Url" style="width: 76%"> <span><button class="btn btn-icon btn-primary" (click)="getkeys()">
|
<div> <input type="text" class="clr-input" formControlName="url" name="url"
|
||||||
|
[(ngModel)]="nodeEditProperties.url" placeholder="Enter Url" style="width: 76%"> <span><button
|
||||||
|
class="btn btn-icon btn-primary" (click)="getkeys()">
|
||||||
<clr-icon shape="view-list"></clr-icon>
|
<clr-icon shape="view-list"></clr-icon>
|
||||||
</button></span></div>
|
</button></span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clr-col-md-4 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label for="workflow_name">Include Date filter</label>
|
<label for="workflow_name">Include Date filter</label>
|
||||||
<input type="checkbox" formControlName="date_param_req" name="date_param_req" [(ngModel)]="nodeEditProperties.date_param_req" clrToggle />
|
<input type="checkbox" formControlName="date_param_req" name="date_param_req"
|
||||||
|
[(ngModel)]="nodeEditProperties.date_param_req" clrToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SureConnect Dropdown -->
|
||||||
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
|
<label for="sureConnectId">SureConnect Connection</label>
|
||||||
|
<select formControlName="sureConnectId" class="clr-select">
|
||||||
|
<option value="">-- Select SureConnect --</option>
|
||||||
|
<option *ngFor="let conn of sureconnectData" [value]="conn.id">
|
||||||
|
{{conn.name || conn.id}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="clr-col-md-4 clr-col-sm-12">
|
<div class="clr-col-md-4 clr-col-sm-12">
|
||||||
<label>Standard Parameters</label>
|
<label>Standard Parameters</label>
|
||||||
<clr-combobox-container style="margin-top: 0; padding-top: 0;">
|
<clr-combobox-container style="margin-top: 0; padding-top: 0;">
|
||||||
<!-- <label style="padding-bottom: 5px; padding-top:0px; font-weight: lighter;" class="p1">Select Left Side Filter</label> -->
|
<!-- <label style="padding-bottom: 5px; padding-top:0px; font-weight: lighter;" class="p1">Select Left Side Filter</label> -->
|
||||||
<clr-combobox formControlName="std_param_html" name="std_param_html" [(ngModel)]="nodeEditProperties.std_param_html" clrMulti="true"
|
<clr-combobox formControlName="std_param_html" name="std_param_html"
|
||||||
required>
|
[(ngModel)]="nodeEditProperties.std_param_html" clrMulti="true" required>
|
||||||
<ng-container *clrOptionSelected="let selected">
|
<ng-container *clrOptionSelected="let selected">
|
||||||
{{selected}}
|
{{selected}}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -92,4 +107,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ import { FormBuilder, FormGroup } from '@angular/forms';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ReportBuilderService } from 'src/app/services/api/report-builder.service';
|
import { ReportBuilderService } from 'src/app/services/api/report-builder.service';
|
||||||
|
import { SureconnectService } from '../../dashboardnew/sureconnect/sureconnect.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -23,11 +24,17 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
date_param_req: '',
|
date_param_req: '',
|
||||||
// folderName:'',
|
// folderName:'',
|
||||||
url: '',
|
url: '',
|
||||||
|
// Add sureConnectId property
|
||||||
|
sureConnectId: null,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
// Add sureconnect data property
|
||||||
|
sureconnectData: any[] = [];
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private route: ActivatedRoute, private reportBuilderService: ReportBuilderService,
|
private route: ActivatedRoute, private reportBuilderService: ReportBuilderService,
|
||||||
private toastr: ToastrService, private _fb: FormBuilder) { }
|
private toastr: ToastrService, private _fb: FormBuilder,
|
||||||
|
private sureconnectService: SureconnectService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.id = this.route.snapshot.params["id"];
|
this.id = this.route.snapshot.params["id"];
|
||||||
@@ -41,11 +48,26 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
date_param_req: [null],
|
date_param_req: [null],
|
||||||
// folderName:[null],
|
// folderName:[null],
|
||||||
url: [null],
|
url: [null],
|
||||||
|
// Add sureConnectId to form
|
||||||
|
sureConnectId: [null],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load sureconnect data first, then load report data
|
||||||
|
this.loadSureconnectData();
|
||||||
this.getById(this.id);
|
this.getById(this.id);
|
||||||
this.listoddatabase();
|
this.listoddatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add method to load sureconnect data
|
||||||
|
loadSureconnectData() {
|
||||||
|
this.sureconnectService.getAll().subscribe((data: any[]) => {
|
||||||
|
this.sureconnectData = data;
|
||||||
|
console.log('Sureconnect data loaded:', this.sureconnectData);
|
||||||
|
}, (error) => {
|
||||||
|
console.log('Error loading sureconnect data:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
databaselist;
|
databaselist;
|
||||||
listoddatabase() {
|
listoddatabase() {
|
||||||
this.reportBuilderService.getdatabse().subscribe((data) => {
|
this.reportBuilderService.getdatabse().subscribe((data) => {
|
||||||
@@ -71,8 +93,7 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
this.builderLine = this.ReportData.rpt_builder2_lines;
|
this.builderLine = this.ReportData.rpt_builder2_lines;
|
||||||
this.lineId = this.builderLine[0].id
|
this.lineId = this.builderLine[0].id
|
||||||
console.log("line data ", this.lineId, this.builderLine);
|
console.log("line data ", this.lineId, this.builderLine);
|
||||||
if(this.builderLine[0].model != '')
|
if (this.builderLine[0].model != '') {
|
||||||
{
|
|
||||||
this.builderLineData = JSON.parse(this.builderLine[0].model);
|
this.builderLineData = JSON.parse(this.builderLine[0].model);
|
||||||
console.log(this.builderLineData);
|
console.log(this.builderLineData);
|
||||||
|
|
||||||
@@ -82,6 +103,11 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
// this.nodeEditProperties.conn_name = this.builderLineData.conn_name;
|
// this.nodeEditProperties.conn_name = this.builderLineData.conn_name;
|
||||||
this.nodeEditProperties.date_param_req = this.builderLineData[0].date_param_req;
|
this.nodeEditProperties.date_param_req = this.builderLineData[0].date_param_req;
|
||||||
this.nodeEditProperties.url = this.builderLineData[0].url;
|
this.nodeEditProperties.url = this.builderLineData[0].url;
|
||||||
|
// Set sureConnectId if it exists in the data
|
||||||
|
this.nodeEditProperties.sureConnectId = this.builderLineData[0].sureConnectId || null;
|
||||||
|
|
||||||
|
// Update form with loaded data
|
||||||
|
this.entryForm.patchValue(this.nodeEditProperties);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
@@ -95,7 +121,7 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
getkeys() {
|
getkeys() {
|
||||||
if (this.nodeEditProperties.url !== null) {
|
if (this.nodeEditProperties.url !== null) {
|
||||||
this.reportBuilderService.getcolumnDetailsByurl(this.nodeEditProperties.url).subscribe(data => {
|
this.reportBuilderService.getcolumnDetailsByurl(this.nodeEditProperties.url).subscribe(data => {
|
||||||
console.log(data);
|
console.log('coloum list data ', data);
|
||||||
this.keysfromurl = data;
|
this.keysfromurl = data;
|
||||||
this.nodeEditProperties.adhoc_param_html = this.keysfromurl;
|
this.nodeEditProperties.adhoc_param_html = this.keysfromurl;
|
||||||
})
|
})
|
||||||
@@ -116,6 +142,8 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
adhoc_param_html: this.nodeEditProperties.adhoc_param_html,
|
adhoc_param_html: this.nodeEditProperties.adhoc_param_html,
|
||||||
date_param_req: this.nodeEditProperties.date_param_req,
|
date_param_req: this.nodeEditProperties.date_param_req,
|
||||||
url: this.nodeEditProperties.url,
|
url: this.nodeEditProperties.url,
|
||||||
|
// Add sureConnectId to the data
|
||||||
|
sureConnectId: this.nodeEditProperties.sureConnectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.builderLineData[0].std_param_html = this.nodeEditProperties.std_param_html;
|
this.builderLineData[0].std_param_html = this.nodeEditProperties.std_param_html;
|
||||||
@@ -124,6 +152,9 @@ export class ReportBuild2editComponent implements OnInit {
|
|||||||
// this.builderLineData.conn_name = this.nodeEditProperties.conn_name ;
|
// this.builderLineData.conn_name = this.nodeEditProperties.conn_name ;
|
||||||
this.builderLineData[0].date_param_req = this.nodeEditProperties.date_param_req;
|
this.builderLineData[0].date_param_req = this.nodeEditProperties.date_param_req;
|
||||||
this.builderLineData[0].url = this.nodeEditProperties.url;
|
this.builderLineData[0].url = this.nodeEditProperties.url;
|
||||||
|
// Add sureConnectId to the data
|
||||||
|
this.builderLineData[0].sureConnectId = this.nodeEditProperties.sureConnectId;
|
||||||
|
|
||||||
console.log(this.builderLineData);
|
console.log(this.builderLineData);
|
||||||
// this.builderLineData.splice(1);
|
// this.builderLineData.splice(1);
|
||||||
console.log(this.builderLineData);
|
console.log(this.builderLineData);
|
||||||
@@ -149,6 +180,8 @@ console.log(this.listBuilder_Lines);
|
|||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.updated = true;
|
this.updated = true;
|
||||||
|
// Update nodeEditProperties with form values including sureConnectId
|
||||||
|
Object.assign(this.nodeEditProperties, this.entryForm.value);
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,11 @@ todayDate;
|
|||||||
adhocList:any[];
|
adhocList:any[];
|
||||||
SQLQuery;
|
SQLQuery;
|
||||||
getUrl;
|
getUrl;
|
||||||
|
// Add sureConnectId property
|
||||||
|
sureConnectId: number | null = null;
|
||||||
stdParamfields;
|
stdParamfields;
|
||||||
DateParam;
|
DateParam;
|
||||||
|
|
||||||
getById(id: number) {
|
getById(id: number) {
|
||||||
this.reportBuilderService.getrbDetailsById(id).subscribe(
|
this.reportBuilderService.getrbDetailsById(id).subscribe(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -120,6 +123,8 @@ todayDate;
|
|||||||
// this.adhocList = JSON.parse(adhocList);
|
// this.adhocList = JSON.parse(adhocList);
|
||||||
this.DateParam = this.builderLineData.date_param_req;
|
this.DateParam = this.builderLineData.date_param_req;
|
||||||
this.getUrl = this.builderLineData.url;
|
this.getUrl = this.builderLineData.url;
|
||||||
|
// Get sureConnectId if it exists
|
||||||
|
this.sureConnectId = this.builderLineData.sureConnectId || null;
|
||||||
console.log(this.adhocList,this.DateParam,this.getUrl)
|
console.log(this.adhocList,this.DateParam,this.getUrl)
|
||||||
this.getStdParam(this.header_id);
|
this.getStdParam(this.header_id);
|
||||||
this.featchData();
|
this.featchData();
|
||||||
@@ -127,14 +132,50 @@ todayDate;
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data using the URL and SureConnect connection if available
|
||||||
|
*/
|
||||||
featchData(){
|
featchData(){
|
||||||
|
// If sureConnectId is available, we might want to modify the request
|
||||||
|
// For now, we'll use the existing implementation but with better error handling
|
||||||
this.reportBuilderService.getAllDetailsByurl(this.getUrl).subscribe(data =>{
|
this.reportBuilderService.getAllDetailsByurl(this.getUrl).subscribe(data =>{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
if(data.body){
|
if(data.body){
|
||||||
|
// Check if the response is XML (starts with <) or JSON
|
||||||
|
if (typeof data.body === 'string' && data.body.trim().startsWith('<')) {
|
||||||
|
// Handle XML response
|
||||||
|
console.log('Received XML response, parsing to JSON');
|
||||||
|
try {
|
||||||
|
this.rows = this.parseXMLToJSON(data.body);
|
||||||
|
this.filterRows = [...this.rows]; // Create a copy
|
||||||
|
console.log('Parsed XML data:', this.rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing XML:', error);
|
||||||
|
this.rows = [];
|
||||||
|
this.filterRows = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle JSON response
|
||||||
|
try {
|
||||||
console.log(JSON.parse(data.body));
|
console.log(JSON.parse(data.body));
|
||||||
this.rows = JSON.parse(data.body);
|
this.rows = JSON.parse(data.body);
|
||||||
this.filterRows = JSON.parse(data.body);
|
this.filterRows = JSON.parse(data.body);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing JSON:', error);
|
||||||
|
this.rows = [];
|
||||||
|
this.filterRows = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no body, initialize with empty arrays
|
||||||
|
this.rows = [];
|
||||||
|
this.filterRows = [];
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
// Handle HTTP errors
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
this.rows = [];
|
||||||
|
this.filterRows = [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,13 +636,10 @@ this.rowdata= [this.rows];
|
|||||||
var j;
|
var j;
|
||||||
var cart = [];
|
var cart = [];
|
||||||
|
|
||||||
for(var i = 0; i < data.length; i++)
|
for (var i = 0; i < data.length; i++) {
|
||||||
{
|
|
||||||
var columnsIn = data[i];
|
var columnsIn = data[i];
|
||||||
if(i==1)
|
if (i == 1) {
|
||||||
{
|
for (var key in columnsIn) {
|
||||||
for(var key in columnsIn)
|
|
||||||
{
|
|
||||||
j = { prop: key, name: key };
|
j = { prop: key, name: key };
|
||||||
cart.push(j)
|
cart.push(j)
|
||||||
|
|
||||||
@@ -702,6 +740,14 @@ selectcolumn(data: any) {
|
|||||||
|
|
||||||
filtered = false;
|
filtered = false;
|
||||||
filterRowsBySelectedValues() {
|
filterRowsBySelectedValues() {
|
||||||
|
// Check if rows is defined and iterable
|
||||||
|
if (!this.rows || !Array.isArray(this.rows)) {
|
||||||
|
console.warn('Rows is not defined or not an array');
|
||||||
|
this.filterRows = [];
|
||||||
|
this.filtered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a filteredRows array to store the filtered data
|
// Create a filteredRows array to store the filtered data
|
||||||
const filteredRows = [];
|
const filteredRows = [];
|
||||||
|
|
||||||
@@ -828,4 +874,81 @@ isDate(value: any): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple XML parser to convert XML to JSON array
|
||||||
|
* Handles the specific format: <User><id>101</id><name>John Doe</name><email>john.doe@example.com</email></User>
|
||||||
|
* @param xmlString The XML string to parse
|
||||||
|
* @returns Array of objects representing the XML data
|
||||||
|
*/
|
||||||
|
private parseXMLToJSON(xmlString: string): any[] {
|
||||||
|
// Remove any XML declaration
|
||||||
|
xmlString = xmlString.replace(/<\?xml[^>]*\?>/g, '');
|
||||||
|
|
||||||
|
// Extract the root element name (e.g., "User" from <User>...</User>)
|
||||||
|
const rootMatch = xmlString.match(/<(\w+)(?:\s[^>]*)?>/);
|
||||||
|
if (!rootMatch) {
|
||||||
|
console.warn('Could not identify root element in XML');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootElement = rootMatch[1];
|
||||||
|
const results: any[] = [];
|
||||||
|
|
||||||
|
// Create a regex to match all instances of the root element
|
||||||
|
const regex = new RegExp(`<${rootElement}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${rootElement}>`, 'g');
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(xmlString)) !== null) {
|
||||||
|
const elementContent = match[1];
|
||||||
|
const item: any = {};
|
||||||
|
|
||||||
|
// Extract all tags and their values
|
||||||
|
const tagRegex = /<(\w+)>(.*?)<\/\1>/g;
|
||||||
|
let tagMatch;
|
||||||
|
|
||||||
|
while ((tagMatch = tagRegex.exec(elementContent)) !== null) {
|
||||||
|
const tagName = tagMatch[1];
|
||||||
|
const tagValue = tagMatch[2];
|
||||||
|
// Try to convert to appropriate type
|
||||||
|
item[tagName] = this.convertValueType(tagValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert string values to appropriate types (number, boolean, etc.)
|
||||||
|
* @param value The string value to convert
|
||||||
|
* @returns The value converted to the appropriate type
|
||||||
|
*/
|
||||||
|
private convertValueType(value: string): any {
|
||||||
|
// Check for empty value
|
||||||
|
if (value === '') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for boolean values
|
||||||
|
if (value.toLowerCase() === 'true') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value.toLowerCase() === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for numeric values
|
||||||
|
if (!isNaN(Number(value)) && !isNaN(parseFloat(value))) {
|
||||||
|
// Check if it's an integer or float
|
||||||
|
if (Number.isInteger(parseFloat(value))) {
|
||||||
|
return parseInt(value, 10);
|
||||||
|
} else {
|
||||||
|
return parseFloat(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as string if no other type matches
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="javascript://" class="nav-link nav-icon modern-nav-icon" routerLinkActive="active"
|
||||||
|
routerLink="/cns-portal/shield-dashboard">
|
||||||
|
<div class="nav-icon-wrapper">
|
||||||
|
<clr-icon shape="shield" solid></clr-icon>
|
||||||
|
<span class="nav-tooltip">Shield Dashboard</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="javascript://" class="nav-link nav-icon modern-nav-icon" routerLinkActive="active"
|
<a href="javascript://" class="nav-link nav-icon modern-nav-icon" routerLinkActive="active"
|
||||||
routerLink="/cns-portal/rerunner/all" (click)="getName()">
|
routerLink="/cns-portal/rerunner/all" (click)="getName()">
|
||||||
<div class="nav-icon-wrapper">
|
<div class="nav-icon-wrapper">
|
||||||
@@ -122,27 +130,27 @@
|
|||||||
<a href="javascript://" clrDropdownItem (click)="switchLanguage('en')" class="modern-lang-item">
|
<a href="javascript://" clrDropdownItem (click)="switchLanguage('en')" class="modern-lang-item">
|
||||||
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
||||||
<span>English</span>
|
<span>English</span>
|
||||||
<div class="lang-flag">ðºð¸</div>
|
<div class="lang-flag">🇺🇸</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript://" clrDropdownItem (click)="switchLanguage('hi')" class="modern-lang-item">
|
<a href="javascript://" clrDropdownItem (click)="switchLanguage('hi')" class="modern-lang-item">
|
||||||
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
||||||
<span>हिनà¥à¤¦à¥</span>
|
<span>हिन्दी</span>
|
||||||
<div class="lang-flag">ð®ð³</div>
|
<div class="lang-flag">🇮🇳</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript://" clrDropdownItem (click)="switchLanguage('ta')" class="modern-lang-item">
|
<a href="javascript://" clrDropdownItem (click)="switchLanguage('ta')" class="modern-lang-item">
|
||||||
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
||||||
<span>தமிழà¯</span>
|
<span>தமிழ்</span>
|
||||||
<div class="lang-flag">ð®ð³</div>
|
<div class="lang-flag">🇮🇳</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript://" clrDropdownItem (click)="switchLanguage('pa')" class="modern-lang-item">
|
<a href="javascript://" clrDropdownItem (click)="switchLanguage('pa')" class="modern-lang-item">
|
||||||
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
||||||
<span>ਪੰà¨à¨¾à¨¬à©</span>
|
<span>ਪੰਜਾਬੀ</span>
|
||||||
<div class="lang-flag">ð®ð³</div>
|
<div class="lang-flag">🇮🇳</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript://" clrDropdownItem (click)="switchLanguage('ml')" class="modern-lang-item">
|
<a href="javascript://" clrDropdownItem (click)="switchLanguage('ml')" class="modern-lang-item">
|
||||||
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
<clr-icon shape="globe" class="lang-icon"></clr-icon>
|
||||||
<span>മലയാളà´</span>
|
<span>മലയാളം</span>
|
||||||
<div class="lang-flag">ð®ð³</div>
|
<div class="lang-flag">🇮🇳</div>
|
||||||
</a>
|
</a>
|
||||||
</clr-dropdown-menu>
|
</clr-dropdown-menu>
|
||||||
</clr-dropdown>
|
</clr-dropdown>
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
|
import { Ad10Component } from './BuilderComponents/angulardatatype/Ad10/Ad10.component';
|
||||||
|
|
||||||
|
import { DefatestComponent } from './BuilderComponents/defu/Defatest/Defatest.component';
|
||||||
|
|
||||||
|
|
||||||
|
import { ChildformComponent } from './BuilderComponents/stpkg/Childform/Childform.component';
|
||||||
|
import { DistrictComponent } from './BuilderComponents/testdata/District/District.component';
|
||||||
|
import { StateComponent } from './BuilderComponents/testdata/State/State.component';
|
||||||
|
import { CountryComponent } from './BuilderComponents/testdata/Country/Country.component';
|
||||||
|
import { Ad9Component } from './BuilderComponents/angulardatatype/Ad9/Ad9.component';
|
||||||
|
import { Ad8Component } from './BuilderComponents/angulardatatype/Ad8/Ad8.component';
|
||||||
|
import { Ad7Component } from './BuilderComponents/angulardatatype/Ad7/Ad7.component';
|
||||||
|
import { Ad6Component } from './BuilderComponents/angulardatatype/Ad6/Ad6.component';
|
||||||
|
import { Adv5Component } from './BuilderComponents/angulardatatype/Adv5/Adv5.component';
|
||||||
|
import { Adv4Component } from './BuilderComponents/angulardatatype/Adv4/Adv4.component';
|
||||||
|
import { SupportComponent } from './BuilderComponents/angulardatatype/Support/Support.component';
|
||||||
|
import { Adv3Component } from './BuilderComponents/angulardatatype/Adv3/Adv3.component';
|
||||||
|
import { Dv2Component } from './BuilderComponents/angulardatatype/Dv2/Dv2.component';
|
||||||
|
import { Adv1Component } from './BuilderComponents/angulardatatype/Adv1/Adv1.component';
|
||||||
|
import { Basicp3Component } from './BuilderComponents/angulardatatype/Basicp3/Basicp3.component';
|
||||||
|
import { Basicp2Component } from './BuilderComponents/angulardatatype/Basicp2/Basicp2.component';
|
||||||
|
import { Basicp1Component } from './BuilderComponents/angulardatatype/Basicp1/Basicp1.component';
|
||||||
|
|
||||||
|
|
||||||
import { SequencegenaratorComponent } from './fnd/sequencegenarator/sequencegenarator.component';
|
import { SequencegenaratorComponent } from './fnd/sequencegenarator/sequencegenarator.component';
|
||||||
import { Component, NgModule } from '@angular/core';
|
import { Component, NgModule } from '@angular/core';
|
||||||
@@ -82,6 +105,17 @@ import { MappingruleallComponent } from './datamanagement/mappingrule/mappingrul
|
|||||||
import { MappingruleaddComponent } from './datamanagement/mappingrule/mappingruleadd/mappingruleadd.component';
|
import { MappingruleaddComponent } from './datamanagement/mappingrule/mappingruleadd/mappingruleadd.component';
|
||||||
import { MappingruleeditComponent } from './datamanagement/mappingrule/mappingruleedit/mappingruleedit.component';
|
import { MappingruleeditComponent } from './datamanagement/mappingrule/mappingruleedit/mappingruleedit.component';
|
||||||
import { Stepper_workflowComponent } from './BuilderComponents/stepperworkflow/Stepper_workflow/Stepper_workflow.component';
|
import { Stepper_workflowComponent } from './BuilderComponents/stepperworkflow/Stepper_workflow/Stepper_workflow.component';
|
||||||
|
import { AllapiregisteryComponent } from './fnd/apiregistery/allapiregistery/allapiregistery.component';
|
||||||
|
import { AddapiregisteryComponent } from './fnd/apiregistery/addapiregistery/addapiregistery.component';
|
||||||
|
import { EditapiregisteryComponent } from './fnd/apiregistery/editapiregistery/editapiregistery.component';
|
||||||
|
import { ApiregisterylineComponent } from './fnd/apiregistery/Apiregisteryline/Apiregisteryline.component';
|
||||||
|
import { Customer_informationComponent } from './BuilderComponents/angulardatatype/Customer_information/Customer_information.component';
|
||||||
|
import { Deployment_typeComponent } from './BuilderComponents/angulardatatype/Deployment_type/Deployment_type.component';
|
||||||
|
import { ManufacturerComponent } from './BuilderComponents/angulardatatype/Manufacturer/Manufacturer.component';
|
||||||
|
import { Order_summaryComponent } from './BuilderComponents/angulardatatype/Order_summary/Order_summary.component';
|
||||||
|
import { ProductComponent } from './BuilderComponents/angulardatatype/Product/Product.component';
|
||||||
|
import { TypesComponent } from './BuilderComponents/angulardatatype/Types/Types.component';
|
||||||
|
import { Test2Component } from './BuilderComponents/testdata/Test2/Test2.component';
|
||||||
import { Token_registeryComponent } from './fnd/Token_registery/Token_registery.component';
|
import { Token_registeryComponent } from './fnd/Token_registery/Token_registery.component';
|
||||||
import { MyworkspaceComponent } from './admin/myworkspace/myworkspace.component';
|
import { MyworkspaceComponent } from './admin/myworkspace/myworkspace.component';
|
||||||
import { ThemeCustomizationComponent } from './theme-customization/theme-customization.component';
|
import { ThemeCustomizationComponent } from './theme-customization/theme-customization.component';
|
||||||
@@ -89,9 +123,10 @@ import { Data_lakeComponent } from './builder/dashboardnew/Data_lake/Data_lake.c
|
|||||||
import { SureconnectComponent } from './builder/dashboardnew/sureconnect/sureconnect.component';
|
import { SureconnectComponent } from './builder/dashboardnew/sureconnect/sureconnect.component';
|
||||||
import { EditsureconnectComponent } from './builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component';
|
import { EditsureconnectComponent } from './builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component';
|
||||||
import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.component';
|
import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.component';
|
||||||
// import { QueryComponent } from './superadmin/query/query.component';
|
import { QueryComponent } from './superadmin/query/query.component';
|
||||||
// import { QueryaddComponent } from './superadmin/queryadd/queryadd.component';
|
import { QueryaddComponent } from './superadmin/queryadd/queryadd.component';
|
||||||
// import { QueryeditComponent } from './superadmin/queryedit/queryedit.component';
|
import { QueryeditComponent } from './superadmin/queryedit/queryedit.component';
|
||||||
|
import { ChartTypePageComponent } from './builder/dashboardnew/chart-type-manager/chart-type-page.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -132,6 +167,8 @@ const routes: Routes = [
|
|||||||
{ path: 'editconnect/:id', component: EditsureconnectComponent },
|
{ path: 'editconnect/:id', component: EditsureconnectComponent },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'reportbuild', component: ReportBuildComponent,
|
path: 'reportbuild', component: ReportBuildComponent,
|
||||||
children: [
|
children: [
|
||||||
@@ -143,9 +180,9 @@ const routes: Routes = [
|
|||||||
|
|
||||||
|
|
||||||
//SUPER ADMIN
|
//SUPER ADMIN
|
||||||
// { path: 'query', component: QueryComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
{ path: 'query', component: QueryComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
||||||
// { path: 'reportQuery/:id/queryadd', component: QueryaddComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
{ path: 'reportQuery/:id/queryadd', component: QueryaddComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
||||||
// { path: 'reportQuery/queryedit/:id', component: QueryeditComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
{ path: 'reportQuery/queryedit/:id', component: QueryeditComponent, canActivate: [AuthGuard], data: { roles: [Role.Admin] } },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -186,9 +223,16 @@ const routes: Routes = [
|
|||||||
{ path: 'editdata/:id', component: EditformnewdashComponent },
|
{ path: 'editdata/:id', component: EditformnewdashComponent },
|
||||||
{ path: 'editdashn/:id', component: EditnewdashComponent },
|
{ path: 'editdashn/:id', component: EditnewdashComponent },
|
||||||
{ path: 'schedule/:id', component: ScheduleComponent },
|
{ path: 'schedule/:id', component: ScheduleComponent },
|
||||||
|
{ path: 'chart-types', component: ChartTypePageComponent },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Shield Dashboard
|
||||||
|
{
|
||||||
|
path: 'shield-dashboard',
|
||||||
|
loadChildren: () => import('./builder/dashboardnew/gadgets/shield-dashboard/shield-dashboard-routing.module').then(m => m.ShieldDashboardRoutingModule)
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'dashboardrunner', component: DashboardrunnerComponent,
|
path: 'dashboardrunner', component: DashboardrunnerComponent,
|
||||||
children: [
|
children: [
|
||||||
@@ -236,8 +280,20 @@ const routes: Routes = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ path: 'SequenceGenerator', component: SequencegenaratorComponent },
|
{ path: 'SequenceGenerator', component: SequencegenaratorComponent },
|
||||||
{ path: 'apiregistery', component: ApiregisteryComponent },
|
|
||||||
|
|
||||||
|
// Api registery
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'apiregistery', component: ApiregisteryComponent,
|
||||||
|
children: [
|
||||||
|
{ path: '', redirectTo: 'all', pathMatch: 'full' },
|
||||||
|
{ path: 'all', component: AllapiregisteryComponent },
|
||||||
|
{ path: 'add', component: AddapiregisteryComponent },
|
||||||
|
{ path: 'edit/:id', component: EditapiregisteryComponent },
|
||||||
|
{ path: 'line/:id', component: ApiregisterylineComponent },
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
// DATA MANAGEMENT
|
// DATA MANAGEMENT
|
||||||
|
|
||||||
@@ -270,18 +326,186 @@ const routes: Routes = [
|
|||||||
|
|
||||||
// buildercomponents
|
// buildercomponents
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Country', component: CountryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv3', component: Adv3Component },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad10', component: Ad10Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Childform', component: ChildformComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'District', component: DistrictComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'State', component: StateComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Country', component: CountryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad9', component: Ad9Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad8', component: Ad8Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad7', component: Ad7Component },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad6', component: Ad6Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv5', component: Adv5Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Support', component: SupportComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv3', component: Adv3Component },
|
||||||
|
|
||||||
|
|
||||||
{ path: 'tokenregistery', component: Token_registeryComponent },
|
{ path: 'tokenregistery', component: Token_registeryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Defatest', component: DefatestComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Country', component: CountryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Defatest', component: DefatestComponent },
|
||||||
|
|
||||||
|
{ path: 'Test2', component: Test2Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Country', component: CountryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Test2', component: Test2Component },
|
||||||
|
|
||||||
|
{ path: 'Childform', component: ChildformComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'District', component: DistrictComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'State', component: StateComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Country', component: CountryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad9', component: Ad9Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad8', component: Ad8Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad7', component: Ad7Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Ad6', component: Ad6Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv5', component: Adv5Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv4', component: Adv4Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Support', component: SupportComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv3', component: Adv3Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Dv2', component: Dv2Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Adv1', component: Adv1Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Basicp3', component: Basicp3Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Basicp2', component: Basicp2Component },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Basicp1', component: Basicp1Component },
|
||||||
|
{ path: 'cust', component: Customer_informationComponent },
|
||||||
|
|
||||||
|
{ path: 'Order_summary', component: Order_summaryComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Types', component: TypesComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Product', component: ProductComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Manufacturer', component: ManufacturerComponent },
|
||||||
|
|
||||||
|
|
||||||
|
{ path: 'Deployment_type', component: Deployment_typeComponent },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{ path: 'Stepper_workflow', component: Stepper_workflowComponent },
|
{ path: 'Stepper_workflow', component: Stepper_workflowComponent },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{ path: '**', component: PageNotFoundComponent },
|
{ path: '**', component: PageNotFoundComponent },
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
|
import { Ad10Component } from './BuilderComponents/angulardatatype/Ad10/Ad10.component';
|
||||||
|
|
||||||
|
import { DefatestComponent } from './BuilderComponents/defu/Defatest/Defatest.component';
|
||||||
|
|
||||||
|
import { ChildformComponent } from './BuilderComponents/stpkg/Childform/Childform.component';
|
||||||
|
import { DistrictComponent } from './BuilderComponents/testdata/District/District.component';
|
||||||
|
import { StateComponent } from './BuilderComponents/testdata/State/State.component';
|
||||||
|
import { CountryComponent } from './BuilderComponents/testdata/Country/Country.component';
|
||||||
|
import { Ad9Component } from './BuilderComponents/angulardatatype/Ad9/Ad9.component';
|
||||||
|
import { Ad8Component } from './BuilderComponents/angulardatatype/Ad8/Ad8.component';
|
||||||
|
import { Ad7Component } from './BuilderComponents/angulardatatype/Ad7/Ad7.component';
|
||||||
|
import { Ad6Component } from './BuilderComponents/angulardatatype/Ad6/Ad6.component';
|
||||||
|
import { Adv5Component } from './BuilderComponents/angulardatatype/Adv5/Adv5.component';
|
||||||
|
import { Adv4Component } from './BuilderComponents/angulardatatype/Adv4/Adv4.component';
|
||||||
|
import { SupportComponent } from './BuilderComponents/angulardatatype/Support/Support.component';
|
||||||
|
import { Adv3Component } from './BuilderComponents/angulardatatype/Adv3/Adv3.component';
|
||||||
|
import { Dv2Component } from './BuilderComponents/angulardatatype/Dv2/Dv2.component';
|
||||||
|
import { Adv1Component } from './BuilderComponents/angulardatatype/Adv1/Adv1.component';
|
||||||
|
import { Basicp3Component } from './BuilderComponents/angulardatatype/Basicp3/Basicp3.component';
|
||||||
|
import { Basicp2Component } from './BuilderComponents/angulardatatype/Basicp2/Basicp2.component';
|
||||||
|
import { Basicp1Component } from './BuilderComponents/angulardatatype/Basicp1/Basicp1.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -74,6 +96,9 @@ import { RadarChartComponent } from './builder/dashboardnew/gadgets/radar-chart/
|
|||||||
import { ScatterChartComponent } from './builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component';
|
import { ScatterChartComponent } from './builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component';
|
||||||
import { ToDoChartComponent } from './builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component';
|
import { ToDoChartComponent } from './builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component';
|
||||||
import { ScheduleComponent } from './builder/dashboardnew/schedule/schedule.component';
|
import { ScheduleComponent } from './builder/dashboardnew/schedule/schedule.component';
|
||||||
|
import { CommonFilterComponent } from './builder/dashboardnew/common-filter/common-filter.component';
|
||||||
|
import { ChartWrapperComponent } from './builder/dashboardnew/common-filter/chart-wrapper.component';
|
||||||
|
import { CompactFilterComponent } from './builder/dashboardnew/common-filter/compact-filter.component';
|
||||||
import { AddextensionComponent } from './fnd/extension/addextension/addextension.component';
|
import { AddextensionComponent } from './fnd/extension/addextension/addextension.component';
|
||||||
import { AllextensionComponent } from './fnd/extension/allextension/allextension.component';
|
import { AllextensionComponent } from './fnd/extension/allextension/allextension.component';
|
||||||
import { EditextensionComponent } from './fnd/extension/editextension/editextension.component';
|
import { EditextensionComponent } from './fnd/extension/editextension/editextension.component';
|
||||||
@@ -91,6 +116,9 @@ import { RadarRunnerComponent } from './builder/dashboardrunner/dashrunnerline/r
|
|||||||
import { ScatterRunnerComponent } from './builder/dashboardrunner/dashrunnerline/scatter-runner/scatter-runner.component';
|
import { ScatterRunnerComponent } from './builder/dashboardrunner/dashrunnerline/scatter-runner/scatter-runner.component';
|
||||||
import { TodoRunnerComponent } from './builder/dashboardrunner/dashrunnerline/todo-runner/todo-runner.component';
|
import { TodoRunnerComponent } from './builder/dashboardrunner/dashrunnerline/todo-runner/todo-runner.component';
|
||||||
|
|
||||||
|
// Import CompactFilterRunnerComponent
|
||||||
|
import { CompactFilterRunnerComponent } from './builder/dashboardrunner/dashrunnerline/compact-filter-runner/compact-filter-runner.component';
|
||||||
|
|
||||||
import { ApiregisteryComponent } from './fnd/apiregistery/apiregistery.component';
|
import { ApiregisteryComponent } from './fnd/apiregistery/apiregistery.component';
|
||||||
|
|
||||||
import { BulkimportComponent } from './datamanagement/bulkimport/bulkimport.component';
|
import { BulkimportComponent } from './datamanagement/bulkimport/bulkimport.component';
|
||||||
@@ -106,18 +134,42 @@ import { MappingruleaddComponent } from './datamanagement/mappingrule/mappingrul
|
|||||||
import { MappingruleallComponent } from './datamanagement/mappingrule/mappingruleall/mappingruleall.component';
|
import { MappingruleallComponent } from './datamanagement/mappingrule/mappingruleall/mappingruleall.component';
|
||||||
import { MappingruleeditComponent } from './datamanagement/mappingrule/mappingruleedit/mappingruleedit.component';
|
import { MappingruleeditComponent } from './datamanagement/mappingrule/mappingruleedit/mappingruleedit.component';
|
||||||
import { Stepper_workflowComponent } from './BuilderComponents/stepperworkflow/Stepper_workflow/Stepper_workflow.component';
|
import { Stepper_workflowComponent } from './BuilderComponents/stepperworkflow/Stepper_workflow/Stepper_workflow.component';
|
||||||
|
import { AllapiregisteryComponent } from './fnd/apiregistery/allapiregistery/allapiregistery.component';
|
||||||
|
import { AddapiregisteryComponent } from './fnd/apiregistery/addapiregistery/addapiregistery.component';
|
||||||
|
import { EditapiregisteryComponent } from './fnd/apiregistery/editapiregistery/editapiregistery.component';
|
||||||
|
import { ApiregisterylineComponent } from './fnd/apiregistery/Apiregisteryline/Apiregisteryline.component';
|
||||||
|
import { Customer_informationComponent } from './BuilderComponents/angulardatatype/Customer_information/Customer_information.component';
|
||||||
|
import { Deployment_typeComponent } from './BuilderComponents/angulardatatype/Deployment_type/Deployment_type.component';
|
||||||
|
import { ManufacturerComponent } from './BuilderComponents/angulardatatype/Manufacturer/Manufacturer.component';
|
||||||
|
import { Order_summaryComponent } from './BuilderComponents/angulardatatype/Order_summary/Order_summary.component';
|
||||||
|
import { ProductComponent } from './BuilderComponents/angulardatatype/Product/Product.component';
|
||||||
|
import { TypesComponent } from './BuilderComponents/angulardatatype/Types/Types.component';
|
||||||
|
import { Test2Component } from './BuilderComponents/testdata/Test2/Test2.component';
|
||||||
import { Token_registeryComponent } from './fnd/Token_registery/Token_registery.component';
|
import { Token_registeryComponent } from './fnd/Token_registery/Token_registery.component';
|
||||||
import { MyworkspaceComponent } from './admin/myworkspace/myworkspace.component';
|
import { MyworkspaceComponent } from './admin/myworkspace/myworkspace.component';
|
||||||
import { ThemeCustomizationComponent } from './theme-customization/theme-customization.component';
|
import { ThemeCustomizationComponent } from './theme-customization/theme-customization.component';
|
||||||
|
import { QueryComponent } from './superadmin/query/query.component';
|
||||||
|
import { QueryaddComponent } from './superadmin/queryadd/queryadd.component';
|
||||||
|
import { QueryeditComponent } from './superadmin/queryedit/queryedit.component';
|
||||||
|
|
||||||
|
import { FieldTypesModule } from '../../shared/components/field-types/field-types.module';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
import { Data_lakeComponent } from './builder/dashboardnew/Data_lake/Data_lake.component';
|
import { Data_lakeComponent } from './builder/dashboardnew/Data_lake/Data_lake.component';
|
||||||
import { CronJobBuilderComponent } from './builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component';
|
import { CronJobBuilderComponent } from './builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component';
|
||||||
import { SureconnectComponent } from './builder/dashboardnew/sureconnect/sureconnect.component';
|
import { SureconnectComponent } from './builder/dashboardnew/sureconnect/sureconnect.component';
|
||||||
import { EditsureconnectComponent } from './builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component';
|
import { EditsureconnectComponent } from './builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component';
|
||||||
import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.component';
|
import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.component';
|
||||||
|
|
||||||
// import { QueryComponent } from './superadmin/query/query.component';
|
// Import Shield Dashboard Module
|
||||||
// import { QueryaddComponent } from './superadmin/queryadd/queryadd.component';
|
import { ShieldDashboardModule } from './builder/dashboardnew/gadgets/shield-dashboard/shield-dashboard.module';
|
||||||
// import { QueryeditComponent } from './superadmin/queryedit/queryedit.component';
|
// Import UnifiedChartComponent
|
||||||
|
import { UnifiedChartComponent } from './builder/dashboardnew/gadgets/unified-chart/unified-chart.component';
|
||||||
|
// Import ChartConfigManagerComponent
|
||||||
|
import { ChartConfigManagerComponent } from './builder/dashboardnew/chart-config-manager/chart-config-manager.component';
|
||||||
|
// Import ChartTypeManagerComponent
|
||||||
|
import { ChartTypeManagerComponent } from './builder/dashboardnew/chart-type-manager/chart-type-manager.component';
|
||||||
|
// Import ChartTypePageComponent
|
||||||
|
import { ChartTypePageComponent } from './builder/dashboardnew/chart-type-manager/chart-type-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -128,71 +180,66 @@ import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.c
|
|||||||
UsermaintanceaddComponent, UsermaintanceeditComponent,
|
UsermaintanceaddComponent, UsermaintanceeditComponent,
|
||||||
SubmenuComponent, ModulesComponent, SessionloggerComponent,
|
SubmenuComponent, ModulesComponent, SessionloggerComponent,
|
||||||
DashboardnewComponent, EditformnewdashComponent, EditnewdashComponent, ScheduleComponent,
|
DashboardnewComponent, EditformnewdashComponent, EditnewdashComponent, ScheduleComponent,
|
||||||
DoughnutChartComponent, LineChartComponent, RadarChartComponent, BarChartComponent, BubbleChartComponent, DynamicChartComponent, ScatterChartComponent, PolarChartComponent, PieChartComponent, FinancialChartComponent, ToDoChartComponent, GridViewComponent,
|
CommonFilterComponent, ChartWrapperComponent, CompactFilterComponent, DoughnutChartComponent, LineChartComponent, RadarChartComponent, BarChartComponent, BubbleChartComponent, DynamicChartComponent, ScatterChartComponent, PolarChartComponent, PieChartComponent, FinancialChartComponent, ToDoChartComponent, GridViewComponent,
|
||||||
|
// Add UnifiedChartComponent to declarations
|
||||||
|
UnifiedChartComponent,
|
||||||
|
// Add ChartConfigManagerComponent to declarations
|
||||||
|
ChartConfigManagerComponent,
|
||||||
|
// Add ChartTypeManagerComponent to declarations
|
||||||
|
ChartTypeManagerComponent,
|
||||||
|
// Add ChartTypePageComponent to declarations
|
||||||
|
ChartTypePageComponent,
|
||||||
DashrunnerlineComponent, BarRunnerComponent, LineRunnerComponent, DoughnutRunnerComponent, GridRunnerComponent, PieRunnerComponent, PolarRunnerComponent, RadarRunnerComponent, ScatterRunnerComponent, TodoRunnerComponent, BubbleRunnerComponent,
|
DashrunnerlineComponent, BarRunnerComponent, LineRunnerComponent, DoughnutRunnerComponent, GridRunnerComponent, PieRunnerComponent, PolarRunnerComponent, RadarRunnerComponent, ScatterRunnerComponent, TodoRunnerComponent, BubbleRunnerComponent,
|
||||||
|
// Add CompactFilterRunnerComponent to declarations
|
||||||
|
CompactFilterRunnerComponent,
|
||||||
ReportBuildComponent, ReportbuildeditComponent, ReportbuildqueryComponent, ReportBuild2Component, ReportBuild2editComponent,
|
ReportBuildComponent, ReportbuildeditComponent, ReportbuildqueryComponent, ReportBuild2Component, ReportBuild2editComponent,
|
||||||
// QueryComponent, QueryaddComponent, QueryeditComponent,
|
QueryComponent, QueryaddComponent, QueryeditComponent,
|
||||||
ExtensionComponent,
|
ExtensionComponent,
|
||||||
AllextensionComponent,
|
AllextensionComponent,
|
||||||
AddextensionComponent, EditextensionComponent, ApiregisteryComponent,
|
AddextensionComponent, EditextensionComponent, ApiregisteryComponent, AllapiregisteryComponent, AddapiregisteryComponent, EditapiregisteryComponent,
|
||||||
DatamanagementComponent, DatamananementworkflowComponent, BulkimportComponent, BulkimportallComponent, BulkimportaddComponent, BulkimporteditComponent, BulkimportlineComponent, BulkimporteditlineComponent, MappingruleComponent,
|
|
||||||
MappingruleallComponent, MappingruleaddComponent, MappingruleeditComponent,
|
ApiregisterylineComponent,
|
||||||
ThemeCustomizationComponent,
|
DatamanagementComponent, DatamananementworkflowComponent, BulkimportComponent, BulkimportallComponent, BulkimportaddComponent, BulkimporteditComponent, BulkimportlineComponent, BulkimporteditlineComponent, MappingruleComponent, MappingruleallComponent,
|
||||||
|
MappingruleaddComponent,
|
||||||
|
MappingruleeditComponent, Stepper_workflowComponent, Customer_informationComponent,
|
||||||
Data_lakeComponent,
|
Data_lakeComponent,
|
||||||
SureconnectComponent,
|
SureconnectComponent,
|
||||||
EditsureconnectComponent,
|
EditsureconnectComponent,
|
||||||
OauthComponent,
|
OauthComponent,
|
||||||
CronJobBuilderComponent,
|
CronJobBuilderComponent,
|
||||||
|
// FileUploadListComponent,
|
||||||
|
|
||||||
|
|
||||||
// buildercomponents
|
// buildercomponents
|
||||||
|
|
||||||
|
|
||||||
|
ThemeCustomizationComponent,
|
||||||
|
Ad10Component,
|
||||||
Token_registeryComponent,
|
Token_registeryComponent,
|
||||||
|
DefatestComponent,
|
||||||
|
Test2Component,
|
||||||
|
Order_summaryComponent,
|
||||||
|
TypesComponent,
|
||||||
|
ProductComponent,
|
||||||
|
ManufacturerComponent,
|
||||||
|
Deployment_typeComponent,
|
||||||
|
ChildformComponent,
|
||||||
|
DistrictComponent,
|
||||||
|
StateComponent,
|
||||||
|
CountryComponent,
|
||||||
|
Ad9Component,
|
||||||
|
Ad8Component,
|
||||||
|
Ad7Component,
|
||||||
|
Ad6Component,
|
||||||
|
Adv5Component,
|
||||||
|
Adv4Component,
|
||||||
|
SupportComponent,
|
||||||
|
Adv3Component,
|
||||||
|
Dv2Component,
|
||||||
|
Adv1Component,
|
||||||
|
Basicp3Component,
|
||||||
|
Basicp2Component,
|
||||||
|
Basicp1Component,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Stepper_workflowComponent,
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
QRCodeModule,
|
QRCodeModule,
|
||||||
@@ -212,6 +259,9 @@ import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.c
|
|||||||
NgChartsModule,
|
NgChartsModule,
|
||||||
NgxChartsModule,
|
NgxChartsModule,
|
||||||
DynamicModule,
|
DynamicModule,
|
||||||
|
FieldTypesModule,
|
||||||
|
SharedModule,
|
||||||
|
ShieldDashboardModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CookieService,
|
CookieService,
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { Ad10Component } from './BuilderComponents/angulardatatype/Ad10/Ad10.component';
|
||||||
|
|
||||||
|
import { DefatestComponent } from './BuilderComponents/defu/Defatest/Defatest.component';
|
||||||
|
|
||||||
|
import { ChildformComponent } from './BuilderComponents/stpkg/Childform/Childform.component';
|
||||||
|
import { DistrictComponent } from './BuilderComponents/testdata/District/District.component';
|
||||||
|
import { StateComponent } from './BuilderComponents/testdata/State/State.component';
|
||||||
|
import { CountryComponent } from './BuilderComponents/testdata/Country/Country.component';
|
||||||
|
import { Ad9Component } from './BuilderComponents/angulardatatype/Ad9/Ad9.component';
|
||||||
|
import { Ad8Component } from './BuilderComponents/angulardatatype/Ad8/Ad8.component';
|
||||||
|
import { Ad7Component } from './BuilderComponents/angulardatatype/Ad7/Ad7.component';
|
||||||
|
import { Ad6Component } from './BuilderComponents/angulardatatype/Ad6/Ad6.component';
|
||||||
|
import { Adv5Component } from './BuilderComponents/angulardatatype/Adv5/Adv5.component';
|
||||||
|
import { Adv4Component } from './BuilderComponents/angulardatatype/Adv4/Adv4.component';
|
||||||
|
import { SupportComponent } from './BuilderComponents/angulardatatype/Support/Support.component';
|
||||||
|
import { Adv3Component } from './BuilderComponents/angulardatatype/Adv3/Adv3.component';
|
||||||
|
import { Dv2Component } from './BuilderComponents/angulardatatype/Dv2/Dv2.component';
|
||||||
|
import { Adv1Component } from './BuilderComponents/angulardatatype/Adv1/Adv1.component';
|
||||||
|
import { Basicp3Component } from './BuilderComponents/angulardatatype/Basicp3/Basicp3.component';
|
||||||
|
import { Basicp2Component } from './BuilderComponents/angulardatatype/Basicp2/Basicp2.component';
|
||||||
|
import { Basicp1Component } from './BuilderComponents/angulardatatype/Basicp1/Basicp1.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
|
||||||
|
import { MainPageComponent } from '../main/fnd/main-page/main-page.component';
|
||||||
|
import { MainRoutingModule } from './main-routing.module';
|
||||||
|
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
|
||||||
|
// import { AboutComponent } from '../main/admin/about/about.component';
|
||||||
|
// import { LayoutComponent } from './layout/layout.component';
|
||||||
|
import { HelperModule } from 'src/app/pipes/helpers.module';
|
||||||
|
import { PasswordResetComponent } from '../main/admin/password-reset/password-reset.component';
|
||||||
|
import { UserComponent } from '../main/admin/user/user.component';
|
||||||
|
|
||||||
|
|
||||||
|
import { AllMenuGroupComponent } from '../main/admin/menu-group/all/all-menu-group.component';
|
||||||
|
import { EditMenuGroupComponent } from '../main/admin/menu-group/edit/edit-menu-group.component';
|
||||||
|
import { MenuGroupComponent } from '../main/admin/menu-group/menu-group.component';
|
||||||
|
import { ReadOnlyMenuGroupComponent } from '../main/admin/menu-group/read-only/readonly-menu-group.component';
|
||||||
|
import { AddMenurComponent } from '../main/admin/menu-register/add-menur/add-menur.component';
|
||||||
|
import { AllMenurComponent } from '../main/admin/menu-register/all-menur/all-menur.component';
|
||||||
|
import { EditMenurComponent } from '../main/admin/menu-register/edit-menur/edit-menur.component';
|
||||||
|
import { MenuRegisterComponent } from '../main/admin/menu-register/menu-register.component';
|
||||||
|
import { ReadonlyMenurComponent } from '../main/admin/menu-register/readonly-menur/readonly-menur.component';
|
||||||
|
import { ProfileSettingComponent } from '../main/admin/profile-setting/profile-setting.component';
|
||||||
|
import { UsermaintanceaddComponent } from '../main/admin/usermaintanceadd/usermaintanceadd.component';
|
||||||
|
import { UsermaintanceeditComponent } from '../main/admin/usermaintanceedit/usermaintanceedit.component';
|
||||||
|
|
||||||
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { CodemirrorModule } from "@ctrl/ngx-codemirror";
|
||||||
|
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||||
|
import { GridsterModule } from 'angular-gridster2';
|
||||||
|
import { DynamicModule } from 'ng-dynamic-component';
|
||||||
|
import { NgChartsModule } from 'ng2-charts';
|
||||||
|
import { CKEditorModule } from 'ng2-ckeditor';
|
||||||
|
|
||||||
|
import { UserRegistrationComponent } from '../main/admin/user-registration/user-registration.component';
|
||||||
|
|
||||||
|
import { QRCodeModule } from 'angularx-qrcode';
|
||||||
|
import { TagInputModule } from 'ngx-chips';
|
||||||
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
|
import { ImageCropperModule } from 'ngx-image-cropper';
|
||||||
|
import { ModulesComponent } from './admin/modules/modules.component';
|
||||||
|
import { SessionloggerComponent } from './admin/sessionlogger/sessionlogger.component';
|
||||||
|
import { SubmenuComponent } from './admin/submenu/submenu.component';
|
||||||
|
|
||||||
|
import { WireframeService } from 'src/app/services/builder/wireframe.service';
|
||||||
|
import { ReportBuildComponent } from './builder/report-build/report-build.component';
|
||||||
|
import { ReportbuildeditComponent } from './builder/report-build/reportbuildedit/reportbuildedit.component';
|
||||||
|
import { ReportbuildqueryComponent } from './builder/report-build/reportbuildquery/reportbuildquery.component';
|
||||||
|
import { ReportBuild2Component } from './builder/report-build2/report-build2.component';
|
||||||
|
import { ReportBuild2editComponent } from './builder/report-build2/report-build2edit/report-build2edit.component';
|
||||||
|
import { ReportRunnerComponent } from './builder/report-runner/report-runner.component';
|
||||||
|
import { ReportrunnereditComponent } from './builder/report-runner/reportrunneredit/reportrunneredit.component';
|
||||||
|
import { Reportrunneredit2Component } from './builder/report-runner/reportrunneredit2/reportrunneredit2.component';
|
||||||
|
|
||||||
|
|
||||||
|
import { DashboardnewComponent } from './builder/dashboardnew/dashboardnew.component';
|
||||||
|
import { EditformnewdashComponent } from './builder/dashboardnew/editformnewdash/editformnewdash.component';
|
||||||
|
import { EditnewdashComponent } from './builder/dashboardnew/editnewdash/editnewdash.component';
|
||||||
|
import { BarChartComponent } from './builder/dashboardnew/gadgets/bar-chart/bar-chart.component';
|
||||||
|
import { BubbleChartComponent } from './builder/dashboardnew/gadgets/bubble-chart/bubble-chart.component';
|
||||||
|
import { DoughnutChartComponent } from './builder/dashboardnew/gadgets/doughnut-chart/doughnut-chart.component';
|
||||||
|
import { DynamicChartComponent } from './builder/dashboardnew/gadgets/dynamic-chart/dynamic-chart.component';
|
||||||
|
import { FinancialChartComponent } from './builder/dashboardnew/gadgets/financial-chart/financial-chart.component';
|
||||||
|
import { GridViewComponent } from './builder/dashboardnew/gadgets/grid-view/grid-view.component';
|
||||||
|
import { LineChartComponent } from './builder/dashboardnew/gadgets/line-chart/line-chart.component';
|
||||||
|
import { PieChartComponent } from './builder/dashboardnew/gadgets/pie-chart/pie-chart.component';
|
||||||
|
import { PolarChartComponent } from './builder/dashboardnew/gadgets/polar-chart/polar-chart.component';
|
||||||
|
import { RadarChartComponent } from './builder/dashboardnew/gadgets/radar-chart/radar-chart.component';
|
||||||
|
import { ScatterChartComponent } from './builder/dashboardnew/gadgets/scatter-chart/scatter-chart.component';
|
||||||
|
import { ToDoChartComponent } from './builder/dashboardnew/gadgets/to-do-chart/to-do-chart.component';
|
||||||
|
import { ScheduleComponent } from './builder/dashboardnew/schedule/schedule.component';
|
||||||
|
import { CommonFilterComponent } from './builder/dashboardnew/common-filter/common-filter.component';
|
||||||
|
import { ChartWrapperComponent } from './builder/dashboardnew/common-filter/chart-wrapper.component';
|
||||||
|
import { AddextensionComponent } from './fnd/extension/addextension/addextension.component';
|
||||||
|
import { AllextensionComponent } from './fnd/extension/allextension/allextension.component';
|
||||||
|
import { EditextensionComponent } from './fnd/extension/editextension/editextension.component';
|
||||||
|
import { ExtensionComponent } from './fnd/extension/extension.component';
|
||||||
|
|
||||||
|
import { BarRunnerComponent } from './builder/dashboardrunner/dashrunnerline/bar-runner/bar-runner.component';
|
||||||
|
import { BubbleRunnerComponent } from './builder/dashboardrunner/dashrunnerline/bubble-runner/bubble-runner.component';
|
||||||
|
import { DashrunnerlineComponent } from './builder/dashboardrunner/dashrunnerline/dashrunnerline.component';
|
||||||
|
import { DoughnutRunnerComponent } from './builder/dashboardrunner/dashrunnerline/doughnut-runner/doughnut-runner.component';
|
||||||
|
import { GridRunnerComponent } from './builder/dashboardrunner/dashrunnerline/grid-runner/grid-runner.component';
|
||||||
|
import { LineRunnerComponent } from './builder/dashboardrunner/dashrunnerline/line-runner/line-runner.component';
|
||||||
|
import { PieRunnerComponent } from './builder/dashboardrunner/dashrunnerline/pie-runner/pie-runner.component';
|
||||||
|
import { PolarRunnerComponent } from './builder/dashboardrunner/dashrunnerline/polar-runner/polar-runner.component';
|
||||||
|
import { RadarRunnerComponent } from './builder/dashboardrunner/dashrunnerline/radar-runner/radar-runner.component';
|
||||||
|
import { ScatterRunnerComponent } from './builder/dashboardrunner/dashrunnerline/scatter-runner/scatter-runner.component';
|
||||||
|
import { TodoRunnerComponent } from './builder/dashboardrunner/dashrunnerline/todo-runner/todo-runner.component';
|
||||||
|
|
||||||
|
import { ApiregisteryComponent } from './fnd/apiregistery/apiregistery.component';
|
||||||
|
|
||||||
|
import { BulkimportComponent } from './datamanagement/bulkimport/bulkimport.component';
|
||||||
|
import { BulkimportaddComponent } from './datamanagement/bulkimport/bulkimportadd/bulkimportadd.component';
|
||||||
|
import { BulkimportallComponent } from './datamanagement/bulkimport/bulkimportall/bulkimportall.component';
|
||||||
|
import { BulkimporteditComponent } from './datamanagement/bulkimport/bulkimportedit/bulkimportedit.component';
|
||||||
|
import { BulkimporteditlineComponent } from './datamanagement/bulkimport/bulkimporteditline/bulkimporteditline.component';
|
||||||
|
import { BulkimportlineComponent } from './datamanagement/bulkimport/bulkimportline/bulkimportline.component';
|
||||||
|
import { DatamanagementComponent } from './datamanagement/datamanagement/datamanagement.component';
|
||||||
|
import { DatamananementworkflowComponent } from './datamanagement/datamananementworkflow/datamananementworkflow.component';
|
||||||
|
import { MappingruleComponent } from './datamanagement/mappingrule/mappingrule.component';
|
||||||
|
import { MappingruleaddComponent } from './datamanagement/mappingrule/mappingruleadd/mappingruleadd.component';
|
||||||
|
import { MappingruleallComponent } from './datamanagement/mappingrule/mappingruleall/mappingruleall.component';
|
||||||
|
import { MappingruleeditComponent } from './datamanagement/mappingrule/mappingruleedit/mappingruleedit.component';
|
||||||
|
import { Stepper_workflowComponent } from './BuilderComponents/stepperworkflow/Stepper_workflow/Stepper_workflow.component';
|
||||||
|
import { AllapiregisteryComponent } from './fnd/apiregistery/allapiregistery/allapiregistery.component';
|
||||||
|
import { AddapiregisteryComponent } from './fnd/apiregistery/addapiregistery/addapiregistery.component';
|
||||||
|
import { EditapiregisteryComponent } from './fnd/apiregistery/editapiregistery/editapiregistery.component';
|
||||||
|
import { ApiregisterylineComponent } from './fnd/apiregistery/Apiregisteryline/Apiregisteryline.component';
|
||||||
|
import { Customer_informationComponent } from './BuilderComponents/angulardatatype/Customer_information/Customer_information.component';
|
||||||
|
import { Deployment_typeComponent } from './BuilderComponents/angulardatatype/Deployment_type/Deployment_type.component';
|
||||||
|
import { ManufacturerComponent } from './BuilderComponents/angulardatatype/Manufacturer/Manufacturer.component';
|
||||||
|
import { Order_summaryComponent } from './BuilderComponents/angulardatatype/Order_summary/Order_summary.component';
|
||||||
|
import { ProductComponent } from './BuilderComponents/angulardatatype/Product/Product.component';
|
||||||
|
import { TypesComponent } from './BuilderComponents/angulardatatype/Types/Types.component';
|
||||||
|
import { Test2Component } from './BuilderComponents/testdata/Test2/Test2.component';
|
||||||
|
import { Token_registeryComponent } from './fnd/Token_registery/Token_registery.component';
|
||||||
|
import { MyworkspaceComponent } from './admin/myworkspace/myworkspace.component';
|
||||||
|
import { ThemeCustomizationComponent } from './theme-customization/theme-customization.component';
|
||||||
|
// import { QueryComponent } from './superadmin/query/query.component';
|
||||||
|
// import { QueryaddComponent } from './superadmin/queryadd/queryadd.component';
|
||||||
|
// import { QueryeditComponent } from './superadmin/queryedit/queryedit.component';
|
||||||
|
|
||||||
|
import { FieldTypesModule } from '../../shared/components/field-types/field-types.module';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { Data_lakeComponent } from './builder/dashboardnew/Data_lake/Data_lake.component';
|
||||||
|
import { CronJobBuilderComponent } from './builder/dashboardnew/Data_lake/cron-job-builder/cron-job-builder.component';
|
||||||
|
import { SureconnectComponent } from './builder/dashboardnew/sureconnect/sureconnect.component';
|
||||||
|
import { EditsureconnectComponent } from './builder/dashboardnew/sureconnect/editsureconnect/editsureconnect.component';
|
||||||
|
import { OauthComponent } from './builder/dashboardnew/sureconnect/oauth/oauth.component';
|
||||||
|
|
||||||
|
// Import Shield Dashboard Module
|
||||||
|
import { ShieldDashboardModule } from './builder/dashboardnew/gadgets/shield-dashboard/shield-dashboard.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
MainPageComponent, PageNotFoundComponent, UserComponent, PasswordResetComponent,
|
||||||
|
MyworkspaceComponent,
|
||||||
|
ReportRunnerComponent, ReportrunnereditComponent, Reportrunneredit2Component, MenuGroupComponent, AllMenuGroupComponent, EditMenuGroupComponent, ReadOnlyMenuGroupComponent, UserRegistrationComponent,
|
||||||
|
MenuRegisterComponent, AddMenurComponent, EditMenurComponent, AllMenurComponent, ReadonlyMenurComponent, ProfileSettingComponent,
|
||||||
|
UsermaintanceaddComponent, UsermaintanceeditComponent,
|
||||||
|
SubmenuComponent, ModulesComponent, SessionloggerComponent,
|
||||||
|
DashboardnewComponent, EditformnewdashComponent, EditnewdashComponent, ScheduleComponent,
|
||||||
|
CommonFilterComponent, ChartWrapperComponent, DoughnutChartComponent, LineChartComponent, RadarChartComponent, BarChartComponent, BubbleChartComponent, DynamicChartComponent, ScatterChartComponent, PolarChartComponent, PieChartComponent, FinancialChartComponent, ToDoChartComponent, GridViewComponent,
|
||||||
|
DashrunnerlineComponent, BarRunnerComponent, LineRunnerComponent, DoughnutRunnerComponent, GridRunnerComponent, PieRunnerComponent, PolarRunnerComponent, RadarRunnerComponent, ScatterRunnerComponent, TodoRunnerComponent, BubbleRunnerComponent,
|
||||||
|
ReportBuildComponent, ReportbuildeditComponent, ReportbuildqueryComponent, ReportBuild2Component, ReportBuild2editComponent,
|
||||||
|
// QueryComponent, QueryaddComponent, QueryeditComponent,
|
||||||
|
ExtensionComponent,
|
||||||
|
AllextensionComponent,
|
||||||
|
AddextensionComponent, EditextensionComponent, ApiregisteryComponent, AllapiregisteryComponent, AddapiregisteryComponent, EditapiregisteryComponent,
|
||||||
|
|
||||||
|
ApiregisterylineComponent,
|
||||||
|
DatamanagementComponent, DatamananementworkflowComponent, BulkimportComponent, BulkimportallComponent, BulkimportaddComponent, BulkimporteditComponent, BulkimportlineComponent, BulkimporteditlineComponent, MappingruleComponent, MappingruleallComponent,
|
||||||
|
MappingruleaddComponent,
|
||||||
|
MappingruleeditComponent, Stepper_workflowComponent, Customer_informationComponent,
|
||||||
|
Data_lakeComponent,
|
||||||
|
SureconnectComponent,
|
||||||
|
EditsureconnectComponent,
|
||||||
|
OauthComponent,
|
||||||
|
CronJobBuilderComponent,
|
||||||
|
// FileUploadListComponent,
|
||||||
|
|
||||||
|
|
||||||
|
// buildercomponents
|
||||||
|
|
||||||
|
|
||||||
|
ThemeCustomizationComponent,
|
||||||
|
Ad10Component,
|
||||||
|
Token_registeryComponent,
|
||||||
|
DefatestComponent,
|
||||||
|
Test2Component,
|
||||||
|
Order_summaryComponent,
|
||||||
|
TypesComponent,
|
||||||
|
ProductComponent,
|
||||||
|
ManufacturerComponent,
|
||||||
|
Deployment_typeComponent,
|
||||||
|
ChildformComponent,
|
||||||
|
DistrictComponent,
|
||||||
|
StateComponent,
|
||||||
|
CountryComponent,
|
||||||
|
Ad9Component,
|
||||||
|
Ad8Component,
|
||||||
|
Ad7Component,
|
||||||
|
Ad6Component,
|
||||||
|
Adv5Component,
|
||||||
|
Adv4Component,
|
||||||
|
SupportComponent,
|
||||||
|
Adv3Component,
|
||||||
|
Dv2Component,
|
||||||
|
Adv1Component,
|
||||||
|
Basicp3Component,
|
||||||
|
Basicp2Component,
|
||||||
|
Basicp1Component,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
QRCodeModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ClarityModule,
|
||||||
|
HelperModule,
|
||||||
|
MainRoutingModule,
|
||||||
|
DragDropModule,
|
||||||
|
HttpClientModule,
|
||||||
|
ImageCropperModule,
|
||||||
|
TagInputModule,
|
||||||
|
CodemirrorModule,
|
||||||
|
CKEditorModule,
|
||||||
|
GridsterModule,
|
||||||
|
NgChartsModule,
|
||||||
|
NgxChartsModule,
|
||||||
|
DynamicModule,
|
||||||
|
FieldTypesModule,
|
||||||
|
SharedModule,
|
||||||
|
ShieldDashboardModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CookieService,
|
||||||
|
WireframeService,
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class MainModule { }
|
||||||
@@ -69,4 +69,13 @@ export class AlertsService {
|
|||||||
}
|
}
|
||||||
return this.apiRequest.get(apiUrl);
|
return this.apiRequest.get(apiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get values for a specific key from API
|
||||||
|
public getValuesFromUrl(url: string, sureId: number | undefined, key: string): Observable<any> {
|
||||||
|
let apiUrl = `chart/getValue?apiUrl=${url}&key=${key}`;
|
||||||
|
if (sureId) {
|
||||||
|
apiUrl += `&sureId=${sureId}`;
|
||||||
|
}
|
||||||
|
return this.apiRequest.get(apiUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user