This commit is contained in:
Gaurav Kumar
2025-11-03 16:50:53 +05:30
parent c4dfb0283c
commit 75ccca8caf
20 changed files with 1460 additions and 548 deletions

View File

@@ -1,61 +1,67 @@
<ol class="breadcrumb breadcrumb-arrow font-trirong"> <ol class="breadcrumb breadcrumb-arrow font-trirong">
<li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li> <li><a href="javascript://" [routerLink]="['/cns-portal/dashboard/order']"><clr-icon shape="home"></clr-icon></a></li>
<li><a href="javascript://"><clr-icon shape="dashboard"></clr-icon> {{ 'Dashboard_builder' | translate }}</a></li> <li><a href="javascript://"><clr-icon shape="dashboard"></clr-icon> {{ 'Dashboard_builder' | translate }}</a></li>
</ol> </ol>
<br> <br>
<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;">
<button class="btn btn-success" [routerLink]="['/cns-portal/shield-dashboard']"> <button class="btn btn-success" [routerLink]="['/cns-portal/shield-dashboard']">
<clr-icon shape="shield"></clr-icon>Shield Dashboard <clr-icon shape="shield"></clr-icon>Shield Dashboard
</button> </button>
<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>
<button class="btn btn-outline" (click)="onExport()"> <!-- Add Chart Config button -->
<clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }} <button class="btn btn-primary" (click)="openChartConfig()">
</button> <clr-icon shape="cog"></clr-icon> Chart Config
<button id="add" class="btn btn-primary" (click)="gotoadd()"> </button>
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }} <button class="btn btn-outline" (click)="onExport()">
</button> <clr-icon shape="export"></clr-icon> {{ 'EXPORT_XLSX' | translate }}
</div> </button>
</div> <button id="add" class="btn btn-primary" (click)="gotoadd()">
<clr-icon shape="plus"></clr-icon> {{ 'ADD' | translate }}
</button>
</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}">
{{ 'Go_to' | translate }} {{ 'Go_to' | translate }}
</ng-container> </ng-container>
</clr-dg-column> </clr-dg-column>
<clr-dg-column [clrDgField]="'dashboard_name'"> <clr-dg-column [clrDgField]="'dashboard_name'">
<ng-container *clrDgHideableColumn="{hidden: false}"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Dashboard_Name' | translate }} {{ 'Dashboard_Name' | translate }}
</ng-container> </ng-container>
</clr-dg-column> </clr-dg-column>
<clr-dg-column [clrDgField]="'description'"> <clr-dg-column [clrDgField]="'description'">
<ng-container *clrDgHideableColumn="{hidden: false}"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Description' | translate }} {{ 'Description' | translate }}
</ng-container> </ng-container>
</clr-dg-column > </clr-dg-column>
<clr-dg-column [clrDgField]="'secuirity_profile'"> <clr-dg-column [clrDgField]="'secuirity_profile'">
<ng-container *clrDgHideableColumn="{hidden: false}"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Security_Profile' | translate }} {{ 'Security_Profile' | translate }}
</ng-container> </ng-container>
</clr-dg-column > </clr-dg-column>
<clr-dg-column [clrDgField]="'add_to_home'"> <clr-dg-column [clrDgField]="'add_to_home'">
<ng-container *clrDgHideableColumn="{hidden: false}"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Add_to_home' | translate }} {{ 'Add_to_home' | translate }}
</ng-container> </ng-container>
</clr-dg-column > </clr-dg-column>
<!-- <clr-dg-column [clrDgField]="'formType'"> <!-- <clr-dg-column [clrDgField]="'formType'">
<ng-container *clrDgHideableColumn="{hidden: false}"> <ng-container *clrDgHideableColumn="{hidden: false}">
Build Status Build Status
</ng-container> </ng-container>
@@ -65,81 +71,86 @@
Testing Testing
</ng-container> </ng-container>
</clr-dg-column > --> </clr-dg-column > -->
<clr-dg-column [clrDgField]="'action'"> <ng-container *clrDgHideableColumn="{hidden: false}"> <clr-dg-column [clrDgField]="'action'"> <ng-container *clrDgHideableColumn="{hidden: false}">
{{ 'Action' | translate }} {{ 'Action' | translate }}
</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;"
<clr-dg-cell>{{user.dashboard_name}}</clr-dg-cell> (click)="goToEdit(user.id)"> {{ 'SET_UP' | translate }}</span></clr-dg-cell>
<clr-dg-cell>{{user.description}}</clr-dg-cell> <clr-dg-cell>{{user.dashboard_name}}</clr-dg-cell>
<clr-dg-cell>{{user.secuirity_profile}}</clr-dg-cell> <clr-dg-cell>{{user.description}}</clr-dg-cell>
<clr-dg-cell>{{user.add_to_home}}</clr-dg-cell> <clr-dg-cell>{{user.secuirity_profile}}</clr-dg-cell>
<clr-dg-cell>{{user.add_to_home}}</clr-dg-cell>
<!-- <clr-dg-cell><input type="radio" id="cb1" class="dots" [ngStyle]="{'background-color': user.build == true ? 'green' : 'red'}"></clr-dg-cell> <!-- <clr-dg-cell><input type="radio" id="cb1" class="dots" [ngStyle]="{'background-color': user.build == true ? 'green' : 'red'}"></clr-dg-cell>
<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 class="tooltip-content">{{ 'Delete' | translate }}</span> <span style="cursor: pointer;"><clr-icon shape="trash" (click)="onDelete(user)" class="red is-error"
</a> style="color: red;"></clr-icon></span>
<span class="tooltip-content">{{ 'Delete' | translate }}</span>
</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"
<clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen> style="color: rgb(0, 130, 236);"></clr-icon></span>
<h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5> <clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen>
<div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div> <h5 style="margin-top: 0">{{ 'Who_Column' | translate }}</h5>
<div>{{ 'Created_At' | translate }}: <code class="clr-code">{{ user.createdAt | date }}</code></div> <div>{{ 'Account_ID' | translate }}: <code class="clr-code">{{ user.accountId }}</code></div>
<div>{{ 'Created_By' | translate }}: <code class="clr-code">{{ user.createdBy }}</code></div> <div>{{ 'Created_At' | translate }}: <code class="clr-code">{{ user.createdAt | date }}</code></div>
<div>{{ 'Updated_At' | translate }}: <code class="clr-code">{{ user.updatedAt | date }}</code></div> <div>{{ 'Created_By' | translate }}: <code class="clr-code">{{ user.createdBy }}</code></div>
<div>{{ 'Updated_By' | translate }}: <code class="clr-code">{{ user.updatedBy }}</code></div> <div>{{ 'Updated_At' | translate }}: <code class="clr-code">{{ user.updatedAt | date }}</code></div>
</clr-signpost-content> <div>{{ 'Updated_By' | translate }}: <code class="clr-code">{{ user.updatedBy }}</code></div>
</clr-signpost> </clr-signpost-content>
</clr-signpost>
</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"
<!-- <button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> --> class="is-error"></clr-icon></button>
</clr-dg-action-overflow> <!-- <button class="action-item" (click)="onDelete(user)">Delete<clr-icon shape="trash" class="is-error"></clr-icon></button> -->
</clr-dg-row> </clr-dg-action-overflow>
</clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10"> <clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{ 'Users_per_page' | translate }}</clr-dg-page-size> <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">{{ 'Users_per_page' | translate }}</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
{{ 'of' | translate }} {{pagination.totalItems}} {{ 'users' | translate }} {{ 'of' | translate }} {{pagination.totalItems}} {{ 'users' | translate }}
</clr-dg-pagination> </clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div>
<clr-modal [(clrModalOpen)]="addModall" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body">
<div class="s-order-dash-pg">
<div class="chart-box" id="word1" (click)="gotoadd()"><br>
<img style="margin: auto; display: block;" src="/assets/images/fromscratch.png" height="90" width="90">
<h5 class="center"> <b>{{ 'Start_from_scratch' | translate }}</b> </h5>
</div>
<div class="chart-box" id="word1"><br>
<img style="margin: auto; display: block;" src="/assets/images/copytemplate.png" height="90" width="90">
<h5 class="center"> <b>{{ 'Import_from_template' | translate }}</b> </h5>
</div>
<div class="chart-box" id="word1"><br>
<img style="margin: auto; display: block;" src="/assets/images/database.png" height="90" width="90">
<h5 class="center"> <b>{{ 'Import_from_public_project' | translate }}</b> </h5>
</div>
</div> </div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="addModall" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> <clr-modal [(clrModalOpen)]="modalDelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body"> <div class="modal-body" *ngIf="rowSelected.id">
<div class="s-order-dash-pg"> <h1 class="delete">{{ 'Are_you_sure_want_to_delete' | translate }}</h1>
<div class="chart-box" id="word1" (click)="gotoadd()"><br> <h2 class="heading">{{rowSelected.id}}</h2>
<img style="margin: auto; display: block;" src="/assets/images/fromscratch.png" height="90" width="90"> <div class="modal-footer">
<h5 class="center"> <b>{{ 'Start_from_scratch' | translate }}</b> </h5> <button type="button" class="btn btn-outline" (click)="modalDelete = false">{{ 'Cancel' | translate }}</button>
</div> <button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary">{{ 'Delete' | translate }}</button>
<div class="chart-box" id="word1" ><br> </div>
<img style="margin: auto; display: block;" src="/assets/images/copytemplate.png" height="90" width="90"> </div>
<h5 class="center"> <b>{{ 'Import_from_template' | translate }}</b> </h5> </clr-modal>
</div>
<div class="chart-box" id="word1"><br>
<img style="margin: auto; display: block;" src="/assets/images/database.png" height="90" width="90">
<h5 class="center"> <b>{{ 'Import_from_public_project' | translate }}</b> </h5>
</div>
</div>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modalDelete" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true">
<div class="modal-body" *ngIf="rowSelected.id">
<h1 class="delete">{{ 'Are_you_sure_want_to_delete' | translate }}</h1>
<h2 class="heading">{{rowSelected.id}}</h2>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="modalDelete = false">{{ 'Cancel' | translate }}</button>
<button type="submit" (click)="delete(rowSelected.id)" class="btn btn-primary" >{{ 'Delete' | translate }}</button>
</div>
</div>
</clr-modal>

View File

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

View File

@@ -1,6 +1,13 @@
<div class="chart-config-manager"> <div class="chart-config-manager">
<h2>Chart Configuration Manager</h2> <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 --> <!-- Error and Success Messages -->
<div class="alert alert-danger" *ngIf="errorMessage"> <div class="alert alert-danger" *ngIf="errorMessage">
<button type="button" class="close" aria-label="Close" (click)="errorMessage = null"> <button type="button" class="close" aria-label="Close" (click)="errorMessage = null">

View File

@@ -121,6 +121,7 @@ export class ChartConfigManagerComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
console.log('ChartConfigManagerComponent initialized');
this.loadChartTypes(); this.loadChartTypes();
} }
@@ -143,8 +144,10 @@ export class ChartConfigManagerComponent implements OnInit {
// Chart Type Methods // Chart Type Methods
loadChartTypes(): void { loadChartTypes(): void {
this.chartTypeLoadingState = ClrLoadingState.LOADING; this.chartTypeLoadingState = ClrLoadingState.LOADING;
console.log('Loading chart types...');
this.chartTypeService.getAllChartTypes().subscribe({ this.chartTypeService.getAllChartTypes().subscribe({
next: (data) => { next: (data) => {
console.log('Chart types loaded:', data);
this.chartTypes = data; this.chartTypes = data;
this.chartTypeLoadingState = ClrLoadingState.SUCCESS; this.chartTypeLoadingState = ClrLoadingState.SUCCESS;
}, },

View File

@@ -14,29 +14,34 @@ export class UiComponentService {
// Get all UI components for a chart type // Get all UI components for a chart type
getUiComponentsByChartType(chartTypeId: number): Observable<UiComponent[]> { getUiComponentsByChartType(chartTypeId: number): Observable<UiComponent[]> {
const url = `${this.uiComponentsUrl}/chart-type/${chartTypeId}`; const url = `${this.uiComponentsUrl}/chart-type/${chartTypeId}`;
console.log(`Fetching UI components for chart type ${chartTypeId} from ${url}`);
return this.apiRequest.get(url); return this.apiRequest.get(url);
} }
// Get UI component by ID // Get UI component by ID
getUiComponentById(id: number): Observable<UiComponent> { getUiComponentById(id: number): Observable<UiComponent> {
const url = `${this.uiComponentsUrl}/${id}`; const url = `${this.uiComponentsUrl}/${id}`;
console.log(`Fetching UI component ${id} from ${url}`);
return this.apiRequest.get(url); return this.apiRequest.get(url);
} }
// Create new UI component // Create new UI component
createUiComponent(uiComponent: Partial<UiComponent>): Observable<UiComponent> { createUiComponent(uiComponent: Partial<UiComponent>): Observable<UiComponent> {
console.log('Creating UI component:', uiComponent);
return this.apiRequest.post(this.uiComponentsUrl, uiComponent); return this.apiRequest.post(this.uiComponentsUrl, uiComponent);
} }
// Update UI component // Update UI component
updateUiComponent(id: number, uiComponent: UiComponent): Observable<UiComponent> { updateUiComponent(id: number, uiComponent: UiComponent): Observable<UiComponent> {
const url = `${this.uiComponentsUrl}/${id}`; const url = `${this.uiComponentsUrl}/${id}`;
console.log(`Updating UI component ${id}:`, uiComponent);
return this.apiRequest.put(url, uiComponent); return this.apiRequest.put(url, uiComponent);
} }
// Delete UI component // Delete UI component
deleteUiComponent(id: number): Observable<void> { deleteUiComponent(id: number): Observable<void> {
const url = `${this.uiComponentsUrl}/${id}`; const url = `${this.uiComponentsUrl}/${id}`;
console.log(`Deleting UI component ${id}`);
return this.apiRequest.delete(url); return this.apiRequest.delete(url);
} }
} }

View File

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

View File

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

View File

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

View File

@@ -20,47 +20,6 @@
</div> </div>
</div> </div>
<!-- Add Chart Type Form -->
<div class="card" *ngIf="showAddChartTypeForm">
<div class="card-header">
<h3>Add New Chart Type</h3>
</div>
<div class="card-block">
<form clrForm>
<clr-input-container>
<label>Name <span class="required">*</span></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" rows="3"></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 || chartTypeLoadingState === ClrLoadingState.LOADING">
<clr-spinner *ngIf="chartTypeLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
Save
</button>
<button class="btn" (click)="showAddChartTypeForm = false">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Chart Types Grid --> <!-- Chart Types Grid -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
@@ -69,7 +28,7 @@
<h3>Chart Types</h3> <h3>Chart Types</h3>
</div> </div>
<div class="clr-col" style="text-align: right;"> <div class="clr-col" style="text-align: right;">
<button class="btn btn-primary" (click)="showAddChartTypeForm = true"> <button class="btn btn-primary" [routerLink]="['/cns-portal/dashboardbuilder/chart-types/add']">
<cds-icon shape="plus"></cds-icon> Add Chart Type <cds-icon shape="plus"></cds-icon> Add Chart Type
</button> </button>
</div> </div>
@@ -98,7 +57,7 @@
<clr-dg-cell>{{chartType.createdAt | date:'short'}}</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>{{chartType.updatedAt | date:'short'}}</clr-dg-cell>
<clr-dg-cell> <clr-dg-cell>
<button class="btn btn-sm btn-icon" (click)="selectChartTypeForEdit(chartType)" title="Edit"> <button class="btn btn-sm btn-icon" [routerLink]="['/cns-portal/dashboardbuilder/chart-types/edit', chartType.id]" title="Edit">
<cds-icon shape="pencil"></cds-icon> <cds-icon shape="pencil"></cds-icon>
</button> </button>
<button class="btn btn-sm btn-icon" (click)="deleteChartType(chartType.id)" title="Delete"> <button class="btn btn-sm btn-icon" (click)="deleteChartType(chartType.id)" title="Delete">
@@ -113,45 +72,4 @@
</clr-datagrid> </clr-datagrid>
</div> </div>
</div> </div>
<!-- Edit Chart Type Form -->
<div class="card" *ngIf="selectedChartType">
<div class="card-header">
<h3>Edit Chart Type</h3>
</div>
<div class="card-block">
<form clrForm>
<clr-input-container>
<label>Name <span class="required">*</span></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" rows="3"></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 || chartTypeLoadingState === ClrLoadingState.LOADING">
<clr-spinner *ngIf="chartTypeLoadingState === ClrLoadingState.LOADING" clrSmall clrInline></clr-spinner>
Update
</button>
<button class="btn" (click)="selectedChartType = null">Cancel</button>
</div>
</form>
</div>
</div>
</div> </div>

View File

@@ -1,60 +1,71 @@
.chart-type-manager { .chart-type-manager {
padding: 20px; padding: 20px;
h2 {
color: #0079b8;
font-weight: 300;
margin-bottom: 20px;
}
.card { .card {
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.card-header { .card-header {
display: flex;
justify-content: space-between;
align-items: center;
h3 { h3 {
margin: 0; margin: 0;
color: #0079b8;
} }
} }
.card-block { .card-block {
padding: 15px; padding: 20px;
form {
margin-bottom: 20px;
}
} }
} }
.form-actions {
display: flex;
gap: 10px;
margin-top: 15px;
}
clr-datagrid {
min-height: 200px;
}
.label { .label {
padding: 4px 8px; padding: 4px 8px;
border-radius: 3px; border-radius: 12px;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
&.label-success {
background-color: #3d9970;
color: white;
}
&.label-danger {
background-color: #d32f2f;
color: white;
}
} }
.label-success { clr-datagrid {
background-color: #318700; margin-top: 10px;
color: white;
clr-dg-cell {
&:first-child {
font-weight: 500;
}
}
} }
.label-danger { .btn-icon {
background-color: #e62200; margin-right: 5px;
color: white;
} }
.required { @media (max-width: 768px) {
color: red; padding: 10px;
}
.alert { .card-header {
margin-bottom: 20px; .clr-row {
flex-direction: column;
.clr-col {
text-align: left !important;
margin-bottom: 10px;
}
}
}
} }
} }

View File

@@ -9,9 +9,6 @@ import { ChartType, ChartTypeService } from './chart-type.service';
}) })
export class ChartTypeManagerComponent implements OnInit { export class ChartTypeManagerComponent implements OnInit {
chartTypes: ChartType[] = []; chartTypes: ChartType[] = [];
selectedChartType: ChartType | null = null;
newChartType: Partial<ChartType> = {};
showAddChartTypeForm = false;
chartTypeLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT; chartTypeLoadingState: ClrLoadingState = ClrLoadingState.DEFAULT;
// Make ClrLoadingState available to template // Make ClrLoadingState available to template
@@ -59,54 +56,6 @@ export class ChartTypeManagerComponent implements OnInit {
}); });
} }
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 { deleteChartType(id: number): void {
if (!confirm('Are you sure you want to delete this chart type?')) { if (!confirm('Are you sure you want to delete this chart type?')) {
return; return;
@@ -126,8 +75,4 @@ export class ChartTypeManagerComponent implements OnInit {
} }
}); });
} }
selectChartTypeForEdit(chartType: ChartType): void {
this.selectedChartType = { ...chartType };
}
} }

View File

@@ -11,6 +11,12 @@ import { Component } from '@angular/core';
.chart-type-page { .chart-type-page {
padding: 20px; padding: 20px;
} }
@media (max-width: 768px) {
.chart-type-page {
padding: 10px;
}
}
`] `]
}) })
export class ChartTypePageComponent { } export class ChartTypePageComponent { }

View File

@@ -0,0 +1,201 @@
<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>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)" title="Edit">
<cds-icon shape="pencil"></cds-icon>
</button>
<button class="btn btn-sm btn-icon" (click)="deleteUiComponent(uiComponent.id)" title="Delete">
<cds-icon shape="trash"></cds-icon>
</button>
</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">
<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>
<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 in the component properties section.</p>
</div>
</div>

View File

@@ -0,0 +1,172 @@
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 { UiComponent, UiComponentService } from '../../chart-config/ui-component.service';
@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;
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 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;
}
const componentData = {
...this.newUiComponent,
chartType: { id: this.chartType.id }
};
this.loadingState = ClrLoadingState.LOADING;
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 };
}
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']);
}
}
}

View File

@@ -22,29 +22,34 @@ export class ChartTypeService {
// Get all chart types // Get all chart types
getAllChartTypes(): Observable<ChartType[]> { getAllChartTypes(): Observable<ChartType[]> {
console.log('Fetching all chart types from', this.chartTypesUrl);
return this.apiRequest.get(this.chartTypesUrl); return this.apiRequest.get(this.chartTypesUrl);
} }
// Get chart type by ID // Get chart type by ID
getChartTypeById(id: number): Observable<ChartType> { getChartTypeById(id: number): Observable<ChartType> {
const url = `${this.chartTypesUrl}/${id}`; const url = `${this.chartTypesUrl}/${id}`;
console.log(`Fetching chart type ${id} from ${url}`);
return this.apiRequest.get(url); return this.apiRequest.get(url);
} }
// Create new chart type // Create new chart type
createChartType(chartType: Partial<ChartType>): Observable<ChartType> { createChartType(chartType: Partial<ChartType>): Observable<ChartType> {
console.log('Creating chart type:', chartType);
return this.apiRequest.post(this.chartTypesUrl, chartType); return this.apiRequest.post(this.chartTypesUrl, chartType);
} }
// Update chart type // Update chart type
updateChartType(id: number, chartType: ChartType): Observable<ChartType> { updateChartType(id: number, chartType: ChartType): Observable<ChartType> {
const url = `${this.chartTypesUrl}/${id}`; const url = `${this.chartTypesUrl}/${id}`;
console.log(`Updating chart type ${id}:`, chartType);
return this.apiRequest.put(url, chartType); return this.apiRequest.put(url, chartType);
} }
// Delete chart type // Delete chart type
deleteChartType(id: number): Observable<void> { deleteChartType(id: number): Observable<void> {
const url = `${this.chartTypesUrl}/${id}`; const url = `${this.chartTypesUrl}/${id}`;
console.log(`Deleting chart type ${id}`);
return this.apiRequest.delete(url); return this.apiRequest.delete(url);
} }
} }

View File

@@ -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 | date:'medium' }}</span>
</div>
<div class="detail-item">
<label>Updated At:</label>
<span>{{ chartType.updatedAt | date:'medium' }}</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>

View File

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

View File

@@ -0,0 +1,117 @@
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) => {
this.chartType = data;
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;
}
});
}
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) => {
this.chartType = data;
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;
}
});
}
}
}

View File

@@ -11,9 +11,6 @@
<button class="btn btn-primary" (click)="openCommonFilterModal()" style="margin-left: 10px;" *ngIf="!fromRunner"> <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>
<button class="btn btn-primary" (click)="openChartConfigManager()" style="margin-left: 10px;" *ngIf="!fromRunner">
<clr-icon shape="cog"></clr-icon> Chart Config
</button>
<div style="display: inline;"> <div style="display: inline;">
{{dashboardName}} {{dashboardName}}
</div> </div>
@@ -735,16 +732,6 @@
</div> </div>
</clr-modal> </clr-modal>
<clr-modal [(clrModalOpen)]="chartConfigModalOpen" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true">
<h3 class="modal-title">Chart Configuration Manager</h3>
<div class="modal-body">
<app-chart-config-manager></app-chart-config-manager>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="closeChartConfigManager()">Close</button>
</div>
</clr-modal>
<!-- Common Filter Modal --> <!-- Common Filter Modal -->
<clr-modal [(clrModalOpen)]="commonFilterModalOpen" [clrModalStaticBackdrop]="true" clrModalSize="lg"> <clr-modal [(clrModalOpen)]="commonFilterModalOpen" [clrModalStaticBackdrop]="true" clrModalSize="lg">
<h3 class="modal-title">Configure Common Filter</h3> <h3 class="modal-title">Configure Common Filter</h3>
@@ -829,4 +816,3 @@
<button type="button" class="btn btn-primary" (click)="saveCommonFilter()">Save</button> <button type="button" class="btn btn-primary" (click)="saveCommonFilter()">Save</button>
</div> </div>
</clr-modal> </clr-modal>

View File

@@ -96,12 +96,12 @@ export class EditnewdashComponent implements OnInit {
identifier: 'scatter_chart' identifier: 'scatter_chart'
}, },
{ {
name: 'Dynamic Chart', name: 'Dynamic Chart',
identifier: 'dynamic_chart' identifier: 'dynamic_chart'
}, },
{ {
name: 'Financial Chart', name: 'Financial Chart',
identifier: 'financial_chart' identifier: 'financial_chart'
}, },
{ {
name: 'To Do', name: 'To Do',
@@ -206,9 +206,6 @@ 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 property for chart config modal
chartConfigModalOpen: boolean = false;
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private router: Router, private router: Router,
private dashboardService: Dashboard3Service, private dashboardService: Dashboard3Service,
@@ -334,7 +331,6 @@ export class EditnewdashComponent implements OnInit {
this.toggle = !this.toggle; this.toggle = !this.toggle;
} }
onDrag(event, identifier) { onDrag(event, identifier) {
console.log("on drag", identifier); console.log("on drag", identifier);
console.log("on drag ", event); console.log("on drag ", event);
@@ -460,7 +456,7 @@ export class EditnewdashComponent implements OnInit {
} }
// Also handle the case where the chart already has the correct name // Also handle the case where the chart already has the correct name
else if (dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType) && else if (dashboard.chartType && chartNameMap.hasOwnProperty(dashboard.chartType) &&
dashboard.name === chartNameMap[dashboard.chartType]) { dashboard.name === chartNameMap[dashboard.chartType]) {
// The name is already correct, no need to change it // The name is already correct, no need to change it
dashboard.component = "Unified Chart"; dashboard.component = "Unified Chart";
} }
@@ -898,7 +894,7 @@ export class EditnewdashComponent implements OnInit {
// Check if we have either datastore or table to fetch columns // Check if we have either datastore or table to fetch columns
if ((item.datastore !== undefined && item.datastore !== '' && item.datastore !== null) || if ((item.datastore !== undefined && item.datastore !== '' && item.datastore !== null) ||
(item.table !== undefined && item.table !== '' && item.table !== null)) { (item.table !== undefined && item.table !== '' && item.table !== null)) {
const datastore = item.datastore; const datastore = item.datastore;
const table = item.table; const table = item.table;
@@ -1013,8 +1009,8 @@ export class EditnewdashComponent implements OnInit {
// Handle both array and string yAxis values // Handle both array and string yAxis values
if (this.selectedyAxis !== undefined && this.selectedyAxis !== null && if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) || ((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
(typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) { (typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
console.log("get y-axis", this.selectedyAxis); console.log("get y-axis", this.selectedyAxis);
this.entryForm.patchValue({ yAxis: this.selectedyAxis }); this.entryForm.patchValue({ yAxis: this.selectedyAxis });
} }
@@ -1296,8 +1292,8 @@ export class EditnewdashComponent implements OnInit {
// Update the form with selected Y-axis values // Update the form with selected Y-axis values
// Handle both array and string yAxis values // Handle both array and string yAxis values
if (this.selectedyAxis !== undefined && this.selectedyAxis !== null && if (this.selectedyAxis !== undefined && this.selectedyAxis !== null &&
((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) || ((Array.isArray(this.selectedyAxis) && this.selectedyAxis.length > 0) ||
(typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) { (typeof this.selectedyAxis === 'string' && this.selectedyAxis !== ''))) {
console.log("get y-axis", this.selectedyAxis); console.log("get y-axis", this.selectedyAxis);
this.entryForm.patchValue({ yAxis: this.selectedyAxis }); this.entryForm.patchValue({ yAxis: this.selectedyAxis });
} }
@@ -1999,7 +1995,7 @@ export class EditnewdashComponent implements OnInit {
(values: string[]) => { (values: string[]) => {
// Update filter options string for dropdown/multiselect // Update filter options string for dropdown/multiselect
if (this.gadgetsEditdata['filterType'] === 'dropdown' || if (this.gadgetsEditdata['filterType'] === 'dropdown' ||
this.gadgetsEditdata['filterType'] === 'multiselect') { this.gadgetsEditdata['filterType'] === 'multiselect') {
this.filterOptionsString = values.join(', '); this.filterOptionsString = values.join(', ');
// Also update the gadgetsEditdata filterOptions array // Also update the gadgetsEditdata filterOptions array
this.gadgetsEditdata['filterOptions'] = values; this.gadgetsEditdata['filterOptions'] = values;
@@ -2017,7 +2013,7 @@ export class EditnewdashComponent implements OnInit {
this.gadgetsEditdata['filterKey'] = key; this.gadgetsEditdata['filterKey'] = key;
// Load available values when filter key changes // Load available values when filter key changes
if (key && (this.gadgetsEditdata['filterType'] === 'dropdown' || if (key && (this.gadgetsEditdata['filterType'] === 'dropdown' ||
this.gadgetsEditdata['filterType'] === 'multiselect')) { this.gadgetsEditdata['filterType'] === 'multiselect')) {
this.loadAvailableValues(key); this.loadAvailableValues(key);
} }
} }
@@ -2049,14 +2045,4 @@ export class EditnewdashComponent implements OnInit {
} }
} }
// Add method to open chart configuration manager
openChartConfigManager(): void {
this.chartConfigModalOpen = true;
}
// Add method to close chart configuration manager
closeChartConfigManager(): void {
this.chartConfigModalOpen = false;
}
} }